【Android】Window和WindowManager那些事兒
每一個Activty都包含一個Window物件,Window作為抽象類,具體的處理邏輯都在其子類PhoneWindow中來處理,PhoneWindow將DecorView設定為應用視窗的根View,DecorView繼承自FrameLayout,是最頂層檢視,DecorView只有一個子元素為LinearLayout。代表整個Window介面,包含通知欄,標題欄,內容顯示欄三塊區域。

盜張圖.png
下面我們先插播一段setContentView的流程,有利於後面對於Window的理解。
1.setContentView的流程
我們在Activity的onCreate裡添加布局檔案setContentView時,其實在DecorView中預設會新增一個id為content的根佈局,我們setContentView的layout正是放在這個佈局裡面的,下面來分析一下setContentView的流程,我們從這裡開始:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }
檢視setContentView原始碼:
@Override public void setContentView(@LayoutRes int layoutResID) { getDelegate().setContentView(layoutResID); }
我們發現getDelegate()方法,它是AppCompatDelegate型別,類似代理的作用,看下getDelegate()的原始碼:
@NonNull public AppCompatDelegate getDelegate() { if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; }
點選create繼續追蹤,最終在AppCompatDelegateImplV9類中找到了setContentView的具體實現邏輯:
public void setContentView(View v) { ensureSubDecor(); ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); contentParent.removeAllViews(); contentParent.addView(v); mOriginalWindowCallback.onContentChanged(); }
表面上看著程式碼量不大,咱們一行一行來看,首先看這個ensureSubDecor()方法,精簡版原始碼如下:
private void ensureSubDecor() { if (!mSubDecorInstalled) { mSubDecor = createSubDecor(); } }
繼續追蹤createSubDecor()方法,原始碼如下:
private ViewGroup createSubDecor() { //獲取Activity的主題 TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); //進行一系列的主題匹配,確定主題型別 if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) { requestWindowFeature(Window.FEATURE_NO_TITLE); } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) { requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR); } mWindow.getDecorView(); //拿到inflater,後面要用 final LayoutInflater inflater = LayoutInflater.from(mContext); ViewGroup subDecor = null; //如果設定的主題是有title的,進入判斷邏輯 if (!mWindowNoTitle) { if (mIsFloating) { //mIsFloating表示類似於Dialog這種型別的視窗,inflater對應的系統預設佈局 subDecor = (ViewGroup) inflater.inflate( R.layout.abc_dialog_title_material, null); mHasActionBar = mOverlayActionBar = false; } else if (mHasActionBar) { TypedValue outValue = new TypedValue(); mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true); //mHasActionBar表示有Actionbar的主題,inflater對應的系統預設佈局 subDecor = (ViewGroup) LayoutInflater.from(themedContext) .inflate(R.layout.abc_screen_toolbar, null); //根據ID拿到根佈局的View mDecorContentParent = (DecorContentParent) subDecor .findViewById(R.id.decor_content_parent); mDecorContentParent.setWindowCallback(getWindowCallback()); } } else { if (mOverlayActionMode) { //Action mode相關 subDecor = (ViewGroup) inflater.inflate( R.layout.abc_screen_simple_overlay_action_mode, null); } else { //表示no title的情況,進入判斷邏輯 subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null); } } if (mDecorContentParent == null) { mTitleView = (TextView) subDecor.findViewById(R.id.title); } //取出ContentFrameLayout也就是一個FrameLayout佈局 final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById( R.id.action_bar_activity_content); //取出PhoneWindow中id為content的ViewGroup,看後面的程式碼是把此ViewGroup的子View取出放在contentView中,然後把contentView設定id為android.R.id.content。 final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content); if (windowContentView != null) { while (windowContentView.getChildCount() > 0) { final View child = windowContentView.getChildAt(0); windowContentView.removeViewAt(0); contentView.addView(child); } windowContentView.setId(View.NO_ID); contentView.setId(android.R.id.content); } //最後把佈局subDecor與PhoneWindow繫結 mWindow.setContentView(subDecor); return subDecor; }
具體邏輯程式碼中的註釋,繞了一大圈,最終呼叫了mWindow.setContentView(subDecor),進入PhoneWindow的setContentView方法:
@Override public void setContentView(View view) { setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); }
繼續追蹤:
@Override public void setContentView(View view, ViewGroup.LayoutParams params) { if (mContentParent == null) { //建立DecorView,並給mContentParent賦值 installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { view.setLayoutParams(params); final Scene newScene = new Scene(mContentParent, view); transitionTo(newScene); } else { //把咱們的layout佈局add進mContentParent裡 mContentParent.addView(view, params); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); //佈局新增完成,回撥onContentChanged方法 if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
有人可能會對mContentParent這個物件有疑問,從上面的程式碼可以看出mContentParent是在installDecor()方法中賦值的,我們來看下這個方法:
private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { mDecor = generateDecor(-1); } else { mDecor.setWindow(this); } if (mContentParent == null) { //是在這裡進行賦值的 mContentParent = generateLayout(mDecor); } }
接著看下generateLayout()方法:
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; protected ViewGroup generateLayout(DecorView decor) { //省略部分程式碼 //注意看這句,通過R.id.content拿到了根佈局的ViewGroup ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } }
至此,咱們在onCreate中通過setContentView的佈局view,通過mContentParent.addView(view, params)新增到頁面中。
2. WindowManager和Window的關係
public interface WindowManager extends ViewManager { //一些異常定義 public static class BadTokenException extends RuntimeException {···} public static class InvalidDisplayException extends RuntimeException {···} //該方法會獲得這個WindowManager例項將Window新增到哪個螢幕上了,換句話說,就是得到WindowManager所管理的螢幕(Display) public Display getDefaultDisplay(); //移除View public void removeViewImmediate(View view); public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { //定義了一些window相關的屬性,包括位置座標,層級,型別等等。 public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17; public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18; ········· } }
WindowManager是一個介面,它的作用就是管理視窗,從程式碼可以看出,它繼承ViewManager,我們看下ViewManager的原始碼:
public interface ViewManager { public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
哎呀媽呀,這種程式碼看著真舒服,因為短啊,關於view的增刪改三個方法,一目瞭然,它的實現類為WindowManagerImpl。如果我們想要對Window進行新增和刪除等操作,就要使用WindowManager了,具體的工作都是由WindowManagerService(WMS)來處理的,WindowManager和WMS通過Binder來進行跨程序通訊。
知道了Window和windowManager,那麼這倆貨是在什麼時候建立的呢?
不用多想,肯定和Activity有關,因為window從屬於Activity,關於Activity的啟動流程,相信大家都見過下面這張圖,雖然android版本一直在升級,但整體流程依然變化不大:

圖片來源於網路
從startActivity開始,經過一系列的呼叫流程,最終會呼叫ActivityThread類的performLaunchActivity方法,performLaunchActivity又會呼叫attach方法,我們重點來看下attach方法:
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) { attachBaseContext(context); //建立window物件PhoneWindow mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); ······ //window和WindowManager建立連線, mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }
如果繼續追蹤會發現,WindowManager最終會呼叫mContext.getSystemService(Context.WINDOW_SERVICE)來獲取,Context.WINDOW_SERVICE其實就是個String常量,類似key的作用:
public static final String WINDOW_SERVICE = "window";
Context的子類ContextImpl中getSystemService方法如下:
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); } @Override public String getSystemServiceName(Class<?> serviceClass) { return SystemServiceRegistry.getSystemServiceName(serviceClass); }
又呼叫了SystemServiceRegistry的getSystemService方法:
/** * Gets a system service from a given context. */ public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? fetcher.getService(ctx) : null; }
發現最終取值是從SYSTEM_SERVICE_FETCHERS中獲取的,而SYSTEM_SERVICE_FETCHERS其實就是個Map集合:
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new HashMap<String, ServiceFetcher<?>>();
其實這裡的邏輯是SystemServiceRegistry在初始化的時候會註冊系統各種服務,通過registerService方法,並把註冊的服務put到map中,以供後續程式使用時獲取:
private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) { SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName); SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); }
WindowManager註冊的程式碼如下:
registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher<WindowManager>() { @Override public WindowManager createService(ContextImpl ctx) { return new WindowManagerImpl(ctx); }});
所以我們最終獲取到的物件就是WindowManagerImpl物件,那麼我們此時回到如下這個地方,可以看到此處會把WindowManagerImpl強轉為WindowManager:
mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
檢視Window的setWindowManager方法如下:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { ·····省略程式碼 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }
會呼叫WindowManagerImpl的createLocalWindowManager方法去建立mWindowManager,而此處WindowManagerImpl持有了Window的引用,也就具備了操作window的能力:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mContext, parentWindow); }
最後我們來看下WindowManagerImpl的部分程式碼:
public final class WindowManagerImpl implements WindowManager { //WindowManagerGlobal單例類 private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); private final Context mContext; //持有window物件 private final Window mParentWindow; public WindowManagerImpl(Context context) { this(context, null); } private WindowManagerImpl(Context context, Window parentWindow) { mContext = context; mParentWindow = parentWindow; } public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mContext, parentWindow); } @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); } @Override public void removeViewImmediate(View view) { mGlobal.removeView(view, true); } @Override public Display getDefaultDisplay() { return mContext.getDisplay(); } }
WindowManagerImpl程式碼其實很少,我們發現它的大部分功能都委託給了WindowManagerGlobal來處理,WindowManagerGlobal是一個單例類,說明在一個程序中只有一個WindowManagerGlobal例項。但是WindowManagerImpl可能會實現多個Window,也就是說在一個程序中WindowManagerImpl可能會有多個例項。
上一張UML關係圖:

window-UML.png
3.Window的新增過程
從上面我們可以知道,最終在addView的時候是呼叫的WindowManagerGlobal的addView方法,我們來下它的程式碼:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ··· ViewRootImpl root; View panelParentView = null; ··· root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); try { //ViewRootImpl的setview方法開始繪製view root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { } } }
這裡又出現了一個新類ViewRootImpl,ViewRootImpl是幹嘛的呢,點開後我們發現這麼一段描述:The top of a view hierarchy, implementing the needed protocol between View
and the WindowManager. 意思就是它是ViewManager和View之間重要的協議,它實現了WindowManagerGlobal中大部分的功能,也就是說WindowManagerGlobal的大部分功能都放在了ViewRootImpl中進行實現。
下面來看下ViewRootImpl的setView方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { requestLayout(); try { res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { } }
這裡我們只列出了關鍵程式碼,requestLayout()表示新增Window之前要進行一次layout佈局過程,以確保在收到任何系統事件之後重新佈局,這個方法是具體繪製view的地方:
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { //檢查執行緒 checkThread(); mLayoutRequested = true; //繪製 scheduleTraversals(); } } void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
在requestLayout()方法中呼叫了checkThread()方法,這段異常提示大家應該很熟悉吧,檢查當前執行緒是否一致,我們平時在子執行緒更新UI就會丟擲這個異常,因為一般在子執行緒操作UI都會呼叫到view.invalidate,而View的重繪會觸發ViewRootImpl的requestLayout,就會去判斷當前執行緒。
接著呼叫了scheduleTraversals()方法,在scheduleTraversals()方法中會去執行TraversalRunnable任務:
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } }
然後呼叫了doTraversal()-->performTraversals(),真正呼叫繪製的就是performTraversals()這個方法了,通過measure,layout,draw三個過程最終把View繪製出來,所以View的繪製是由ViewRootImpl完成,另外當手動呼叫invalidate,postInvalidate,requestInvalidate也會最終呼叫performTraversals,來重新繪製View:
private void performTraversals() { ······ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); performLayout(lp, desiredWindowWidth, desiredWindowHeight); performDraw(); ······ }
requestLayout()之後會呼叫mWindowSession的addToDisplay方法,mWindowSession是IWindowSession型別,它的實現類是Session類:
@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outOutsets, outInputChannel); }
IWindowSession檔案其實是個adil檔案,core/java/android/view/IWindowSession.aidl,由此可見,addToDisplay是一次IPC的過程,上面的程式碼最終呼叫了WindowManagerService的addView方法,最終遠端呼叫把window新增上去。
final WindowManagerPolicy mPolicy = new PhoneWindowManager(); public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { int[] appOp = new int[1]; //mPolicy實際上是一個PhoneWindowManager型別,在checkAddPermission方法中,首先判斷視窗型別是否是系統級別的 //如果不是系統級別的視窗,則返回一個ADD_OKAY,否則需要SYSTEM_ALERT_WINDOW或者INTERNAL_SYSTEM_WINDOW許可權 int res = mPolicy.checkAddPermission(attrs, appOp); if (res != WindowManagerGlobal.ADD_OKAY) { return res; } synchronized(mWindowMap) { boolean addToken = false; WindowToken token = mTokenMap.get(attrs.token); if (token == null) { //如果視窗是子視窗 if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } //如果是輸入法視窗 if (type == TYPE_INPUT_METHOD) { return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } //構造WindowToken物件 token = new WindowToken(this, attrs.token, -1, false); addToken = true; } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { // 獲取應用的AppWindowToken AppWindowToken atoken = token.appWindowToken; if (atoken == null) { return WindowManagerGlobal.ADD_NOT_APP_TOKEN; } else if (atoken.removed) { return WindowManagerGlobal.ADD_APP_EXITING; } if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) { if (localLOGV) Slog.v( TAG, "**** NO NEED TO START: " + attrs.getTitle()); return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED; } } else if (type == TYPE_INPUT_METHOD) { if (token.windowType != TYPE_INPUT_METHOD) { //如果是輸入法視窗,token的windowType必須是ADD_BAD_APP_TOKEN型別 return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } .... // 在視窗的有效性檢查完成之後,為當前視窗建立一個WindowState物件,來維護視窗的狀態以及根據適當的機制來調整視窗的狀態 WindowState win = new WindowState(this, session, client, token, attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent); // 如果客戶端已經被銷燬 if (win.mDeathRecipient == null) { return WindowManagerGlobal.ADD_APP_EXITING; } if (outInputChannel != null && (attrs.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { //如果輸出Channel的讀通道為空,則建立通道 String name = win.makeInputChannelName(); InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); win.setInputChannel(inputChannels[0]); inputChannels[1].transferTo(outInputChannel); //向InputManager中註冊通道,以便當前視窗可以接收到事件 mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); } } }
下面一張圖來總結一下WindowManager新增View的呼叫流程:

呼叫流程.png
4.Window層級
我們都知道,在做懸浮窗的時候,有時候可能並不會只顯示一個或者在和SurfaceView同時使用的時候,都會涉及到Window的層級問題,因為Window是由WindowManager來管理的,所以層級相關自然是在WindowManager中定義的。
WindowManager中LayoutParams.type型別決定了這個View顯示視窗的型別,不同型別顯示的視窗層級(z軸)是不一樣的。大方面來講可以分為應用視窗(APPLICATION_WINDOW)、子視窗(SUB_WINDOW)、系統視窗(SYSTEM_WINDOW)三種類型,應用視窗z軸範圍是1~99,子視窗的範圍是1001~1999,系統視窗是(2000~2999),所以要實現浮動視窗我們只能在系統視窗範圍中實現,window層級大小如下圖:

圖片來自網路.png