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的兜底方案,防止异常暴露。
1741

被折叠的 条评论
为什么被折叠?



