中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android ANR的原理是什么

發布時間:2021-06-23 11:39:44 來源:億速云 閱讀:210 作者:chen 欄目:開發技術

這篇文章主要介紹“Android ANR的原理是什么”,在日常操作中,相信很多人在Android ANR的原理是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Android ANR的原理是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

目錄
  • 卡頓原理

  • 卡頓監控

  • ANR原理

卡頓原理

主線程有耗時操作會導致卡頓,卡頓超過閥值,觸發ANR。 應用進程啟動時候,Zygote會反射調用ActivityThread的main方法,啟動loop循環。 ActivityThread(api29)

public static void main(String[] args) {
        Looper.prepareMainLooper();
        ...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

Looper的loop方法:

// 在線程中運行消息隊列。一定要調用
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、消息處理前回調
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            ...
            // 3、消息開始處理
            msg.target.dispatchMessage(msg);
            ...
            // 4、消息處理完回調
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
        }
}

loop中for循環存在,主線程可以長時間運行。在主線程執行任務,可以通過Handler post一個任務到消息隊列去,loop循環拿到msg,交給msg的target(Handler)處理。

可能導致卡頓兩個地方:

  • 注釋1 queue.next()

  • 注釋3 dispatchMessage耗時

MessageQueue.next 耗時代碼(api29)

@UnsupportedAppUsage
    Message next() {
        for (;;) {
            // 1、nextPollTimeoutMillis不為0則阻塞
            nativePollOnce(ptr, nextPollTimeoutMillis);
            // 2、先判斷當前第一條消息是不是同步屏障消息,
            if (msg != null && msg.target == null) {
                    // 3、遇到同步屏障消息,就跳過去取后面的異步消息來處理,同步消息相當于被設立了屏障
                    // 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、如果沒有取到異步消息,下次循環到注視1,nativePollOnce為-1,會一直阻塞
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
        }
    }
  1. MessageQueue是鏈表數據結構,判斷MessageQueue頭部(第一個消息)是不是同步屏障消息(給同步消息加一層屏障,讓同步消息不被處理,只會處理異步消息);

  2. 如果遇到同步屏障消息,就會跳過MessageQueue中同步消息,只會處理里面的異步消息來處理。如果沒有異步消息則到注釋5,nextPollTimeoutMillis為-1,下次循環調用注釋1的nativePollOnce就會阻塞;

  3. 如果looper能正常獲取消息,不論異步/同步消息,處理流程一樣,在注釋4,判斷是否延時,如果是,nextPollTimeoutMillis被賦值,下次調用注釋1的nativePollOnce就會阻塞一段時間。如果不是delay消息,直接返回msg,給handler處理。

next方法不斷從MessageQueue取消息,有消息就處理,沒有消息就調用nativePollOnce阻塞,底層是Linux的epoll機制,Linux IO多路復用。

Linux IO多路復用方案有select、poll、epoll。其中epoll性能最優,支持并發量最大。

  • select: 是操作系統提供的系統調用函數,可以把文件描述符的數組發給操作系統,操作系統去遍歷,確定哪個描述符可以讀寫,告訴我們去處理。

  • poll:和select主要區別,去掉了select只能監聽1024個文件描述符的限制。

  • epoll:針對select的三個可優化點進行改進。

1、內核中保持一份文件描述符集合,無需用戶每次重新傳入,只需要告訴內核修改部分。
2、內核不再通過輪詢方式找到就緒的文件描述符,通過異步IO事件喚醒。
3、內核僅會將有IO的文件描述符返回給用戶,用戶無需遍歷整個文件描述符集合。

同步屏障消息

Android App是無法直接調用同步消息屏障的,MessageQueue(api29)代碼

@TestApi
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        ...
    }

系統高優先級的操作使用到同步屏障消息,例如: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的繪制過程不被主線程其他任務影響,View在繪制之前會先往MessageQueue插入同步屏障消息,然后再注冊Vsync信號監聽,Choreographer$FrameDisplayEventReceiver監聽接收vsync信號回調的。

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、發送異步消息
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
              }
              
                      @Override
            public void run() {
                // 2、doFrame優先執行
                doFrame(mTimestampNanos, mFrame);
              }
            }

收到Vsync信號回調,注釋1往主線程MessageQueue post一個異步消息,保證注釋2的doFrame優先執行。

doFrame才是View真正開始繪制的地方,會調用ViewRootIml的doTraversal、performTraversals,而performTraversals里面會調用View的onMeasure、onLayout、onDraw。

雖然app無法發送同步屏障消息,但是使用異步消息是允許的。

異步消息 SDK中限制了App不能post異步消息到MessageQueue中,Message類

@UnsupportedAppUsage
    /*package*/ int flags;

謹慎使用異步消息,使用不當,可能出現主線程假死。

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);
        }
    }
  1. Handler#post(Runnable r)

  2. 構造方法傳CallBack

  3. Handler重寫handlerMessage方法

應用卡頓,一般都是Handler處理消息太耗時導致的(方法本身、算法效率、cpu被搶占、內存不足、IPC超時等)

卡頓監控

卡頓監控方案一 Looper#loop

// 在線程中運行消息隊列。一定要調用
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、消息處理前回調
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            ...
            // 3、消息開始處理
            msg.target.dispatchMessage(msg);
            ...
            // 4、消息處理完回調
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
        }
}

注釋2和4的logging.println是api提供接口,可監聽Handler耗時,通過Looper.getMainLooper().setMessageLogging(printer),拿到消息前后的時間。監聽到卡頓后,dispatchMessage早已調用結束,堆棧不包含卡頓代碼。

定時獲取主線程堆棧,時間為key,堆棧信息為value,保存map中,發生卡頓,取出卡頓時間內的堆棧可行。適合線下使用。

  1. logging.println存在字符串拼接,頻繁調用,創建大量對象,內存抖動。

  2. 后臺頻繁獲取主線程堆棧,對性能影響,獲取主線程堆棧,暫停主線程的運行。

卡頓監控方案二

對于線上卡頓監控,需要字節碼插樁技術。

通過Gradle Plugin+ASM,編譯期在每個方法開始和結束位置分別插入一行代碼,統計耗時。例如微信Matrix使用的卡頓監控方案。注意問題:

  1. 避免方法數暴增:分配獨立ID作為參數

  2. 過濾簡單函數:添加黑明單降低非必要函數統計

微信Matrix做大量優化,包體積增加1%~2%,幀率下降2幀以內,灰度包使用。

ANR原理

  • Service Timeout:前臺服務20s內未執行完成,后臺服務是10s

  • BroadcastQueue Timeout:前臺廣播10s內執行完成,后臺60s

  • ContentProvider Timeout:publish超時10s

  • InputDispatching Timeout:輸入事件分發超過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觸發流程

埋炸彈

后臺sevice調用:Context.startService--> AMS.startService--> ActiveService.startService--> ActiveService.realStartServiceLocked

private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
                // 1、發送delay消息(SERVICE_TIMEOUT_MSG)
                bumpServiceExecutingLocked(r, execInFg, "create");
                try {
                    //  2、通知AMS創建服務
                    app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                    app.getReportedProcState());
                }
            }

注釋1內部調用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;
        // 發送delay消息,前臺服務是20s,后臺服務是200s
        mAm.mHandler.sendMessageDelayed(msg,
                proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    }

注釋2通知AMS啟動服務前,注釋1發送handler延遲消息,20s內(前臺服務)沒有處理完,則ActiveServices#serviceTimeout被調用。

拆炸彈

啟動一個Service,先要經過AMS管理,然后AMS通知應用執行Service的生命周期,ActivityThread的handlerCreateService方法被調用。

@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調用
            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方法被調用 注釋2,調用AMS的serviceDoneExecuting方法,最終會調用ActiveServices.serviceDoneExecutingLocked

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
            boolean finishing) {
                //移除delay消息 
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
       
       }

onCreate調用后,就會移除delay消息,炸彈拆除。

引爆炸彈,假設Service的onCreate執行超過10s,那么炸彈就會引爆,也就是ActiveServices#serviceTimeout方法會被調用。api29

void serviceTimeout(ProcessRecord proc) {
        if (anrMessage != null) {
            proc.appNotResponding(null, null, null, null, false, anrMessage);
        }
}

所有ANR,最終帶調用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,發一個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,直接殺進程
            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,會調用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);
        }
  }
  1. 寫入event log

  2. 寫入main log

  3. 生成tracesFile

  4. 輸出ANR logcat(控制臺可以看到)

  5. 如果沒有獲取tracesFile,會發SIGNAL_QUIT信號,觸發收集線程堆棧信息流程,寫入traceFile

  6. 輸出到drapbox

  7. 后臺ANR,直接殺進程

  8. 錯誤報告

  9. 彈出ANR dialog 調用AppErrors#handleShowAnrUi方法。

ANR觸發流程,埋炸彈--》拆炸彈的過程
啟動Service,onCreate方法調用之前會使用Handler延時10s的消息,Service的onCreate方法執行完,會把延遲消息移除掉。
假如Service的onCreate方法耗時超過10s,延時消息就會被正常處理,觸發ANR,收集cpu、堆棧消息,彈ANR dialog

抓取系統的data/anr/trace.txt文件,但是高版本系統需要root權限才能讀取這個目錄。

ANRWatchDog github.com/SalomonBrys…

自動檢測ANR開源庫

到此,關于“Android ANR的原理是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

安达市| 霍州市| 九江县| 虹口区| 丰宁| 紫金县| 河源市| 新民市| 饶河县| 民和| 乌审旗| 岗巴县| 新丰县| 东丰县| 阳新县| 安康市| 丹东市| 罗定市| 和政县| 博乐市| 法库县| 上虞市| 舟山市| 大悟县| 开阳县| 元朗区| 东丽区| 顺平县| 平山县| 嘉峪关市| 冷水江市| 阿克苏市| 武山县| 彭阳县| 昭通市| 高陵县| 乐亭县| 白朗县| 修武县| 鄂托克前旗| 海淀区|