Window表示一個窗口,是View的實際管理者。在第4章的事件分發(fā)中已經(jīng)知道了,點擊事件是通過Window->DecorView->View來傳遞的。
Window是一個抽象類,具體實現(xiàn)是PhoneWindow類。我們可以通過WindowManager來操作Window,具體實現(xiàn)是在WindowManagerService中實現(xiàn)的;WindowManager和WindowManagerService通過IPC連接。
8.1 Window和WindowManager
8.1.1 創(chuàng)建一個Window
首先,通過WindowManager添加一個窗口:
Button button = new Button(this);
button.setText("button");
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION, 0,
PixelFormat.TRANSPARENT);
lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
lp.gravity = Gravity.LEFT | Gravity.TOP;
lp.x = 100;
lp.y = 300;
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.addView(button, lp);
這里注意,原書中代碼報錯,必須給窗口指定一個type,我這里指定的WindowManager.LayoutParams.TYPE_APPLICATION;不清楚是否是系統(tǒng)版本的問題。
8.1.2 Type
Type有三種類型:
應(yīng)用Window
應(yīng)用類Window對應(yīng)一個Activity。取值在1~99之間。子Window
子Window附屬于一個特定的父Window,而不能單獨存在。取值在1000~1999之間。系統(tǒng)Window
系統(tǒng)Window需要系統(tǒng)權(quán)限才能創(chuàng)建。取值在2000~2999之間。
8.1.3 Flag
Flag參數(shù)表示W(wǎng)indow的屬性,可以控制Window的顯示特性。
FLAG_NOT_FOCUSABLE
表示W(wǎng)indow不需要獲取焦點,也不需要接受各種輸入事件。FLAG_NOT_TOUCH_MODAL
表示W(wǎng)indow會處理當(dāng)前區(qū)域以內(nèi)的單擊事件,同時把當(dāng)前Window區(qū)域以外的單擊事件傳遞到底層。FLAG_SHOW_WHEN_LOCKED
表示W(wǎng)indow可以顯示在鎖屏的界面上。
8.1.4 權(quán)限
想要顯示系統(tǒng)級的Window是需要申請權(quán)限的。例如當(dāng)type = TYPE_SYSTEM_ERROR的時候,需要在AndroidManifest中添加<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />。
在Android 6.0及以后,需要手動開啟懸浮窗權(quán)限:
/**
* 檢查是否有懸浮窗權(quán)限
*/
@TargetApi(Build.VERSION_CODES.M)
private void checkOverlay() {
if (!Settings.canDrawOverlays(this)) {
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("權(quán)限禁止")
.setMessage("懸浮窗權(quán)限被禁止,點擊確定前往設(shè)置")
.setPositiveButton("確定",
(dialog1, which) -> jumpToOverlaySetting())
.setCancelable(false)
.create();
dialog.show();
}
}
private void jumpToOverlaySetting() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivityForResult(intent, 1);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
checkOverlay();
}
}
然后在onActivityResult()中,再判斷是否開啟權(quán)限。
如果不想申請權(quán)限,使用type = TYPE_TOAST也可以創(chuàng)建一個懸浮窗。但是在Android 7.1.1(API 25)開始,TYPE_TOAST被限制使用[1]。如果想要在7.1.1以后使用懸浮窗,必須開啟權(quán)限。
8.2 Window的內(nèi)部機制
Window是一個抽象概念,而不是實際存在的。每一個Window都對應(yīng)著一個View和一個ViewRootImpl,Window和View是通過ViewRootImpl來建立聯(lián)系的。Window實際存在的形式是View。
對Window的操作只能通過WindowManager來進行。WindowManager主要提供了三個方法:
- addView(View, LayoutParams)
- removeView(View)
- updateView(View, LayoutParams)
可以看到,WindowManager的操作對象是View。
8.2.1 Window的添加過程
在WindowManager的實現(xiàn)類WindowManagerImpl中,找到addView()方法:
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
而mGlobal的定義:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
這個單例類,在第四章[2]的時候已經(jīng)接觸過了。這里我們看到,WindowManager的添加、刪除、更新三個方法,其實都全權(quán)交給了WindowManagerGlobal。
轉(zhuǎn)到WindowManagerGlobal的addView()方法中:
public void addView(View view,
ViewGroup.LayoutParams params,
Display display,
Window parentWindow) {
// ... 省略若干代碼
// 創(chuàng)建ViewRoot
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 將View添加到列表中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
這里主要是將待添加的View、ViewRoot、LayoutParams保存到列表中,創(chuàng)建了ViewRoot,以及調(diào)用了ViewRoot的setView方法。
接下來繼續(xù)跟蹤setView方法。
public void setView(View view,
WindowManager.LayoutParams attrs,
View panelParentView) {
// ...省略若干代碼
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
// ...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
// ...
}
requestLayout()之前有一個注釋:
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
大概意思是說,為了保證在接收到其他系統(tǒng)事件之前布局好,我們在將View添加到Window之前進行第一次的布局。
然后調(diào)用了WindowSession.addToDisplay()方法。mWindowSession是通過WindowManagerGlobal.getWindowSession()來獲取的,而跟蹤進去發(fā)現(xiàn)發(fā)現(xiàn)這是一個Binder對象,進行了和WindowManagerService之間的IPC。也就是說,Window的添加最后交給了WindowManagerService來進行。
8.2.2 Window的刪除過程
Window的刪除的過程和添加類似,通過WindowManager.removeView() -> ViewRoot.die() -> WindowSession.remove()最終交付WindowManagerService來處理。
在這個過程中,會調(diào)用View的onDetachedFromWindow()回調(diào)。然后會調(diào)用WindowManager.doRemoveView()來將之前添加到列表中的View、ViewRoot、LayoutParams(8.2.1)給刪掉。
8.2.3 Window的更新過程
更新過程相較添加和刪除比較簡單,WindowManager.updateViewLayout() -> ViewRoot.setLayoutParams() -> ViewRoot.scheduleTraversals()
最終會調(diào)用ViewRoot.performTraversals()方法,在這里會對View進行measure、layout、draw的步驟。同時會調(diào)用relayoutWindow -> WindowSession.relayoutWindow(),通過WindowSession來更新Window視圖,這個過程仍然是經(jīng)過IPC,由WindowManagerService來實現(xiàn)的。
8.2.* 小結(jié)
- Window是一個抽象概念,實際上是不存在的,通過View來體現(xiàn)。View不能單獨存在,而必須依托于Window;而Window的具體呈現(xiàn)方式則是通過View。
- 一個Window對應(yīng)一個ViewRoot對應(yīng)一個View,ViewRoot是WindowManager和View之間的紐帶。而ViewRoot所代表的意義,則是這個Window的ViewTree的根節(jié)點。
WindowManager ----> ViewRoot --(IPC)-> WindowManagerService 這是操作Window的一般流程。而所有關(guān)于Window的操作,最終都是在WindowManagerService中實現(xiàn)的。
8.3 Window的創(chuàng)建過程
8.3.1 Activity的Window
在第四章里面,我分析過Activity的啟動流程:第4章 View的工作原理。
當(dāng)系統(tǒng)將要啟動一個Activity的時候,會調(diào)用ActivityThread.performLaunchActivity()方法,在這個方法中會創(chuàng)建Activity實例,并調(diào)用Activity的attach方法。在attach方法中,會賦予Activity上下文,創(chuàng)建Window并將其和Activity綁定。Activity實現(xiàn)了Window.Callback接口,所以Activity可以收到Window的若干回調(diào),比如onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent等。
Window實例的創(chuàng)建,是通過PolicyManager的工廠方法makeNewWindow(Context)來進行的。
當(dāng)我們在Activity調(diào)用setContentView方法時,會創(chuàng)建DecorView并將xml的布局加載進content中。DecorView雖然已經(jīng)創(chuàng)建完畢,但是他還沒有被WindowManager添加到Window中。Window的概念,更多表示的是一種抽象的功能集合。在Window和DecorView都創(chuàng)建完畢之后,ActivityThread會調(diào)用handleResumeActivity方法->makeVisible方法,在其中會調(diào)用WindowManager.addView(),這就回到了8.2.1的流程了,通過addView將DecorView添加到Window。
(有點吃驚,書中的內(nèi)容和我之前在第四章中分析的幾乎一模一樣,不過這也說明我沒有誤入歧途。)
8.3.2 Dialog的Window
Dialog和Activity創(chuàng)建Window的方式類似。
1、創(chuàng)建Window
2、初始化DecorView并將Dialog的視圖添加進去
3、將DecorView添加到Window中
在Dialog被關(guān)閉時,他會通過WindowManager來移除DecorView。
8.3.3 Toast的Window
Toast也是基于Window來實現(xiàn)的,不過過程和Dialog不同。
總的來說,Toast是通過IPC過程調(diào)用NotificationManagerService來進行顯示和隱藏的。
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}