未处理异常处理器 UncaughtExceptionHandler 实现 崩溃日志保存 与 重启应用
2016-05-26 13:59
746 查看
前言
当我们编写程序的时候 , 遇到会抛出异常的方法的时候 , 我们一般会采取 try … catch 的方式:try { bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri)); } catch (FileNotFoundException e) { e.printStackTrace(); return null; }
但是万一我们捕捉不到的时候 , 程序就会FC , 弹出 某某某应用程序已停止运行 的对话框. 然后用户在受到打击之后还要重新点开应用 这从两点来说都是很不好的 , 也是这就我们这次要实现的功能
当APP在用户使用的时候 , 我们无法处理得到的异常
应用在崩溃之后没有响应的处理
说个题外的小技巧
手动获取日志的方法 (需要ROOT权限)
/data/system/dropbox 下存有所有应用的异常日志文件 , 非常时候可以手动提取
生成的日志文件:
崩溃后的提示:
重启后显示上次的异常日志:
开始编写异常处理器
要使用到的一些参数
/** * Created by OCWVAR * Package: com.ocwvar.surfacetest.ExceptionHandler * Date: 2016/5/25 9:20 * Project: SurfaceTest * 未处理异常接收器 * * 在重新启动Activity时会传递数据包 Bundle * 也可直接使用 OCExceptionHandler.handleIncomingBundle() 方法进行处理 * * 数据: * IsRecover .布尔类型. 区别这个数据是否为崩溃重启的数据 永远为 true * hasLogs .布尔类型. 是否成功生成了日志文件 * Throwable .Serializable序列化类型. 上次崩溃的异常对象 * * * 参数: * SLEEPTIME_RESTART_ACTIVITY 重新启动应用程序指定Activity间隔时间. 毫秒. 1000ms = 1s * RESTART_ACTIVITY 重新启动的Activity类 * LOG_NAME_HEAD 日志文件名开头 * LOG_SAVE_FOLDER 日志保存目录 * SAVE_LOGS 是否生成日志 */ public class OCExceptionHandler{ private final static long SLEEPTIME_RESTART_ACTIVITY = 2000; private final static Class RESTART_ACTIVITY = MainActivity.class; private final static String LOG_NAME_HEAD = "OCLog"; private final static String LOG_SAVE_FOLDER = "/log/"; private final static boolean SAVE_LOGS = true; public final static String THROWABLE_OBJECT = "Throwable"; public final static String IS_RECOVERY = "IsRecover"; public final static String HAS_LOGS = "hasLogs"; private boolean logsCreated = false; ... }
1.引用 Thread.UncaughtExceptionHandler 接口 继承 Application 类
(继承Application只是为了设置 setDefaultUncaughtExceptionHandler , 和使用 ApplicationContext 而已 , 如果可以有其他实现方式可以不继承)这是就是我们一开始最基础的配置 public class OCExceptionHandler extends Application implements Thread.UncaughtExceptionHandler { @Override public void onCreate() { super.onCreate(); //设置捕捉全局未处理异常为我们这个类 Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(Thread thread, Throwable ex){ ... }
2.在程序崩溃之后 , 启动对应的Activity
/** * 重新启动应用程序 * @param activityClass 要启动的Activity */ private void restartActivity(Class activityClass , Throwable throwable){ //创建用于启动的 Intent , 与对应的数据 Intent intent = new Intent(getApplicationContext(),activityClass); intent.putExtra("IsRecover",true); intent.putExtra("hasLogs",logsCreated); intent.putExtra("Throwable",throwable); PendingIntent pendingIntent = PendingIntent.getActivity( getApplicationContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT ); //获取闹钟管理器 , 用于定时执行我们的启动任务 AlarmManager mgr = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE); //设置执行PendingIntent的时间是当前时间+SLEEPTIME_RESTART_ACTIVITY 参数的值 mgr.set(AlarmManager.RTC, System.currentTimeMillis() + SLEEPTIME_RESTART_ACTIVITY , pendingIntent); }
3.在程序崩溃的时候记录下日志文件
/** * 创建日志文件 * @param throwable 要记录的异常 * @return 执行结果 */ private boolean createLogs(Throwable throwable){ if (throwable == null || !SAVE_LOGS){ return false; } if (Build.VERSION.SDK_INT >= 23 && !checkPermission()){ Log.e("异常处理--保存日志", "保存失败 , Android 6.0+ 系统. 内存卡读写权限没有获取" ); return false; } if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ //如果内存卡或内置储存已经挂载 //创建储存目录 String savePath = Environment.getExternalStorageDirectory().getPath()+LOG_SAVE_FOLDER; File file = new File(savePath); file.mkdirs(); if (file.canWrite()){ //如果目录可以写入 FileWriter fileWriter; PrintWriter printWriter; //得到当前的日期与时间 , 精确到秒 String exceptionTime = DateFormat.format("yyyy-MM-dd hh:mm:ss", new Date()).toString(); //创建日志文件对象 file = new File(savePath + LOG_NAME_HEAD + " " + exceptionTime + " .log"); try { if (file.createNewFile()){ //如果文件创建成功 , 则写入文件 fileWriter = new FileWriter(file,true); printWriter = new PrintWriter(fileWriter); printWriter.println("Date:"+exceptionTime+"\n"); printWriter.println("Exception Class Name: "); printWriter.println(throwable.getStackTrace()[0].getClassName()); printWriter.println(""); printWriter.println("Exception Class Position: "); printWriter.println("Line number: "+throwable.getStackTrace()[0].getLineNumber()); printWriter.println(""); printWriter.println("Exception Cause: "); printWriter.println(throwable.getMessage()); printWriter.println(""); printWriter.println("-----------------------------------\nException Message: \n"); for (int i = 0; i < throwable.getStackTrace().length; i++) { printWriter.println(throwable.getStackTrace()[i]); } //清空与关闭用到的流 printWriter.flush(); fileWriter.flush(); printWriter.close(); fileWriter.close(); Log.w("异常处理--保存日志", "日志保存成功" ); return true; }else { Log.e("异常处理--保存日志", "保存失败 , 存在相同名称的日志文件" ); return false; } } catch (IOException e) { Log.e("异常处理--保存日志", "保存失败 , 无法创建日志文件或写入流失败" ); return false; } }else { //目录不可写入 , 操作失败 Log.e("异常处理--保存日志", "保存失败 , 无法写入目录" ); file = null; return false; } }else { Log.e("异常处理--保存日志", "保存失败 , 储存未挂载" ); return false; } } /** * 检查内存卡读写权限 针对Android 6.0+ * @return 是否有权限 */ @TargetApi(23) private boolean checkPermission(){ return checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; }
4.结尾工作 , 使用我们创建的方法
在方法public void uncaughtException(Thread thread, Throwable ex)中调用我们的方法@Override public void uncaughtException(Thread thread, Throwable ex) { //记录日志生成结果 , 用于给Intent传递日志生存的结果 logsCreated = createLogs(ex); restartActivity(RESTART_ACTIVITY , ex); //应用已经崩溃, 需要先终止当前的应用线程. 否则会ANR System.exit(2); }
然后在配置文件中使用我们这个类 (如果你是用你自己的Application类就不需要了):
<application android:icon="@mipmap/ic_launcher" android:label="TEST TEST TEST" android:theme="@style/AppTheme" //在这里进行注册Application类 android:name=".ExceptionHandler.OCExceptionHandler"> ... </application>
注意
有的大佬可能会想在程序崩溃的时候显示一个Toast或对话框 , 但这是不行的 , 无论你是直接执行还是用Handler .我看了下Stack Overflow上的QA , 有个人提出的观点 , 也是我最认同的观点 :当应用崩溃的时候 , 已经没有能用的ApplicationContext了 ,所以你用了之后都是没反应的.
但我发现如果你用的是Activity.Context就能达到目的 , 但是这会导致Activity无法被回收的风险 , 所以非常不建议这么用. 毕竟为了显示一句话而导致内存泄漏这就捡了芝麻丢了西瓜.
弥补缺点
我们既然不方便在 UncaughtExceptionHandler 里面进行提示 , 但我们既然会传 Intent 给启动的Activity , 那么我们直接用它来做就行了./** * 处理上次崩溃重启传回Activity的Bundle数据 * @param bundle 传入的Bundle数据 * @return True: 处理成功 False:不是上次崩溃时传入的数据 */ public static boolean handleIncomingBundle(@NonNull Bundle bundle , Context context){ //判断这个Bundle是不是我们崩溃后传回的数据 if (bundle.get(IS_RECOVERY) != null){ if (bundle.getBoolean(HAS_LOGS)){ Toast.makeText(context, "程序已恢复 , 崩溃日志已生成", Toast.LENGTH_SHORT).show(); }else { Toast.makeText(context, "程序已恢复", Toast.LENGTH_SHORT).show(); } Throwable throwable = (Throwable)bundle.getSerializable(THROWABLE_OBJECT); if (throwable != null){ Log.e("上次崩溃日志", "---------------------------------------------"); for (int i = 0; i < throwable.getStackTrace().length; i++) { System.out.println(throwable.getStackTrace()[i]); } Log.e("上次崩溃日志", "---------------------------------------------"); }else { Log.e("上次崩溃日志", "日志丢失 或 记录失败 !"); } return true; }else { return false; } }
在要启动的Activity中进行使用即可:
if (getIntent().getExtras() != null){ OCExceptionHandler.handleIncomingBundle(getIntent().getExtras(),getApplicationContext()); }
大佬们看完有啥要说的啊 ~~~
点个顶呗 QAQ
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories