【原始碼學習】window 的刪除及更新過程
此類文章主要是用來記錄學習原始碼的過程,更多的參考別人的分析過程自己去追蹤原始碼,然後做下的記錄。看 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 方法中,主要有兩個回撥
onDetachedFromWindow
和onDetachedFromWindowInternal
,這些就是我們常用的監聽,可以在裡面做一些自己的資源回收工作。 - 清除資料、訊息、回撥等。
- 呼叫 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 的刪除過程就全部完成,也很簡單,用一個圖來簡單表示:
更新
相對於新增和刪除,更新操作要相對簡單一些,前面還是一樣,更新操作是由 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 開發藝術探索》——任玉剛
相關文章
- cesium原始碼編譯除錯及呼叫全過程原始碼編譯除錯
- 新手寫的Vue原始碼學習記錄(渲染過程)Vue原始碼
- Window 的新增過程
- Spring 原始碼學習 - 單例bean的例項化過程Spring原始碼單例Bean
- 刪除window10 Terminal的alias
- React 原始碼學習(八):元件更新React原始碼元件
- MySQL超大表刪除資料過程MySql
- Kubernetes原始碼分析之Pod的刪除原始碼
- Android學習筆記14-從原始碼分析Toast的建立過程Android筆記原始碼AST
- underscorejs原始碼學習系列(未完,更新中)JS原始碼
- memcached的學習過程
- 深入Vue - 原始碼目錄及構建過程分析Vue原始碼
- Linux原始碼包安裝過程及注意事項Linux原始碼
- MySQL 批量更新、刪除資料shell指令碼MySql指令碼
- mssql sqlserver 批量刪除所有儲存過程的方法分享SQLServer儲存過程
- vue 原始碼學習(二) 例項初始化和掛載過程Vue原始碼
- vue 原始碼學習(二) 例項初始化和掛載過程Vue原始碼
- Runtime原始碼 方法呼叫的過程原始碼
- Glide的load()過程原始碼分析IDE原始碼
- mysql 資料插入和更新及刪除詳情FSSHMySql
- Ant Design 原始碼倉庫被刪除原始碼
- Spring原始碼分析(七)SpringAOP生成代理類及執行的過程Spring原始碼
- 如何刪除win10更新檔案_win10刪除更新檔案的方法Win10
- CnosDB的資料更新和刪除
- Java學習過程Java
- Dubbo原始碼學習之-通過原始碼看看dubbo對netty的使用原始碼Netty
- Babylon-AST初探-程式碼更新&刪除(Update & Remove)ASTREM
- vue 原始碼學習(一) 目錄結構和構建過程簡介Vue原始碼
- mybatis原始碼學習:基於動態代理實現查詢全過程MyBatis原始碼
- win10怎麼刪除更新檔案 win10刪除更新檔案的方法Win10
- 原始碼分析OKHttp的執行過程原始碼HTTP
- 走近原始碼:Redis的啟動過程原始碼Redis
- 深度學習訓練過程中的學習率衰減策略及pytorch實現深度學習PyTorch
- 原始碼包安裝過程原始碼
- samba原始碼安裝及除錯Samba原始碼除錯
- 效能優化的過程學習優化
- Android FrameWork學習(二)Android系統原始碼除錯AndroidFramework原始碼除錯
- 通過原始碼學習@functools.lru_cache原始碼