💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] 本文为《Android开发艺术探索》一书第8章“理解Window和WindowManager”读书笔记。 # Window和WindowManager ![](https://img.kancloud.cn/27/2a/272a20ae7493e435306de73c220ff3c6_281x418.png) Window表示一个窗口的概念,Window是一个抽象类,它的具体实现是PhoneWindow。 WindowManager是外界访问Window的入口,创建Window通过WindowManager即可完成。Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。 ## 通过WindowManager创建Window WindowManager提供了三个方法:addView、updateViewLayout、removeView,这三个方法定义在ViewManager中,而WindowManager继承自ViewManager。 通过WindowManager的这三个方法,可以创建一个Window(添加View即可),更新Window中的View,以及删除一个Window(只需删除其中的View即可)。每一个Window都对应着一个View和一个ViewRootImpl,Window和View是通过ViewRootImpl来建立联系的。 Window有三种类型,分别是应用Window、子Window和系统Window。应用类Window对应着一个Activity;子Window不能单独存在,需附属在特定的父Window之中,比如常见的Dialog;系统Window需要声明权限才能创建,比如Toast和系统状态栏。 # Window的内部机制 Window是以View的形式存在的,可以理解为Window是窗户框,而View是玻璃以及玻璃上的窗花。接下来看看WindowManager的源码,看如何创建、更新以及删除Window。 ## Window的添加 WindowManager是一个接口,它的实现是WindowManagerImpl。 ```java @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } ``` addView方法通过桥接模式委托给了WindowManagerGlobal类: ```java 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>(); public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { // 1、检查参数是否合法 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; // 2、如果是子Window,调整布局参数 if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // ... } ViewRootImpl root; View panelParentView = null; // ... // 3、创建ViewRootImpl root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); // 4、将View添加到列表中 mViews.add(view); mRoots.add(root); mParams.add(wparams); // 5、通过ViewRootImpl更新界面,完成Window的添加 try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // ... } } ``` 可以看到,WindowManagerGlobal类中,维护着几个列表,分别存储着 * mViews存储的是所有Window对应的View * mRoots存储的是所有Window对应的ViewRootImpl * mParams存储的是所有Window对应的布局参数 * mDyingViews存储的是正在被删除的View 添加Window的过程重点关注4和5,即将View添加到列表中以及通过ViewRootImpl更新界面。 看看ViewRootImpl的setView方法: ```java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { // ... mView = view; requestLayout(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); // ... } ``` ViewRootImpl的setView方法中主要做了两步:更新界面和添加Window。添加Window是通过Session的addToDisplay方法完成的,mWindowSession的类型是IWindowSession,它是一个Binder对象,实现类是Session,Session的内部会通过WindowManagerService来实现Window的添加,是一个IPC调用: ```plain // IWindowSession.aidl interface IWindowSession { int add(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, out Rect outContentInsets, out Rect outStableInsets, out InputChannel outInputChannel); int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets, out Rect outOutsets, out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel); int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, out Rect outContentInsets, out Rect outStableInsets); int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outContentInsets, out Rect outStableInsets); void remove(IWindow window); } ``` ```java // Session.java // 文件所在目录: frameworks/base/services/core/java/com/android/server/wm/Session.java public class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { //... @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); } } ``` ```java // WindowManagerService.java public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { //... public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { //... } } ``` 接下来重点看看更新界面的requestLayout方法: ```java public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } } ``` scheduleTraversals方法代码如下: ```java void scheduleTraversals() { if (!mTraversalScheduled) { //... mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // ... } } ``` 其中的mTraversalRunnable为: ```java final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { //... performTraversals(); } } private void performTraversals() { //... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); performLayout(lp, mWidth, mHeight); performDraw(); } private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { //... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } ``` 可以看到,最终调用performTraversals开始熟悉的测量布局绘制,进行界面更新。测量方法中mView为我们在调用ViewRootImpl的setView方法时添加进来的View。 ## Window的删除 Window的删除过程同添加过程一样,都是由WindowManagerImpl桥接给WindowManagerGlobal来实现,看看WindowManagerGlobal的removeView方法: ```java // WindowManagerGlobal.java public void removeView(View view, boolean immediate) { //... int index = findViewLocked(view, true); removeViewLocked(index, immediate); //... } // WindowManagerGlobal.java private int findViewLocked(View view, boolean required) { final int index = mViews.indexOf(view); //... return index; } ``` 先通过遍历mViews拿到待删除View的索引,再根据索引删除View。removeViewLock方法如下: ```java // WindowManagerGlobal.java private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); //... boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } } ``` 根据索引拿到待删除View对应的ViewRootImpl,删除操作是由ViewRootImpl的die方法完成的,调用die方法后将View添加到正在等待删除View的集合mDyingViews中。ViewRootImpl的die方法如下: ```java // ViewRootImpl.java boolean die(boolean immediate) { // 直接删除,立即操作 if (immediate && !mIsInTraversal) { doDie(); return false; } //... // 通过Handler发送消息,异步删除 mHandler.sendEmptyMessage(MSG_DIE); return true; } void doDie() { if (mAdded) { dispatchDetachedFromWindow(); } //... WindowManagerGlobal.getInstance().doRemoveView(this); } ``` ViewRootHandler在收到MSG_DIE消息后,也是调用的doDie方法。doDie方法中: 1、调用WindowManager的doRemoveView方法,将当前Window关联的ViewRootImpl、LayoutParams、View进行移除。 ```java // WindowManagerGlobal.java void doRemoveView(ViewRootImpl root) { synchronized (mLock) { final int index = mRoots.indexOf(root); if (index >= 0) { mRoots.remove(index); mParams.remove(index); final View view = mViews.remove(index); mDyingViews.remove(view); } } //... } ``` 2、调用dispatchDetachedFromWindow方法,做的是删除View的操作: ```java // ViewRootImpl.java void dispatchDetachedFromWindow() { // 1、垃圾回收相关工作,如清除数据和消息、移除回调 // 2、通过Session的remove方法删除Window try { mWindowSession.remove(mWindow); } catch (RemoteException e) { } // 3、回调View的相关方法,方便做些资源回收操作 mView.dispatchDetachedFromWindow(); //... } ``` Session的remove方法同样是一个IPC过程,最终会调用WindowManagerService的removeWindow方法。 ## Window的更新 Window的更新同样是由WindowManagerGlobal来完成的。 ```java // WindowManagerGlobal.java public void updateViewLayout(View view, ViewGroup.LayoutParams params) { //... final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; // 1、为View设置新的LayoutParams view.setLayoutParams(wparams); synchronized (mLock) { int index = findViewLocked(view, true); // 2、获取当前View对应的ViewRootImpl ViewRootImpl root = mRoots.get(index); // 3、更新LayoutParams集合 mParams.remove(index); mParams.add(index, wparams); // 4、为ViewRootImpl设置新的LayoutParams root.setLayoutParams(wparams, false); } } ``` 1、为View设置新的LayoutParams 2、为ViewRootImpl设置新的LayoutParams 3、更新LayoutParams集合 在为ViewRootImpl设置新的LayoutParams时,会调用scheduleTraversals方法开始对View重新布局,包括测量、布局、绘制三个过程;此外ViewRootImpl还会通过WindowSession来更新Window的视图,最终调用WindowManagerService的relayoutWindow来实现,是一个IPC过程。 ## 总结 WindowManager是外界操作Window的入口,提供了添加、更新、删除View三个方法 ### Window的添加 1、WindowManager是一个接口,实现类是WindowManagerImpl,WindowManagerImpl的addView方法通过桥接模式委托给了WindowManagerGlobal。 2、WindowManagerGlobal存储着所有Window对应的View、ViewRootImpl以及LayoutParams 3、WindowManagerGlobal的addView方法中,首先创建一个ViewRootImpl,并通过ViewRootImpl来完成更新界面和添加Window的操作; 4、ViewRootImpl更新界面时,会依次调用根View的测量、布局、绘制方法;再通过WindowSession来完成Window的添加 ### Window的删除 1、Window的删除操作同样是由WindowManagerGlobal来完成的 2、首先从所有Window对应View的集合mViews中,获取待删除View的索引 3、根据索引拿到待删除Window对应的ViewRootImpl,具体的删除工作由ViewRootImpl完成 4、ViewRootImpl首先通过WindowSession的remove方法删除Window,再调用WindowManager的doRemoveView方法移除待删除Window对应的ViewRootImpl、LayoutParams、View等 ### Window的更新 1、Window的更新操作也是由WindowManagerGlobal来完成 2、首先为View以及ViewRootImpl设置新的LayoutParams,并更新LayoutParams集合 3、ViewRootImpl设置新的LayoutParams时,会调用scheduleTraversals对View进行重新测量、布局、绘制;然后通过WindowSession老更新Window视图,最终由WindowManagerService的relayoutWindow方法完成Window的更新。 ### Session 添加Window、删除Window都是通过mWindowSession来完成的,mWindowSession的类型是IWindowSession,它是一个Binder代理对象,实现类是Session。Session的内部是通过WindowManagerService来实现Window的添加和删除的,是一个IPC调用的过程。 # Window的创建源码分析 ## Activity的Window创建 ActivityThread的performLaunchActivity方法完成Activity的启动过程,在该方法内部会通过类加载器创建Activity的实例对象,并调用其attach方法为其关联运行过程中需要的一些上下文环境变量等。先看看Activity的attach方法代码: ```java // Activity.java 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, IBinder assistToken) { //... mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); //... 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(); //... } ``` 可以看到,在attach方法中创建了Window对象,并为其设置回调接口。由于Activity实现了Window的Callback接口,所以Window接收到外界的状态改变时就会回调Activity的方法,如onAttachedToWindow、dispatchTouchEvent等。 由于Activity的视图是由setContentView开始的,我们以此为入口: ```java // Activity.java public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } ``` PhoneWindow的setContentView代码如下: ```java // PhoneWindow.java public void setContentView(int layoutResID) { if (mContentParent == null) { // 1、如果没有DecorView,就进行创建 installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { // 2、将View添加到DecorView的mContentParent中 mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { // 3、回调Activity的onContentChanged方法通知Activity视图已经发生改变 cb.onContentChanged(); } mContentParentExplicitlySet = true; } ``` 主要有以下几个流程: **1、如果没有DecorView就进行创建** ![](https://img.kancloud.cn/27/2a/272a20ae7493e435306de73c220ff3c6_281x418.png) DecorView是一个FrameLayout,是Window的顶级视图,一般包含标题栏和内容栏,因主体不同可能没有标题栏,但一定有一个id为`com.android.internal.R.id.content`的ViewGroup,对应着上面代码中的mContentParent。 当mContentParent为空时,意味着DecorView也不存在,installDecor源码如下: ```java // PhoneWindow.java private void installDecor() { if (mDecor == null) { mDecor = generateDecor(-1); //... } else { mDecor.setWindow(this); } if (mContentParent == null) { mContentParent = generateLayout(mDecor); //... } } // PhoneWindow.java protected DecorView generateDecor(int featureId) { //... return new DecorView(context, featureId, this, getAttributes()); } // PhoneWindow.java protected ViewGroup generateLayout(DecorView decor) { ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } //... return contentParent; } ``` installDecor方法会依次创建DecorView、mContentParent。 **2、将View添加到DecorView的mContentParent中** 在PhoneWindow的setContentView方法中,`mLayoutInflater.inflate(layoutResID, mContentParent);`一行代码将View添加到mContentParent中。 **3、回调Activity的onContentChanged方法,通知Activity视图已发生改变** 将View成功添加到mContentParent中后,会回调Window.Callback的onContentChanged方法,我们可以重写Activity的onContentChanged做相关处理。 将View添加到DecorView的mContentParent之后,DecorView还没被WindowManager添加到Window中。在ActivityThread的handleResumeActivity方法中,会调用Activity的makeVisible方法: ```java // ActivityThread.java public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { //... if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } ``` ```java // Activity.java void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); // 添加包含DecorView的Window wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); } ``` 至此,Activity中包含DecorView的Window就被创建出来了。 ## Dialog的Window创建 Dialog的Window的创建过程和Activity类似。 **1、创建Window** ```java // Dialog.java Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { //... mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final Window w = new PhoneWindow(mContext); mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } ``` **2、初始化DecorView并将Dialog视图添加到DecorView中** ```java // Dialog.java public void setContentView(@LayoutRes int layoutResID) { mWindow.setContentView(layoutResID); } ``` **3、将DecorView添加到Window中并显示** Dialog的显示: ```java // Dialog.java public void show() { //... onStart(); mDecor = mWindow.getDecorView(); //... mWindowManager.addView(mDecor, l); mShowing = true; //... } ``` Dialog的隐藏: ```java Dialog.java public void dismiss() { if (Looper.myLooper() == mHandler.getLooper()) { dismissDialog(); } else { mHandler.post(mDismissAction); } } void dismissDialog() { //... try { mWindowManager.removeViewImmediate(mDecor); } finally { //... } } ``` ## Toast的Window创建 Toast属于系统级Window,Toast的show方法和cancel方法分别用于显示和隐藏Toast,它们的内部是一个IPC过程。在Toast内部有两类IPC过程,一类是Toast访问NotificationManagerService,另一类是NotificationManagerService回调Toast里的TN接口。 ```java // Toast.java 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; final int displayId = mContext.getDisplayId(); try { service.enqueueToast(pkg, tn, mDuration, displayId); } catch (RemoteException e) { // Empty } } ``` 首先看到show方法中调用NotificationManagerService的enqueueToast方法时会把TN对象传递过去,后面NotificationManagerService就可以回调TN接口了。 INotificationManager的实现类在NotificationManagerService类中: ```java // NotificationManagerService.java private final IBinder mService = new INotificationManager.Stub() { @Override public void enqueueToast(String pkg, ITransientNotification callback, int duration) { //... synchronized (mToastQueue) { try { ToastRecord record; int index; // All packages aside from the android package can enqueue one toast at a time if (!isSystemToast) { index = indexOfToastPackageLocked(pkg); } else { index = indexOfToastLocked(pkg, callback); } // If the package already has a toast, we update its toast // in the queue, we don't move it to the end of the queue. if (index >= 0) { record = mToastQueue.get(index); record.update(duration); try { record.callback.hide(); } catch (RemoteException e) { } record.update(callback); } else { Binder token = new Binder(); mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY); record = new ToastRecord(callingPid, pkg, callback, duration, token); mToastQueue.add(record); index = mToastQueue.size() - 1; } keepProcessAliveIfNeededLocked(callingPid); // If it's at index 0, it's the current toast. It doesn't matter if it's // new or just been updated. Call back and tell it to show itself. // If the callback fails, this will remove it from the list, so don't // assume that it's valid after this. if (index == 0) { showNextToastLocked(); } } finally { Binder.restoreCallingIdentity(callingId); } } } //... } ``` 在enqueueToast方法中,Toast是被封装为ToastRecord并添加到一个名为mToastQueue的队列中。可以看到,如果当前应用已经有一个Toast,就更新ToastRecord,否则创建一个新的ToastRecord并添加到队列。然后执行showNextToastLocked方法: ```java // NotificationManagerService.java void showNextToastLocked() { ToastRecord record = mToastQueue.get(0); while (record != null) { try { record.callback.show(record.token); scheduleDurationReachedLocked(record); return; } catch (RemoteException e) { //... } } } ``` Toast的显示是由ToastRecord的callback来完成的,也就是Toast中的TN对象的远程Binder。Toast显示以后,NMS还会调用scheduleDurationReachedLocked方法来发送一个延时消息,在相应的时间后移除Toast: ```java private void scheduleDurationReachedLocked(ToastRecord r) { mHandler.removeCallbacksAndMessages(r); Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r); long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; mHandler.sendMessageDelayed(m, delay); } ``` 来看看TN实现类的代码: ```java // Toast.java private static class TN extends ITransientNotification.Stub { //... TN(String packageName, @Nullable Looper looper) { //... 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; } } } }; } /** * schedule handleShow into the right thread */ @Override @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public void show(IBinder windowToken) { mHandler.obtainMessage(SHOW, windowToken).sendToTarget(); } /** * schedule handleHide into the right thread */ @Override public void hide() { mHandler.obtainMessage(HIDE).sendToTarget(); } public void cancel() { mHandler.obtainMessage(CANCEL).sendToTarget(); } public void handleShow(IBinder windowToken) { //... mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); //.. try { mWM.addView(mView, mParams); trySendAccessibilityEvent(); } catch (WindowManager.BadTokenException e) { /* ignore */ } } @UnsupportedAppUsage public void handleHide() { //... mWM.removeViewImmediate(mView); } } ``` 最终,在TN的handleShow方法中会将Toast的视图添加到Window中,在handleHide中将Toast的视图从Window移除。 # 参考文档 源码分析_Android UI何时刷新_Choreographer:[https://www.jianshu.com/p/d7be5308d06e](https://www.jianshu.com/p/d7be5308d06e)