1. Window和WindowManager
Window表示一個窗口的概念,如在創(chuàng)建對話框時就需要Window來進(jìn)行。Window通過WindowManager來操作Window,但WIndow的具體實現(xiàn)都需要使用WindowManagerService
1.1 Window
- WindowFlags
| 參數(shù) | 作用 |
|---|---|
FLAG_NOT_TOUCH_MODAL |
表示Window不需要獲取焦點,也不需要接受各種輸入時間,此標(biāo)記會同時啟用FLAG_NOT_TOUCHABLE
|
FLAG_NOT_TOUCHABLE |
在此模式下,系統(tǒng)會將當(dāng)前Window區(qū)域以外的單擊事件傳遞給底層的Window,當(dāng)前Window區(qū)域的點擊由自己處理 |
FLAG_SHOW_WHEN_LOCKED |
讓Window顯示在鎖屏上 |
- WindowType
- 應(yīng)用
Window:對應(yīng)著一個Activity - 子
Window:不能單獨存在,需要附屬在特定的父Window上 - 系統(tǒng)
Window:需要聲明權(quán)限才能創(chuàng)建的Window,例如Toast和系統(tǒng)狀態(tài)欄
如果采用TYPE_SYSTEM_ERROR,需要聲明權(quán)限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
- Window分層
Z-index,應(yīng)用Window的層級范圍1-99,子Window范圍1000-1999,系統(tǒng)Window層級范圍是2000-2999
- WindowManager
- 增刪改
Window實際上是操作Window里的View
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = new Button(this);
btn.setText("TEST");
WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
params.gravity = Gravity.LEFT| Gravity.TOP;
params.x = 100;
params.y = 300;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
getWindowManager().addView(btn, params);
}
}
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int rawX = (int) motionEvent.getX();
int rawY = (int) motionEvent.getY();
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_MOVE:
params.x = rawX;
params.y = rawY;
mWindow.updateViewLayout(btn, params);
}
return false;
}
}
);

2. Window的內(nèi)部機(jī)制
Window每一個都對應(yīng)著每一個View和每一個ViewRootImpl,因此Window不是實際存在的,而是以View的形式存在的
2.1 Window的添加過程
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowManagerGlobal的實現(xiàn)步驟:
步驟一:檢查參數(shù)是否合法,如果是子Window則需要調(diào)整一些參數(shù)
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
// 如果是子View,則會調(diào)整參數(shù)
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
步驟二:創(chuàng)建ViewRootImpl并將View添加到列表中
在WindowManagerGlobal內(nèi)部有如下幾個列表比較重要:
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
-
mViews存儲的是所有Window所對應(yīng)的View, -
mRoots存儲的是所有Window所對應(yīng)的ViewRootImpl -
mParams存儲的是所有Window所對應(yīng)的布局參數(shù) -
mDyingViews則存儲了那些正在被刪除的View對象,或者說是那些已經(jīng)調(diào)用removeView方法但是刪除操作還未完成的Window對象。
在addView中通過如下方式將Window的一系列對象添加到列表中:
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
步驟三:通過ViewRootImpl來更新界面并完成Window的添加過程
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
接著會通過mWindowSession最終來完成Window的添加過程。mWindowSession的類型是IWindowSession,它是一個Binder對象,真正的實現(xiàn)類是Session,也就是Window的添加過程是一次IPC調(diào)用。
在Session內(nèi)部會通過WindowManagerService來實現(xiàn)Window的添加
2.2 Window的刪除過程
WindowManagerGlobal#removeView
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true); // 找到待刪除的View的Index
View curView = mRoots.get(index).getView(); // 找到待刪除的View
removeViewLocked(index, immediate); // 進(jìn)行刪除
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
WindowManagerGlobal#removeViewLocked
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index); // 從mRoots數(shù)組中找到對應(yīng)的root
View view = root.getView(); // 找到對應(yīng)的View
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.add(view);
}
}
}
-
removeViewLocked是通過ViewRootImpl來完成刪除操作的。在WindowManager中提供了兩種刪除接口removeView和removeViewImmediate,它們分別表示異步刪除和同步刪除 -
removeViewImmediate需要特別注意,一般來說不需要使用此方法來刪除Window以免發(fā)生意外的錯誤。這里主要說異步刪除的情況,具體的刪除操作由ViewRootImpl的die方法來完成。在異步刪除的情況下,die方法只是發(fā)送了一個請求刪除的消息后就立刻返回了,這個時候View并沒有完成刪除操作,所以最后會將其添加到mDyingViews中,mDyingViews表示待刪除的View列表。ViewRootImpl的die方法如下所示:
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.
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
同步刪除:doDie()方法,內(nèi)部會調(diào)用dispatchDetachedFromWindow方法
異步刪除:發(fā)送消息mHandler.sendEmptyMessage(MSG_DIE);
dispatchDetachedFromWindow方法:
- 垃圾回收相關(guān)工作,比如清楚數(shù)據(jù)和消息、移除回調(diào)等
- 通過
Session的remove方法刪除Window,最終會調(diào)用WindowManagerService的removeWindow方法 - 調(diào)用
View的dispatchDetachedFromWindow - 調(diào)用
WindowManagerGlobal的doRemoveView方法刷新數(shù)據(jù),包括mRoots,mParams以及mDyingViews,需要將當(dāng)前Window所關(guān)聯(lián)的這三類對象從列表中刪除。
2.3 Window的更新過程
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);//替換掉老的LayoutParams
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index); //先刪除再增加
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
首先它需要更新View的LayoutParams并替換掉老的LayoutParams,接著再更新ViewRootImpl中的LayoutParams,這一步是通過ViewRootImpl的setLayoutParams方法來實現(xiàn)的。在ViewRootImpl中會通過scheduleTraversals方法來對View重新布局,包括測量,布局,重繪這三個過程。除了View本身的重繪以外,ViewRootImpl還會通過WindowSession來更新Window的視圖,這個過程最終是由WindowManagerService的relayoutWindow()來具體實現(xiàn)的,它同樣是一個IPC過程。
3. Window的創(chuàng)建過程
3.1 Activity的Window啟動過程

3.2 Dialog的Window創(chuàng)建過程

3.3 Toast的Window創(chuàng)建過程
-
TN是一個Binder類,在NMS處理Toast請求時,需要Handler將其從Binder線程池中切換到發(fā)送Toast的線程,因此Toast無法在沒有Looper的線程中彈出 -
enqueueToast將Toast請求封裝成ToastRecord加入mToastQueue,mToastQueue最多存在50個彈出請求 -
ToastRecord與NMS之間通過TN對象Callback的show,hide方法完成Toast的顯示和隱藏功能,都是IPC

