转载自: https://blog.csdn.net/xuanzhiqiang01/article/details/79362947
Thread.UncaughtExceptionHandler接口
当程序发生异常终止时,如果我们没有对其进行异常捕获处理,系统会调用该线程组的默认异常捕获处理器。我们想要对应用全局的异常进行捕获,实现思路就是自己写一个捕获器去替换系统默认的,然后对设备信息、版本信息、异常信息进行收集并上传到服务器进行分析。
Android 提供了这样一个接口Thread.UncaughtExceptionHandler
/**
* Implemented by objects that want to handle cases where a thread is being
* terminated by an uncaught exception. Upon such termination, the handler
* is notified of the terminating thread and causal exception. If there is
* no explicit handler set then the thread's group is the default handler.
*/
public static interface UncaughtExceptionHandler {
/**
* The thread is being terminated by an uncaught exception. Further
* exceptions thrown in this method are prevent the remainder of the
* method from executing, but are otherwise ignored.
*
* @param thread the thread that has an uncaught exception
* @param ex the exception that was thrown
*/
void uncaughtException(Thread thread, Throwable ex);
}
该接口就是系统异常终止时,用来处理异常的约束。
系统默认异常处理器:
Thread.getDefaultUncaughtExceptionHandler();
实现思路
- 自定义CrashHandler实现接口Thread.UncaughtExceptionHandler
- 在应用Application初始化时替换系统默认的异常处理器
自定义CrashHandler需要做的工作:
- 收集设备信息、版本信息、异常信息
- 写入本地记录
- 上传到服务器
实现示例
public class CrashHandler implements UncaughtExceptionHandler {
private static CrashHandler mInstance;
private Context mContext;
private UncaughtExceptionHandler mDefautHandler;
private static final String TAG = "CrashHandlerTag";
private CrashHandler() {
}
public static CrashHandler getInstance() {
if (mInstance == null) {
synchronized (CrashHandler.class) {
if (mInstance == null) {
mInstance = new CrashHandler();
}
}
}
return mInstance;
}
public void init(Context context) {
mContext = context;
mDefautHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (ex != null) {
// 收集设备信息、版本信息、异常信息
String info = collectDeviceInfo(mContext, ex);
// 本地存储
saveInfo(info);
Log.d(TAG, "已捕获到异常 ");
//不进行延时处理的话kill不能执行完成
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// 异常已经处理,结束进程
Process.killProcess(Process.myPid());
System.exit(1);
}
}, 100);
} else {
Log.d(TAG, "没有捕获异常 ");
//没有处理还交给系统默认的处理器
if (mDefautHandler != null) {
mDefautHandler.uncaughtException(thread, ex);
}
}
}
/**
* 收集设备信息
*
* @param c
* @param ex
*/
private String collectDeviceInfo(Context c, Throwable ex) {
Map<String, String> infos = new HashMap<>();
//收集版本信息
try {
PackageManager pm = c.getPackageManager();
PackageInfo pi = pm.getPackageInfo(c.getPackageName(), PackageManager.GET_ACTIVITIES);
if (pi != null) {
String versionCode = pi.versionCode + "";
String versionName = TextUtils.isEmpty(pi.versionName) ? "没有版本名称" : pi.versionName;
infos.put("versionCode", versionCode);
infos.put("versionName", versionName);
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
//收集设备信息
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
} catch (Exception e) {
}
}
// 收集异常信息
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.close();
String result = writer.toString();
// 转化为字符串
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key + "=" + value + "\n");
}
sb.append(result);
return sb.toString();
}
/**
* 保存异常信息到本地
* @param infos
*/
private void saveInfo(String infos) {
Log.d(TAG, "输出log日志: " + infos);
// 把采集到的信息写入到本地文件
// outToSdcard(infos);
}
/**
* 保存异常信息到sdcard中
*
* @param errorlog
*/
public void outToSdcard(String errorlog) {
String sdPath = Environment.getExternalStorageDirectory().getPath() + "/error_log";
//新建文件
File sdCardDir = new File(sdPath);
if (!sdCardDir.exists()) {
if (!sdCardDir.mkdirs()) {
try {
sdCardDir.createNewFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}
try {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date());
String fileName = time + ".txt";
//新建文件
File saveFile = new File(sdCardDir, fileName);
if (!saveFile.exists()) {
saveFile.createNewFile();
}
final FileOutputStream outStream = new FileOutputStream(saveFile);
try {
if (errorlog != null) {
outStream.write(errorlog.getBytes());
outStream.close();
}
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "流写入错误:" + e.toString());
}
Log.d(TAG, "日志输出成功! ");
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意: outToSdcard保存到sdcard时,需要添加并打开写入权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
使用示例
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
//全局异常捕获
CrashHandler.getInstance().init(getApplicationContext());
}
}
造错实例
//动态授权
private void permission() {
if (Build.VERSION.SDK_INT >= 23) {
String[] mPermissionList = new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
};
ActivityCompat.requestPermissions(UserActivity.this, mPermissionList, 123);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
permission();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Toast.makeText(UserActivity.this, "子线程toast,会报线程异常", Toast.LENGTH_SHORT).show();
}
});
thread.start();
}
原始代码,50行报错:
日志输出:
2021-09-09 11:32:02.045 17993-18012/com.example.sqlitemanager D/CrashHandlerTag: 输出报错日志: MEIZU_FINGERPRINT=7.1.2-1576008878_stable
SUPPORTED_64_BIT_ABIS=[Ljava.lang.String;@dc55e30
versionCode=1
BOARD=M6Note
BOOTLOADER=unknown
TYPE=user
ID=N2G47H
TIME=1576011277000
BRAND=Meizu
TAG=Build
HARDWARE=qcom
SERIAL=721QACSM222VX
SUPPORTED_ABIS=[Ljava.lang.String;@8dbeaa9
CPU_ABI=arm64-v8a
IS_DEBUGGABLE=false
RADIO=unknown
MANUFACTURER=Meizu
IS_EMULATOR=false
SUPPORTED_32_BIT_ABIS=[Ljava.lang.String;@97bdf73
TAGS=release-keys
CPU_ABI2=
UNKNOWN=unknown
PERMISSIONS_REVIEW_REQUIRED=false
USER=flyme
FINGERPRINT=Meizu/meizu_M6Note_CN/M6Note:7.1.2/N2G47H/m1721.Flyme_8.0.1568701887:user/release-keys
HOST=Mz-Builder-l7
versionName=1.0
PRODUCT=meizu_M6Note_CN
DISPLAY=Flyme 8.0.0.0A
MODEL=M6 Note
DEVICE=M6Note
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.widget.Toast$TN$2.<init>(Toast.java:368)
at android.widget.Toast$TN.<init>(Toast.java:368)
at android.widget.Toast.<init>(Toast.java:112)
at android.widget.Toast.makeText(Toast.java:276)
at com.example.sqlitemanager.UserActivity$1.run(UserActivity.java:50)
at java.lang.Thread.run(Thread.java:761)
2021-09-09 11:32:02.045 17993-18012/com.example.sqlitemanager D/CrashHandlerTag: 已捕获异常
2021-09-09 11:32:02.045 17993-18012/com.example.sqlitemanager D/CrashHandlerTag: 进程杀死前