1. Window和WindowManager
Window
表示一個(gè)窗口的概念,如在創(chuàng)建對(duì)話(huà)框時(shí)就需要Window
來(lái)進(jìn)行益楼。Window
通過(guò)WindowManager
來(lái)操作Window
猾漫,但WIndow
的具體實(shí)現(xiàn)都需要使用WindowManagerService
1.1 Window
- WindowFlags
參數(shù) | 作用 |
---|---|
FLAG_NOT_TOUCH_MODAL |
表示Window 不需要獲取焦點(diǎn),也不需要接受各種輸入時(shí)間感凤,此標(biāo)記會(huì)同時(shí)啟用FLAG_NOT_TOUCHABLE
|
FLAG_NOT_TOUCHABLE |
在此模式下静袖,系統(tǒng)會(huì)將當(dāng)前Window 區(qū)域以外的單擊事件傳遞給底層的Window ,當(dāng)前Window 區(qū)域的點(diǎn)擊由自己處理 |
FLAG_SHOW_WHEN_LOCKED |
讓Window 顯示在鎖屏上 |
- WindowType
- 應(yīng)用
Window
:對(duì)應(yīng)著一個(gè)Activity
- 子
Window
:不能單獨(dú)存在俊扭,需要附屬在特定的父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的層級(jí)范圍1-99萨惑,子Window范圍1000-1999捐康,系統(tǒng)Window層級(jí)范圍是2000-2999
- WindowManager
- 增刪改
Window
實(shí)際上是操作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
每一個(gè)都對(duì)應(yīng)著每一個(gè)View
和每一個(gè)ViewRootImpl
,因此Window不是實(shí)際存在的庸蔼,而是以View
的形式存在的
2.1 Window的添加過(guò)程
@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
的實(shí)現(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,則會(huì)調(diào)整參數(shù)
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
步驟二:創(chuàng)建ViewRootImpl并將View添加到列表中
在WindowManagerGlobal
內(nèi)部有如下幾個(gè)列表比較重要:
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
存儲(chǔ)的是所有Window
所對(duì)應(yīng)的View
姐仅, -
mRoots
存儲(chǔ)的是所有Window
所對(duì)應(yīng)的ViewRootImpl
-
mParams
存儲(chǔ)的是所有Window
所對(duì)應(yīng)的布局參數(shù) -
mDyingViews
則存儲(chǔ)了那些正在被刪除的View
對(duì)象花枫,或者說(shuō)是那些已經(jīng)調(diào)用removeView
方法但是刪除操作還未完成的Window
對(duì)象。
在addView
中通過(guò)如下方式將Window
的一系列對(duì)象添加到列表中:
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
步驟三:通過(guò)ViewRootImpl來(lái)更新界面并完成Window的添加過(guò)程
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
接著會(huì)通過(guò)mWindowSession
最終來(lái)完成Window
的添加過(guò)程掏膏。mWindowSession
的類(lèi)型是IWindowSession
劳翰,它是一個(gè)Binder
對(duì)象,真正的實(shí)現(xiàn)類(lèi)是Session
馒疹,也就是Window
的添加過(guò)程是一次IPC
調(diào)用佳簸。
在Session
內(nèi)部會(huì)通過(guò)WindowManagerService
來(lái)實(shí)現(xiàn)Window
的添加
2.2 Window的刪除過(guò)程
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ù)組中找到對(duì)應(yīng)的root
View view = root.getView(); // 找到對(duì)應(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
是通過(guò)ViewRootImpl
來(lái)完成刪除操作的。在WindowManager
中提供了兩種刪除接口removeView
和removeViewImmediate
颖变,它們分別表示異步刪除和同步刪除 -
removeViewImmediate
需要特別注意生均,一般來(lái)說(shuō)不需要使用此方法來(lái)刪除Window
以免發(fā)生意外的錯(cuò)誤听想。這里主要說(shuō)異步刪除的情況,具體的刪除操作由ViewRootImpl
的die
方法來(lái)完成马胧。在異步刪除的情況下汉买,die
方法只是發(fā)送了一個(gè)請(qǐng)求刪除的消息后就立刻返回了,這個(gè)時(shí)候View
并沒(méi)有完成刪除操作佩脊,所以最后會(huì)將其添加到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)部會(huì)調(diào)用dispatchDetachedFromWindow
方法
異步刪除:發(fā)送消息mHandler.sendEmptyMessage(MSG_DIE);
dispatchDetachedFromWindow
方法:
- 垃圾回收相關(guān)工作,比如清楚數(shù)據(jù)和消息葫男、移除回調(diào)等
- 通過(guò)
Session
的remove
方法刪除Window
抱冷,最終會(huì)調(diào)用WindowManagerService
的removeWindow
方法 - 調(diào)用
View
的dispatchDetachedFromWindow
- 調(diào)用
WindowManagerGlobal
的doRemoveView
方法刷新數(shù)據(jù),包括mRoots
梢褐,mParams
以及mDyingViews
旺遮,需要將當(dāng)前Window
所關(guān)聯(lián)的這三類(lèi)對(duì)象從列表中刪除。
2.3 Window的更新過(guò)程
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
耿眉,這一步是通過(guò)ViewRootImpl
的setLayoutParams
方法來(lái)實(shí)現(xiàn)的。在ViewRootImpl
中會(huì)通過(guò)scheduleTraversals
方法來(lái)對(duì)View重新布局鱼响,包括測(cè)量鸣剪,布局,重繪這三個(gè)過(guò)程丈积。除了View本身的重繪以外筐骇,ViewRootImpl
還會(huì)通過(guò)WindowSession
來(lái)更新Window
的視圖,這個(gè)過(guò)程最終是由WindowManagerService
的relayoutWindow()
來(lái)具體實(shí)現(xiàn)的江滨,它同樣是一個(gè)IPC
過(guò)程铛纬。
3. Window的創(chuàng)建過(guò)程
3.1 Activity的Window啟動(dòng)過(guò)程
3.2 Dialog的Window創(chuàng)建過(guò)程
3.3 Toast的Window創(chuàng)建過(guò)程
-
TN
是一個(gè)Binder
類(lèi),在NMS
處理Toast
請(qǐng)求時(shí)唬滑,需要Handler
將其從Binder
線(xiàn)程池中切換到發(fā)送Toast
的線(xiàn)程告唆,因此Toast
無(wú)法在沒(méi)有Looper
的線(xiàn)程中彈出 -
enqueueToast
將Toast
請(qǐng)求封裝成ToastRecord
加入mToastQueue
,mToastQueue
最多存在50個(gè)彈出請(qǐng)求 -
ToastRecord
與NMS
之間通過(guò)TN
對(duì)象Callback
的show
,hide
方法完成Toast的顯示和隱藏功能晶密,都是IPC