您好,登錄后才能下訂單哦!
這篇文章主要介紹“Android ANR的原理是什么”,在日常操作中,相信很多人在Android ANR的原理是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Android ANR的原理是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
卡頓原理
卡頓監(jiān)控
ANR原理
主線程有耗時操作會導(dǎo)致卡頓,卡頓超過閥值,觸發(fā)ANR。 應(yīng)用進(jìn)程啟動時候,Zygote會反射調(diào)用ActivityThread的main方法,啟動loop循環(huán)。 ActivityThread(api29)
public static void main(String[] args) { Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
Looper的loop方法:
// 在線程中運(yùn)行消息隊列。一定要調(diào)用 public static void loop() { for (;;) { // 1、取消息 Message msg = queue.next(); // might block ... // This must be in a local variable, in case a UI event sets the logger // 2、消息處理前回調(diào) final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } ... // 3、消息開始處理 msg.target.dispatchMessage(msg); ... // 4、消息處理完回調(diào) if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } } }
loop中for循環(huán)存在,主線程可以長時間運(yùn)行。在主線程執(zhí)行任務(wù),可以通過Handler post一個任務(wù)到消息隊列去,loop循環(huán)拿到msg,交給msg的target(Handler)處理。
可能導(dǎo)致卡頓兩個地方:
注釋1 queue.next()
注釋3 dispatchMessage耗時
MessageQueue.next 耗時代碼(api29)
@UnsupportedAppUsage Message next() { for (;;) { // 1、nextPollTimeoutMillis不為0則阻塞 nativePollOnce(ptr, nextPollTimeoutMillis); // 2、先判斷當(dāng)前第一條消息是不是同步屏障消息, if (msg != null && msg.target == null) { // 3、遇到同步屏障消息,就跳過去取后面的異步消息來處理,同步消息相當(dāng)于被設(shè)立了屏障 // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } // 4、正常消息處理,判斷是否延時 if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // 5、如果沒有取到異步消息,下次循環(huán)到注視1,nativePollOnce為-1,會一直阻塞 // No more messages. nextPollTimeoutMillis = -1; } } }
MessageQueue是鏈表數(shù)據(jù)結(jié)構(gòu),判斷MessageQueue頭部(第一個消息)是不是同步屏障消息(給同步消息加一層屏障,讓同步消息不被處理,只會處理異步消息);
如果遇到同步屏障消息,就會跳過MessageQueue中同步消息,只會處理里面的異步消息來處理。如果沒有異步消息則到注釋5,nextPollTimeoutMillis為-1,下次循環(huán)調(diào)用注釋1的nativePollOnce就會阻塞;
如果looper能正常獲取消息,不論異步/同步消息,處理流程一樣,在注釋4,判斷是否延時,如果是,nextPollTimeoutMillis被賦值,下次調(diào)用注釋1的nativePollOnce就會阻塞一段時間。如果不是delay消息,直接返回msg,給handler處理。
next方法不斷從MessageQueue取消息,有消息就處理,沒有消息就調(diào)用nativePollOnce阻塞,底層是Linux的epoll機(jī)制,Linux IO多路復(fù)用。
Linux IO多路復(fù)用方案有select、poll、epoll。其中epoll性能最優(yōu),支持并發(fā)量最大。
select: 是操作系統(tǒng)提供的系統(tǒng)調(diào)用函數(shù),可以把文件描述符的數(shù)組發(fā)給操作系統(tǒng),操作系統(tǒng)去遍歷,確定哪個描述符可以讀寫,告訴我們?nèi)ヌ幚怼?/p>
poll:和select主要區(qū)別,去掉了select只能監(jiān)聽1024個文件描述符的限制。
epoll:針對select的三個可優(yōu)化點(diǎn)進(jìn)行改進(jìn)。
1、內(nèi)核中保持一份文件描述符集合,無需用戶每次重新傳入,只需要告訴內(nèi)核修改部分。 2、內(nèi)核不再通過輪詢方式找到就緒的文件描述符,通過異步IO事件喚醒。 3、內(nèi)核僅會將有IO的文件描述符返回給用戶,用戶無需遍歷整個文件描述符集合。
同步屏障消息
Android App是無法直接調(diào)用同步消息屏障的,MessageQueue(api29)代碼
@TestApi public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { ... }
系統(tǒng)高優(yōu)先級的操作使用到同步屏障消息,例如:View繪制的時候ViewRootImpl的scheduleTraversals方法,插入同步屏障消息,繪制完成后移除同步屏障消息。ViewRootImpl api29
@UnsupportedAppUsage void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } void unscheduleTraversals() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); mChoreographer.removeCallbacks( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } }
為了保證View的繪制過程不被主線程其他任務(wù)影響,View在繪制之前會先往MessageQueue插入同步屏障消息,然后再注冊Vsync信號監(jiān)聽,Choreographer$FrameDisplayEventReceiver監(jiān)聽接收vsync信號回調(diào)的。
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { @Override public void onVsync(long timestampNanos, long physicalDisplayId, int frame) { Message msg = Message.obtain(mHandler, this); // 1、發(fā)送異步消息 msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); } @Override public void run() { // 2、doFrame優(yōu)先執(zhí)行 doFrame(mTimestampNanos, mFrame); } }
收到Vsync信號回調(diào),注釋1往主線程MessageQueue post一個異步消息,保證注釋2的doFrame優(yōu)先執(zhí)行。
doFrame才是View真正開始繪制的地方,會調(diào)用ViewRootIml的doTraversal、performTraversals,而performTraversals里面會調(diào)用View的onMeasure、onLayout、onDraw。
雖然app無法發(fā)送同步屏障消息,但是使用異步消息是允許的。
異步消息 SDK中限制了App不能post異步消息到MessageQueue中,Message類
@UnsupportedAppUsage /*package*/ int flags;
謹(jǐn)慎使用異步消息,使用不當(dāng),可能出現(xiàn)主線程假死。
Handler#dispatchMessage
/** * Handle system messages here. */ public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
Handler#post(Runnable r)
構(gòu)造方法傳CallBack
Handler重寫handlerMessage方法
應(yīng)用卡頓,一般都是Handler處理消息太耗時導(dǎo)致的(方法本身、算法效率、cpu被搶占、內(nèi)存不足、IPC超時等)
卡頓監(jiān)控方案一 Looper#loop
// 在線程中運(yùn)行消息隊列。一定要調(diào)用 public static void loop() { for (;;) { // 1、取消息 Message msg = queue.next(); // might block ... // This must be in a local variable, in case a UI event sets the logger // 2、消息處理前回調(diào) final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } ... // 3、消息開始處理 msg.target.dispatchMessage(msg); ... // 4、消息處理完回調(diào) if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } } }
注釋2和4的logging.println是api提供接口,可監(jiān)聽Handler耗時,通過Looper.getMainLooper().setMessageLogging(printer),拿到消息前后的時間。監(jiān)聽到卡頓后,dispatchMessage早已調(diào)用結(jié)束,堆棧不包含卡頓代碼。
定時獲取主線程堆棧,時間為key,堆棧信息為value,保存map中,發(fā)生卡頓,取出卡頓時間內(nèi)的堆??尚小_m合線下使用。
logging.println存在字符串拼接,頻繁調(diào)用,創(chuàng)建大量對象,內(nèi)存抖動。
后臺頻繁獲取主線程堆棧,對性能影響,獲取主線程堆棧,暫停主線程的運(yùn)行。
卡頓監(jiān)控方案二
對于線上卡頓監(jiān)控,需要字節(jié)碼插樁技術(shù)。
通過Gradle Plugin+ASM,編譯期在每個方法開始和結(jié)束位置分別插入一行代碼,統(tǒng)計耗時。例如微信Matrix使用的卡頓監(jiān)控方案。注意問題:
避免方法數(shù)暴增:分配獨(dú)立ID作為參數(shù)
過濾簡單函數(shù):添加黑明單降低非必要函數(shù)統(tǒng)計
微信Matrix做大量優(yōu)化,包體積增加1%~2%,幀率下降2幀以內(nèi),灰度包使用。
Service Timeout:前臺服務(wù)20s內(nèi)未執(zhí)行完成,后臺服務(wù)是10s
BroadcastQueue Timeout:前臺廣播10s內(nèi)執(zhí)行完成,后臺60s
ContentProvider Timeout:publish超時10s
InputDispatching Timeout:輸入事件分發(fā)超過5s,包括按鍵和觸摸事件。
ActivityManagerService api29
// How long we allow a receiver to run before giving up on it. static final int BROADCAST_FG_TIMEOUT = 10*1000; static final int BROADCAST_BG_TIMEOUT = 60*1000;
ANR觸發(fā)流程
埋炸彈
后臺sevice調(diào)用:Context.startService--> AMS.startService--> ActiveService.startService--> ActiveService.realStartServiceLocked
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { // 1、發(fā)送delay消息(SERVICE_TIMEOUT_MSG) bumpServiceExecutingLocked(r, execInFg, "create"); try { // 2、通知AMS創(chuàng)建服務(wù) app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo), app.getReportedProcState()); } }
注釋1內(nèi)部調(diào)用scheduleServiceTimeoutLocked
void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.executingServices.size() == 0 || proc.thread == null) { return; } Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; // 發(fā)送delay消息,前臺服務(wù)是20s,后臺服務(wù)是200s mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); }
注釋2通知AMS啟動服務(wù)前,注釋1發(fā)送handler延遲消息,20s內(nèi)(前臺服務(wù))沒有處理完,則ActiveServices#serviceTimeout被調(diào)用。
拆炸彈
啟動一個Service,先要經(jīng)過AMS管理,然后AMS通知應(yīng)用執(zhí)行Service的生命周期,ActivityThread的handlerCreateService方法被調(diào)用。
@UnsupportedAppUsage private void handleCreateService(CreateServiceData data) { try { Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); // 1、service onCreate調(diào)用 service.onCreate(); mServices.put(data.token, service); try { // 2、拆炸彈 ActivityManager.getService().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } }
注釋1,Service的onCreate方法被調(diào)用 注釋2,調(diào)用AMS的serviceDoneExecuting方法,最終會調(diào)用ActiveServices.serviceDoneExecutingLocked
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) { //移除delay消息 mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); }
onCreate調(diào)用后,就會移除delay消息,炸彈拆除。
引爆炸彈,假設(shè)Service的onCreate執(zhí)行超過10s,那么炸彈就會引爆,也就是ActiveServices#serviceTimeout方法會被調(diào)用。api29
void serviceTimeout(ProcessRecord proc) { if (anrMessage != null) { proc.appNotResponding(null, null, null, null, false, anrMessage); } }
所有ANR,最終帶調(diào)用ProcessRecord的appNotResponding方法。api29
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo, String parentShortComponentName, WindowProcessController parentProcess, boolean aboveSystem, String annotation) { // 1、寫入event log // Log the ANR to the event log. EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags, annotation); // 2、收集需要的log、anr、cpu等,放到StringBuilder中。 // Log the ANR to the main log. StringBuilder info = new StringBuilder(); info.setLength(0); info.append("ANR in ").append(processName); if (activityShortComponentName != null) { info.append(" (").append(activityShortComponentName).append(")"); } info.append("\n"); info.append("PID: ").append(pid).append("\n"); if (annotation != null) { info.append("Reason: ").append(annotation).append("\n"); } if (parentShortComponentName != null && parentShortComponentName.equals(activityShortComponentName)) { info.append("Parent: ").append(parentShortComponentName).append("\n"); } ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); // 3、dump堆棧信息,包括java堆棧和native堆棧,保存到文件中 // For background ANRs, don't pass the ProcessCpuTracker to // avoid spending 1/2 second collecting stats to rank lastPids. File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, (isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids, nativePids); String cpuInfo = null; // 4、輸出ANR日志 Slog.e(TAG, info.toString()); if (tracesFile == null) { // 5、沒有抓到tracesFile,發(fā)一個SIGNAL_QUIT信號 // There is no trace file, so dump (only) the alleged culprit's threads to the log Process.sendSignal(pid, Process.SIGNAL_QUIT); } // 6、輸出到drapbox mService.addErrorToDropBox("anr", this, processName, activityShortComponentName, parentShortComponentName, parentPr, annotation, cpuInfo, tracesFile, null); synchronized (mService) { // 7、后臺ANR,直接殺進(jìn)程 if (isSilentAnr() && !isDebugging()) { kill("bg anr", true); return; } // 8、錯誤報告 // Set the app's notResponding state, and look up the errorReportReceiver makeAppNotRespondingLocked(activityShortComponentName, annotation != null ? "ANR " + annotation : "ANR", info.toString()); // 9、彈出ANR dialog,會調(diào)用handleShowAnrUi方法 Message msg = Message.obtain(); msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem); mService.mUiHandler.sendMessage(msg); } }
寫入event log
寫入main log
生成tracesFile
輸出ANR logcat(控制臺可以看到)
如果沒有獲取tracesFile,會發(fā)SIGNAL_QUIT信號,觸發(fā)收集線程堆棧信息流程,寫入traceFile
輸出到drapbox
后臺ANR,直接殺進(jìn)程
錯誤報告
彈出ANR dialog 調(diào)用AppErrors#handleShowAnrUi方法。
ANR觸發(fā)流程,埋炸彈--》拆炸彈的過程 啟動Service,onCreate方法調(diào)用之前會使用Handler延時10s的消息,Service的onCreate方法執(zhí)行完,會把延遲消息移除掉。 假如Service的onCreate方法耗時超過10s,延時消息就會被正常處理,觸發(fā)ANR,收集cpu、堆棧消息,彈ANR dialog
抓取系統(tǒng)的data/anr/trace.txt文件,但是高版本系統(tǒng)需要root權(quán)限才能讀取這個目錄。
ANRWatchDog github.com/SalomonBrys…
自動檢測ANR開源庫
到此,關(guān)于“Android ANR的原理是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。