Window顧名思義就是窗口,Android Window的實(shí)現(xiàn)類(lèi)是PhoneWindow。WindowManager是訪(fǎng)問(wèn)Window的入口,通過(guò)它可以創(chuàng)建Window,WindowManager的具體實(shí)現(xiàn)在WindowService中,Window與WindowService之間的交互是一種IPC過(guò)程。Android中的界面都是通過(guò)Window來(lái)呈現(xiàn)的,比如Activity、Dialog和Toast等,他們的界面都是附加在Window上的,因此View的實(shí)際管理者是Window。
1.Window與WindowManager
在了解Window的工作機(jī)制之前我們先來(lái)看下如何使用WindowManager添加一個(gè)Window。
WindowManager windowManager = getWindowManager();
Button btAdd = new Button(this);
btAdd.setText("手動(dòng)添加按鈕");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
layoutParams.gravity = Gravity.LEFT;
layoutParams.x = 100;
layoutParams.y = 200;
windowManager.addView(btAdd,layoutParams);
上面代碼是將一個(gè)Button添加到坐標(biāo)(100,200)的位置。下面簡(jiǎn)單介紹下WindowManager.LayoutParams中的flags與type這兩個(gè)參數(shù)。
flags表示的是Window的屬性,有很多選擇項(xiàng),簡(jiǎn)單介紹幾種。
FLAG_NOT_FOCUSABLE:
表示W(wǎng)indow不需要獲取焦點(diǎn),又不需要接受任何輸入事件,次標(biāo)記還會(huì)同時(shí)啟用FLAG_NOT_TOUCH_MODAL,最終事件會(huì)傳遞給下層有焦點(diǎn)的Window。
FLAG_NOT_TOUCH_MODAL:
此模式下系統(tǒng)會(huì)將當(dāng)前Window區(qū)域以外的單擊事件傳遞給底層的Window,當(dāng)前區(qū)域以?xún)?nèi)的單擊事件則自己處理。
FLAG_SHOW_WHEN_LOCKED:
開(kāi)啟當(dāng)前模式,可以讓W(xué)indow顯示在鎖屏界面上。
如果了解其他的屬性,建議還是看下源碼:
/** Window flag: as long as this window is visible to the user, allow
* the lock screen to activate while the screen is on.
* This can be used independently, or in combination with
* {@link #FLAG_KEEP_SCREEN_ON} and/or {@link #FLAG_SHOW_WHEN_LOCKED} */
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
/** Window flag: everything behind this window will be dimmed.
* Use {@link #dimAmount} to control the amount of dim. */
public static final int FLAG_DIM_BEHIND = 0x00000002;
/** Window flag: blur everything behind this window.
* @deprecated Blurring is no longer supported. */
@Deprecated
public static final int FLAG_BLUR_BEHIND = 0x00000004;
type表示W(wǎng)indow的類(lèi)型,一般Window有三種類(lèi)型:應(yīng)用Window、子Window和系統(tǒng)Window。應(yīng)用類(lèi)的Window對(duì)應(yīng)著一個(gè)Activity。子Window是不能單獨(dú)存在的,他需要在特定的父Window之中,比如常見(jiàn)的Dialog就是一個(gè)子Window。系統(tǒng)Window需要聲明特殊的權(quán)限才能創(chuàng)建,比如Toast跟系統(tǒng)狀態(tài)欄等。
Window是分層的,每個(gè)Window都有對(duì)應(yīng)的z-ordered,層級(jí)大的會(huì)覆蓋在層級(jí)小的Window的上面,這和HTML中的z-index的概念是完全一致的。在三類(lèi)Window中,應(yīng)用Window的層級(jí)范圍是1~99,子Window的層級(jí)范圍是1000~1999,系統(tǒng)Window的層級(jí)范圍是2000~2999,這些層級(jí)范圍對(duì)應(yīng)著WindowManager.LayoutParams的type參數(shù)。如果想要Window位于所有Window的最頂層,那么采用較大的層級(jí)即可。很顯然系統(tǒng)Window的層級(jí)是最大的,而且系統(tǒng)層級(jí)有很多值,一般我們可以選用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,如果采用TYPE_SYSTEM_ERROR,只需要為type參數(shù)指定這個(gè)層級(jí)即可:mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;同時(shí)聲明權(quán)限:<uses-permissionandroid: name= "android.permission .SYSTEM_ALERT_WINDOW"/>。因?yàn)橄到y(tǒng)類(lèi)型的Window是需要檢查權(quán)限的,如果不在AndroidManifest 中使用相應(yīng)的權(quán)限,那么創(chuàng)建Window的時(shí)候就會(huì)報(bào)錯(cuò)。
WindowManager的功能比較簡(jiǎn)單,常用的就是三個(gè)方法:addView、updateViewLayout和removeView,這三個(gè)方法都定義在ViewManager中,WindowManager繼承了ViewManager。
/** Interface to let you add and remove child views to an Activity. To get an instance
* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
*/
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
* <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
* secondary {@link Display} and the specified display can't be found
* (see {@link android.app.Presentation}).
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
對(duì)于我們開(kāi)發(fā)者來(lái)說(shuō),WindowManager常用的就只有這三個(gè)功能,當(dāng)然這三個(gè)方法也就足夠用了。WindowManager操作Window其實(shí)就是在操作里面的View。通過(guò)這些方法,我們可以實(shí)現(xiàn)諸如隨意拖拽位置的Window等效果。
2.Window的內(nèi)部機(jī)制
Window是一個(gè)抽象類(lèi),每個(gè)Window都對(duì)應(yīng)一個(gè)View跟一個(gè)ViewRootImpl,Window跟View是通過(guò)ViewRootImpl建立聯(lián)系的,因此Window并不實(shí)際存在,它是以View的形式存在的。從WindowManager的定義跟主要方法也能看出,View是Window存在的實(shí)體。下面就具體介紹下Window的addView、updateViewLayout和removeView。
2.1Window的添加過(guò)程。
Window的添加過(guò)程需要通過(guò)WindowManager的addView來(lái)實(shí)現(xiàn),不過(guò)WindowManager是一個(gè)接口,真正實(shí)現(xiàn)是在WindowManagerImpl中,三個(gè)主要操作,先上源碼:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
上面的源碼很明顯,WindowManagerImpl也沒(méi)有直接實(shí)現(xiàn)Window的三大操作,而是由WindowManagerGlobal來(lái)處理的,代碼段:private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); 可以看出,WindowManagerGlobal以工廠(chǎng)的形式向外提供自己的實(shí)例。WindowManagerImpl這種工作模式是典型的橋接模式,將所有的操作委托給WindowManagerGlobal來(lái)實(shí)現(xiàn)。具體看下addView的源碼:
1.檢查參數(shù)是否合法,子Window還需要調(diào)整布局:
......
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
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;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
......
2.創(chuàng)建ViewRootImpl并將View添加到列表中
WindowManagerGlobal中的幾個(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則是Window中對(duì)應(yīng)的對(duì)應(yīng)的ViewRootImpl,mParams則是對(duì)應(yīng)的布局。mDyingViews存儲(chǔ)的是正在被刪除的View。
......
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
......
上面源碼表示了addView添加View的過(guò)程。
3.通過(guò)ViewRootImpl來(lái)更新界面,完成Window的添加過(guò)程
......
// 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;
}
......
ViewRootImpl的setView方法在界面View的時(shí)候有說(shuō)到,在setView內(nèi)部,通過(guò)requestLayout方法實(shí)現(xiàn)View的更新。
......
// 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();
......
接著通過(guò)WindowSession來(lái)完成Window的添加過(guò)程。下面的源碼中,mWindowSession是IWindowSession的實(shí)例,這是一個(gè)Binder對(duì)象,真正的實(shí)現(xiàn)類(lèi)是Session,也就是說(shuō)Window的添加過(guò)程是一次IPC調(diào)用。
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
Session內(nèi)部會(huì)通過(guò)WindowManagerService來(lái)實(shí)現(xiàn)Window的添加過(guò)程。
介紹到這里,各位就發(fā)現(xiàn),Window的添加請(qǐng)求是交給WindowManagerService去處理的,WindowManagerService內(nèi)部會(huì)為每一個(gè)應(yīng)用保留一個(gè)單獨(dú)的Session。具體的代碼的邏輯大家看下源碼,這里主要介紹部分源碼,還是以流程為主。
2.2Window的刪除過(guò)程
Window的刪除過(guò)程與添加過(guò)程一樣,都是先通過(guò)WindowManagerImpl然后通過(guò)WindowManagerGlobal來(lái)實(shí)現(xiàn)的:
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 curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
removeView的過(guò)程還是比較簡(jiǎn)潔的,先f(wàn)indViewLocked找到待刪除的View的索引,這個(gè)索引是上面說(shuō)的ArrayList mViews的index,然后刪除掉這個(gè)就可以了。
private int findViewLocked(View view, boolean required) {
final int index = mViews.indexOf(view);
if (required && index < 0) {
throw new IllegalArgumentException("View=" + view + " not attached to window manager");
}
return index;
}
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.add(view);
}
}
}
在WindowManager中提供了兩種刪除的接口:removeView跟removeViewImmediate,他們分別表示異步跟同步刪除,removeViewImmediate方法一般不會(huì)使用,以免刪除Window發(fā)生意外錯(cuò)誤。我們重點(diǎn)看下異步刪除的情況。代碼段6可以看到,刪除操作是通過(guò)ViewRootImpl的die方法完成的,具體看下這個(gè)方法:
/**
* @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.
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;
}
看上面的代碼,你會(huì)發(fā)現(xiàn),die方法只是發(fā)了一個(gè)請(qǐng)求,然后就返回了,再看代碼段6,View被加到mDyingViews中了。異步刪除可以看到發(fā)送了一個(gè)message,MSG_DIE,然后ViewRootImpl的handler會(huì)處理此消息然后調(diào)用die方法,同步的話(huà)就直接刪除了。這也是這兩種刪除方式的區(qū)別。真正刪除View的邏輯在doDie方法的dispatchDetachedFromWindow方法中。
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
if (mAdded && !mFirst) {
destroyHardwareRenderer();
if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
if (mWindowAttributesChanged || viewVisibilityChanged) {
// If layout params have been changed, first give them
// to the window manager to make sure it has the correct
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
}
mSurface.release();
}
}
mAdded = false;
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
setAccessibilityFocus(null, null);
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mSurface.release();
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
try {
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
// Dispose the input channel after removing the window so the Window Manager
// doesn't interpret the input channel being closed as an abnormal termination.
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
mDisplayManager.unregisterDisplayListener(mDisplayListener);
unscheduleTraversals();
}
從上面的源碼可以看到,dispatchDetachedFromWindow主要做了3件事:
(1)垃圾回收相關(guān)工作
(2)通過(guò)Session的remove方法刪除Window
(3)調(diào)用View的dispatchDetachedFromWindow方法,內(nèi)部調(diào)用View的onDetachedFromWindow方法,這個(gè)也是做一些資源回收比較合適的時(shí)機(jī),比如終止動(dòng)畫(huà)、停止線(xiàn)程等。
最終doDie方法調(diào)用WindowManagerGlobal的doRemoveView刷新數(shù)據(jù)。
2.3Window的更新過(guò)程
介紹完Window的刪除,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);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
看源碼還是很簡(jiǎn)單的,首先更新LayoutParams,然后通過(guò)ViewRootImpl的setLayoutParams更新ViewRootImpl中的LayoutParams,然后ViewRootImpl通過(guò)scheduleTraversals對(duì)View重新布局,包括測(cè)量,布局,繪制這三個(gè)過(guò)程。除了View本身重繪之外,ViewRootImpl會(huì)通過(guò)Session來(lái)更新Window視圖,同樣也是有WindowManagerService的relayoutWindow來(lái)實(shí)現(xiàn)的,也是一個(gè)IPC過(guò)程。