模版方法模式与实战:编写Android应用崩溃处理工具类
原理和应用
- AsyncTask
- View的绘制流程
等等。。
模版方法模式注重于
- 封装不可变部分,扩展可变部分
- 提取公共部分代码,便于维护
比如View的绘制流程:
- public final void measure(..) –> prptected void onMeasure(..)
- public final void layout(..) –> prptected void onLayout(..)
- public final void draw(..) –> prptected void onDraw(..)
xxx方法定义为了final,表示核心逻辑的顺序不能修改;onMeasure可以被重写,意味着可以对这部分进行自定义(扩展)。
流程确定,把具体实现细节抽象出来。
实战:应用崩溃处理
使用模版方法模式实现一个应用崩溃处理工具类。
崩溃处理的两个个流程:
- 收集崩溃数据
- 保存崩溃数据
- 应用杀死前的回调处理
我们把这三个具体细节抽象出来,让子类去实现:
public abstract class CrashHandler<T> {
abstract T collectData(Context context, Thread t, Throwable e);
abstract void saveData(Context context, T data);
abstract void onProcessEnd(Context context);
}
然后编写崩溃处理的逻辑:
public void install(Context context) {
mContext = context;
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(final Thread t, final Throwable e) {
new Thread() {
@Override
public void run() {
//执行子类实现的具体逻辑
saveData(mContext, collectData(mContext, t, e));
//退出程序
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
}.start();
}
});
}
整个工具代码如下:
抽象类CrashHandler:
package com.newsapp.tool;
import android.content.Context;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
/**
* 崩溃处理抽象类
* @author mingC
* @date 2018/8/5
*/
public abstract class CrashHandler<T> {
private Context mContext;
/**
* 崩溃数据收集
* @param t 崩溃线程
* @param e 崩溃原因
* @return 崩溃数据
*/
abstract T collectData(Context context, Thread t, Throwable e);
/**
* 保存崩溃数据,可以在这里做保存到文件或者上传到服务器的操作
* @param data
*/
abstract void saveData(Context context, T data);
/**
* 在应用被杀死之前最后做点什么
*/
abstract void onProcessEnd(Context context);
private void showTipToast() {
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "很抱歉,程序出现异常即将退出.", Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
}
public void install(Context context) {
mContext = context;
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(final Thread t, final Throwable e) {
e.printStackTrace();
showTipToast();
Log.e("CrashHandler", "线程" + t.getName() + "出现未捕获的异常,正在进行崩溃处理...");
final long beginTime = System.currentTimeMillis();
new Thread() {
@Override
public void run() {
long transactTime = System.currentTimeMillis() - beginTime;
//执行子类实现的具体逻辑
saveData(mContext, collectData(mContext, t, e));
Log.e("CrashHandler", "崩溃处理完成,处理时间为(ms):" + transactTime);
try {
Thread.sleep(1500);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//留给子类扩展
onProcessEnd(mContext);
// 退出程序,有些机型会退出失败直到ANR
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
}.start();
}
});
}
}
子类RealCrashHandler:
package com.newsapp.tool;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import com.newsapp.main.MainActivity;
import com.newsapp.util.Util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* 崩溃处理实现类
* @author mingC
* @date 2018/8/5
*/
public class RealCrashHandler extends CrashHandler<RealCrashHandler.CrashData>{
static class CrashData {
Map<String, String> map = new HashMap<>();
}
@Override
CrashData collectData(Context context, Thread t, Throwable e) {
CrashData data = new CrashData();
//保存异常信息
int s = 0;
for (StackTraceElement stackTraceElement : e.getStackTrace()) {
data.map.put("err-stack" + (s++), stackTraceElement.toString());
}
//保存应用信息
PackageManager pm = context.getPackageManager();
PackageInfo pi = null;
try {
pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
} catch (PackageManager.NameNotFoundException e1) {
e1.printStackTrace();
}
if (pi != null) {
String versionName = pi.versionName == null ? "null" : pi.versionName;
String versionCode = pi.versionCode + "";
data.map.put("versionName", versionName);
data.map.put("versionCode", versionCode);
}
//保存设备信息
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
data.map.put(field.getName(), field.get(null).toString());
Log.d("CrashHandler", field.getName() + " : " + field.get(null));
} catch (Exception ex) {
ex.printStackTrace();
}
}
return data;
}
public void readCrashInfo(Context context) {
Log.d("RealCrashHandler", "正在读取崩溃数据");
try {
File file = new File(context.getFilesDir(), "crash-info");
if (! file.exists()) {
Log.d("RealCrashHandler", "无崩溃数据");
return;
}
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
Log.d("RealCrashHandler", line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
void saveData(Context context, RealCrashHandler.CrashData data) {
try {
FileOutputStream out = new FileOutputStream(new File(context.getFilesDir(), "crash-info"));
for (Map.Entry<String, String> entry : data.map.entrySet()) {
String row = entry.getKey() + "-" + entry.getValue() + "\n";
out.write(row.getBytes());
}
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
void onProcessEnd(Context context) {
Log.d("RealCrashHandler", "应用即将重启");
restartApp(1000);
}
//重启APP,用定时器实现
private void restartApp(long delay) {
Intent intent = new Intent(Util.getContext(), MainActivity.class);
PendingIntent restartIntent = PendingIntent.getActivity(
Util.getContext(), 0, intent, 0);
AlarmManager mgr = (AlarmManager)Util.getContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + delay,
restartIntent);
}
}
使用,在Application中开启:
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
RealCrashHandler crashHandler = new RealCrashHandler();
crashHandler.install(this);
//读取上一次的崩溃数据,可以在崩溃的时候把数据上传,如果上传失败也可以在下一次应用开启时上传
crashHandler.readCrashInfo(this);
}
}