android优化-ANR

概述

  1. 什么是ANR?
    ANR全名为Application Not Reaponding,也就是"应用无响应",当操作在一段时间内系统无法处理时,系统层面会弹出如下的ANR对话框

    《android优化-ANR》 ANR

  2. 为什么会产生ANR
    在Android里,App的响应能力是有Activity Manage和Windows Manger系统服务来监控的通常在如下两种情况下会弹出ANR对话框:
  • 5s内无法响应用户输入事件(例如键盘输入,触摸屏幕等).
  • BroadcastReceiver在10s内无法结束.
    造成以上两种情况的首要原因就是在主线程(UI线程)里面做了太多的阻塞耗时操作,例如文件读写,数据库读写,网络查询等.
  1. 如何避免ANR
    知道了ANR产生的原因,那么想要避免ANR,也就很简单了,就一条规则:

不要在主线程(UI线程)里面做繁重的操作.
这里面实际涉及两个问题:

  1. 哪些地方是运行在主线程的?
  2. 不在主线程做,在哪做?

ANR分析

  1. 获取ANR产生的trace文件
    ANR产生时,系统会生成一个traces.txt的文件放在/data/anr/下.可以通过adb命令将其导出到本地:
$adb pull data/anr/traces.txt .
  1. 分析traces.txt
    获取到的tracs.txt文件一般去下:

如下是ANR产生所生成的tracs.txt文件

----- pid 2976 at 2016-09-08 23:02:47 -----
Cmd line: com.anly.githubapp  // 最新的ANR发生的进程(包名)

...

DALVIK THREADS (41):
"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x73467fa8 self=0x7fbf66c95000
  | sysTid=2976 nice=0 cgrp=default sched=0/0 handle=0x7fbf6a8953e0
  | state=S schedstat=( 0 0 0 ) utm=60 stm=37 core=1 HZ=100
  | stack=0x7ffff4ffd000-0x7ffff4fff000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x35fc9e33> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x35fc9e33> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:985) // 主线程中sleep过长时间, 阻塞导致无响应.
  at com.tencent.bugly.crashreport.crash.c.l(BUGLY:258)
  - locked <@addr=0x12dadc70> (a com.tencent.bugly.crashreport.crash.c)
  at com.tencent.bugly.crashreport.CrashReport.testANRCrash(BUGLY:166)  // 产生ANR的那个函数调用
  - locked <@addr=0x12d1e840> (a java.lang.Class<com.tencent.bugly.crashreport.CrashReport>)
  at com.anly.githubapp.common.wrapper.CrashHelper.testAnr(CrashHelper.java:23)
  at com.anly.githubapp.ui.module.main.MineFragment.onClick(MineFragment.java:80) // ANR的起点
  at com.anly.githubapp.ui.module.main.MineFragment_ViewBinding$2.doClick(MineFragment_ViewBinding.java:47)
  at butterknife.internal.DebouncingOnClickListener.onClick(DebouncingOnClickListener.java:22)
  at android.view.View.performClick(View.java:4780)
  at android.view.View$PerformClick.run(View.java:19866)
  at android.os.Handler.handleCallback(Handler.java:739)
  at android.os.Handler.dispatchMessage(Handler.java:95)
  at android.os.Looper.loop(Looper.java:135)
  at android.app.ActivityThread.main(ActivityThread.java:5254)
  at java.lang.reflect.Method.invoke!(Native method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

拿到trace信息后,一切好说.
如上trace信息中的添加的中文注释已经基本说明了trace文件应该怎么分析:
1文件最上的即为最新产生的ANR的trace信息
2前面两行表明ANR发生的进程pid,时间,以及进程名字;
3寻找我们的代码点.然后往前推,看方法调用栈,追溯到问题产生的根源
>以上的ANR trace是属于相对简单,还有可能你并没有在主线程中做过于耗时的操作,然而还是ANR了,这就有可能是如下两种情况:

CPU满负荷

这个时候你看到的trace信息可能会包含这样的信息:

Process:com.anly.githubapp
...
CPU usage from 3330ms to 814ms ago:
6% 178/system_server: 3.5% user + 1.4% kernel / faults: 86 minor 20 major
4.6% 2976/com.anly.githubapp: 0.7% user + 3.7% kernel /faults: 52 minor 19 major
0.9% 252/com.android.systemui: 0.9% user + 0% kernel
...

100%TOTAL: 5.9% user + 4.1% kernel + 89% iowait

最后一句表明了:
1当时CPU占用100%,满负荷了.
2其中绝大多数是被iowait即I/O操作占用了.
此时分析方法调用栈,一般来说会发现方法中有频繁的文件读取或是数据库读写操作放在主线程来做.

内存原因

其实内存原因有可能会导致ANR,例如如果由于内存泄漏,App可使用内存所剩无几,我们点击按钮启动一个大图片作为背景的activity,就可能会产生ANR,这时可能trace信息是这样的:

// 以下trace信息来自网络, 用来做个示例
Cmdline: android.process.acore

DALVIK THREADS:
"main"prio=5 tid=3 VMWAIT
|group="main" sCount=1 dsCount=0 s=N obj=0x40026240self=0xbda8
| sysTid=1815 nice=0 sched=0/0 cgrp=unknownhandle=-1344001376
atdalvik.system.VMRuntime.trackExternalAllocation(NativeMethod)
atandroid.graphics.Bitmap.nativeCreate(Native Method)
atandroid.graphics.Bitmap.createBitmap(Bitmap.java:468)
atandroid.view.View.buildDrawingCache(View.java:6324)
atandroid.view.View.getDrawingCache(View.java:6178)

...

MEMINFO in pid 1360 [android.process.acore] **
native dalvik other total
size: 17036 23111 N/A 40147
allocated: 16484 20675 N/A 37159
free: 296 2436 N/A 2732

可以看到free的内存已经所剩无几

当然这种情况可能更多的是得到OOM的异常

ANR的处理

针对三种情况,一般的处理情况如下:

  1. 主线程阻塞的
    开辟单独的子线程来处理耗时阻塞事物
  2. CPU满负荷,I/O阻塞
    I/O阻塞一般来说就是文件读写或数据库操作执行在主线程了,也可以通过开辟子线程的方式异步执行.
  3. 内存不够用
    增大VM内存,使用largeHeap属性,排查内存泄漏.

深入了解ANR

哪些地方是执行在主线程的

  1. Activity的所有生命周期都是执行主线程的.
  2. Service默认是在执行主线程的.
  3. BroadcastRecevier的onReceive回调是执行在主线程的
  4. 没有使用子线程的looper的Handler的handleMessage,post(Runnable)是执行在主线程的.
  5. AsyncTask的回调中除了doInBackground,其他都是执行在主线程的.
  6. View的post(Runnable)是执行在主线程的.

使用子线程的方式有哪些?

  1. 启动Thread方式
    这个其实也是java实现多线程的方式.有两种实现方法,继承Thread或实现Runnable接口.
  2. 使用AsyncTask
    这个是Android特有的方式,AsyncTask顾名思义,就是异步任务的意思.
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    // Do the long-running work in here
    // 执行在子线程
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    // This is called each time you call publishProgress()
    // 执行在主线程
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    // This is called when doInBackground() is finished
    // 执行在主线程
    protected void onPostExecute(Long result) {
        showNotification("Downloaded " + result + " bytes");
    }
}

// 启动方式
new DownloadFilesTask().execute(url1, url2, url3);
  1. HandlerThread
    Android中结合Handler和Thread的一种方式,默认情况下Handler的handleMessage是执行在主线程的,但是如果我给这个Handler传入了子线程的looper,handleMessage就会执行在这个子线程中的.HandlerThread正是这样一个结合体:
// 启动一个名为new_thread的子线程
HandlerThread thread = new HandlerThread("new_thread");
thread.start();

// 取new_thread赋值给ServiceHandler
private ServiceHandler mServiceHandler;
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);

private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
      super(looper);
    }
    
    @Override
    public void handleMessage(Message msg) {
      // 此时handleMessage是运行在new_thread这个子线程中了.
    }
}
  1. InterService
    Service是运行在主线程的,然而IntentService是运行在子线程的.实际上IntentService就是实现了一个HandlerTHread + ServiceHandler的模式.
  2. 特别提醒
    使用Thread和HandlerThread时,为了使效果更好,建议设置Thread的优先级偏低一些:
Process.setTHreadPriority(THREAD_PRIORITY_BACKGROUND);

如果没有做任何优先级设置的话,你创建的Thread默认和UI Thread是具有同样的优先级的.你懂得,同样的优先级的Thread,CPU调度上还是会阻塞掉你的UI Thread,导致ANR的.

文章来自:
https://www.jianshu.com/p/6d855e984b99

    原文作者:小庄bb
    原文地址: https://www.jianshu.com/p/a52d0063838e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞