Android ANR优化(二)

Android ANR优化 2

在实际情况中,当Android项目的用户量特别大时候,一些细小的问题也会被放大,ANR问题就是一个典型的例子。
一些ANR问题只会发生在用户实际使用的情景,当系统资源比较紧张等一些特殊情况下才会遇到,而这些ANR问题有很大一部分是因为我们的代码不合理导致,这就需要我们定位问题,修复问题,并且在以后的代码设计中尽量避免这些不合理。
*近工作中集中分析了项目的大量的用户自动上报的ANR问题日志,虽然网上ANR相关的文章已经很多了,在这里还是做一个总结。

提纲

一. 什么情况下会出现ANR
二. ANR机制的原理
三. 如何分析ANR问题
四. 如何避免ANR问题

一.什么情况下会出现ANR问题:

ANR(Application Not responding)。Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR。
具体来说,ANR会在以下几种情况中出现:

  1. 输入事件(按键和触摸事件)5s内没被处理: Input event dispatching timed out
  2. BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s):Timeout of broadcast BroadcastRecord
    07-27 19:18:47.448 1707 1766 W BroadcastQueue: Receiver during timeout: ResolveInfo{ccd831e com.example.qintong.myapplication/.MyBroadCastReciever m=0x108000}
    07-27 19:18:47.502 3513 3728 I WtEventController: ANR com.example.qintong.myapplication 7573
  3. service 前台20s后台200s未完成启动 Timeout executing service
  4. ContentProvider的publish在10s内没进行完:timeout publishing content providers
    在android文档(https://developer.android.com/training/articles/perf-anr.html)中,只写了*种和第二种情况,而根据源码和实际的实验,我们能发现service的启动和provider的publish同样会造成anr问题。
    这里需要注意的是,在后三种情况,以BroadcastReviever为例,在onRecieve()方法执行10秒内没发生*种ANR(也就是在这个过程中没有输入事件或输入事件还没到5s)才会发生Receiver timeout,否则将先发生事件无相应ANR,所以onRecieve()是有可能执行不到10s就发生ANR的,所以不要在onRecieve()方法里面干活,service的onCreate()和ContentProvider的onCreate()也一样,他们都是主线程的,不要在这些方法里干活,这个会在本文*后再细说。

二.ANR机制的实现原理:

文章:http://gityuan.com/2016/07/02/android-anr/从源码角度详细的分析了ANR机制实现的原理。对于上一章讲到的1-4中情况,分别找到了其源码中是如何实现的,对于每一种大概原理如下:1.在进行相关操作调用hander.sendMessageAtTime()发送一个ANR的消息,延时时间为ANR发生的时间(如前台Service是当前时间20s之后)。2.进行相关的操作3.操作结束后向remove掉该条message。如果相关的操作在规定时间没有执行完成,该条message将被handler取出并执行,就发生了ANR。
下面以BroadcastReceiver为例详细介绍:
BroadcastQueue.processNextBroadcast()

     final void processNextBroadcast(boolean fromMsg) {
        ...
        synchronized (mService) {
            ...
            do {
                if (mOrderedBroadcasts.size() == 0) {
                    ...
                    if (mService.mProcessesReady && r.dispatchTime > 0) {
                        long now = SystemClock.uptimeMillis();
                        if ((numReceivers > 0) &&
                                (now > r.dispatchTime + (2 * mTimeoutPeriod * numReceivers))) {
                            //1.发送延时消息
                            broadcastTimeoutLocked(false); // forcibly finish this broadcast
                            forceReceive = true;
                            r.state = BroadcastRecord.IDLE;
                        }
                    }

                    if (r.state != BroadcastRecord.IDLE) {
                        if (DEBUG_BROADCAST) Slog.d(TAG,
                                "processNextBroadcast("
                                        + mQueueName + ") called when not idle (state="
                                        + r.state + ")");
                        return;
                    }

                    if (r.receivers == null || r.nextReceiver >= numReceivers
                            || r.resultAbort || forceReceive) {
                        // No more receivers for this broadcast!  Send the final
                        // result if requested...
                        if (r.resultTo != null) {
                            try {
                                //2. 处理广播消息
                                performReceiveLocked(r.callerApp, r.resultTo,
                                        new Intent(r.intent), r.resultCode,
                                        r.resultData, r.resultExtras, false, false, r.userId);
                                // Set this to null so that the reference
                                // (local and remote) isn't kept in the mBroadcastHistory.
                                r.resultTo = null;
                            } catch (RemoteException e) {
                                ...
                            }
                        }
                        //3.取消延时消息
                        cancelBroadcastTimeoutLocked();
                        ...
                    }
                } while (r == null) ;
                ...
            }
        }
    }

1.发送延时消息:broadcastTimeoutLocked(false):

    final void broadcastTimeoutLocked(boolean fromMsg) {
    ...
        long now = SystemClock.uptimeMillis();
        if (fromMsg) {
            if (mService.mDidDexOpt) {
                // Delay timeouts until dexopt finishes.
                mService.mDidDexOpt = false;
                long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
                setBroadcastTimeoutLocked(timeoutTime);
                return;
            }
            if (!mService.mProcessesReady) {
                return;
            }

            long timeoutTime = r.receiverTime + mTimeoutPeriod;
            if (timeoutTime > now) {
                setBroadcastTimeoutLocked(timeoutTime);
                return;
            }
        }

他调用了setBroadcastTimeoutLocked(long timeoutTime):

    final void setBroadcastTimeoutLocked(long timeoutTime) {
        if (! mPendingBroadcastTimeoutMessage) {
            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
            mHandler.sendMessageAtTime(msg, timeoutTime);
            mPendingBroadcastTimeoutMessage = true;
        }
    }

传入setBroadcastTimeoutLocked(long timeoutTime)的时间xxx + mTimeoutPeriod,mTimeoutPeriod就是onRecieve()可以执行的时间,在BroadcastQueue初始化时候被赋值,前台队列为10s后台队列为60s:
ActivityManagerService.java:

    public ActivityManagerService(Context systemContext) {
        ...
        static final int BROADCAST_FG_TIMEOUT = 10 * 1000;
        static final int BROADCAST_BG_TIMEOUT = 60 * 1000;
        ...
        mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
                "foreground", BROADCAST_FG_TIMEOUT, false);
        mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
                "background", BROADCAST_BG_TIMEOUT, true);
        ...
    }
  1. performReceiveLocked()为广播的实际处理,就不展开了
  2. cancelBroadcastTimeoutLocked() :
    该方法的主要工作是当service启动完成,则移除服务超时消息SERVICE_TIMEOUT_MSG。
    final void cancelBroadcastTimeoutLocked() {
        if (mPendingBroadcastTimeoutMessage) {
            mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
            mPendingBroadcastTimeoutMessage = false;
        }
    }

三.如何分析ANR问题:

从前文可以明确,ANR问题是由于主线程的任务在规定时间内没处理完任务,而造成这种情况的原因大致会有一下几点:

  1. 主线程在做一些耗时的工作
  2. 主线程被其他线程锁
  3. cpu被其他进程占用,该进程没被分配到足够的cpu资源。

判断一个ANR属于哪种情况便是分析ANR问题的关键。那么拿到一个anr的日志,应该如何分析呢?
在发生ANR的时候,系统会收集ANR相关的信息提供给开发者:首先在Log中有ANR相关的信息,其次会收集ANR时的CPU使用情况,还会收集trace信息,也就是当时各个线程的执行情况。trace文件保存到了/data/anr/traces.txt中,此外,ANR前后该进程打印出的log也有一定价值。一般来说可以按一下思路来分析:

  1. 从log中找到ANR反生的信息:可以从log中搜索“ANR in”或“am_anr”,会找到ANR发生的log,该行会包含了ANR的时间、进程、是何种ANR等信息,如果是BroadcastReceiver的ANR可以怀疑BroadCastReceiver.onRecieve()的问题,如果的Service或Provider就怀疑是否其onCreate()的问题。
  2. 在该条log之后会有CPU usage的信息,表明了CPU在ANR前后的用量(log会表明截取ANR的时间),从各种CPU Usage信息中大概可以分析如下几点:
    (1). 如果某些进程的CPU占用百分比较高,几乎占用了所有CPU资源,而发生ANR的进程CPU占用为0%或非常低,则认为CPU资源被占用,进程没有被分配足够的资源,从而发生了ANR。这种情况多数可以认为是系统状态的问题,并不是由本应用造成的。
    (2). 如果发生ANR的进程CPU占用较高,如到了80%或90%以上,则可以怀疑应用内一些代码不合理消耗掉了CPU资源,如出现了死循环或者后台有许多线程执行任务等等原因,这就要结合trace和ANR前后的log进一步分析了。
    (3). 如果CPU总用量不高,该进程和其他进程的占用过高,这有一定概率是由于某些主线程的操作就是耗时过长,或者是由于主进程被锁造成的。
  3. 除了上述的情况(1)以外,分析CPU usage之后,确定问题需要我们进一步分析trace文件。trace文件记录了发生ANR前后该进程的各个线程的stack。对我们分析ANR问题*有价值的就是其中主线程的stack,一般主线程的trace可能有如下几种情况:
    (1). 主线程是running或者native而对应的栈对应了我们应用中的函数,则很有可能就是执行该函数时候发生了超时。
    (2). 主线程被block:非常明显的线程被锁,这时候可以看是被哪个线程锁了,可以考虑优化代码。如果是死锁问题,就更需要及时解决了。
    (3). 由于抓trace的时刻很有可能耗时操作已经执行完了(ANR -> 耗时操作执行完毕 ->系统抓trace),这时候的trace就没有什么用了,主线程的stack就是这样的:
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 obj=0x757855c8 self=0xb4d76500
  | sysTid=3276 nice=0 cgrp=default sched=0/0 handle=0xb6ff5b34
  | state=S schedstat=( 50540218363 186568972172 209049 ) utm=3290 stm=1764 core=3 HZ=100
  | stack=0xbe307000-0xbe309000 stackSize=8MB
  | held mutexes=
  kernel: (couldn't read /proc/self/task/3276/stack)
  native: #00 pc 0004099c  /system/lib/libc.so (__epoll_pwait+20)
  native: #01 pc 00019f63  /system/lib/libc.so (epoll_pwait+26)
  native: #02 pc 00019f71  /system/lib/libc.so (epoll_wait+6)
  native: #03 pc 00012ce7  /system/lib/libutils.so (_ZN7android6Looper9pollInnerEi+102)
  native: #04 pc 00012f63  /system/lib/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv+130)
  native: #05 pc 00086abd  /system/lib/libandroid_runtime.so (_ZN7android18NativeMessageQueue8pollOnceEP7_JNIEnvP8_jobjecti+22)
  native: #06 pc 0000055d  /data/dalvik-cache/arm/system@framework@boot.oat (Java_android_os_MessageQueue_nativePollOnce__JI+96)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:323)
  at android.os.Looper.loop(Looper.java:138)
  at android.app.ActivityThread.main(ActivityThread.java:5528)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:740)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:630)

当然这种情况很有可能是由于该进程的其他线程消耗掉了CPU资源,这就需要分析其他线程的trace以及ANR前后该进程自己输出的log了。

四.如何降低ANR的概率:

有一些操作是很危险的,非常容易发生ANR,在写代码时候一定要避免:

  1. 主线程读取数据:在Android中主线程去读取数据是非常不好的,Android是不允许主线程从网络读数据的,但系统允许主线程从数据库或者其他地方获取数据,但这种操作ANR风险很大,也会造成掉帧等,影响用户体验。
    (1). 避免在主线程query provider,首先这会比较耗时,另外这个操作provider那一方的进程如果挂掉了或者正在启动,我们应用的query就会很长时间不会返回,我们应该在其他线程中执行数据库query、provider的query等获取数据的操作。
    (2). sharePreference的调用:针对sharePreference的优化点有很多,文章http://weishu.me/2016/10/13/sharedpreference-advices/ 详细介绍了几点sharepreference使用时候的注意事项。首先sharePreference的commit()方法是同步的,apply()方法一般是异步执行的。所以在主线程不要用其commit(),用apply()替换。其次sharePreference的写是全量写而非增量写,所以尽量都修改完同一apply,避免改一点apply一次(apply()方法在Activity stop的时候主线程会等待写入完成,提交多次就很容易卡)。并且存储文本也不宜过大,这样会很慢。另外,如果写入的是json或者xml,由于需要加和删转义符号,速度会比较慢。
  2. 不要在broadcastReciever的onRecieve()方法中干活,这一点很容易被忽略,尤其应用在后台的时候。为避免这种情况,一种解决方案是直接开的异步线程执行,但此时应用可能在后台,系统优先级较低,进程很容易被系统杀死,所以可以选择开个IntentService去执行相应操作,即使是后台Service也会提高进程优先级,降低被杀可能性。
  3. 各个组件的生命周期函数都不应该有太耗时的操作,即使对于后台Service或者ContentProvider来讲,应用在后台运行时候其onCreate()时候不会有用户输入引起事件无响应ANR,但其执行时间过长也会引起Service的ANR和ContentProvider的ANR。
  4. 尽量避免主线程的被锁的情况,在一些同步的操作主线程有可能被锁,需要等待其他线程释放相应锁才能继续执行,这样会有一定的ANR风险,对于这种情况有时也可以用异步线程来执行相应的逻辑。另外, 我们要避免死锁的发生(主线程被死锁基本就等于要发生ANR了)。

Android ANR优化(一)

1, 你碰到ANR了吗

在App使用过程中, 你可能遇到过这样的情况:

%title插图%num
ANR

恭喜你, 这就是传说中的ANR.

1.1 何为ANR

ANR全名Application Not Responding, 也就是”应用无响应”. 当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的ANR对话框.

1.2 为什么会产生ANR

在Android里, App的响应能力是由Activity Manager和Window Manager系统服务来监控的. 通常在如下两种情况下会弹出ANR对话框:

  • 5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).
  • BroadcastReceiver在10s内无法结束.

造成以上两种情况的首要原因就是在主线程(UI线程)里面做了太多的阻塞耗时操作, 例如文件读写, 数据库读写, 网络查询等等.

1.3 如何避免ANR

知道了ANR产生的原因, 那么想要避免ANR, 也就很简单了, 就一条规则:

不要在主线程(UI线程)里面做繁重的操作.

这里面实际上涉及到两个问题:

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

稍后解答.

2, ANR分析

2.1 获取ANR产生的trace文件

ANR产生时, 系统会生成一个traces.txt的文件放在/data/anr/下. 可以通过adb命令将其导出到本地:

$adb pull data/anr/traces.txt .

2.2 分析traces.txt

2.2.1 普通阻塞导致的ANR

获取到的tracs.txt文件一般如下:

如下以GithubApp代码为例, 强行sleep thread产生的一个ANR.

----- 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了. 这就有可能是如下两种情况了:

2.2.2 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操作占用了.

此时分析方法调用栈, 一般来说会发现是方法中有频繁的文件读写或是数据库读写操作放在主线程来做了.

2.2.3 内存原因

其实内存原因有可能会导致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的异常…

2.2 ANR的处理

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

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

3, 深入一点

没有人愿意在出问题之后去解决问题.
高手和新手的区别是, 高手知道怎么在一开始就避免问题的发生. 那么针对ANR这个问题, 我们需要做哪些层次的工作来避免其发生呢?

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

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

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

上面我们几乎一直在说, 避免ANR的方法就是在子线程中执行耗时阻塞操作. 那么在Android中有哪些方式可以让我们实现这一点呢.

3.2.1 启Thread方式

这个其实也是Java实现多线程的方式. 有两种实现方法, 继承Thread 或 实现Runnable接口:

继承Thread

class PrimeThread extends Thread {
    long minPrime;
    PrimeThread(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
         . . .
    }
}

PrimeThread p = new PrimeThread(143);
p.start();

实现Runnable接口

class PrimeRun implements Runnable {
    long minPrime;
    PrimeRun(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
         . . .
    }
}

PrimeRun p = new PrimeRun(143);
new Thread(p).start();

3.2.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);

3.2.3 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这个子线程中了.
    }
}

3.2.4 IntentService

Service是运行在主线程的, 然而IntentService是运行在子线程的.
实际上IntentService就是实现了一个HandlerThread + ServiceHandler的模式.

以上HandlerThread的使用代码示例也就来自于IntentService源码.

3.2.5 Loader

Android 3.0引入的数据加载器, 可以在Activity/Fragment中使用. 支持异步加载数据, 并可监控数据源在数据发生变化时传递新结果. 常用的有CursorLoader, 用来加载数据库数据.

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
// 使用LoaderManager来初始化Loader
getLoaderManager().initLoader(0, null, this);

//如果 ID 指定的加载器已存在,则将重复使用上次创建的加载器。
//如果 ID 指定的加载器不存在,则 initLoader() 将触发 LoaderManager.LoaderCallbacks 方法 //onCreateLoader()。在此方法中,您可以实现代码以实例化并返回新加载器

// 创建一个Loader
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    // This is called when a new Loader needs to be created.  This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    Uri baseUri;
    if (mCurFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(mCurFilter));
    } else {
        baseUri = Contacts.CONTENT_URI;
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + Contacts.DISPLAY_NAME + " != '' ))";
    return new CursorLoader(getActivity(), baseUri,
            CONTACTS_SUMMARY_PROJECTION, select, null,
            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}

// 加载完成
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in.  (The framework will take care of closing the
    // old cursor once we return.)
    mAdapter.swapCursor(data);
}

具体请参看官网Loader介绍.

3.2.6 特别注意

使用Thread和HandlerThread时, 为了使效果更好, 建议设置Thread的优先级偏低一点:

Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);

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

结语

对于ANR问题, 个人认为还是预防为主, 认清代码中的阻塞点, 善用线程. 同时形成良好的编程习惯, 要有MainThread和Worker Thread的概念的…(实际上人的工作状态也是这样的~~哈哈)