关于Crash和ANR以及应用保活
来源:互联网 发布:bi工具竞品 知乎 编辑:程序博客网 时间:2024/05/02 05:04
如何记录应用crash
首先,引起应用crash的可能是我们对于NullPointer等未进行合理的处理导致的,所以这些抛出的异常没有被很好的处理,那么如果我们的应用发生了这样的crash,应该怎么去监听呢?
解决办法:
设置默认的未处理异常处理机制,例如:
public class CrashHandler implements Thread.UncaughtExceptionHandler { private static final String TAG = "CrashHandler"; private static CrashHandler instance = new CrashHandler(); public static CrashHandler getInstance() { return instance; } @Override public void uncaughtException(Thread thread, Throwable throwable) { Calendar calendar = null; calendar = Calendar.getInstance(); calendar.setTimeInMillis(SystemClock.currentThreadTimeMillis()); Log.d(TAG, "uncaughtException: here will show crash!"); Log.d(TAG, "uncaughtException: time = " + calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND) + ":" + calendar.get(Calendar.MILLISECOND)); Log.d(TAG, "uncaughtException: thread = " + thread + " = " + thread.getName()); Log.e(TAG, "uncaughtException: ", throwable); }}
在Application中设置
Thread.setDefaultUncaughtExceptionHandler(CrashHandler.getInstance());
这种方式下,当出现了未处理的异常的时候,我们可以在这里尝试处理,如果可以搞定或者是偶然性的异常,我们可以在这里尝试重新启动我们的应用,而且经过我们的处理之后并不会弹出crash窗口。
通过广播监听crash和anr
Android系统运行中,也会去捕获应用的crash和anr,如果捕获到这种情况就会调用AMS的handleApplicationCrash的函数:
[-->ActivityManagerService.java::handleApplicationCrash]public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) { ProcessRecord r = findAppProcess(app, "Crash"); ...... //调用addErrorToDropBox函数,第一个参数是一个字符串,为“crash” addErrorToDropBox("crash", r, null, null, null, null, null, crashInfo); ......}
接下来看addErrorToDropBox:
[-->ActivityManagerService.java::addErrorToDropBox]public void addErrorToDropBox(String eventType, ProcessRecord process, ActivityRecord activity, ActivityRecord parent, String subject, final String report, final File logFile, final ApplicationErrorReport.CrashInfo crashInfo) { /* dropbox日志文件的命名有一定的规则,其前缀都是一个特定的tag(标签), tag由两部分组成,合起来是“进程类型_事件类型”。 下边代码中的processClass函数返回该进程的类型,包括“system_server”、“system_app” 和“data_app”3种。eventType用于指定事件类型,目前也有3种类型:“crash”、“wtf” (what a terrible failure)和“anr” */ final String dropboxTag = processClass(process) + "_" + eventType; //获取DBMS Bn端的对象DropBoxManager final DropBoxManager dbox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE); /* 对于DBMS,不仅通过tag标识文件名,还可以根据配置的情况,允许或禁止特定tag日志 文件的记录。isTagEnable将判断DBMS是否禁止该标签,如果该tag已被禁止,则不允许记 录日志文件 */ if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; //创建一个StringBuilder,用于保存日志信息 final StringBuilder sb = new StringBuilder(1024); appendDropBoxProcessHeaders(process, sb); ......//将信息保存到字符串sb中 //单独启动一个线程用于向DBMS添加信息 Thread worker = new Thread("Error dump: " + dropboxTag) { @Override public void run() { if (report != null) { sb.append(report); } if (logFile != null) { try {//如果有log文件,那么把log文件内容读到sb中 sb.append(FileUtils.readTextFile(logFile, 128 * 1024, "\n\n[[TRUNCATED]]")); } ...... } //读取crashInfo信息,一般记录的是调用堆栈信息 if (crashInfo != null && crashInfo.stackTrace != null) { sb.append(crashInfo.stackTrace); } String setting = Settings.Secure.ERROR_LOGCAT_PREFIX + dropboxTag; //查询Settings数据库,判断该tag类型的日志是否对所记录的信息有行数限制,例如某些tag的日志文件只准记录1000行的信息 int lines = Settings.Secure.getInt(mContext.getContentResolver(), setting, 0); if (lines > 0) { sb.append("\n"); InputStreamReader input = null; try { //创建一个新进程以运行logcat,后面的参数都是logcat常用的参数 java.lang.Process logcat = new ProcessBuilder("/system/bin/logcat", "-v", "time", "-b", "events", "-b", "system", "-b", "main", "-t", String.valueOf(lines)) .redirectErrorStream(true).start(); //由于新进程的输出已经重定向,因此这里可以获取最后lines行的信息,不熟悉ProcessBuidler的读者可以查看SDK中关于它的用法说明 ...... } } //调用DBMS的addText dbox.addText(dropboxTag, sb.toString()); } }; if (process == null || process.pid == MY_PID) { worker.run(); //如果是SystemServer进程崩溃了,则不能在别的线程执行 } else { worker.start(); }}
很容易可以看出,上边主要是在生成错误日志,最后调用了addText将函数的内容传给了DBMS:
[-->DropBoxManager.java::addText]public void addText(String tag, String data) {/* mService和DBMS交互。DBMS对外只提供一个add函数用于日志添加,而DBM提供了3个函数, 分别是addText、addData、addFile,以方便使用*/ try { mService.add(new Entry(tag, 0, data)); } ......}
这里和service进行了交互,尝试去添加一个entry:
[-->DropBoxManagerService.java::add]public void add(DropBoxManager.Entry entry) { File temp = null; OutputStream output = null; final String tag = entry.getTag();//先取出这个Entry的tag try { int flags = entry.getFlags(); ...... //做一些初始化工作,包括生成dropbox目录、统计当前已有的dropbox文件信息等 init(); if (!isTagEnabled(tag)) return;//如果该tag被禁止,则不能生成日志文件 long max = trimToFit(); long lastTrim = System.currentTimeMillis(); //BlockSize一般是4KB byte[] buffer = new byte[mBlockSize]; //从Entry中得到一个输入流。与Java I/O相关的类比较多,且用法非常灵活,建议读者阅读《Java编程思想》中“Java I/O系统”一章 InputStream input = entry.getInputStream(); ...... int read = 0; while (read < buffer.length) { int n = input.read(buffer, read, buffer.length - read); if (n <= 0) break; read += n; } //先生成一个临时文件,命名方式为“drop线程id.tmp” temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp"); int bufferSize = mBlockSize; if (bufferSize > 4096) bufferSize = 4096; if (bufferSize < 512) bufferSize = 512; FileOutputStream foutput = new FileOutputStream(temp); output = new BufferedOutputStream(foutput, bufferSize); //生成GZIP压缩文件 if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) { output = new GZIPOutputStream(output); flags = flags | DropBoxManager.IS_GZIPPED; } /* DBMS很珍惜/data分区,若所生成文件的size大于一个BlockSize, 则一定要先压缩 */ ...... //写文件,这段代码非常烦琐,其主要目的是尽量节省存储空间 /* 生成一个EntryFile对象,并保存到DBMS内部的一个数据容器中。 DBMS除了负责生成文件外,还提供查询的功能,这个功能由getNextEntry完成。 另外,刚才生成的临时文件在createEntry函数中会重命为带标签的名字, 读者可自行分析createEntry函数 */ long time = createEntry(temp, tag, flags); temp = null; Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag); dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time); if (!mBooted) { dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } //发送DROPBOX_ENTRY_ADDED广播。系统中目前还没有程序接收该广播 mContext.sendBroadcast(dropboxIntent, android.Manifest.permission.READ_LOGS); }......}
可以看到这里会尝试去发送一个广播android.intent.action.DROPBOX_ENTRY_ADDED
所以我们如果定义一个广播接收器,接收广播即可捕获广播,不过这样子需要权限
<uses-permission android:name="android.permission.READ_LOGS"/>
在安卓4.1的版本之上,为了LogCat的安全性,想要拿到此项权限必须是System签名或者Root的App或者adb才能看到这些东西,所以这种方式现在很难做到!
在Android4.4的版本上可以尝试使用如下代码(估计之后的版本都需要Root):
public void requestPermission(){ String pname = getPackageName(); String[] CMDLINE_GRANTPERMS = { "su", "-c", null }; if (getPackageManager().checkPermission(android.Manifest.permission.READ_LOGS, pname) != 0) { if (android.os.Build.VERSION.SDK_INT >= 16) { try { // format the commandline parameter CMDLINE_GRANTPERMS[2] = String.format("pm grant %s android.permission.READ_LOGS", pname); java.lang.Process p = Runtime.getRuntime().exec(CMDLINE_GRANTPERMS); int res = p.waitFor(); if (res != 0) throw new Exception("failed to become root"); } catch (Exception e) { } } } }
在Android6.0以上发现,动态申请权限毫无用处,尝试了各种手段后都无法申请,所以如果你搞定了这个,请联系我,联系方式:im.noclay@gmail.com。 谢谢Thanks♪(・ω・)ノ
如何对自己的应用保活
- 采用双进程保活机制,利用另一个进程的service对自己的应用判断存活与否,否则则重新启动自己的应用
- 将应用设为system应用,并添加persistent=”true”的属性,安卓系统会在应用被强杀的时候尝试重新启动。
- 关于Crash和ANR以及应用保活
- 关于TCP保活功能及其应用
- Android 调试native的crash和anr
- 应用保活套路
- LWIP[转]关于TCP保活功能及其应用
- 关于保活定时器
- Crash和ANR简介及一些测试方法
- Android ANR和Crash问题小结--分析log
- 关于ANR和Force Close
- android应用保活机制
- Android 应用保活笔记
- android应用保活1
- 关于进程保活问题
- 关于ANR
- 应用双进程白色保活
- 关于保活,两种可行方式
- 关于service保活的思考
- 关于进程保活的三两事
- 《深度学习Ng》课程学习笔记01week1——深度学习概论
- OSI七层协议
- HAOI2015[BZOJ2110] T1 【树形DP】
- github 中的 watch、star、fork
- 堆排序
- 关于Crash和ANR以及应用保活
- 来到博客的第一天
- 重载和重写的区别
- A GENTLE GUIDE TO USING BATCH NORMALIZATION IN TENSORFLOW
- yii文件上传
- Spring Boot集成MyBatis——注解方式
- 第3章 揭秘C的语法
- 双边滤波器原理及Matlab实现
- SSH协议(2)-安全威胁及解决办法