Android异常crash监听

背景

目前做的项目为座舱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);
    }

    @Override
    public 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);
            //发送崩溃广播通知其他APP
            MyUtils.sendApaExitBroadcast(BaseConfigApplication.getApplication());
            //  off
            MyUtils.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)){
            //拦截DeadSystemException
            return 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() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            LogUtils.i(TAG, "AIDLAliveHelp----onServiceDisconnected");
            mIAliveInterface = null;

        }

        @Override
        public 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() {
        @Override
        public 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() {
                    @Override
                    public 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 {
	 @Override
    public void onCreate() {
        super.onCreate();
		.......

        mAidlAliveHelp = new AIDLAliveHelp(this);

		.......
    }

  	@RequiresApi(api = Build.VERSION_CODES.R)
    @Override
    public 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;
    }

 	@Override
    public void onDestroy() {
        if (mAidlAliveHelp != null) {
            mAidlAliveHelp.unbindService();
        }
        super.onDestroy();
    }


}

AidlService配置:独立的远程进程。

<service
            android:name=".service.AIDLService"
            android:enabled="true"
            android:exported="true"
            android:permission="com.hozonauto.panoramic.permission.REMOTE_CONTROL"
            android:process=":remote" />

综上:2个方案并行,可以解决应用进程Crash或者底层Crash的兜底方案,防止异常暴露。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值