日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android App 优化之 ANR 详解

發(fā)布時(shí)間:2023/12/19 Android 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android App 优化之 ANR 详解 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

為了便于閱讀, 應(yīng)邀將Android App性能優(yōu)化系列, 轉(zhuǎn)移到掘金原創(chuàng)上來(lái).
掘金的新出的"收藏集"功能可以用來(lái)做系列文集了.

今天先來(lái)聊聊ANR.

1, 你碰到ANR了嗎

在App使用過(guò)程中, 你可能遇到過(guò)這樣的情況:

恭喜你, 這就是傳說(shuō)中的ANR.

1.1 何為ANR

ANR全名Application Not Responding, 也就是"應(yīng)用無(wú)響應(yīng)". 當(dāng)操作在一段時(shí)間內(nèi)系統(tǒng)無(wú)法處理時(shí), 系統(tǒng)層面會(huì)彈出上圖那樣的ANR對(duì)話框.

1.2 為什么會(huì)產(chǎn)生ANR

在Android里, App的響應(yīng)能力是由Activity Manager和Window Manager系統(tǒng)服務(wù)來(lái)監(jiān)控的. 通常在如下兩種情況下會(huì)彈出ANR對(duì)話框:

  • 5s內(nèi)無(wú)法響應(yīng)用戶輸入事件(例如鍵盤輸入, 觸摸屏幕等).
  • BroadcastReceiver在10s內(nèi)無(wú)法結(jié)束.

造成以上兩種情況的首要原因就是在主線程(UI線程)里面做了太多的阻塞耗時(shí)操作, 例如文件讀寫, 數(shù)據(jù)庫(kù)讀寫, 網(wǎng)絡(luò)查詢等等.

1.3 如何避免ANR

知道了ANR產(chǎn)生的原因, 那么想要避免ANR, 也就很簡(jiǎn)單了, 就一條規(guī)則:

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

這里面實(shí)際上涉及到兩個(gè)問(wèn)題:

  • 哪些地方是運(yùn)行在主線程的?
  • 不在主線程做, 在哪兒做?
  • 稍后解答.

    2, ANR分析

    2.1 獲取ANR產(chǎn)生的trace文件

    ANR產(chǎn)生時(shí), 系統(tǒng)會(huì)生成一個(gè)traces.txt的文件放在/data/anr/下. 可以通過(guò)adb命令將其導(dǎo)出到本地:

    $adb pull data/anr/traces.txt .復(fù)制代碼

    2.2 分析traces.txt

    2.2.1 普通阻塞導(dǎo)致的ANR

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

    如下以GithubApp代碼為例, 強(qiáng)行sleep thread產(chǎn)生的一個(gè)ANR.

    ----- pid 2976 at 2016-09-08 23:02:47 ----- Cmd line: com.anly.githubapp // 最新的ANR發(fā)生的進(jìn)程(包名)...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過(guò)長(zhǎng)時(shí)間, 阻塞導(dǎo)致無(wú)響應(yīng).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) // 產(chǎn)生ANR的那個(gè)函數(shù)調(diào)用- 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的起點(diǎn)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>復(fù)制代碼

    拿到trace信息, 一切好說(shuō).
    如上trace信息中的添加的中文注釋已基本說(shuō)明了trace文件該怎么分析:

  • 文件最上的即為最新產(chǎn)生的ANR的trace信息.
  • 前面兩行表明ANR發(fā)生的進(jìn)程pid, 時(shí)間, 以及進(jìn)程名字(包名).
  • 尋找我們的代碼點(diǎn), 然后往前推, 看方法調(diào)用棧, 追溯到問(wèn)題產(chǎn)生的根源.
  • 以上的ANR trace是屬于相對(duì)簡(jiǎn)單, 還有可能你并沒(méi)有在主線程中做過(guò)于耗時(shí)的操作, 然而還是ANR了. 這就有可能是如下兩種情況了:

    2.2.2 CPU滿負(fù)荷

    這個(gè)時(shí)候你看到的trace信息可能會(huì)包含這樣的信息:

    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復(fù)制代碼

    最后一句表明了:

  • 當(dāng)是CPU占用100%, 滿負(fù)荷了.
  • 其中絕大數(shù)是被iowait即I/O操作占用了.
  • 此時(shí)分析方法調(diào)用棧, 一般來(lái)說(shuō)會(huì)發(fā)現(xiàn)是方法中有頻繁的文件讀寫或是數(shù)據(jù)庫(kù)讀寫操作放在主線程來(lái)做了.

    2.2.3 內(nèi)存原因

    其實(shí)內(nèi)存原因有可能會(huì)導(dǎo)致ANR, 例如如果由于內(nèi)存泄露, App可使用內(nèi)存所剩無(wú)幾, 我們點(diǎn)擊按鈕啟動(dòng)一個(gè)大圖片作為背景的activity, 就可能會(huì)產(chǎn)生ANR, 這時(shí)trace信息可能是這樣的:

    // 以下trace信息來(lái)自網(wǎng)絡(luò), 用來(lái)做個(gè)示例 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復(fù)制代碼

    可以看到free的內(nèi)存已所剩無(wú)幾.

    當(dāng)然這種情況可能更多的是會(huì)產(chǎn)生OOM的異常...

    2.2 ANR的處理

    針對(duì)三種不同的情況, 一般的處理情況如下

  • 主線程阻塞的
    開(kāi)辟單獨(dú)的子線程來(lái)處理耗時(shí)阻塞事務(wù).

  • CPU滿負(fù)荷, I/O阻塞的
    I/O阻塞一般來(lái)說(shuō)就是文件讀寫或數(shù)據(jù)庫(kù)操作執(zhí)行在主線程了, 也可以通過(guò)開(kāi)辟子線程的方式異步執(zhí)行.

  • 內(nèi)存不夠用的
    增大VM內(nèi)存, 使用largeHeap屬性, 排查內(nèi)存泄露(這個(gè)在內(nèi)存優(yōu)化那篇細(xì)說(shuō)吧)等.

  • 3, 深入一點(diǎn)

    沒(méi)有人愿意在出問(wèn)題之后去解決問(wèn)題.
    高手和新手的區(qū)別是, 高手知道怎么在一開(kāi)始就避免問(wèn)題的發(fā)生. 那么針對(duì)ANR這個(gè)問(wèn)題, 我們需要做哪些層次的工作來(lái)避免其發(fā)生呢?

    3.1 哪些地方是執(zhí)行在主線程的

  • Activity的所有生命周期回調(diào)都是執(zhí)行在主線程的.
  • Service默認(rèn)是執(zhí)行在主線程的.
  • BroadcastReceiver的onReceive回調(diào)是執(zhí)行在主線程的.
  • 沒(méi)有使用子線程的looper的Handler的handleMessage, post(Runnable)是執(zhí)行在主線程的.
  • AsyncTask的回調(diào)中除了doInBackground, 其他都是執(zhí)行在主線程的.
  • View的post(Runnable)是執(zhí)行在主線程的.
  • 3.2 使用子線程的方式有哪些

    上面我們幾乎一直在說(shuō), 避免ANR的方法就是在子線程中執(zhí)行耗時(shí)阻塞操作. 那么在Android中有哪些方式可以讓我們實(shí)現(xiàn)這一點(diǎn)呢.

    3.2.1 啟Thread方式

    這個(gè)其實(shí)也是Java實(shí)現(xiàn)多線程的方式. 有兩種實(shí)現(xiàn)方法, 繼承Thread 或 實(shí)現(xiàn)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();復(fù)制代碼

    實(shí)現(xiàn)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();復(fù)制代碼

    3.2.2 使用AsyncTask

    這個(gè)是Android特有的方式, AsyncTask顧名思義, 就是異步任務(wù)的意思.

    private class DownloadFilesTask extends AsyncTask{// Do the long-running work in here// 執(zhí)行在子線程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()// 執(zhí)行在主線程protected void onProgressUpdate(Integer... progress) {setProgressPercent(progress[0]);}// This is called when doInBackground() is finished// 執(zhí)行在主線程protected void onPostExecute(Long result) {showNotification("Downloaded " + result + " bytes");} }// 啟動(dòng)方式 new DownloadFilesTask().execute(url1, url2, url3);復(fù)制代碼

    3.2.3 HandlerThread

    Android中結(jié)合Handler和Thread的一種方式. 前面有云, 默認(rèn)情況下Handler的handleMessage是執(zhí)行在主線程的, 但是如果我給這個(gè)Handler傳入了子線程的looper, handleMessage就會(huì)執(zhí)行在這個(gè)子線程中的. HandlerThread正是這樣的一個(gè)結(jié)合體:

    // 啟動(dòng)一個(gè)名為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) {// 此時(shí)handleMessage是運(yùn)行在new_thread這個(gè)子線程中了.} }復(fù)制代碼

    3.2.4 IntentService

    Service是運(yùn)行在主線程的, 然而IntentService是運(yùn)行在子線程的.
    實(shí)際上IntentService就是實(shí)現(xiàn)了一個(gè)HandlerThread + ServiceHandler的模式.

    以上HandlerThread的使用代碼示例也就來(lái)自于IntentService源碼.

    3.2.5 Loader

    Android 3.0引入的數(shù)據(jù)加載器, 可以在Activity/Fragment中使用. 支持異步加載數(shù)據(jù), 并可監(jiān)控?cái)?shù)據(jù)源在數(shù)據(jù)發(fā)生變化時(shí)傳遞新結(jié)果. 常用的有CursorLoader, 用來(lái)加載數(shù)據(jù)庫(kù)數(shù)據(jù).

    // Prepare the loader. Either re-connect with an existing one, // or start a new one. // 使用LoaderManager來(lái)初始化Loader getLoaderManager().initLoader(0, null, this);//如果 ID 指定的加載器已存在,則將重復(fù)使用上次創(chuàng)建的加載器。 //如果 ID 指定的加載器不存在,則 initLoader() 將觸發(fā) LoaderManager.LoaderCallbacks 方法 //onCreateLoader()。在此方法中,您可以實(shí)現(xiàn)代碼以實(shí)例化并返回新加載器// 創(chuàng)建一個(gè)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); }復(fù)制代碼

    具體請(qǐng)參看官網(wǎng)Loader介紹.

    3.2.6 特別注意

    使用Thread和HandlerThread時(shí), 為了使效果更好, 建議設(shè)置Thread的優(yōu)先級(jí)偏低一點(diǎn):

    Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);復(fù)制代碼

    因?yàn)槿绻麤](méi)有做任何優(yōu)先級(jí)設(shè)置的話, 你創(chuàng)建的Thread默認(rèn)和UI Thread是具有同樣的優(yōu)先級(jí)的, 你懂的. 同樣的優(yōu)先級(jí)的Thread, CPU調(diào)度上還是可能會(huì)阻塞掉你的UI Thread, 導(dǎo)致ANR的.

    結(jié)語(yǔ)

    對(duì)于ANR問(wèn)題, 個(gè)人認(rèn)為還是預(yù)防為主, 認(rèn)清代碼中的阻塞點(diǎn), 善用線程. 同時(shí)形成良好的編程習(xí)慣, 要有MainThread和Worker Thread的概念的...(實(shí)際上人的工作狀態(tài)也是這樣的~~哈哈)

    總結(jié)

    以上是生活随笔為你收集整理的Android App 优化之 ANR 详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 性福网站 | 国产精品久久久久久三级 | 午夜av在线播放 | 日本少妇做爰全过程毛片 | 99热99re6国产在线播放 | 久久精品久久久久 | 黄色美女一级片 | 国产综合久久久久久鬼色 | 免费成人av | 国产露脸无套对白在线播放 | 欧美极品视频在线观看 | 黄瓜视频在线免费看 | 顶级尤物极品女神福利视频 | 色香蕉av| 福利在线电影 | 老头把女人躁得呻吟 | 79日本xxxxxxxxx18| 日韩大片一区 | 高清国产mv在线观看 | 黑人玩弄人妻一区二区三区 | 欧美在线观看视频一区 | 澳门久久| 日韩电影在线观看一区 | 天堂中文在线观看 | 亚洲一区电影在线观看 | 欧美久久成人 | www.玖玖玖 | 午夜宅男影院 | 亚洲永久网站 | 欧美老司机 | 超碰97观看 | 国产91在线精品 | 欧美人与动物xxxxx | 亚洲精品成人影视 | 粉嫩av一区二区三区四区五区 | 亚洲精品网站在线播放gif | 精品人妻一区二区三 | 又黄又爽的视频 | 欧美激情三级 | 91偷拍精品一区二区三区 | 亚洲天堂h | 极品白嫩少妇无套内谢 | 菊肠扩张playh | 日韩精品免费一区二区在线观看 | 青青青草视频在线 | 国产欧美日韩综合精品 | 黄网站在线观看 | 午夜精品福利一区二区 | 每日av更新 | 人妻无码一区二区三区久久 | 在线观看国产一区二区三区 | 欧美乱大交xxxxx潮喷 | 国产老妇伦国产熟女老妇视频 | 强行糟蹋人妻hd中文 | 九九色精品 | 91欧美激情一区二区三区 | 舒淇裸体午夜理伦 | 性猛交xxxx乱大交3 | 日韩精品影视 | 青青草原av在线 | 亚洲中文字幕无码av | 国产一级视频免费观看 | 火影忍者羞羞漫画 | www.亚洲高清 | 日韩欧美二区三区 | 天堂影视在线观看 | 国产一区精品视频 | av巨作 | 日韩av成人在线观看 | 亚洲国产一区二区在线 | 视频二区欧美 | 免费观看色 | 日本毛片在线观看 | 免费人成在线观看网站 | 黄色a在线观看 | 免费网站av| 日韩av手机在线免费观看 | 99热国内精品 | 亚洲成人精| 亚洲av无码不卡一区二区三区 | 最近2019中文字幕大全第二页 | 国产a国产片 | 一级美女大片 | 女人高潮特级毛片 | 中文字幕一区二区人妻视频 | 神马午夜伦| 91九色蝌蚪视频 | 久久白浆| 欧美午夜网站 | 制服丝袜亚洲 | 日批动态图 | 玖玖999 | 少妇全黄性生交片 | 69视频网 | 国产女人呻吟高潮抽搐声 | 国产成人手机视频 | 精品电影一区二区 | 国产全肉乱妇杂乱视频 | www黄在线观看 |