Android UncaughtExceptionHandler进行全局异常捕获

简介:

        在实际开发过程中,我们的APP由于各种原因,难免会有Crash现象(应用程序XXX已经停止)。这样给用户一种很不友好的感觉,那么我们如何去处理这种情况呢?答案就在实现UncaughtchExceptionHanlder,复写uncaughtException()方法。

        当crash发生的时候,系统会调用UncaughtchExceptionHanlder#uncaughtException()。在uncaughtException()中我们可以选择收集错误信息,然后保存在SD卡中,在合适的时机将错误日志上传至服务器。这样开发人员在后期维护的时候,就可以有针对性的修复BUG。由于默认的异常处理器是Thread类的静态成员,所以它的作用对象是当前进程的所有线程。下面是一个常用的标准的异常处理器三步走

)实现自定义CrashHandler

package com.example.crashhandler;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.os.Process;
import android.util.Log;
import android.widget.Toast;

import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest.HttpMethod;

public class CrashHandler implements UncaughtExceptionHandler {

	private static final String TAG = "CrashHandler";
	private UncaughtExceptionHandler mDefaultHandler;
	private static CrashHandler crashHandler = new CrashHandler();

	private Context mContext;
	/** 错误日志文件 */
	private File logFile = new File(Environment.getExternalStorageDirectory(),"crashLog.trace");

	private CrashHandler() {

	}

	public static CrashHandler getInstance() {
		if (crashHandler == null) {
			synchronized (CrashHandler.class) {
				if (crashHandler == null) {
					crashHandler = new CrashHandler();
				}
			}
		}
		return crashHandler;
	}

	public void init(Context context) {
		mContext = context;
		mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		//设置为线程默认的异常处理器
		Thread.setDefaultUncaughtExceptionHandler(this);
	}

	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		// 打印异常信息
		ex.printStackTrace();
		// 我们没有处理异常 并且默认异常处理不为空 则交给系统处理
		if (!handlelException(ex) && mDefaultHandler != null) {
			// 系统处理
			mDefaultHandler.uncaughtException(thread, ex);
		} else {
			try {
				Thread.sleep(3 * 1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			try {
				// 上传错误日志到服务器
				upLoadErrorFileToServer(logFile);
			} catch (Exception e) {
				e.printStackTrace();
			}
			Intent intent = new Intent(mContext, SplashActivity.class);
			// 新开任务栈
			intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			mContext.startActivity(intent);
			// 杀死我们的进程
			Timer timer = new Timer();
			timer.schedule(new TimerTask() {

				@Override
				public void run() {
					Process.killProcess(Process.myPid());
				}
			}, 2 * 1000);

		}
	}

	private boolean handlelException(Throwable ex) {
		if (ex == null) {
			return false;
		}

		// 使用Toast来显示异常信息
		new Thread() {
			@Override
			public void run() {
				Looper.prepare();
				Toast.makeText(mContext, "程序发生异常,即将重启", Toast.LENGTH_LONG)
						.show();
				Looper.loop();
			}
		}.start();

		PrintWriter pw = null;
		try {
			if (!logFile.exists()) {
				logFile.createNewFile();
			}
			pw = new PrintWriter(logFile);
			// 收集手机及错误信息
			logFile = collectInfoToSDCard(pw, ex);
			pw.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return true;
	}

	/**
	 * 上传错误日志到服务器
	 * 
	 * @param logFile
	 * @throws IOException 
	 */
	private void upLoadErrorFileToServer(File errorFile) {
		
	}

	/**
	 * 收集手机信息
	 * 
	 * @throws NameNotFoundException
	 */
	private File collectInfoToSDCard(PrintWriter pw, Throwable ex)
			throws NameNotFoundException {

		PackageManager pm = mContext.getPackageManager();
		PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),PackageManager.GET_ACTIVITIES);
		// 错误发生时间
		String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
		pw.print("time : ");
		pw.println(time);
		// 版本信息
		pw.print("versionCode : ");
		pw.println(pi.versionCode);
		// 应用版本号
		pw.print("versionName : ");
		pw.println(pi.versionName);
		try {
			/** 暴力反射获取数据 */
			Field[] Fields = Build.class.getDeclaredFields();
			for (Field field : Fields) {
				field.setAccessible(true);
				pw.print(field.getName() + " : ");
				pw.println(field.get(null).toString());
			}
		} catch (Exception e) {
			Log.i(TAG, "an error occured when collect crash info" + e);
		}

		// 打印堆栈信息
		ex.printStackTrace(pw);
		return logFile;
	}
}


2)在MyApplication中实例化CrashHanlder

package com.example.crashhandler;

import android.app.Application;

public class MyApplication extends Application {
	
	private static MyApplication mInstance;

	public static MyApplication getInstance(){
		 return mInstance;
	}
	
	@Override
	public void onCreate() {
		super.onCreate();
		mInstance = this;
		CrashHandler crashHandler = CrashHandler.getInstance();
		crashHandler.init(this);
		
	}
}

3)应用到Manifest.xml中

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.crashhandler"
    android:versionCode="1"
    android:versionName="1.0.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />
    
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".SplashActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity" >
        </activity>
    </application>

</manifest>

        至此,我们的自定义异常处理器就算写完了。注释比较多,就不赘述了。有不清楚的读者请下面评论,我会一一回复。下面,测试下我们的异常处理器。代码很简单,SplshActivity---->MainActivity。在MainActivity中点击按钮抛出一个空指针异常。详情看代码

package com.example.crashhandler;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class SplashActivity extends Activity {
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_splash);
		MyApplication.getInstance();
		// 两秒后进入MainActivity
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			
			@Override
			public void run() {
				Intent intent = new Intent(SplashActivity.this, MainActivity.class);
				startActivity(intent);
			}
		}, 2*1000);
	}
}


package com.example.crashhandler;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity{

	private Button crash;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		crash = (Button) findViewById(R.id.crash);
		crash.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				throw new NullPointerException();
			}
		});
	}

}

点击按钮之后,触发我们的全局未捕获异常处理器。手机错误信息保存至SD卡,随后新开任务栈重启SplashActivity。点我下载Demo源代码

待完善:

  • 对SD卡是否存在进行判断
  • 对错误日志文件轮询,新开service上传至服务器

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
Android开发
教你在Android手机上使用全局代理!
前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下: 一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。
14867 0
|
存储 XML 设计模式
一个简单的Android网络访问全局码判断及通用数据解析方案
我们在开发中,网络请求经常会遇到各种错误码的判断。比如下面这样:
120 0
|
Android开发
【Android 逆向】函数拦截原理 ( 通过修改 GOT 全局偏移表拦截函数 | 通过在实际被调用的函数中添加跳转代码实现函数拦截 )
【Android 逆向】函数拦截原理 ( 通过修改 GOT 全局偏移表拦截函数 | 通过在实际被调用的函数中添加跳转代码实现函数拦截 )
165 0
【Android 逆向】函数拦截原理 ( 通过修改 GOT 全局偏移表拦截函数 | 通过在实际被调用的函数中添加跳转代码实现函数拦截 )
|
Java Linux Android开发
【Android 逆向】函数拦截原理 ( 可执行程序基本结构 | GOT 全局偏移表 | 可执行程序函数调用步骤 )
【Android 逆向】函数拦截原理 ( 可执行程序基本结构 | GOT 全局偏移表 | 可执行程序函数调用步骤 )
212 0
【Android 逆向】函数拦截原理 ( 可执行程序基本结构 | GOT 全局偏移表 | 可执行程序函数调用步骤 )
|
存储 安全 Android开发
【Android 安装包优化】资源混淆 ( resources.arsc 资源映射表文件格式 | 头文件 数据格式 | 全局字符串池 数据格式 | 包数据 数据格式 | 包头 数据格式 )(二)
【Android 安装包优化】资源混淆 ( resources.arsc 资源映射表文件格式 | 头文件 数据格式 | 全局字符串池 数据格式 | 包数据 数据格式 | 包头 数据格式 )(二)
242 0
【Android 安装包优化】资源混淆 ( resources.arsc 资源映射表文件格式 | 头文件 数据格式 | 全局字符串池 数据格式 | 包数据 数据格式 | 包头 数据格式 )(二)
|
存储 Android开发 数据格式
【Android 安装包优化】资源混淆 ( resources.arsc 资源映射表文件格式 | 头文件 数据格式 | 全局字符串池 数据格式 | 包数据 数据格式 | 包头 数据格式 )(一)
【Android 安装包优化】资源混淆 ( resources.arsc 资源映射表文件格式 | 头文件 数据格式 | 全局字符串池 数据格式 | 包数据 数据格式 | 包头 数据格式 )(一)
185 0
【Android 安装包优化】资源混淆 ( resources.arsc 资源映射表文件格式 | 头文件 数据格式 | 全局字符串池 数据格式 | 包数据 数据格式 | 包头 数据格式 )(一)
|
Java Android开发
【Android NDK 开发】JNI 引用 ( 弱全局引用 | NewWeakGlobalRef | DeleteWeakGlobalRef )
【Android NDK 开发】JNI 引用 ( 弱全局引用 | NewWeakGlobalRef | DeleteWeakGlobalRef )
183 0
|
Java Android开发
【Android NDK 开发】JNI 引用 ( 全局引用 | NewGlobalRef | DeleteGlobalRef )
【Android NDK 开发】JNI 引用 ( 全局引用 | NewGlobalRef | DeleteGlobalRef )
584 0
|
Android开发
Android项目中利用组合控件自定义全局的顶部标题栏
Android项目中利用组合控件自定义全局的顶部标题栏
Android项目中利用组合控件自定义全局的顶部标题栏
|
Android开发
Android在Application层级维护和管理全局所有Activity的方法ActivityLifecycleCallbacks
Android在Application层级维护和管理全局所有Activity的方法ActivityLifecycleCallbacks 经常看到有些项目中经常性的把所有activity继承自一个base的Activity,然后在每一次启动新activity时候添加当前activity到一个全局List那样的列表中,已达到全局管理和维护activity的目的,这种做法大概是四五年前的技术解决方案。
1712 0