Android App 优化之 ANR 详解

为了便于阅读, 应邀将Android App性能优化系列, 转移到掘金原创上来.
掘金的新出的"收藏集"功能可以用来做系列文集了.

今天先来聊聊ANR.

1, 你碰到ANR了吗

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

恭喜你, 这就是传说中的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  (a com.tencent.bugly.crashreport.crash.c)at com.tencent.bugly.crashreport.CrashReport.testANRCrash(BUGLY:166)  // 产生ANR的那个函数调用- locked  (a java.lang.Class)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)@addr=0x12d1e840>@addr=0x12dadc70>0x35fc9e33>0x35fc9e33>复制代码

拿到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.acoreDALVIK 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{// 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 calledif (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);}@Overridepublic 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 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 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的概念的...(实际上人的工作状态也是这样的~~哈哈)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/457591.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信高级群发接口正文乱码解决方案

content里面的内空如果含有html标签的话&#xff0c;需要对内容进行一下转义。如果里面含有style".."类似于这样的带""号的内容的话&#xff0c;就更需要注意了。 foreach ($news as &$item) {foreach ($item as $key > $val){if ($key content){$…

python *args和**kwargs以及序列解包

DAY 8. *args和**kwargs *args&#xff1a;多值元组&#xff0c;**kwargs多值字典&#xff0c;他们是python函数传参时两个特殊的参数&#xff0c;args和kwargs并不是强制的&#xff0c;但习惯使用这两个&#xff0c;如果在函数参数列表中声明了*args&#xff0c;则允许传递任…

解读直播连麦与点播加密

近年来&#xff0c;直播热潮持续升温。有需求就会有变革&#xff0c;直播的相关技术也在不断更新&#xff0c;为直播行业带来更好地服务。如&#xff1a;直播连麦与点播加密技术等。 直播连麦&#xff0c;即主播与连麦者通过互动直播中心进行实时互动&#xff0c;信息在云端混流…

血红蛋白判断access程序答案_普渡大学开发智能手机应用程序 帮助评估贫血症情况...

医生诊断贫血疾病的方法之一&#xff0c;就是通过观察患者的眼皮&#xff0c;判断眼皮是否发红&#xff0c;从而判断红细胞的数量。但对医生来说&#xff0c;面临的挑战是&#xff0c;这种简单的测试不够精确&#xff0c;无法不从患者身上抽取血样就能给出诊断。美国普渡大学的…

学习笔记:AC自动机

话说AC自动机有什么用......我想要自动AC机 AC自动机简介&#xff1a; 首先简要介绍一下AC自动机&#xff1a;Aho-Corasick automation&#xff0c;该算法在1975年产生于贝尔实验室&#xff0c;是著名的多模匹配算法之一。一个常见的例子就是给出n个单词&#xff0c;再给出一段…

python闭包和装饰器

DAY 9. 闭包和装饰器 9.1 闭包 闭包就是内部函数对外部函数作用域内变量的引用 可以看出 闭包是针对函数的&#xff0c;还有两个函数&#xff0c;内部函数和外部函数闭包是为了让内部函数引用外部函数作用域内的变量的 我们先写两个函数 def fun1():print("我是fun1&q…

学历是铜牌,能力是银牌,人脉是金牌,思维是王牌

有人工作&#xff0c;有人上学&#xff0c;大家千万不要错过这篇文章&#xff0c;能看到这篇文章也是一种幸运&#xff0c;真的受益匪浅&#xff0c;对我有很大启迪&#xff0c;这篇文章将会改变你我的一生&#xff0c;真的太好了&#xff0c;希望与有缘人分享&#xff0c;也希…

石头剪刀布python编程_《python核心编程第二版》练习题——游戏:石头剪刀布

习题里比较有意思的一个题目&#xff0c;实现石头剪刀布这个游戏&#xff0c;起初设计的时候走弯路了(主要时被习题里那个“尽量少用if判断”给整晕了)&#xff0c;想的太复杂&#xff0c;后来发现其实非常简单&#xff0c;完全可以不写if语句。还是枚举法&#xff1a;#! /usr/…

SpringMvc面试题

f-sm-1. 讲下SpringMvc和Struts1,Struts2的比较的优势 性能上Struts1>SpringMvc>Struts2 开发速度上SpringMvc和Struts2差不多,比Struts1要高f-sm-2. 讲下SpringMvc的核心入口类是什么,Struts1,Struts2的分别是什么 SpringMvc的是DispatchServlet,Struts1的是ActionServl…

python 鸭子类型

DAY 10. 鸭子类型 这个概念来源于美国印第安纳州的诗人詹姆斯惠特科姆莱利&#xff08;James Whitcomb Riley,1849-1916&#xff09;的诗句&#xff1a;”When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”…

thinkphp一句话疑难解决笔记

URL_PATHINFO_DEPR, depr表示 网页路径"分隔符",用"-", 有利于seo,注意是从 sername/index.php(开始的)/home-user-login-var-value开始的,pathinfo也支持普通的参数传值(仅仅支持参数...). 在thinkphp中,有两个地方使用depr,另一个就是tpl的文件目录组织分…

python选取特定行_pandas.DataFrame选取/排除特定行的方法

pandas.DataFrame选取特定行使用Python进行数据分析时&#xff0c;经常要使用到的一个数据结构就是pandas的DataFrame&#xff0c;如果我们想要像Excel的筛选那样&#xff0c;只要其中的一行或某几行&#xff0c;可以使用isin()方法&#xff0c;将需要的行的值以列表方式传入&a…

学校选址_洛谷U3451_带权中位数

题目描述 在一条大路一旁有许多栋楼&#xff0c;每栋楼里有许多小学生&#xff08;哈哈哈一波小学生来袭&#xff01;&#xff09;。但是这条路上没有小学&#xff01;&#xff01;&#xff01;&#xff01;所以唯恐世界不乱的牛A打算在路上&#xff08;汽车什么的都不敢来这个…

python 重载的实现(single-dispatch generic function)

DAY 11. python 重载 函数重载是指允许定义参数数量或类型不同的同名函数&#xff0c;程序在运行时会根据所传递的参数类型选择应该调用的函数 &#xff0c;但在默认情况下&#xff0c;python是不支持函数重载的&#xff0c;定义同名函数会发生覆盖 def foo(a:int):print(fin…

SQL中的多表查询,以及JOIN的顺序重要么?

说法是&#xff0c;一般来说&#xff0c;JOIN的顺序不重要&#xff0c;除非你要自己定制driving table。 示例&#xff1a; SELECT a.account_id, c.fed_id, e.fname, e.lname-> FROM account AS a INNER JOIN customer AS c-> ON a.cust_id c.cust_id-> INNER JOIN …

python可变对象 不可变对象_【Python】可变对象和不可变对象

在 Python 中一切都可以看作为对象。每个对象都有各自的 id, type 和 value。id: 当一个对象被创建后&#xff0c;它的 id 就不会在改变&#xff0c;这里的 id 其实就是对象在内存中的地址&#xff0c;可以使用 id() 去查看对象在内存中地址。type: 和 id 一样当对象呗创建之后…

MySQL 调优基础(三) Linux文件系统

Linux的文件系统有点像MySQL的存储引擎&#xff0c;它支持各种各样的文件系统。它最上层是通过 virtual files system虚拟文件系统作为一个抽象接口层来对外提供调用的。然后下层的各种文件系统实现这些调用接口就行了。 1. Linux 中的 日志文件系统和非日志文件系统 文件内容的…

python 经典类和新式类

DAY 12. python新式类和旧式类 继承自object基类的类叫做新式类&#xff0c;否则叫做旧式类&#xff0c;python3中的类默认是新式类&#xff0c;之前版本默认是旧式类 rootkail:~# python python 2.7.15 (default,Jul 28 2018,11:29:29) [GCC 8.1.0] on linux2 Type "he…

Why does pthread_cond_signal not work?【转】

转自&#xff1a;http://stackoverflow.com/questions/16819169/why-does-pthread-cond-signal-not-work# 0 down vote favorite I am currently learing all around POSIX threads (pthread). I now have created a simple program which increased a shared value by 7 until…

Android开发技术周报 Issue#72

新闻 Android N 最初预览版&#xff1a;开发者 API 和工具教程 Gradle依赖的统一管理 理解Java垃圾回收机制 浅谈 Android 编程思想和架构 由Android 65K方法数限制引发的思考 Android音频开发&#xff08;1&#xff09;&#xff1a;基础知识 Android音频开发&#xff08;…