面試官:爲何 Activity.finish() 以後 10s 才 onDestroy ?

Android 面試進階指南目錄java

計算機網絡android

  1. http 速查

Android程序員

  1. 面試官:任務棧?返回棧?啓動模式?傻傻分不清楚?
  2. 面試官:嘮嘮 Activity 的生命週期
  3. 面試官: 說一說 Context
  4. 面試官:爲何不能使用 Application Context 顯示 Dialog?
  5. 面試官:OutOfMemoryError 能夠被 try catch 嗎 ?
  6. 面試官:爲何 Activity.finish() 以後 10s 才 onDestroy ?
  7. 面試官:如何監測應用的 FPS ?
  8. 面試官:爲何 View.post 能夠獲取到視圖寬高?

目錄

沒有及時回調的 onStop/onDestroy

交流羣裏碰到一個頗有意思的問題,調用 Activity.finish() 以後 10s 纔回調 onDestroy() 。 由此產生了一些不可控問題,例如在 onDestroy() 中釋放資源不及時,賦值狀態異常等等。我以前倒沒有遇到過相似的問題,可是 AOSP 老是咱們最好的老師。從 Activity.finish() 開始擼了一遍流程,找到了問題的答案。面試

在讀源碼以前,咱們先來複現一下 10s onDestroy 的場景。寫一個最簡單的 FirstActivity 跳轉到 SecondActivity 的場景,並記錄下各個生命週期和調用 finish() 的時間間隔。數組

class FirstActivity : BaseLifecycleActivity() {

    private val binding by lazy { ActivityFirstBinding.inflate(layoutInflater) }
    var startTime = 0L

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.goToSecond.setOnClickListener {
            start<SecondActivity>()
            finish()
            startTime = System.currentTimeMillis()
        }
    }

    override fun onPause() {
        super.onPause()
        Log.e("finish","onPause() 距離 finish() :${System.currentTimeMillis() - startTime} ms")
    }

    override fun onStop() {
        super.onStop()
        Log.e("finish","onStop() 距離 finish() :${System.currentTimeMillis() - startTime} ms")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.e("finish","onDestroy() 距離 finish() :${System.currentTimeMillis() - startTime} ms")
    }
}
複製代碼

SecondActivity 是一個普通的沒有進行任何操做的空白 Activity 。點擊按鈕跳轉到 SecondActivity,打印日誌以下:markdown

FirstActivity: onPause,onPause() 距離 finish() :5 ms
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume
FirstActivity: onStop,onStop() 距離 finish() :660 ms
FirstActivity: onDestroy,onDestroy() 距離 finish() :663 ms
複製代碼

能夠看到正常狀況下,FirstActivity 回調 onPause 以後,SecondActivity 開始正常的生命週期流程,直到 onResume 被回調,對用戶可見時,FirstActivity 纔會回調 onPause 和 onDestroy 。時間間隔也都在正常範圍之內。網絡

咱們再模擬一個在 SecondActivity 啓動時進行大量動畫的場景,源源不斷的向主線程消息隊列塞消息。修改一下 SecondActivity 的代碼。app

class SecondActivity : BaseLifecycleActivity() {

    private val binding by lazy { ActivitySecondBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        postMessage()
    }

    private fun postMessage() {
        binding.secondBt.post {
            Thread.sleep(10)
            postMessage()
        }
    }
}
複製代碼

再來看一下日誌:異步

FirstActivity: onPause, onPause() 距離 finish() :6 ms
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume
FirstActivity: onStop, onStop() 距離 finish() :10033 ms
FirstActivity: onDestroy, onDestroy() 距離 finish() :10037 ms
複製代碼

FirstActivity 的 onPause() 沒有受到影響。由於在 Activity 跳轉過程當中,目標 Activity 只有在前一個 Activity onPause() 以後纔會開始正常的生命週期。而 onStoponDestroy() 整整過了 10s 纔回調。ide

對比以上兩個場景,咱們能夠猜想,當 SecondActivity 的主線程過於繁忙,沒有機會停下來喘口氣的時候,會形成 FirstActivity 沒法及時回調 onStoponDestroy 。基於以上猜想,咱們就能夠從 AOSP 中來尋找答案了。

接下來就是大段的枯燥的源碼分析了。帶着問題去讀 AOSP,可讓這個過程不是那麼 「枯燥」,並且必定會有不少不同的收穫。

從 Activity.finish() 提及

如下源代碼基於 Android 9.0 版本。

> Activity.java public void finish() {
    finish(DONT_FINISH_TASK_WITH_ACTIVITY);
}
複製代碼

重載了帶參數的 finish() 方法。參數是 DONT_FINISH_TASK_WITH_ACTIVITY ,含義也很直白,不會銷燬 Activity 所在的任務棧。

> Activity.java private void finish(int finishTask) {
    // mParent 通常爲 null,在 ActivityGroup 中會使用到
    if (mParent == null) {
        ......
        try {
			// Binder 調用 AMS.finishActivity()
            if (ActivityManager.getService()
                    .finishActivity(mToken, resultCode, resultData, finishTask)) {
                mFinished = true;
            }
        } catch (RemoteException e) {
        }
    } else {
        mParent.finishFromChild(this);
    }
    ......
}
複製代碼

這裏的 mParent 大多數狀況下都是 null ,不須要考慮 else 分支的狀況。一些大齡 Android 程序員可能會了解 ActivityGroup,在此種狀況下 mParent 可能會不爲 null。(由於我還年輕,因此沒有使用過 ActivityGroup,就不過多解釋了。)其中 Binder 調用了 AMS.finishActivity() 方法。

> ActivityManagerService.java public final boolean finishActivity(IBinder token, int resultCode, Intent resultData, int finishTask) {
    ......
    synchronized(this) {
        // token 持有 ActivityRecord 的弱引用
        ActivityRecord r = ActivityRecord.isInStackLocked(token);
        if (r == null) {
            return true;
        }
        ......
        try {
            boolean res;
            final boolean finishWithRootActivity =
                    finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
            // finishTask 參數是 DONT_FINISH_TASK_WITH_ACTIVITY,進入 else 分支
            if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
                    || (finishWithRootActivity && r == rootR)) {
                res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
                        finishWithRootActivity, "finish-activity");
            } else {
            	// 調用 ActivityStack.requestFinishActivityLocked()
                res = tr.getStack().requestFinishActivityLocked(token, resultCode,
                        resultData, "app-request", true);
            }
            return res;
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }
}

複製代碼

注意方法參數中的 token 對象,在上一篇文章 爲何不能使用 Application Context 顯示 Dialog? 中詳細介紹過,Token 是 ActivityRecord 的靜態內部類,它持有外部 ActivityRecord 的弱引用。繼承自 IApplicationToken.Stub ,是一個 Binder 對象。ActivityRecord 就是對當前 Activity 的具體描述,包含了 Activity 的全部信息。

傳入的 finishTask() 方法的參數是 DONT_FINISH_TASK_WITH_ACTIVITY,因此接着會調用 ActivityStack.requestFinishActivityLocked() 方法。

> ActivityStack.java final boolean requestFinishActivityLocked(IBinder token, int resultCode, Intent resultData, String reason, boolean oomAdj) {
    ActivityRecord r = isInStackLocked(token);
    if (r == null) {
        return false;
    }

    finishActivityLocked(r, resultCode, resultData, reason, oomAdj);
    return true;
}

    final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData, String reason, boolean oomAdj) {
        // PAUSE_IMMEDIATELY 爲 true,在 ActivityStackSupervisor 中定義
    return finishActivityLocked(r, resultCode, resultData, reason, oomAdj, !PAUSE_IMMEDIATELY);
}
複製代碼

最後調用的是一個重載的 finishActivityLocked() 方法。

> ActivityStack.java

// 參數 pauseImmediately 是 false
final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData, String reason, boolean oomAdj, boolean pauseImmediately) {
    if (r.finishing) { // 重複 finish 的狀況
        return false;
    }

    mWindowManager.deferSurfaceLayout();
    try {
		// 標記 r.finishing = true,
		// 前面會作重複 finish 的檢測就是依賴這個值
        r.makeFinishingLocked();
        final TaskRecord task = r.getTask();
        ......
		// 暫停事件分發
        r.pauseKeyDispatchingLocked();

        adjustFocusedActivityStack(r, "finishActivity");

		// 處理 activity result
        finishActivityResultsLocked(r, resultCode, resultData);

        // mResumedActivity 就是當前 Activity,會進入此分支
        if (mResumedActivity == r) {
            ......
            // Tell window manager to prepare for this one to be removed.
            r.setVisibility(false);

            if (mPausingActivity == null) {
				// 開始 pause mResumedActivity
                startPausingLocked(false, false, null, pauseImmediately);
            }
            ......
        } else if (!r.isState(PAUSING)) {
            // 不會進入此分支
            ......
        } 
        return false;
    } finally {
        mWindowManager.continueSurfaceLayout();
    }
}
複製代碼

調用 finish 以後確定是要先 pause 當前 Activity,沒毛病。接着看 startPausingLocked() 方法。

> ActivityStack.java final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming, boolean pauseImmediately) {
        ......
        ActivityRecord prev = mResumedActivity;

        if (prev == null) {
            // 沒有 onResume 的 Activity,不能執行 pause
            if (resuming == null) {
                mStackSupervisor.resumeFocusedStackTopActivityLocked();
            }
            return false;
        }
        ......

        mPausingActivity = prev;
		// 設置當前 Activity 狀態爲 PAUSING
        prev.setState(PAUSING, "startPausingLocked");
        ......

        if (prev.app != null && prev.app.thread != null) {
            try {
                ......
                // 1. 經過 ClientLifecycleManager 分發生命週期事件
                // 最終會向 H 發送 EXECUTE_TRANSACTION 事件
                mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken,
                        PauseActivityItem.obtain(prev.finishing, userLeaving,
                                prev.configChangeFlags, pauseImmediately));
            } catch (Exception e) {
                mPausingActivity = null;
            }
        } else {
            mPausingActivity = null;
        }
        ......
        // mPausingActivity 在前面已經賦值,就是當前 Activity
        if (mPausingActivity != null) { 
            ......
            if (pauseImmediately) { // 這裏是 false,進入 else 分支
                completePauseLocked(false, resuming);
                return false;
            } else {
				// 2. 發送一個延時 500ms 的消息,等待 pause 流程一點時間
				// 最終會回調 activityPausedLocked() 方法
                schedulePauseTimeout(prev);
                return true;
            }
        } else {
            // 不會進入此分支
        }
    }
複製代碼

這裏面有兩步重點操做。第一步是註釋 1 處經過 ClientLifecycleManager 分發生命週期流程。第二步是發送一個延時 500ms 的消息,等待一下 onPause 流程。可是若是第一步中在 500ms 內已經完成了流程,則會取消這個消息。因此這兩步的最終邏輯實際上是一致的。這裏就直接看第一步。

mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken,
                        PauseActivityItem.obtain(prev.finishing, userLeaving,
                                prev.configChangeFlags, pauseImmediately));
複製代碼

ClientLifecycleManager 我在以前的一篇文章 從源碼看 Activity 生命週期(上篇) 作過詳細介紹。它會向主線程的 Handler H 發送 EXECUTE_TRANSACTION 事件,調用 XXXActivityItemexecute()postExecute() 方法。execute() 方法中會 Binder 調用 ActivityThread 中對應的 handleXXXActivity() 方法。在這裏就是 handlePauseActivity() 方法,其中會經過 Instrumentation.callActivityOnPause(r.activity) 方法回調 Activity.onPause()

> Instrumentation.java public void callActivityOnPause(Activity activity) {
    activity.performPause();
}
複製代碼

到這裏,onPause() 方法就被執行了。可是流程沒有結束,接着就該顯示下一個 Activity 了。前面剛剛說過會調用 PauseActivityItemexecute()postExecute() 方法。execute() 方法回調了當前 Activity.onPause(),而 postExecute() 方法就是去尋找要顯示的 Activity 。

> PauseActivityItem.java public void postExecute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) {
    try {
        ActivityManager.getService().activityPaused(token);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}
複製代碼

Binder 調用了 AMS.activityPaused() 方法。

> ActivityManagerService.java public final void activityPaused(IBinder token) {
    synchronized(this) {
        ActivityStack stack = ActivityRecord.getStackLocked(token);
        if (stack != null) {
            stack.activityPausedLocked(token, false);
        }
    }
}
複製代碼

調用了 ActivityStack.activityPausedLocked() 方法。

> ActivityStack.java final void activityPausedLocked(IBinder token, boolean timeout) {
    final ActivityRecord r = isInStackLocked(token);
    if (r != null) {
        // 看這裏
        mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
        if (mPausingActivity == r) {
            mService.mWindowManager.deferSurfaceLayout();
            try {
                // 看這裏
                completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
            } finally {
                mService.mWindowManager.continueSurfaceLayout();
            }
            return;
        } else {
            // 不會進入 else 分支
        }
    }
}
複製代碼

上面有這麼一行代碼 mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r) ,移除的就是以前延遲 500ms 的消息。接着看 completePauseLocked() 方法。

> ActivityStack.java private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
    ActivityRecord prev = mPausingActivity;

    if (prev != null) {
		// 設置狀態爲 PAUSED
        prev.setState(PAUSED, "completePausedLocked");
        if (prev.finishing) { // 1. finishing 爲 true,進入此分支
            prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false,
                    "completedPausedLocked");
        } else if (prev.app != null) {
            // 不會進入此分支
        } else {
            prev = null;
        }
        ......
    }

    if (resumeNext) {
		// 當前獲取焦點的 ActivityStack
        final ActivityStack topStack = mStackSupervisor.getFocusedStack();
        if (!topStack.shouldSleepOrShutDownActivities()) {
			// 2. 恢復要顯示的 activity
            mStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null);
        } else {
            checkReadyForSleep();
            ActivityRecord top = topStack.topRunningActivityLocked();
            if (top == null || (prev != null && top != prev)) {
                mStackSupervisor.resumeFocusedStackTopActivityLocked();
            }
        }
    }
    ......
}
複製代碼

這裏分了兩步走。註釋1 處判斷了 finishing 狀態,還記得 finishing 在何處被賦值爲 true 的嗎?在 Activity.finish() -> AMS.finishActivity() -> ActivityStack.requestFinishActivityLocked() -> ActivityStack.finishActivityLocked() 方法中。因此接着調用的是 finishCurrentActivityLocked() 方法。註釋2 處就是來顯示應該顯示的 Activity ,就再也不追進去細看了。

再跟到 finishCurrentActivityLocked() 方法中,看這名字,確定是要 stop/destroy 沒跑了。

> ActivityStack.java

/* * 把前面帶過來的參數標出來 * prev, FINISH_AFTER_VISIBLE, false,"completedPausedLocked" */
final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj, String reason) {
     
    // 獲取將要顯示的棧頂 Activity
    final ActivityRecord next = mStackSupervisor.topRunningActivityLocked(
            true /* considerKeyguardState */);

	// 1. mode 是 FINISH_AFTER_VISIBLE,進入此分支
    if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible)
            && next != null && !next.nowVisible) {
        if (!mStackSupervisor.mStoppingActivities.contains(r)) {
			// 加入到 mStackSupervisor.mStoppingActivities
            addToStopping(r, false /* scheduleIdle */, false /* idleDelayed */);
        }
		// 設置狀態爲 STOPPING
        r.setState(STOPPING, "finishCurrentActivityLocked");
        return r;
    }

    ......

    // 下面會執行 destroy,可是代碼並不能執行到這裏
    if (mode == FINISH_IMMEDIATELY
            || (prevState == PAUSED
                && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode()))
            || finishingActivityInNonFocusedStack
            || prevState == STOPPING
            || prevState == STOPPED
            || prevState == ActivityState.INITIALIZING) {
        boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm:" + reason);
        ......
        return activityRemoved ? null : r;
    }
    ......
}
複製代碼

註釋 1 處 mode 的值是 FINISH_AFTER_VISIBLE ,而且如今新的 Activity 尚未 onResume,因此 r.visible || r.nowVisiblenext != null && !next.nowVisible 都是成立的,並不會進入後面的 destroy 流程。雖然看到這還沒獲得想要的答案,可是起碼是符合預期的。若是在這就直接 destroy 了,延遲 10s 才 onDestroy 的問題就無疾而終了。

對於這些暫時還不銷燬的 Activity 都執行了 addToStopping(r, false, false) 方法。咱們繼續追進去。

> ActivityStack.java void addToStopping(ActivityRecord r, boolean scheduleIdle, boolean idleDelayed) {
    if (!mStackSupervisor.mStoppingActivities.contains(r)) {
        mStackSupervisor.mStoppingActivities.add(r);
        ......
    }
    ......
    // 省略的代碼中,對 mStoppingActivities 的存儲容量作了限制。超出限制可能會提早出發銷燬流程
}
複製代碼

這些在等待銷燬的 Activity 被保存在了 ActivityStackSupervisormStoppingActivities 集合中,它是一個 ArrayList<ActivityRecord>

整個 finish 流程就到此爲止了。前一個 Activity 被保存在了 ActivityStackSupervisor.mStoppingActivities 集合中,新的 Activity 被顯示出來了。

問題彷佛進入了困境,何時回調 onStop/onDestroy 呢?其實這個纔是根本問題。上面擼了一遍 finish() 並看不到本質,可是能夠幫助咱們造成一個完整的流程,這個一直是看 AOSP 最大的意義,幫助咱們把零碎的上層知識造成一個完整的閉環。

是誰指揮着 onStop/onDestroy 的調用?

回到正題來,在 Activity 跳轉過程當中,爲了保證流暢的用戶體驗,只要前一個 Activity 與用戶不可交互,即 onPause() 被回調以後,下一個 Activity 就要開始本身的生命週期流程了。因此 onStop/onDestroy 的調用時間是不肯定的,甚至像文章開頭的例子中,整整過了 10s 纔回調。那麼,究竟是由誰來驅動 onStop/onDestroy 的執行呢?咱們來看看下一個 Activity 的 onResume 過程。

直接看 ActivityThread.handleResumeActivity() 方法,相信你們對生命週期的調用流程也很熟悉了。

> ActivityThread.java public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
    ......
    // 回調 onResume
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ......
    final Activity a = r.activity;
    ......
    if (r.window == null && !a.mFinished && willBeVisible) {
        ......
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
				// 添加 decorView 到 WindowManager
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    } else if (!willBeVisible) {
        ......
    }
    ......

    // 主線程空閒時會執行 Idler
    Looper.myQueue().addIdleHandler(new Idler());
}
複製代碼

handleResumeActivity() 方法是整個 UI 顯示流程的重中之重,它首先會回調 Activity.onResume() , 而後將 DecorView 添加到 Window 上,其中又包括了建立 ViewRootImpl,建立 Choreographer,與 WMS 進行 Binder 通訊,註冊 vsync 信號,著名的 measure/draw/layout。這一塊的源碼真的很值得一讀,不過不是這篇文章的重點,後面會單獨來捋一捋。

在完成最終的界面繪製和顯示以後,有這麼一句代碼 Looper.myQueue().addIdleHandler(new Idler())IdleHandler 不知道你們是否熟悉,它提供了一種機制,當主線程消息隊列空閒時,會執行 IdleHandler 的回調方法。至於怎麼算 「空閒」,咱們能夠看一下 MessageQueue.next() 方法。

> MessageQueue.java Message next() {
    ......
    int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        // 阻塞方法,主要是經過 native 層的 epoll 監聽文件描述符的寫入事件來實現的。
        // 若是 nextPollTimeoutMillis = -1,一直阻塞不會超時。
        // 若是 nextPollTimeoutMillis = 0,不會阻塞,當即返回。
        // 若是 nextPollTimeoutMillis > 0,最長阻塞nextPollTimeoutMillis毫秒(超時),若是期間有程序喚醒會當即返回。
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // msg.target == null表示此消息爲消息屏障(經過postSyncBarrier方法發送來的)
                // 若是發現了一個消息屏障,會循環找出第一個異步消息(若是有異步消息的話),全部同步消息都將忽略(日常發送的通常都是同步消息)
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 消息觸發時間未到,設置下一次輪詢的超時時間
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 獲得 Message
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse(); // 標記 FLAG_IN_USE
                    return msg;
                }
            } else {
                nextPollTimeoutMillis = -1;
            }
            ......

            /* * 兩個條件: * 1. pendingIdleHandlerCount = -1 * 2. 這次取到的 mMessage 爲空或者須要延遲處理 */
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // 沒有 idle handler 須要運行,繼續循環
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // 下一次 next 時,pendingIdleHandlerCount 又會被置爲 -1,不會致使死循環
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                // 執行 Idler
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // 將 pendingIdleHandlerCount 置零
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}
複製代碼

在正常的消息處理機制以後,額外對 IdleHandler 進行了處理。當本次取到的 Message 爲空或者須要延時處理的時候,就會去執行 mIdleHandlers 數組中的 IdleHandler 對象。其中還有一些關於 pendingIdleHandlerCount 的額外邏輯來防止循環處理。

因此,不出意外的話,當新的 Activity 完成頁面繪製並顯示以後,主線程就能夠停下歇一歇,來執行 IdleHandler 了。再回來 handleResumeActivity() 中來,Looper.myQueue().addIdleHandler(new Idler()) ,這裏的 IdlerIdleHandler 的一個具體實現類。

> ActivityThread.java

private class Idler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        ActivityClientRecord a = mNewActivities;
        ......
        }
        if (a != null) {
            mNewActivities = null;
            IActivityManager am = ActivityManager.getService();
            ActivityClientRecord prev;
            do {
                if (a.activity != null && !a.activity.mFinished) {
                    try {
                        // 調用 AMS.activityIdle()
                        am.activityIdle(a.token, a.createdConfig, stopProfiling);
                        a.createdConfig = null;
                    } catch (RemoteException ex) {
                        throw ex.rethrowFromSystemServer();
                    }
                }
                prev = a;
                a = a.nextIdle;
                prev.nextIdle = null;
            } while (a != null);
        }
        ......
        return false;
    }
}
複製代碼

Binder 調用了 AMS.activityIdle()

> ActivityManagerService.java public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
        
    final long origId = Binder.clearCallingIdentity();
    synchronized (this) {
        ActivityStack stack = ActivityRecord.getStackLocked(token);
        if (stack != null) {
            ActivityRecord r =
                    mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */,
                            false /* processPausingActivities */, config);
            ......
        }
    }
}
複製代碼

調用了 ActivityStackSupervisor.activityIdleInternalLocked() 方法。

> ActivityStackSupervisor.java final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout, boolean processPausingActivities, Configuration config) {

    ArrayList<ActivityRecord> finishes = null;
    ArrayList<UserState> startingUsers = null;
    int NS = 0;
    int NF = 0;
    boolean booting = false;
    boolean activityRemoved = false;

    ActivityRecord r = ActivityRecord.forTokenLocked(token);
       
    ......
    // 獲取要 stop 的 Activity
    final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r,
            true /* remove */, processPausingActivities);
    NS = stops != null ? stops.size() : 0;
    if ((NF = mFinishingActivities.size()) > 0) {
        finishes = new ArrayList<>(mFinishingActivities);
        mFinishingActivities.clear();
    }

    // 該 stop 的 stop
    for (int i = 0; i < NS; i++) {
        r = stops.get(i);
        final ActivityStack stack = r.getStack();
        if (stack != null) {
            if (r.finishing) {
                stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false,
                        "activityIdleInternalLocked");
            } else {
                stack.stopActivityLocked(r);
            }
        }
    }

    // 該 destroy 的 destroy
    for (int i = 0; i < NF; i++) {
        r = finishes.get(i);
        final ActivityStack stack = r.getStack();
        if (stack != null) {
            activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");
        }
    }
    ......

    return r;
}
複製代碼

stopsfinishes 分別是要 stop 和 destroy 的兩個 ActivityRecord 數組。stops 數組是經過 ActivityStackSuperVisor.processStoppingActivitiesLocked() 方法獲取的,追進去看一下。

> ActivityStackSuperVisor.java final ArrayList<ActivityRecord> processStoppingActivitiesLocked(ActivityRecord idleActivity, boolean remove, boolean processPausingActivities) {
    ArrayList<ActivityRecord> stops = null;

    final boolean nowVisible = allResumedActivitiesVisible();
    // 遍歷 mStoppingActivities
    for (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx) {
        ActivityRecord s = mStoppingActivities.get(activityNdx);
        ......
    }
    return stops;
}
複製代碼

中間的詳細處理邏輯就不看了,咱們只須要關注這裏遍歷的是 ActivityStackSuperVisor 中的 mStoppingActivities 集合 。在前面分析 finish() 流程到最後的 addToStopping() 方法時提到過,

這些在等待銷燬的 Activity 被保存在了 ActivityStackSupervisormStoppingActivities 集合中,它是一個 ArrayList<ActivityRecord>

看到這裏,終於打通了流程。再回頭想一下文章開頭的例子,因爲人爲的在 SecondActivity 不間斷的向主線程塞消息,致使 Idler 遲遲沒法被執行,onStop/onDestroy 也就不會被回調。

誰讓 onStop/onDestroy 延遲了 10s ?

對,不會被回調。 可實際狀況是這樣嗎?並非,明明是過了 10s 被回調。這就說明了即便主線程遲遲沒有機會執行 Idler,系統仍然提供了兜底機制,防止已經不須要的 Activity 長時間沒法被回收,從而形成內存泄漏等問題。從實際現象就能夠猜想到,這個兜底機制就是 onResume 以後 10s 主動去進行釋放操做。

再回到以前顯示待跳轉 Activity 的 ActivityStackSuperVisor.resumeFocusedStackTopActivityLocked() 方法。我這裏就不帶着你們追進去了,直接給出調用鏈。

ASS.resumeFocusedStackTopActivityLocked() -> ActivityStack.resumeTopActivityUncheckedLocked() -> ActivityStack.resumeTopActivityInnerLocked() -> ActivityRecord.completeResumeLocked() -> ASS.scheduleIdleTimeoutLocked()

> ActivityStackSuperVisor.java void scheduleIdleTimeoutLocked(ActivityRecord next) {
    Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG, next);
    mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT);
}
複製代碼

IDLE_TIMEOUT 的值是 10,這裏延遲 10s 發送了一個消息。這個消息是在 ActivityStackSupervisorHandler 中處理的。

private final class ActivityStackSupervisorHandler extends Handler {
......
case IDLE_TIMEOUT_MSG: {
    activityIdleInternal((ActivityRecord) msg.obj, true /* processPausingActivities */);
    } break;
......
}

void activityIdleInternal(ActivityRecord r, boolean processPausingActivities) {
    synchronized (mService) {
        activityIdleInternalLocked(r != null ? r.appToken : null, true /* fromTimeout */,
                processPausingActivities, null);
    }
}
複製代碼

忘記 activityIdleInternalLocked 方法的話能夠 ctrl+F 向上搜索一下。若是 10s 內主線程執行了 Idler 的話,就會移除這個消息。

到這裏,全部的問題就所有理清了。

最後

說一些題外話,Android 面試進階指南 是我在小專欄維護的一個付費專欄,且已經有部分付費用戶。本文是第九篇文章了,爲了維護付費用戶的權益,沒有辦法把全部文章都同步過來。若是你對這個專欄感興趣,不妨 戳進來 看一看。

Activity 的 onStop/onDestroy 是依賴 IdleHandler 來回調的,正常狀況下當主線程空閒時會調用。可是因爲某些特殊場景下的問題,致使主線程遲遲沒法空閒,onStop/onDestroy 也會遲遲得不到調用。但這並不意味着 Activity 永遠得不到回收,系統提供了一個兜底機制,當 onResume 回調 10s 以後,若是仍然沒有獲得調用,會主動觸發。

雖然有兜底機制,但不管如何這確定不是咱們想看到的。若是咱們項目中的 onStop/onDestroy 延遲了 10s 調用,該如何排查問題呢?能夠利用 Looper.getMainLooper().setMessageLogging() 方法,打印出主線程消息隊列中的消息。每處理一條消息,都會打印以下內容:

logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
複製代碼

另外,因爲 onStop/onDestroy 調用時機的不肯定性,在作資源釋放等操做的時候,必定要考慮好,以免產生資源沒有及時釋放的狀況。

相關文章
相關標籤/搜索