Android异常crash监听
- 背景
- 方案
- 使用CrashHandle
- 使用远程服务
背景
目前做的项目为座舱app,与智驾交互很多。如果遇到异常crash退出,会导致智驾域控的状态机不对,无法重置。所以要解决当前问题,需要在APP崩溃的时候发送重置信号,解决状态机不同步问题。
方案
使用CrashHandle
public class CrashHandler implements Thread.UncaughtExceptionHandler {private static final String TAG = "APA-CrashHandler";private static final String INT_CRASH_TIME = "int_crash_time";private static final String DEAD_SYSTEM_EXCEPTION = "DeadSystemException";private static final String DEAD_OBJECT_EXCEPTION = "DeadObjectException";private static final int MAX_CRASH_TIME = 3;private static CrashHandler instance;private Context mContext;private Thread.UncaughtExceptionHandler mOriginalHandler;public static CrashHandler getInstance() {if (instance == null) {instance = new CrashHandler();}return instance;}/*** 初始化*/public void init(Context context) {mContext = context;mOriginalHandler = Thread.getDefaultUncaughtExceptionHandler();Thread.setDefaultUncaughtExceptionHandler(this);}/*** 把崩溃信息放在sd卡缓存目录下,若没有sd卡,就放在data/data中的缓存目录下** <p>SDCard/Android/data/你的应用包名/cache/<p/>*/private static File getDiskCrashDir(Context context, String dirName) {String cachePath;if (context.getExternalCacheDir() != null) {cachePath = context.getExternalCacheDir().getPath();} else {cachePath = context.getFilesDir().getPath();}return new File(cachePath + File.separator + dirName);}@Overridepublic void uncaughtException(Thread thread, Throwable ex) {try {DateFormat formatter = new SimpleDateFormat("MM.dd@HH.mm", Locale.CHINA);String time = formatter.format(new Date());Log.e(TAG, "APA_CRASH time : " + time + " " + ex.toString());StackTraceElement[] stackTrace = ex.getStackTrace();for (int i = 0; i < stackTrace.length; i++) {Log.e(TAG, "APA_CRASH stackTraceElement ---<" + i+">---" + stackTrace[i].toString());}handleCrash();dealwithUnity(ex);if (BuildConfig.DEBUG) {saveCrash2File(ex);}uploadExceptionToService(ex);//发送崩溃广播通知其他APPMyUtils.sendApaExitBroadcast(BaseConfigApplication.getApplication());// offMyUtils.STATUS_APA_LIFE = STATUS_APA_OFF;//处理其他特殊情况CarConnectionHelper mCarHelper = CarConnectionHelper.getInstance();if (mCarHelper != null) {if (mCarHelper.isConnected()) {int avmSts = mCarHelper.getIntProperty(HzVehiclePropertyIds.HZ_AVM_STATUS, AREA_DEFAULT, -1);LogUtils.i(TAG, "crash apa fail : avmSts : "+avmSts);if (avmSts == 6) {//发送信号同步给智驾Log.d(TAG, "avmSts == 6 need aidl seed off");mCarHelper.sendCanMsg(HzVehiclePropertyIds.HZ_HPA_ON, AppConstants.AREA_DEFAULT, 2);Thread.sleep(100);//发送信号同步给智驾 ,信号属于同一组,防止中间件没收到mCarHelper.sendCanMsg(HzVehiclePropertyIds.HZ_APA_MODE_SET, AppConstants.AREA_DEFAULT, 2);}}}if (isInterruptException(ex)) {// 异常逻辑 1: 继续执行,进程不结束resumeMainThreadLoop();//异常逻辑 2: 重启
// restartApp(mContext);return;}} catch (Exception e) {Log.e("uncaughtException又抛出的异常", ex.toString());}mOriginalHandler.uncaughtException(thread, ex);}private void dealwithUnity(Throwable ex) {//暂不处理了,全部有远程服务处理
// String exError = ex.toString();
// if(exError.contains("FATAL EXCEPTION [UnityMain]")){
// Log.e(TAG,"dealwithUnity");
// FilePermissionHelper.fixUpDirectoryPermission(FilePermissionHelper.unityFilePath);
// }}private boolean isInterruptException(Throwable e){if (e.toString().contains(DEAD_SYSTEM_EXCEPTION) || e.toString().contains(DEAD_OBJECT_EXCEPTION)){//拦截DeadSystemExceptionreturn true;}return false;}/*** 重启服务* @param context the context*/public void restartApp(Context context){// 重新启动应用程序或者系统服务try {Log.d(TAG, "Restarting");final Intent intent = new Intent();intent.setClassName("com.hozonauto.panoramic","com.hozonauto.panoramic.service.AIDLService");
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, pendingIntent);} catch (Exception e) {Log.e("uncaughtException restartApp crash", e.toString());e.printStackTrace();}// 退出应用程序或者停止服务System.exit(0);}public void resumeMainThreadLoop() {if (Looper.myLooper() != Looper.getMainLooper()) {return;}try {Looper.loop();} catch (Exception e) {Log.e("uncaughtException resumeMainThreadLoop crash", e.toString());uncaughtException(Thread.currentThread(), e);}}private void uploadExceptionToService(Throwable ex) {//上传操作// 一定要获取一些硬件信息(在不侵犯隐私的前提下)}private void handleCrash() {
// if (MyApplication.getInstance() != null) {
// //elapsedTime 这个是 系统启动后到发送崩溃的时间
// final long elapsedTime = SystemClock.elapsedRealtime() - MyApplication.getInstance().getStartTime();
// Log.e(TAG, "elapsedTime:" + elapsedTime);
// //如果在启动后10s内就崩溃了,那就需要清除缓存了,很大概率就是缓存出现了脏数据,导致启动读取缓存数据就崩溃了。
// if (elapsedTime < 10 * 1000) {
// int time = (Integer) SPUtil.get(MyApplication.getInstance(),"app",INT_CRASH_TIME, 0);
// time += 1;
// Log.e(TAG, "崩溃次数" + time);
// if (time >= MAX_CRASH_TIME) {
// Log.e(TAG, "崩溃次数达到上限,清除缓存");
// //reset
// SPUtil.put(MyApplication.getInstance(),"app",INT_CRASH_TIME, 0);
// //到达上限 清除缓存相关数据
// CacheUtil.clean();
// } else {
// SPUtil.put(MyApplication.getInstance(),"app",INT_CRASH_TIME, time);
// }
// }
// } else {
// Log.e(TAG, "获取不到context");
// }}/*** 保存错误信息到文件中** @param ex 崩溃信息* @throws IOException*/public void saveCrash2File(Throwable ex) throws IOException {// StringBuilder sb = new StringBuilder();
// Writer writer = new StringWriter();
// PrintWriter printWriter = new PrintWriter(writer);
// ex.printStackTrace(printWriter);
// Throwable cause = ex.getCause(); // 返回此异常的原因(尝试加载类时发生错误引发的异常;否则返回 null)
// while (cause != null) {
// cause.printStackTrace(printWriter);
// cause = cause.getCause();
// }
// printWriter.close();
// String result = writer.toString();
// sb.append(result);
// try {
// // 用于格式化日期,作为日志文件名的一部分
// DateFormat formatter = new SimpleDateFormat("MM.dd@HH.mm", Locale.CHINA);
// String time = formatter.format(new Date());
// String fileName = time + ".log";
// File dir = getDiskCrashDir(mContext, "crash");
// if (!dir.exists()) {
// if (!dir.mkdirs()) {
// return;
// }
// }
// String filePath = dir.getAbsolutePath() + File.separator + fileName;
// FileOutputStream fos = new FileOutputStream(filePath);
// fos.write(sb.toString().getBytes());
// fos.close();
// } catch (IOException e) {
// Log.e(TAG,e.toString());
// }}}
使用远程服务
可以通过绑定服务来监听主进程是否死亡,IBinder.DeathRecipient
来处理死亡监听。
/*** @author hj*/
public class AIDLAliveHelp {private static final String TAG = AIDLService.class.getSimpleName();private final Context mContext;public AIDLAliveHelp(Context context) {mContext = context;}@RequiresApi(api = Build.VERSION_CODES.R)public void bindAliveService() {try {LogUtils.d(TAG, "bindAliveService");Intent intent = new Intent();ComponentName component;component = new ComponentName("com.hozonauto.panoramic","com.hozonauto.panoramic.service.ApaAliveService");intent.setComponent(component);mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);} catch (Exception e) {Log.e(TAG, "Exception creating of bindAliveService : " + e.getMessage());}}private IAliveInterface mIAliveInterface;ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceDisconnected(ComponentName name) {LogUtils.i(TAG, "AIDLAliveHelp----onServiceDisconnected");mIAliveInterface = null;}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {LogUtils.i(TAG, "AIDLAliveHelp---onServiceConnected");mIAliveInterface = IAliveInterface.Stub.asInterface(service);try {if (service != null) {service.linkToDeath(mDeathRecipient, 0);}} catch (Exception e) {Log.e(TAG, "AIDLAliveHelp---onServiceConnected has error : " + e.getMessage());}}};private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {@Overridepublic void binderDied() {LogUtils.d(TAG, "AIDLAliveHelp---IAliveInterface is died");try {mContext.unbindService(mConnection);CarConnectionHelper mCarHelper = CarConnectionHelper.getInstance();if (mCarHelper != null) {if (mCarHelper.isConnected()) {int avmSts = mCarHelper.getIntProperty(HzVehiclePropertyIds.HZ_AVM_STATUS, AREA_DEFAULT, -1);LogUtils.w(TAG, "main app binderDied : avmSts : " + avmSts);if (avmSts == 6) {LogUtils.i(TAG, "avmSts == 6 need to send off mode");mCarHelper.sendCanMsg(HzVehiclePropertyIds.HZ_APA_MODE_SET, AppConstants.AREA_DEFAULT, 2);}}}MyUtils.sendApaExitBroadcast(BaseConfigApplication.getApplication());ThreadUtils.getMainHandler().postDelayed(new Runnable() {@Overridepublic void run() {boolean system = FilePermissionHelper.checkDirectoryOwner(FilePermissionHelper.unityFilePath, "system");LogUtils.w(TAG, "main app binderDied : unity files system "+ system);FilePermissionHelper.executeError();}},1000);} catch (Exception e) {LogUtils.e(TAG, "IAliveInterface binderDied has error :" + e.getMessage());e.printStackTrace();}mIAliveInterface = null;}};public void unbindService() {if (mContext != null) {try {MyApplication.getApplication().unbindService(mConnection);} catch (Exception e) {e.printStackTrace();}}}
}
在远程服务里调用该工具类:
public class AIDLService extends Service {@Overridepublic void onCreate() {super.onCreate();.......mAidlAliveHelp = new AIDLAliveHelp(this);.......}@RequiresApi(api = Build.VERSION_CODES.R)@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {LogUtils.i(TAG, "------ onStartCommand() -----");// if (intent != null && intent.getAction() != null) { }if (mAidlAliveHelp != null) {mAidlAliveHelp.bindAliveService();}return START_STICKY;}@Overridepublic void onDestroy() {if (mAidlAliveHelp != null) {mAidlAliveHelp.unbindService();}super.onDestroy();}}
AidlService
配置:独立的远程进程。
<serviceandroid:name=".service.AIDLService"android:enabled="true"android:exported="true"android:permission="com.hozonauto.panoramic.permission.REMOTE_CONTROL"android:process=":remote" />
综上:2个方案并行,可以解决应用进程Crash或者底层Crash的兜底方案,防止异常暴露。