概述
ANR全称是Application Not Responding,意思是应用程序无响应。相信从事Android开发的肯定遇到过。ANR的直观体验是用户在操作App的过程中,感觉界面卡顿,当界面卡顿超过一定时间(一般5秒),就会出现ANR对话框。ANR对于一个应用来说是不能承受之痛,其影响并不比应用发生Crash小。
ANR产生的原因
只有当应用程序的UI线程响应超时才会引起ANR,超时产生原因一般有两种。
- 当前的事件没有机会得到处理,例如UI线程正在响应另一个事件,当前事件由于某种原因被阻塞了。
- 当前的事件正在处理,但是由于耗时太长没能及时完成。
根据ANR产生的原因不同,超时事件也不尽相同,从本质上将,产生ANR的原因有三种,大致可以对应到Android中四大组件中的三个(Activity/View,BroadcastReceiver和Service)。
KeyDispatchTimeout
最常见的一种类型,原因就是View的点击事件或者触摸事件在特定的时间(5s)内无法得到响应。
BroadcastTimeout
原因是BroadcastReceiver的onReceive()函数运行在主线程中,在特定的时间(10s)内无法完成处理。
ServiceTimeout
比较少出现的一种类型,原因是Service的各个生命周期函数在特定时间(20s)内无法完成处理。
典型的ANR问题场景
- 应用程序UI线程存在耗时操作。例如在UI线程中进行联网请求,数据库操作或者文件操作等。
- 应用程序的UI线程等待子线程释放某个锁,从而无法处理用户的输入。
- 耗时的动画需要大量的计算工作,可能导致CPU负载过重。
ANR的定位和分析
当发生ANR时,可以通过结合Logcat日志和生成的位于手机内部存储的/data/anr/traces.tex文件进行分析和定位。
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)
现在找到了ANR产生的原因和位置了,就可以对产生ANR的代码进行修复。这么一个一个的找肯定不现实,那我们就来说说如何避免和检测ANR。
ANR的避免和检测
为了避免在开发中引入可能导致应用发生ANR的问题,除了切记不要在主线程中作耗时操作,我们也可以借助于一些工具来进行检测,从而更有效的避免ANR的引入。
StrictMode
严格模式StrictMode是Android SDK提供的一个用来检测代码中是否存在违规操作的工具类,StrictMode主要检测两大类问题。
- 线程策略 ThreadPolicy
- detectCustomSlowCalls:检测自定义耗时操作
- detectDiskReads:检测是否存在磁盘读取操作
- detectDiskWrites:检测是否存在磁盘写入操作
- detectNetWork:检测是否存在网络操作
- 虚拟机策略VmPolicy
- detectActivityLeaks:检测是否存在Activity泄露
- detectLeakedClosableObjects:检测是否存在未关闭的Closeable对象泄露
- detectLeakedSqlLiteObjects:检测是否存在Sqlite对象泄露
- setClassInstanceLimit:检测类实例个数是否超过限制
可以看到,ThreadPolicy可以用来检测可能催在的主线程耗时操作,需要注意的是我们只能在Debug版本中使用它,发布到市场上的版本要关闭掉。StrictMode的使用很简单,我们只需要在应用初始化的地方例如Application或者MainActivity类的onCreate方法中执行如下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// 开启线程模式
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyDialog() 打印logcat,当然也可以定位到dropbox,通过文件保存相应的log
.build());
// 开启虚拟机模式
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
}
}
上面的初始化代码调用penaltyLog表示在Logcat中打印日志,调用detectAll方法表示启动所有的检测策略,我们也可以根据应用的具体要求只开启某些策略,语句如下:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.detectActivityLeaks()
.penaltyLog()
.build());
BlockCanary
BlockCanary是一个非侵入式式的性能监控函数库,它的用法和leakCanary类似,只不过后者监控应用的内存泄露,而BlockCanary主要用来监控应用主线程的卡顿。它的基本原理是利用主线程的消息队列处理机制,通过对比消息分发开始和结束的时间点来判断是否超过设定的时间,如果是,则判断为主线程卡顿。它的集成很简单,首先在build.gradle中添加依赖
一般选取以下其中一个 case 引入即可
dependencies {
compile 'com.github.markzhai:blockcanary-android:1.5.0'
// 仅在debug包启用BlockCanary进行卡顿监控和提示的话,可以这么用
debugCompile 'com.github.markzhai:blockcanary-android:1.5.0'
releaseCompile 'com.github.markzhai:blockcanary-no-op:1.5.0'
}
然后在Application类中进行配置和初始化即可
public class AnrDemoApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 在主进程初始化调用哈
BlockCanary.install(this, new AppBlockCanaryContext()).start();
}
}
实现自己监控的上下文
public class AppBlockCanaryContext extends BlockCanaryContext {
// 实现各种上下文,包括应用标示符,用户uid,网络类型,卡慢判断阙值,Log保存位置等
/**
* Implement in your project.
*
* @return Qualifier which can specify this installation, like version + flavor.
*/
public String provideQualifier() {
return "unknown";
}
/**
* Implement in your project.
*
* @return user id
*/
public String provideUid() {
return "uid";
}
/**
* Network type
*
* @return {@link String} like 2G, 3G, 4G, wifi, etc.
*/
public String provideNetworkType() {
return "unknown";
}
/**
* Config monitor duration, after this time BlockCanary will stop, use
* with {@code BlockCanary}'s isMonitorDurationEnd
*
* @return monitor last duration (in hour)
*/
public int provideMonitorDuration() {
return -1;
}
/**
* Config block threshold (in millis), dispatch over this duration is regarded as a BLOCK. You may set it
* from performance of device.
*
* @return threshold in mills
*/
public int provideBlockThreshold() {
return 1000;
}
/**
* Thread stack dump interval, use when block happens, BlockCanary will dump on main thread
* stack according to current sample cycle.
* <p>
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
* </p>
*
* @return dump interval (in millis)
*/
public int provideDumpInterval() {
return provideBlockThreshold();
}
/**
* Path to save log, like "/blockcanary/", will save to sdcard if can.
*
* @return path of log files
*/
public String providePath() {
return "/blockcanary/";
}
/**
* If need notification to notice block.
*
* @return true if need, else if not need.
*/
public boolean displayNotification() {
return true;
}
/**
* Implement in your project, bundle files into a zip file.
*
* @param src
files before compress
* @param dest files compressed
* @return true if compression is successful
*/
public boolean zip(File[] src, File dest) {
return false;
}
/**
* Implement in your project, bundled log files.
*
* @param zippedFile zipped file
*/
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
/**
* Packages that developer concern, by default it uses process name,
* put high priority one in pre-order.
*
* @return null if simply concern only package with process name.
*/
public List<String> concernPackages() {
return null;
}
/**
* Filter stack without any in concern package, used with @{code concernPackages}.
*
* @return true if filter, false it not.
*/
public boolean filterNonConcernStack() {
return false;
}
/**
* Provide white list, entry in white list will not be shown in ui list.
*
* @return return null if you don't need white-list filter.
*/
public List<String> provideWhiteList() {
LinkedList<String> whiteList = new LinkedList<>();
whiteList.add("org.chromium");
return whiteList;
}
/**
* Whether to delete files whose stack is in white list, used with white-list.
*
* @return true if delete, false it not.
*/
public boolean deleteFilesInWhiteList() {
return true;
}
/**
* Block interceptor, developer may provide their own actions.
*/
public void onBlock(Context context, BlockInfo blockInfo) {
}
}
在AndroidManifest.xml文件中声明Application,一定不要忘记
现在就已经将BlockCanary集成到应用里面了,接下来,编译安装到手机上,点击测试按钮,将产生一个ANR,效果如图:
最后
以上就是怕孤单棒棒糖为你收集整理的ANR产生的原因及定位分析的全部内容,希望文章能够帮你解决ANR产生的原因及定位分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复