Android中的ANR &OOM&FC


ANR(Application Not Responding)程序无响应

概述

  • 主线程(UI线程、Main线程)及Android的单线程模型原则

当应用启动,系统会创建一个主线程,在这个主线程主要负责创建UI控件,更新UI控件,向UI组件分发事件,也是在这个主线程里,你的应用和Android的UI组件(android.widget and android.view)发生交互。

系统不会为每个组件单独创建线程,在同一个进程里的UI组件都会在UI线程里实例化,系统对每一个组件的调用都从UI线程分发出去。结果就是,响应系统回调的方法(比如响应用户动作的onKeyDown()和各种生命周期回调)永远都是在UI线程里运行。
  
另外,Andoid UI组件并不是线程安全的,所以你不能从非UI线程来操纵UI组件。你必须把所有的UI操作放在UI线程里,所以Android的单线程模型有两条原则:

  1. 不要阻塞UI线程
  2. 不要在UI线程之外访问Android UI组件(主要是这两个包中的组件:android.widget和android.view)。

当App做一些比较耗时的工作的时候,除非你合理地实现,否则单线程模型的性能会很差。特别的是,如果所有的工作都在UI线程,做一些比较耗时的工作比如访问网络或者数据库查询,都会阻塞UI线程,违反单线程模型第一条原则,导致事件停止分发(包括绘制事件)。对于用户来说,应用看起来像是卡住了,更坏的情况是,如果UI线程blocked的时间太长(大约超过5秒),用户就会看到ANR(Application Not Responding)的对话框,如下图:

  • 子线程(Worker线程)

根据单线程模型的两条原则,首先,要保证应用的响应性,不能阻塞UI线程,所以当你的操作不是即时的那种,你应该把他们放进子线程中(也叫做background或者叫worker线程)。
  
比如点击按钮后,下载一个图片然后在ImageView中展示:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

这段代码用新的线程来处理网络操作,但是它违反了第二条原则:从非UI线程访问UI组件会导致未定义和不能预料的行为。

为了解决这个问题,Android提供了一些方法,从其他线程访问UI线程:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)
  • Handler+Message机制
  • AsyncTask机制

比如,上面这段代码可以这么改:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

这么改之后就是线程安全的了。但是,当操作变得复杂的时候,这种代码会变得非常复杂,为了处理非UI线程和UI线程之间更加复杂的交互,可以考虑在worker线程中使用一个Handler,来处理UI线程中传来的消息。也可以继承这个类AsyncTask 。

  • 与UI线程通讯

只有在UI线程中的对象才能操作UI线程中的对象,为了将非UI线程中的数据传送到UI线程,可以使用一个 Handler运行在UI线程中。Handler是Android framework中管理线程的部分,一个Handler对象负责接收发送消息然后处理消息。
  
你可以为一个新的线程创建一个Handler,也可以创建一个Handler然后将它和已有线程连接。如果你将一个Handler和你的UI线程连接,处理消息的代码就将会在UI线程中执行。可以在你创建线程池的类的构造方法中实例化Handler的对象,然后用全局变量存储这个对象。
  
要和UI线程连接,实例化Handler的时候应该使用Handler(Looper) 这个构造方法。这个构造方法使用了一个 Looper 对象,这是Android系统中线程管理的framework的另一个部分。当你用一个特定的 Looper实例来创建一个 Handler时,这个 Handler就运行在这个 Looper的线程中。

在Handler中,要覆写handleMessage() 方法。Android系统会在Handler管理的相应线程收到新消息时调用这个方法。

一个特定线程的所有Handler对象都会收到同样的方法。(这是一个“一对多”的关系)。

ANR定义

在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。所以一个流畅的合理的应用程序中不能出现anr,而让用户每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样系统不会显示ANR给用户。默认情况下,在android中Activity的最长执行时间是5秒,BroadcastReceiver的最长执行时间则是10秒。

什么会引发ANR

在Android里,应用程序的响应性是由ActivityManager和WindowManager系统服务监视的 。当它监测到以下情况中的一个时,Android就会针对特定的应用程序显示ANR:

  • 在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)
  • BroadcastReceiver在10秒内没有执行完毕
  • Service Timeout:服务在20s内未执行完成(小概率事件)
  • ContentProvider Timeout:内容提供者执行超时

造成以上两点的原因有很多,比如在主线程中做了非常耗时的操作,常见的耗时操作:

  • 网络访问
  • 数据库操作
  • I/O操作(从4.0之后网络IO不允许在主线程中)
  • SD读写操作
  • 耗时的数据运算
  • 多线程死锁
  • 错误的操作,比如Thread.wait或者Thread.sleep等

如何避免ANR

  • 运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)

  • 应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver),建议使用IntentService处理。

  • 避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。

  • 高耗时的计算如改变位图尺寸, 应该在子线程里(或者以数据库操作为例,通过异步请求的方式)来完成。然而,不是说你的主线程阻塞在那里等待子线程的完成——也不是调用 Thread.wait()或是Thread.sleep()。替代的方法是,主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由于5秒输入事件的超时引发的ANR对话框。

  • 使用AsyncTask处理耗时IO操作

  • Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。

  • 通常100到200毫秒就会让人察觉程序反应慢,为了更加提升响应,可以使用下面的几种经验解决方法:

    • 如果程序正在后台处理用户的输入,建议使用让用户得知进度,比如使用ProgressBar控件。
    • 程序启动时可以选择加上欢迎界面,避免让用户察觉卡顿。
    • 使用Systrace和TraceView找出影响响应的问题。

如何分析ANR

ANR发生时都会在log中输出错误信息,从log中可以获得ANR的类型,CPU的使用情况,CPU使用率过高有可能是CPU饥饿导致了ANR。CPU使用率过低说明主线程被block了,如果IOwait高是因为主线程进行I/O操作造成的。

如果开发机器上出现问题,我们也可以通过查看/data/anr/traces.txt即可,最新的ANR信息在最开始部分。我们从stacktrace中即可找到出问题的具体行数。本例中问题出现在MainActivity.java 27行,因为这里调用了Thread.sleep方法。

root@htc_m8tl:/ # cat /data/anr/traces.txt | more


----- pid 30307 at 2015-05-30 14:51:14 -----
Cmd line: com.example.androidyue.bitmapdemo

JNI: CheckJNI is off; workarounds are off; pins=0; globals=272

DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)

"main" prio=5 tid=1 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 obj=0x416eaf18 self=0x416d8650
  | sysTid=30307 nice=0 sched=0/0 cgrp=apps handle=1074565528
  | state=S schedstat=( 0 0 0 ) utm=5 stm=4 core=3
  at java.lang.VMThread.sleep(Native Method)
  at java.lang.Thread.sleep(Thread.java:1044)
  at java.lang.Thread.sleep(Thread.java:1026)
  at com.example.androidyue.bitmapdemo.MainActivity$1.run(MainActivity.java:27)
  at android.app.Activity.runOnUiThread(Activity.java:4794)
  at com.example.androidyue.bitmapdemo.MainActivity.onResume(MainActivity.java:33)
  at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1282)
  at android.app.Activity.performResume(Activity.java:5405)

一个特例

BroadcastReceiver过了60秒居然没有ANR?

public class NetworkReceiver extends BroadcastReceiver{
    private static final String LOGTAG = "NetworkReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i(LOGTAG, "onReceive intent=" + intent);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.i(LOGTAG, "onReceive end");
    }
}

实际上已经发生了ANR,只是没有进行对话框弹出而已。这种ANR就是background ANR,即后台程序的ANR,我们可以通过过滤日志验证

adb logcat | grep "NetworkReceiver|ActivityManager|WindowManager"
I/NetworkReceiver( 4109): onReceive intent=Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x8000010 cmp=com.example.androidyue.bitmapdemo/.NetworkReceiver (has extras) }
I/ActivityManager(  462): No longer want com.android.exchange (pid 1054): empty #17
I/NetworkReceiver( 4109): onReceive end
W/BroadcastQueue(  462): Receiver during timeout: ResolveInfo{5342dde4 com.example.androidyue.bitmapdemo.NetworkReceiver p=0 o=0 m=0x108000}
E/ActivityManager(  462): ANR in com.example.androidyue.bitmapdemo
E/ActivityManager(  462): Reason: Broadcast of Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x8000010 cmp=com.example.androidyue.bitmapdemo/.NetworkReceiver (has extras) }
E/ActivityManager(  462): Load: 0.37 / 0.2 / 0.14
E/ActivityManager(  462): CPU usage from 26047ms to 0ms ago:
E/ActivityManager(  462):   0.4% 58/adbd: 0% user + 0.4% kernel / faults: 1501 minor
E/ActivityManager(  462):   0.3% 462/system_server: 0.1% user + 0.1% kernel
E/ActivityManager(  462):   0% 4109/com.example.androidyue.bitmapdemo: 0% user + 0% kernel / faults: 6 minor
E/ActivityManager(  462): 1.5% TOTAL: 0.5% user + 0.9% kernel + 0% softirq
E/ActivityManager(  462): CPU usage from 87ms to 589ms later:
E/ActivityManager(  462):   1.8% 58/adbd: 0% user + 1.8% kernel / faults: 30 minor
E/ActivityManager(  462):     1.8% 58/adbd: 0% user + 1.8% kernel
E/ActivityManager(  462): 4% TOTAL: 0% user + 4% kernel
W/ActivityManager(  462): Killing ProcessRecord{5326d418 4109:com.example.androidyue.bitmapdemo/u0a10063}: background ANR
I/ActivityManager(  462): Process com.example.androidyue.bitmapdemo (pid 4109) has died.

除了日志,我们还可以根据前面提到的查看traces.txt文件。

我们可以在Android开发者选项—>高级—>显示所有”应用程序无响应“勾选即可对后台ANR也进行弹窗显示,方便查看了解程序运行情况

总结

ANR异常也是在程序中自己经常遇到的问题,主要的解决办法自己最常用的就是不要在主线程中做耗时的操作,而应放在子线程中来实现,比如采用Handler+Message的方式,或者是有时候需要做一些和网络相互交互的耗时操作就采用Asyntask异步任务的方式(它的底层其实Handler+Mesage有所区别的是它是线程池)等,在主线程中更新UI。


OOM(Out Of Memory)内存溢出

OOM是Android中比较重要的一个知识点,会单独放到一篇博文里面


FC(Force Close)强制关闭

程序或ROM出现了比较严重的错误,必须退出重启。用户过多原因大概有一下:

  • Error

    • OOM 内存耗尽
    • StackOverFlowError 堆栈溢出
  • RuntimeException 运行时错误

如下图:

常见的有比如空指针啦,类没有找到啦,资源没找到,就连Android API使用的顺序错误也可能导致(比如 setContentView()之前进行了findViewById()操作)

解决办法:日志

以上的问题大多是我们写代码时犯下的逻辑错误或者优化做的非常差,这是绝对绝对不允许出现的。至于解决办法就是DEBUG你懂得。常用的方法无非就是Log打印日志或者借助工具,其实能够熟练运用logcat,明白log各段的大致意思、擅于运用Filter就能够解决大多数问题了。

如何避免弹出ForceClose窗口,并重启App

可以实现Thread.UncaughtExceptionHandler接口的uncaughtException方法,此处定义一个MyExceptionHandler,代码如下:

public class MyExceptionHandler implements Thread.UncaughtExceptionHandler {
    private Activity activity;
    public MyExceptionHandler(Activity activity) {
        this.activity = activity;
    }
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
    Log.i("tag",  "截获到forceclose,异常原因为:" + "\n" +
                ex.toString()+"  Thread:" + thread.getId());
        Intent intent = new Intent(activity, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
                | Intent.FLAG_ACTIVITY_CLEAR_TASK
                | Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(
                MyApplication.getInstance().getBaseContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        // AlarmManager 是为了设置一个计时器来延迟两秒再执行 pendingIntent 的,也就是重启我们的Activity的任务
        AlarmManager mgr = (AlarmManager) MyApplication.getInstance().getBaseContext()
                .getSystemService(Context.ALARM_SERVICE);
        mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, pendingIntent);

        // This will finish your activity manually
        activity.finish();

        // This will stop your application and take out from it.
        System.exit(2);
    }
}

在uncaughtException方法中,第一个参数thread是线程,指的是发生异常的那个Thread,而不一定是uncaughtException注册的Thread,第二个参数ex是异常,在uncaughtException方法里将进程杀死,想要哪个线程可以处理未捕获异常,thread.setDefaultUncaughtExceptionHandler(this); 这句代码都要在那个线程中执行一次.

在进入RelaunchActivity之后,会产生FC事件,如下:

public class RelaunchActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_relaunch);
        // 设置处理异常的handler
        Thread.setDefaultUncaughtExceptionHandler(new MyExceptionHandler(this));
        Button forcecloseBtn = (Button) findViewById(R.id.forceclose_btn);
        forcecloseBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 产生ForceClose的代码
                System.out.println("" + 1/0);
            }
        }); 
    }
}

如下所示:

捕获的日志:

源码

参考资料
http://www.cnblogs.com/wonderful0714/p/4588705.html
http://blog.sina.com.cn/s/blog_618199e60101kvbl.html
http://www.cnblogs.com/mengdd/p/3418780.html
http://droidyue.com/blog/2015/07/18/anr-in-android/index.html
http://www.jianshu.com/p/9db73a26a8bd
http://gityuan.com/2016/07/02/android-anr/
http://blog.csdn.net/u012974916/article/details/24578927
http://blog.csdn.net/u010983881/article/details/51906920

QinPeng Zhu wechat
扫一扫,关注我的公众号获取更多资讯!
学习分享,感谢鼓励!