【原始碼學習】window 的刪除及更新過程

耳東_發表於2018-07-18

此類文章主要是用來記錄學習原始碼的過程,更多的參考別人的分析過程自己去追蹤原始碼,然後做下的記錄。看 Android 原始碼是一個痛苦的過程,之前幾次嘗試都以失敗而告終,這裡把這個過程記錄下來,算是對自己的一種激勵。

上一篇分析了 window 的新增過程,這一篇來繼續分析 window 的刪除和更新過程。和新增類似的,分析了新增的過程,刪除和更新已經很明瞭了。

刪除

和新增一樣,也是通過 WindowManagerImpl,然後在委託給 WindowManagerGlobal 的 removeView 方法實現。

WindowManagerImpl 提供了兩種刪除 view 的方法,分別為同步和非同步:


    // 非同步刪除
    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    // 同步刪除
    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

WindowManagerGlobal 的 removeView 方法如下:

    public void removeView(View view, boolean immediate) {
        // 先檢測是否為空
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            //從mViews 列表中找到當前view的位置
            int index = findViewLocked(view, true);
            //從 ViewRootImpl 中獲取到被新增的view
            View curView = mRoots.get(index).getView();
            // 在這裡做進一步的刪除
            removeViewLocked(index, immediate);
            // 如果從 ViewRootImpl 獲取到的 view 和要移除的view一樣就返回,否則丟擲異常
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

WindowManagerGlobal.removeView 中沒有做具體的刪除操作,而是做了一些檢測以及查詢工作,然後交給了它的removeViewLocked 繼續,進入這個方法看看:

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            //關閉軟鍵盤
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        // 傳送刪除訊息
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                // mDyingViews 是待刪除view的列表
                // 這裡的的邏輯是如果是非同步刪除就將view新增到待被刪除的列表中(為什麼是這樣繼續向下看 die 方法)
                mDyingViews.add(view);
            }
        }
    }

removeViewLocked 方法也非常簡單,可以看到它呼叫了當前 view 對應的 ViewRootImpl 的 die 方法,然而它也沒有真正實現刪除操作,那麼就進入這個方法看一看:

    /**
     * @param immediate True, do now if not in traversal. False, put on queue and do later.
     * @return True, request has been queued. False, request has been completed.
     */
    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        //如果是同步刪除,並且沒有在遍歷執行中則執行 doDie 方法立即刪除
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }
        // 如果沒有在繪製就銷燬渲染器
        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        //傳送訊息進行非同步刪除。
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

可以看到同步刪除和非同步刪除的邏輯是不同的,同步刪除立即呼叫了 doDie 方法,非同步刪除則通過 handler 傳送了 MSG_DIE 訊息。在 handler 接收了訊息後也立馬呼叫了 doDie 方法。因為handler內部是佇列的形式來處理訊息,所以非同步刪除呼叫 doDie 的時機要比同步刪除稍晚一些,所以非同步刪除立即返回了 true 並且在 WindowManagerGlobal.removeViewLocked 方法中將要刪除的 view 新增到了 mDyingViews 列表中。

回憶新增 view 的過程在 WindowManagerGlobal.addView 中對 mDyingViews 有一個檢測。

那麼刪除 view 的操作應該是在 ViewRootImpl.doDie 中執行的了?繼續檢視 ViewRootImpl.doDie 方法:

  void doDie() {
        // 檢查呼叫執行緒 只有建立 view 的執行緒才能修改它(奇怪的是它並沒有檢測說必須要在主執行緒?)
        checkThread();

        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            //mAdded 在 setView 方法中會被設為 true
            if (mAdded) {
                // 呼叫此方法來進行刪除操作
                dispatchDetachedFromWindow();
            }
            ...

            mAdded = false;
        }
        // 最終呼叫 WindowManagerGlobal的doRemoveView方法來刪除資料
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

在 doDie 中也沒有完成真正的刪除操作,而是呼叫了 ViewRootImpl.dispatchDetachedFromWindow 方法並且最終呼叫了 WindowManagerGlobal 的 doRemoveView 方法。

先檢視 dispatchDetachedFromWindow:

// 這裡只列出部分程式碼
    void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            // 呼叫了 view 的dispatchDetachedFromWindow方法
            mView.dispatchDetachedFromWindow();
        }

        ...
        //清除相關資料、訊息、回撥等
        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;
        mSurface.release();

        ...
        try {
            // 通過 IPC 機制刪除 window
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }

        ...
    }

可以看到 ViewRootImpl.dispatchDetachedFromWindow 方法主要做了三件事情:

  • 呼叫了 view 的 dispatchDetachedFromWindow 方法,在 view.dispatchDetachedFromWindow 方法中,主要有兩個回撥 onDetachedFromWindowonDetachedFromWindowInternal,這些就是我們常用的監聽,可以在裡面做一些自己的資源回收工作。
  • 清除資料、訊息、回撥等。
  • 呼叫 session 的 remove 方法,和新增一樣最終它會呼叫 WindowManagerService 的 removeWindow 方法。

同時在 doDie 的最後還呼叫了 WindowManagerGlobal 的 doRemoveView 方法,這個方法很簡單:

    void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
        }
        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }
    }

在 doRemoveView 方法中清除了和要刪除的 view 相關的一切資料。

至此 window 的刪除過程就全部完成,也很簡單,用一個圖來簡單表示:

window刪除

更新

相對於新增和刪除,更新操作要相對簡單一些,前面還是一樣,更新操作是由 viewManager 介面提供的 updateViewLayout 方法實現,它的實現還是在 WindowManagerImpl,最終被委託給了 WindowManagerGlobal.updateViewLayout,所以檢視這個方法:

//WindowManagerGlobal.updateViewLayout
    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        ...
        view.setLayoutParams(wparams);
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            // 刪除掉老的 LayoutParams
            mParams.remove(index);
            // 替換成新的 LayoutParams
            mParams.add(index, wparams);
            // 更新 ViewRootImpl 的 LayoutParams 
            root.setLayoutParams(wparams, false);
        }
    }

updateViewLayout 方法很簡單,它首先替換了舊的佈局引數,然後又通過 ViewRootImpl.setLayoutParams 更新 ViewRootImpl 中的佈局引數,ViewRootImpl.setLayoutParams 方法中主要是呼叫了 ViewRootImpl.scheduleTraversals 方法:

// ViewRootImpl.scheduleTraversals
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 最終呼叫了 doTraversal 來執行 測量、佈局、重繪操作。
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            // 這裡最終通過 IPC 機制呼叫 wms 來更新檢視
            pokeDrawLockIfNeeded();
        }
    }

在 scheduleTraversals 方法中,執行了 view 的三大流程—— 測量、佈局、重繪,並且通過 IPC 機制進行了檢視的更新。

結語

以上就是 window 進行刪除和更新的流程分析。水平所限有些地方還不是太清楚,留作以後進一步發掘吧。

參考

  • 《Android 開發藝術探索》——任玉剛

相關文章