笔记37 | Android App优化之ANR详解
2017-11-20 09:18
501 查看
地址
http://blog.csdn.net/xiangyong_1521/article/details/78579099什么是ADR
ANR全名Application Not Responding, 也就是”应用无响应”. 当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的ANR对话框.在Android里, App的响应能力是由Activity Manager和Window Manager系统服务来监控的. 通常在如下两种情况下会弹出ANR对话框:
5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).
BroadcastReceiver在10s内无法结束.
ServiceTimeout(20 seconds) – 小概率类型(Service在特定的时间内无法处理完成)
造成以上情况的首要原因就是在主线程(UI线程)里面做了太多的阻塞耗时操作, 例如文件读写, 数据库读写, 网络查询等等.
如何避免ADR
知道了ANR产生的原因, 那么想要避免ANR, 也就很简单了, 就一条规则:不要在主线程(UI线程)里面做繁重的操作.
如何分析ADR
a. ANR产生时, 系统会生成一个traces.txt的文件放在/data/anr/下. 可以通过adb命令将其导出到本地:$adb pull data/anr/traces.txt .
b. 如下以GithubApp代码为例, 强行sleep thread产生的一个ANR.
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文件该怎么分析:
文件最上的即为最新产生的ANR的trace信息.
前面两行表明ANR发生的进程pid, 时间, 以及进程名字(包名).
寻找我们的代码点, 然后往前推, 看方法调用栈, 追溯到问题产生的根源.
以上的ANR trace是属于相对简单, 还有可能你并没有在主线程中做过于耗时的操作, 然而还是ANR了. 这就有可能是以下情况了:
a. 当是CPU占用100%, 满负荷了.
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
b. 其中绝大数是被iowait即I/O操作占用了.
c. 其实内存原因有可能会导致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的内存已所剩无几.
ANR的处理
总结针对三种不同的情况, 一般的处理情况如下a. 主线程阻塞的(开辟单独的子线程来处理耗时阻塞事务)
b. CPU满负荷, I/O阻塞的
I/O阻塞一般来说就是文件读写或数据库操作执行在主线程了, 也可以通过开辟子线程的方式异步执行.
c. 内存不够用的
增大VM内存, 使用largeHeap属性, 排查内存泄露(这个在内存优化那篇细说吧)等.
怎么提前避免ANR
Activity的所有生命周期回调都是执行在主线程的.Service默认是执行在主线程的.
BroadcastReceiver的onReceive回调是执行在主线程的.
没有使用子线程的looper的Handler的handleMessage, post(Runnable)是执行在主线程的.
AsyncTask的回调中除了doInBackground, 其他都是执行在主线程的.
View的post(Runnable)是执行在主线程的.
使用子线程的方式有哪些
a. 继承Threadclass 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();
b. 实现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();
c. 使用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);
d. 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这个子线程中了. } }
e. IntentService
Service是运行在主线程的, 然而IntentService是运行在子线程的.
实际上IntentService就是实现了一个HandlerThread + ServiceHandler的模式.
以上HandlerThread的使用代码示例也就来自于IntentService源码.
f. 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); }
注意:使用Thread和HandlerThread时, 为了使效果更好, 建议设置Thread的优先级偏低一点:
因为如果没有做任何优先级设置的话, 你创建的Thread默认和UI Thread是具有同样的优先级的, 你懂的. 同样的优先级的Thread, CPU调度上还是可能会阻塞掉你的UI Thread, 导致ANR的.
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
相关文章推荐
- Android App优化之ANR详解
- Android App优化之ANR详解
- Android App优化之ANR详解
- Android App优化之ANR详解
- Android App优化之ANR详解
- Android App优化之ANR详解
- Android App优化之ANR详解
- Android-ANR-Android App优化之ANR详解
- App优化之ANR详解
- androidのAPP性能之终端兼容优化笔记
- Android优化技术详解-第七章 代码优化(笔记)
- Android笔记之:App自动化之使用Ant编译项目多渠道打包的使用详解
- Effective Android:app优化------ ANR、OOM、Bitmap、UI卡顿
- Android笔记之:App应用之发布各广告平台版本的详解
- [转]Android笔记: android APP 内存与速度的优化问题
- 详解Android App卡顿优化问题
- Android App性能优化笔记之一:性能优化是什么及为什么?
- Android App性能优化笔记之一:性能优化是什么及为什么?
- Android优化技术详解-第五章 Android内存系统(笔记)
- Android笔记之:App自动化之使用Ant编译项目多渠道打包的使用详解