概述 Window表示一个窗口,但在日常开发中我们接触的不多。我们常见的如Toast和PopWindow都是属于Window。Window是一个抽象类,而Window的具体实现类是PhoneWindow。如果我们需要创建一个Window,只需要通过WindowManager去实现。而它的具体实现是在WindowManagerService中。我们需要知道Android所有的视图都是附加在Window上的,换句话说Window是View的直接管理者。
Window与WindowManager 在分析Window得工作机制之前,我们先看看如何添加一个Window,具体的我们可以通过WindowManager去添加,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 TextView text = new TextView(this ); text.setText("just a test" ); 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.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; layoutParams.gravity = Gravity.CENTER; WindowManager windowManager = getWindowManager(); windowManager.addView(text, layoutParams);
代码中我们并没有通过setContentView去设置布局,而是直接通过WindowManager去addView实现的。同时,我们需要去添加一个窗口的权限:
1 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
Window的分类 在上述代码中,我们可以设置Window的级别。Window有三种级别,分别为:应用Window、系统Window和子Window。应用Window对应一个Activity,子Window必须要依附在一个父Window上。
Window
层级
应用 Window
1~99
子 Window
1000~1999
系统Window
2000~2999
我们可以通过WindowManager.LayoutParams的type去设置,如果设置系统Window则需要加上上面的权限,否则会出现异常。
WindowManager的内部机制 在开发中,我们无法直接使用Window,所有对Window的操作都是经过WindowManager去操作的。WindowManager所提供的功能很简单,即添加View,删除View和更新View。这三个的方法是在ViewManager接口中。代码如下:
1 2 3 4 5 6 7 8 9 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以及view的layoutparms属性即可进行添加和更新,如果需要删除,只需传入需要删除的view即可。
Window的内部机制 Window的添加过程 前面说到如果需要对Window操作都需要通过WindowManager去实现,不过WindowManager只是一个接口,而它的真正实现类是WindowManagerImpl,在WindowManagerImpl的三大操作代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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并没有去处理,而是交给WindowManagerGlobal去处理。WindowManagerGlobal的addView主要是如下几个步骤:
1、检查参数;如果是子Window就去做相应的调整:
1 2 3 4 5 6 7 8 9 10 11 12 13 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); }
2、创建 ViewRootImpl 并将 View 添加到集合中: 在WindowManagerGlobal中有如下几个集合:
1 2 3 4 5 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 所对应的 View
mRoots
Window 所对应的 ViewRootImpl
mParams
Window 所对应的布局参数
mDyingViews
正在被删除还没完全移除的 View 对象
addView 操作时会将相关对象添加到对应集合中:
1 2 3 4 5 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams);
3、通过 ViewRootImpl 来更新界面并完成 Window 的添加过程: 最后通过如下代码进行addView:
1 root.setView(view, wparams, panelParentView);
在setView内部,我们会通过requsetLayout去异步刷新UI,requsetLayout代码如下:
1 2 3 4 5 6 7 public void requestLayout () { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true ; scheduleTraversals(); } }
这是View的绘制入口,我们接着往下看,后面它会调用:
1 2 3 4 5 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel);
它是通过WindowSession去完成最后的添加。WindowSession是一个Binder对象,最终实现类是Session,我们继续看代码:
1 2 3 4 5 6 7 @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); }
可以发现,最后是通过调用WMS的addWindow去添加的。具体细节不做深入~~
Window的删除过程 删除过程和添加过程类似,我们直接看WindowManagerGlobal的removeView方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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); } }
这边会通过调用findViewLocked方法去拿到待删除View的索引。然后通过removeViewLocked去实现删除逻辑,具体看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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); } } }
这边会调用ViewRootImpl的die方法去处理,我们继续看代码:
1 2 3 4 5 6 7 8 boolean die (boolean immediate) { if (immediate && !mIsInTraversal) { doDie(); return false ; } ··· mHandler.sendEmptyMessage(MSG_DIE); return true ; }
这边会判断同步还是异步,如果是同步就直接调用doDie方法,否则会通过Handler发送消息调用doDie方法去处理,在doDie方法中真正实现删除逻辑的是dispatchDetachedFromWindow方法。主要是做了如下几件事:
GC回收相关
一个IPC过程:mWindowSession.remove(mWindow);
调用WindowManagerGlobal.getInstance().doRemoveView(this);去移除关于当前Windows的数据。
Window的更新过程 过程基本和添加和删除类似,我们来看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 ); } }
这段代码主要做了如下几件事:
更新view的layoutparms属性
找到view的索引
移除之前的view
添加当前的view
设置view的layoutparms属性
Window的创建过程 我们知道View是依附在Window上的。换句话说有View的地方就有Window。所以Activity、Dialog和Toast都是依附在Window之上的。所以我们来看看这三者的创建过程。
Activity的Window创建过程 Activity的Window的创建过程会牵扯到Activity的启动流程。启动流程比较复杂,最后会通过ActivityThread得performLaunchActivity去启动Activity。在此方法中会调用attach方法,为其关联一系列的上下文变量。我们来看看Activity的attch方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 mWindow = new PhoneWindow(this , window, activityConfigCallback); mWindow.setWindowControllerCallback(this ); mWindow.setCallback(this ); mWindow.setOnWindowDismissedCallback(this ); mWindow.getLayoutInflater().setPrivateFactory(this ); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0 ) { mWindow.setUiOptions(info.uiOptions); } `··· mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0 ); if (mParent != null ) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; mWindow.setColorMode(info.colorMode);
可以看出Window的具体实现类是PhoneWindow。到这边其实Window就已经创建完成了,Activity会调用setContentView将Window显示出来。
Dialog的Window创建过程 Dialog的创建过程基本和Activity类似,具体我们来看Window的构造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (themeResId == ResourceId.ID_NULL) { final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true ); themeResId = outValue.resourceId; } mContext = new ContextThemeWrapper(context, themeResId); } else { mContext = context; } mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final Window w = new PhoneWindow(mContext); mWindow = w; w.setCallback(this ); w.setOnWindowDismissedCallback(this ); w.setOnWindowSwipeDismissedCallback(() -> { if (mCancelable) { cancel(); } }); w.setWindowManager(mWindowManager, null , null ); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this ); }
这边同样是通过PhoneWindow去创建的,创建完成后,依旧是通过setContentView去添加指定布局。我们在来看看show方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ··· mDecor = mWindow.getDecorView(); if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { final ApplicationInfo info = mContext.getApplicationInfo(); mWindow.setDefaultIcon(info.icon); mWindow.setDefaultLogo(info.logo); mActionBar = new WindowDecorActionBar(this ); } WindowManager.LayoutParams l = mWindow.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0 ) { WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); nl.copyFrom(l); nl.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; l = nl; } mWindowManager.addView(mDecor, l); mShowing = true ; ···
可以看出,在show的时候,它会拿到decorView,然后将DecorView添加到WindowManager中去。
Toast的Window创建过程 Toast和Window、Activity的区别就是它具有定时取消的功能,所以其内部有两组IPC过程,一个是INotificationManager,其通过getService拿到一个NotificationManagerService也就是NMS。第二这是NMS的回调也就是TN。具体的实现,我们来看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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) { } } public void cancel () { mTN.cancel(); }
这边主要是通过NMS像TN进行一个回调。我们来看看TN的handler处理了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 mHandler = new Handler(looper, null ) { @Override public void handleMessage (Message msg) { switch (msg.what) { case SHOW: { IBinder token = (IBinder) msg.obj; handleShow(token); break ; } case HIDE: { handleHide(); mNextView = null ; break ; } case CANCEL: { handleHide(); mNextView = null ; try { getService().cancelToast(mPackageName, TN.this ); } catch (RemoteException e) { } break ; } } } };
我们可以发现其通过handleShow以及handleHide去显示和影藏,我们可以看到在handleShow中他会将Toast的视图去添加到Window中去,具体代码如下:
1 2 3 4 5 6 7 8 mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); ··· try { mWM.addView(mView, mParams); trySendAccessibilityEvent(); } catch (WindowManager.BadTokenException e) { }
而在handleHide方法中,它会将当前窗口给移除掉:
1 2 3 4 if (mView.getParent() != null ) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this ); mWM.removeViewImmediate(mView); }
总结 任何 View 都是附属在一个Window上面的,Window表示一个窗口的概念,也是一个抽象的概念,Window 并不是实际存在的,它是以View的形式存在的。WindowManager 是外界也就是我们访问Window的入口,Window的具体实现位于WindowManagerService 中,WindowManagerService 和 WindowManager 的交互是一个 IPC 过程。