Android优化笔记--崩溃优化

      崩溃率是衡量一个应用质量高低的基本指标,这一点是大部分开发者都比较认可的;Android 的两种崩溃类型:     Android 崩溃分为 Java 崩溃和 Native 崩溃;
     Java 崩溃就是在 Java 代码中,出现了未捕获异常,导致程序异常退出。Native 崩溃又是怎么产生的呢?一般都是因为在 Native 代码中访问非法地址,也可能是地址对齐出现了问题,或者发生了程序主动 abort,这些都会产生相应的 signal 信号,导致程序异常退出。所以,“崩溃”就是程序出现异常,而一个产品的崩溃率,跟我们如何捕获、处理这些异常有比较大的关系。Java 崩溃的捕获比较简单;而Native崩溃,很多同学都比较懵;因为大部分做应用层开发的同学对c/c++不太熟悉,所以看到这些日志比较费力;一般 一个完整的Native崩溃从捕获到解析要经历如下流程:

  1 编译端。编译 C/C++ 代码时,需要将带符号信息的文件保留下来。
  2 客户端。捕获到崩溃时候,将收集到尽可能多的有用信息写入日志文件,然后选择合适的时机上传到服务器。
  3 服务端。读取客户端上报的日志文件,寻找适合的符号文件,生成可读的 C/C++ 调用栈。

 解决崩溃思路如下:

 1 查看崩溃现场

 常见崩溃如下:

数组越界:

Process: com.xxx.tony, PID: 17736
    java.lang.IndexOutOfBoundsException: Invalid index 1, size is 1
        at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
        at java.util.ArrayList.get(ArrayList.java:308)
        at com.xxx.tony.view.adapter.RecyclerViewAdapter.isItemHeader(RecyclerViewAdapter.java:81)
        at com.xxx.tony.view.adapter.StickHeaderDecoration.onDrawOver(StickHeaderDecoration.java:98)
        at android.support.v7.widget.RecyclerView.draw(RecyclerView.java:4111)
        at android.view.View.updateDisplayListIfDirty(View.java:14167)
        at android.view.View.getDisplayList(View.java:14189)
        at android.view.View.draw(View.java:14959)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3405)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3198)
        at android.view.View.updateDisplayListIfDirty(View.java:14162)
        at android.view.View.getDisplayList(View.java:14189)
        at android.view.View.draw(View.java:14959)
        at android.view.ViewGroup.drawChild(ViewGroup.java:3405)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3198)
        at android.view.View.updateDisplayListIfDirty(View.java:14162)
        at android.view.View.getDisplayList(View.java:14189)

1. 1崩溃信息从崩溃的基本信息,我们可以对崩溃有初步的判断。进程名、线程名。崩溃的进程是前台进程还是后台进程,崩溃是不是发生在 UI 线程。崩溃堆栈和类型。崩溃是属于 Java 崩溃、Native 崩溃,还是 ANR,对于不同类型的崩溃我们关注的点也不太一样。特别需要看崩溃堆栈的栈顶,看具体崩溃在系统的代码,还是我们自己的代码里面。

2. 系统信息

       系统的信息有时候会带有一些关键的线索,对我们解决问题有非常大的帮助。Logcat。这里包括应用、系统的运行日志。由于系统权限问题,获取到的 Logcat 可能只包含与当前 App 相关的。其中系统的 event logcat 会记录 App 运行的一些基本情况,记录在文件 /system/etc/event-log-tags 中。还需要考虑机型、系统、厂商、CPU、ABI、Linux 版本,设备状态:是否 root、是否是模拟器等等:

3. 内存信息
OOM、ANR、虚拟内存耗尽等,很多崩溃都跟内存有直接关系。一般情况下,手机内存越小,崩溃的概率会越高:

3.1 系统剩余内存。关于系统内存状态,可以直接读取文件 /proc/meminfo。当系统可用内存很小(低于 MemTotal 的 10%)时,OOM、大量 GC、系统频繁自杀拉起等问题都非常容易出现。

3.2 应用使用内存。包括 Java 内存、RSS(Resident Set Size)、PSS(Proportional Set Size),我们可以得出应用本身内存的占用大小和分布。PSS 和 RSS 通过 /proc/self/smap 计算,可以进一步得到例如 apk、dex、so 等更加详细的分类统计。

3.3 虚拟内存。虚拟内存可以通过 /proc/self/status 得到,通过 /proc/self/maps 文件可以得到具体的分布情况。有时候我们一般不太重视虚拟内存,但是很多类似 OOM、tgkill 等问题都是虚拟内存不足导致的。

Name:     com.sample.name   // 进程名
FDSize:   800               // 当前进程申请的文件句柄个数
VmPeak:   3004628 kB        // 当前进程的虚拟内存峰值大小
VmSize:   2997032 kB        // 当前进程的虚拟内存大小
Threads:  600               // 当前进程包含的线程个数

4. 资源信息
有的时候我们会发现应用堆内存和设备内存都非常充足,还是会出现内存分配失败的情况,这跟资源泄漏可能有比较大的关系。

5. 应用信息
除了系统,其实我们的应用更懂自己,可以留下很多相关的信息。崩溃场景。崩溃发生在哪个 Activity 或 Fragment,发生在哪个业务中。关键操作路径。不同于开发过程详细的打点日志,我们可以记录关键的用户操作路径,这对我们复现崩溃会有比较大的帮助。其他自定义信息。不同的应用关心的重点可能不太一样。

 

常见获得logcat和Jave堆栈的方法:
一. 获取logcat
logcat日志流程是这样的,应用层 --> liblog.so --> logd,底层使用ring buffer来存储数据。
获取的方式有以下三种:
   1. 通过logcat命令获取。
   优点:非常简单,兼容性好。
   缺点:整个链路比较长,可控性差,失败率高,特别是堆破坏或者堆内存不足时,基本会失败。
   2. hook liblog.so实现。通过hook liblog.so 中__android_log_buf_write 方法,将内容重定向到自己的buffer中。
   优点:简单,兼容性相对还好。
   缺点:要一直打开。
   3. 自定义获取代码。通过移植底层获取logcat的实现,通过socket直接跟logd交互。
   优点:比较灵活,预先分配好资源,成功率也比较高。
   缺点:实现非常复杂

二. 获取Java 堆栈
   native崩溃时,通过unwind只能拿到Native堆栈。我们希望可以拿到当时各个线程的Java堆栈
   1. Thread.getAllStackTraces()。
    优点:简单,兼容性好。
    缺点:
        a. 成功率不高,依靠系统接口在极端情况也会失败。
        b. 7.0之后这个接口是没有主线程堆栈。
        c. 使用Java层的接口需要暂停线程
   2. hook libart.so。通过hook ThreadList和Thread的函数,获得跟ANR一样的堆栈。为了稳定性,我们会在fork子进程执行。
   优点:信息很全,基本跟ANR的日志一样,有native线程状态,锁信息等等。
   缺点:黑科技的兼容性问题,失败时可以用Thread.getAllStackTraces()兜底

你可能感兴趣的:(android优化,android)