一 概述
本篇文章主要讲 Window、WindowManager、WindowManagerService 三者之间的关系及其运行机制。总的来说 Window 表示的是一种抽象的功能集合,具体实现为 PhoneWindow。WindowManager 是外界访问 Window 的入口,对 Window 的访问必须通过 WindowManager,而 WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。Android 中所有的视图都是通过 Window 来呈现的,不管是 Activity、Dialog、Toast,他们的视图都是附加在 Window 上的。Window 是一个抽象概念,每一个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系。View 才是 Window 存在的实体,可以理解为 WindowManager 中的 addView() 方法,即为 add 一个 Window。
如上图所示我们平时所见的一个窗口是由一个根 View 和一个 Window 绑定,然后这一个 Window 统一被 WindowManagerService 所管理。这些被 WindowManagerService 所管理的 Window 按照一定的次序和位置通过 SurfaceFlinger 显示到最终屏幕上。上图里面没有提到 WindowManager ,WindowManager 在其中扮演的是这么一个角色:用户想添加,更新或者移除 Window 是通过调用 WindowManager 的方法来操作。而用户的这些操作指令,从 WindowManager 通过 IPC 传递到 WindowManagerService,最后由 WindowManagerService 来统筹安排这些 Window 的布局和次序。接下来详细说明其中的运作机制。
1.1 Window
Window 表示一个窗口。其实不管是 Activity、Dialog、还是 Toast 他们的视图实际上都是附加在 Window 上的。Window 有三种类型,分别是应用 Window、子 Window 和系统 Window。应用类 Window 对应一个 Acitivity,子 Window 不能单独存在,需要依附在特定的父 Window 中,比如常见的一些 Dialog 就是一个子 Window。系统 Window 是需要声明权限才能创建的 Window,比如 Toast 和系统状态栏都是系统 Window。
Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的 Window 上面。在三种 Window 中,应用 Window 层级范围是 1~99,子 Window 层级范围是 1000~1999,系统 Window 层级范围是 2000~2999,这些层级范围对应着 WindowManager.LayoutParams 的 type 参数,如果想要 Window 位于所有 Window 的最顶层,那么采用较大的层级即可,很显然系统 Window 的层级是最大的,当我们采用系统层级时,需要声明权限。
Window 是一个抽象概念,每一个 Window 都对应着一个根 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系。如下图所示:
其中 ViewRootImpl 负责对 View 的渲染,具体如何实现在后续文章会介绍,在此先行略过。
1.2 WindowManager
WindowManager 是整个窗口管理机制里面的枢纽,也是需要重点要介绍的。WindowManager 实现了 ViewManager 接口,这个接口定义了我们对于 Window 的基本操作:
public interface ViewManager
{public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view);
}
这三个方法其实就是 WindowManager 对外提供的主要功能,即添加 View、更新 View 和删除 View。看一个通过 WindowManager 添加 Window 的例子:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Button floatingButton = new Button(MainActivity.this);floatingButton.setText("button");WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT,0, 0,PixelFormat.TRANSPARENT);// flag 设置 Window 属性layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;// type 设置 Window 类别(层级)layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;layoutParams.gravity = Gravity.CENTER;WindowManager windowManager = getWindowManager();windowManager.addView(floatingButton, layoutParams);}
}
如下效果:
代码中并没有调用 Activity 的 setContentView 方法,而是直接通过 WindowManager 添加 Window,并将其 type 设置为 TYPE_APPLICATION_OVERLAY。表示在所有应用之上。由效果图可以看到,这个 Button 并不是在 MainActivity 对应的那个 View 里面,而是一个类似独立的存在。其实从这里可以感受到真正承载 View 的其实是 Window,同时也可以猜出,之所以 Activity 会对应一个页面是因为 Activity 持有 Window 从而来持有 View,对于窗口显示来说,Activity 其实不是必须存在的。比如我们常用的 Toast ,它其实就没有与之对应的 Activity,而是类似于上述 Button 产生的方式,产生在界面上的。接下来我们用源码的角度看下 Activity 中是如何创建 Window 的。
1.3 Activity 中 Window 创建过程
在 Activity 启动过程中 ActivityThread 会调用 performLaunchActivity() 这个函数,这个函数里面会经过层层深入会调用 Activity 的 OnCreate() 方法,而在 performLaunchActivity() 中调 OnCreate() 方法前会调用 Activity 的 attach() 方法,今天我们要关注的就是这个方法:
// Activity.java
final void attach(Context context, ActivityThread aThread, ......) {attachBaseContext(context);......// 创建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);......// 设置管理Activity的Window的WindowManagermWindowManager = mWindow.getWindowManager();......
}
在 attach() 这个方法中创建了 Activity 的 Window,同时为该 Window 绑定 WindowManager,将这个 WindowManager 传给 Activity 的成员变量 mWindowManager。同时我们可以看到 Window 的具体实现类是 PhoneWindow。
进去 setWindowManager() 方法中看一下:
public void setWindowManager(WindowManager wm, IBinder appToken,String appName, boolean hardwareAccelerated) {......if (wm == null) {wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);}mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
该方法里面可以看到如果传入的 wm 为空则将其重新赋值。这里其实是获取了 WindowManagerService 的代理,因为 WindowManagerService 和 Activity 所在的应用不在一个进程里,这里是通过 Binder 通信获取的一个 WindowManagerService 代理。获取完 WindowManagerService 代理后通过它来创建出一个真正要用的 WindowManager 并赋值。即这句代码:
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);public WindowManagerImpl createLocalWindowManager(Window parentWindow) {return new WindowManagerImpl(mContext, parentWindow);
}
这个函数很简单,只是创建了一个 WindowManagerImpl 对象。从这里可以看到 WindowManager 真正的实现类是WindowManagerImpl。
到此我们对 Activity 的 attach() 方法分析的差不多了,这个方法主要是创建出 Activity 的 Window 对象,并且获取了管理该 Window 的 WindowManager。而此时其实并没有将 Activity 对应的 View 附属到这个 Window 中。而将这个 View 附属到 Window 的代码其实就是我们在 OnCreate() 中使用的 setContentView(int layoutResID):
public void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();
}
该方法最终调用的是 getWindow() 的 setContentView,而 getWindow() 获取的就是在 attach() 中创建的 PhoneWindow,我们再进去看一下:
public void setContentView(int layoutResID) {if (mContentParent == null) {installDecor();//创建DecorView} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene);} else {//inflate layoutResIDmLayoutInflater.inflate(layoutResID, mContentParent);}mContentParent.requestApplyInsets();final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}mContentParentExplicitlySet = true;
}
这里如果没有 DecorView 就创建一个,DecorView 是 Activity 中的顶级 View,是一个 FrameLayout,一般来说它的内部包含标题栏和内容栏,但是这个会随着主题的变化而改变,不管怎么样,内容栏是一定存在的,并且有固定 id:”android.R.id.content”。而内容栏里面就是将用户自定义的 layout 放进去。之后再回调 onContentChanged() 通知 Activity 视图已经发生改变。此时就将 View 和 Window 关联了起来。不过现在仍然没有将对应的画面展示到手机屏幕上。因为此时还没讲 Window 加入到 WindowManager 中,更不要说还要提交给 WindowManagerService 来统筹安排展示了。
Window 加入到 WindowManager 这一过程要在调用完 Acitivy 的 onResume() 方法后来实现,之后会调用 Activity 的 makeVisible():
void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);
}
这个函数里首先判断 Window 是否已经添加到 WindowManager 中,没有的话取出在刚刚 attach() 方法中创建的 WindowManager,将 DecorView 加入进去,这里的 DecorView 其实就是 Window 所持有那个。然后再将 DecorView 设置为显示状态,来显示我们的布局。
至此才正真将 Window 加入到 WindowManager 中。接下来我们看一下 WindowManager 里面的几个核心方法。
1.4 WindowManager的核心方法
在实际使用中无法直接访问 Window,对 Window 的访问必须通过 WindowManager。我们已经知道 WindowManager 提供的三个接口方法 addView、updateViewLayout 以及 removeView 都是针对 View 的,而这些 View 都被其对应的 Window 所持有,所以上面这些操作实际上相当于对 Window 的操作,WindowManager 是一个接口,它的真正实现由上文可知是 WindowManagerImpl 类。
看一下 WindowManagerImpl 的这三个方法:
@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);
}public void removeView(View view) {mGlobal.removeView(view, false);
}
可以看到这三个方法都交给了 mGlobal 去实现,而 mGlobal 是一个 WindowManagerGlobal 类。我们看一下 WindowManagerGlobal 的 addView() 方法,至于 updateViewLayout() 和 removeView() 原理类似,只是一个用来添加 View,另两个用来更新和移除 View。
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {......final WindowManager.LayoutParams wparams =(WindowManager.LayoutParams) params;......ViewRootImpl root;View panelParentView = null;synchronized (mLock) {......int index = findViewLocked(view, false);if (index >= 0) {......}......root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);// do this last because it fires off messages to start doing thingstry {root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.if (index >= 0) {removeViewLocked(index, true);}throw e;}}}
在 WindowManagerGlobal 内部有如下几个集合比较重要:
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 对象,或者说是那些已经调用了 removeView 方法但是操作删除还未完成的 Window 对象。在 addView () 方法中将这些相关对象添加到对应集合中。最后调用 root.setView() 方法:
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {mView = view;...... int res; /* = WindowManagerImpl.ADD_OKAY; */ requestLayout();......try {mOrigWindowType = mWindowAttributes.type;mAttachInfo.mRecomputeGlobalAttributes = true;collectViewAttributes();res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,mTempInsets);setFrame(mTmpFrame);} catch (RemoteException e) {......} finally {......}...... }}
}
setView() 中会调用一个很重要的方法 requestLayout(),其主要是用来刷新页面,其中还有一个很重要的方法 addToDisplay:
framework/base/services/core/java/com/android/server/wm/Session.java
@Overridepublic int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,Rect outStableInsets, Rect outOutsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,InsetsState outInsetsState) {return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,outInsetsState);}
mWindowSession 的类型是 IWindowSession,它是一个 Binder 对象,真正的实现类是 Session,这也就是之前提到的 IPC 调用的位置。也就是说在这里,完成了 WindowManager 和 WindowManagerService 的通信,将 Window 信息传给了WindowManagerService。
1.5 WindowManagerService
上文已经通过 WindowManagerService 的代理调用了其 addWindow() 方法,该方法非常复杂。主要的操作就是将传来的 Window 根据它的参数,尤其是其 type,保存起来。方便 SurfaceFlinger 渲染。
WindowManagerService 服务大致按照以下方式来控制哪些窗口需要显示以及要显示在哪里:
1.由于每一个 Activity 窗口的大小都等于屏幕的大小,因此,只要对每一个 Activity 窗口设置一个不同的 Z 轴位置,然后就可以使得位于最上面的,即当前被激活的 Activity 窗口,才是可见的。
2.每一个子窗口的 Z 轴位置都比它的父窗口大,但是大小要比父窗口小,这时候 Activity 窗口及其所弹出的子窗口都可以同时显示出来。
3.对于非全屏 Activity 窗口来说,它会在屏幕的上方留出一块区域,用来显示状态栏。这块留出来的区域称对于屏幕来说,称为装饰区(decoration),而对于 Activity 窗口来说,称为内容边衬区(Content Inset)。
4.输入法窗口只有在需要的时候才会出现,它同样是出现在屏幕的装饰区或者说 Activity 窗口的内容边衬区的。
5.对于壁纸窗口,它出现需要壁纸的 Activity 窗口的下方,这时候要求 Activity 窗口是半透明的,这样就可以将它后面的壁纸窗口一同显示出来。
6.两个 Activity 窗口在切换过程,实际上就是前一个窗口显示退出动画而后一个窗口显示开始动画的过程,而在动画的显示过程中,窗口的大小会有一个变化的过程,这样就导致前后两个 Activity 窗口的大小不再都等于屏幕的大小,因而它们就有可能同时都处于可见的状态。事实上,Activity 窗口的切换过程是相当复杂的,因为即将要显示的 Activity 窗口可能还会被设置一个启动窗口(Starting Window)。一个被设置了启动窗口的 Activity 窗口要等到它的启动窗口显示了之后才可以显示出来。
同时在 Android 系统中,WindowManagerService 服务是通过一个实现了 WindowManagerPolicy 接口的策略类来计算一个窗口的位置和大小的。例如,在 Phone 平台上,这个策略类就是 PhoneWindowManager。这样做的好处就是对于不同的平台实现不同的策略类来达到不同的窗口控制模式。
1.6 总结
1.创建 Window,将 View 和 ViewRootImpl 同 Window 绑定。
2.WindowManager 的 addView()、updateViewLayout() 和 removeView() 方法操作 Window。
3.将 WindowManager 这些操作方法转移给 WindowManagerGobal 来调用。
4.调用与 Window 绑定的 ViewRootImpl 的 setView() 方法。
5.setView() 方法里面会通过 mWindowSession 这个 Binder 对象将 Window 传给 WindowManagerService。
6.WindowManagerService 来管理各个 Window 的大小和显示位置,来让 SurfaceFlinger 渲染。
二 WMS启动过程
在 Android 系统中,从设计的角度来看,窗口管理系统是基于 C/S 模式的。整个窗口系统分为服务端和客户端两大部分,客户端负责请求创建窗口和使用窗口,服务端完成窗口的维护,窗口显示等。
在 Client 端,并不是直接和 WindowManagerService 交互,而是直接和本地对象 WindowManager 交互,然后由 WindowManager 完成和 WindowManagerService 的交互。对于 Android 应用来说这个交互是透明的,应用感觉不到 WindowManagerService 的存在。本篇文章我们来介绍窗口管理服务 WindowManagerService 的启动过程。
2.1 WMS全貌
2.2 WMS功能
窗口管理
- WMS 是窗口的管理者,它负责窗口的启动、添加和删除,另外窗口的大小和层级也是由 WMS 进行管理的。窗口管理的核心成员有 DisplayContent、WindowToken 和 WindowState。
窗口动画
- 窗口间进行切换时,使用窗口动画可以显得更炫一些,窗口动画由 WMS 的动画子系统来负责,动画子系统的管理者为 WindowAnimator。
输入系统的中转站
- 通过对窗口的触摸从而产生触摸事件,InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS 是窗口的管理者,因此,WMS “理所应当” 的成为了输入系统的中转站。
Surface管理
- 窗口并不具备有绘制的功能,因此每个窗口都需要有一块 Surface 来供自己绘制。为每个窗口分配 Surface 是由 WMS 来完成的。
- WMS 的职责可以简单总结为下图。
2.3 WMS重要成员
说明:
- WMS
继承于 IWindowManager.Stub,作为 Binder 服务端 -
mSessions
ArraySet 类型的变量,元素类型为 Session,保存着所有的 Session 对象,Session 继承于 IWindowSession.Stub,作为 Binder 服务端,它主要用于进程间通信,其他的应用程序进程想要和 WMS 进程进行通信就需要经过 Session,并且每个应用程序进程都会对应一个Session,WMS 保存这些 Session 用来记录所有向 WMS 提出窗口管理服务的客户端。 -
mPolicy
WindowManagerPolicy 类型的变量,是窗口管理策略的接口类,用来定义一个窗口策略所要遵循的通用规范,并提供了 WindowManager 所有的特定的 UI 行为。具体实现类为 PhoneWindowManager,这个实现类在 WMS 创建时被创建。WMP 允许定制窗口层级和特殊窗口类型以及关键的调度和布局。 -
DisplayContent 的成员变量 mTokenMap,保存所有的 WindowToken 对象,以 IBinder 为 key,可以是 IAppWindowToken 或者其他 Binder 的 Bp 端;另一端情况:ActivityRecord.Token extends IApplicationToken.Stub
- mWindowMap
WindowHashMap 类型的变量,WindowHashMap 继承了 HashMap,它限制了 HashMap 的 key 值的类型为 IBinder,value 值的类型为 WindowState。保存 WMS 中所有的 WindowState 对象 - mResizingWindows
ArrayList 类型的变量,元素类型为 WindowState。mResizingWindows 是用来存储正在调整大小的窗口的列表。 - mAnimator
WindowAnimator 类型的变量,用于管理窗口的动画以及特效动画。 - mH
H 类型的变量,系统的 Handler 类,用于将任务加入到主线程的消息队列中,这样代码逻辑就会在主线程中执行。
涉及代码如下:
frameworks/base/services/java/com/android/server/SystemServer.java
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
frameworks/base/services/core/java/com/android/server/DisplayThread.java
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
frameworks/base/services/core/java/com/android/server/wm/AppWindowToken.java
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java
frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java
frameworks/base/services/core/java/com/android/server/wm/TaskPositioningController.java
frameworks/base/services/core/java/com/android/server/wm/DragDropController.java
2.4 SystemServer
frameworks/base/services/java/com/android/server/SystemServer.java
2.4.1 main
/*** The main entry point from zygote.*/
public static void main(String[] args) {new SystemServer().run();
}private void run() {......// 初始化系统上下文createSystemContext();// 初始化SystemServiceManagermSystemServiceManager = new SystemServiceManager(mSystemContext);......// 启动相互依赖关系复杂的服务startBootstrapServices();// 启动相互独立的基本服务startCoreServices();// 启动其他服务startOtherServices();......
}
2.4.2 startOtherServices
private void startOtherServices() {// 启动WMS前, 需要先启动SensorServiceConcurrentUtils.waitForFutureNoInterrupt(mSensorServiceStart, START_SENSOR_SERVICE);mSensorServiceStart = null;WindowManagerService wm = WindowManagerService.main(context,inputManager, !mFirstBoot, mOnlyCore, new PhoneWindowManager(),mActivityManagerService.mActivityTaskManager);ServiceManager.addService(Context.WINDOW_SERVICE, wm, false,DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);......// 所有WMS相关的实体对象初始化完成wm.onInitReady();......// Display readywm.displayReady();......// WMS readywm.systemReady();......final WindowManagerService windowManagerF = wm;// 启动SystemUIService服务startSystemUi(context, windowManagerF);......
}private static void startSystemUi(Context context,WindowManagerService windowManager) {Intent intent = new Intent();intent.setComponent(new ComponentName("com.android.systemui","com.android.systemui.SystemUIService"));intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);context.startServiceAsUser(intent, UserHandle.SYSTEM);// System UI已启动windowManager.onSystemUiStarted();
}
2.5 WindowManagerService
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
2.5.1 组成元素
// ActivityTaskManager服务相关
// 管理Activity和其容器(如task/stacks/displays)的系统服务
final IActivityTaskManager mActivityTaskManager;
final ActivityTaskManagerService mAtmService;// DisplayManager服务相关, 管理Display属性
final DisplayManagerInternal mDisplayManagerInternal;
final DisplayManager mDisplayManager;
final DisplayWindowSettings mDisplayWindowSettings;// ActivityManager服务相关, 用于和四大组件交互
final IActivityManager mActivityManager;
final ActivityManagerInternal mAmInternal;// PowerManager服务相关, 管理电源状态
PowerManager mPowerManager;
PowerManagerInternal mPowerManagerInternal;// 输入管理服务
final InputManagerService mInputManager;
// 包管理本地系统服务
final PackageManagerInternal mPmInternal;// 根窗口容器
RootWindowContainer mRoot;
// 提供UI相关行为的策略类, 其实现类为PhoneWindowManager
WindowManagerPolicy mPolicy;
// 在一个单独的task中执行动画和Surface操作的类
final WindowAnimator mAnimator;
// 用来确定Window和Surface位置的类
final WindowSurfacePlacer mWindowPlacerLocked;
// 任务快照管理器(当App不可见时, 会将Task的快照以Bitmap形式存在缓存中)
final TaskSnapshotController mTaskSnapshotController;
// Task定位控制器
final TaskPositioningController mTaskPositioningController;
// View的拖/拉操作控制器
final DragDropController mDragDropController;// 当前活跃状态的Session连接队列(通常一个进程中包含一个Session, 用于和WindowManager交互)
final ArraySet<Session> mSessions = new ArraySet<>();
// <IBinder, WindowState>客户端Window token和服务端WindowState的映射
final WindowHashMap mWindowMap = new WindowHashMap();// AppWindowToken是一个窗口容器类, 可以理解为正在显示Window的App
// 或Activity的窗口令牌(继承于WindowToken)
// replace超时的AppWindowToken令牌列表
final ArrayList<AppWindowToken> mWindowReplacementTimeouts = new ArrayList<>();
// WindowState表示服务端描述的Window
final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>();
final ArrayList<WindowState> mForceRemoves = new ArrayList<>();
ArrayList<WindowState> mWaitingForDrawn = new ArrayList<>();
private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>();
WindowState[] mPendingRemoveTmp = new WindowState[20];// 主线程Handler
final H mH = new H();
2.5.2 启动过程
2.5.2.1 main
public static WindowManagerService main(final Context context,final InputManagerService im, final boolean showBootMsgs,final boolean onlyCore, WindowManagerPolicy policy,ActivityTaskManagerService atm) {return main(context, im, showBootMsgs, onlyCore, policy, atm, SurfaceControl.Transaction::new);
}public static WindowManagerService main(final Context context,final InputManagerService im, final boolean showBootMsgs,final boolean onlyCore, WindowManagerPolicy policy,ActivityTaskManagerService atm, TransactionFactory transactionFactory) {//运行在"android.display"线程DisplayThread.getHandler().runWithScissors(() ->sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy, atm, transactionFactory), 0);return sInstance;
}
在 “android.display” 线程中执行 WindowManagerService 对象的初始化过程,其中 final H mH = new H();此处 H 继承于 Handler,无参初始化的过程,便会采用当前所在线程的 Looper。那就是说WindowManagerService.H.handleMessage() 方法运行在 “android.display” 线程。
2.5.2.2 Handler.runWithScissors
Handler.java
public final boolean runWithScissors(final Runnable r, long timeout) {//当前线程跟当前Handler都指向同一个Looper,则直接运行if (Looper.myLooper() == mLooper) {r.run();return true;}BlockingRunnable br = new BlockingRunnable(r);return br.postAndWait(this, timeout);
}
2.5.2.3 postAndWait
Handler.java ::BlockingRunnable
private static final class BlockingRunnable implements Runnable {private final Runnable mTask;private boolean mDone;public BlockingRunnable(Runnable task) {mTask = task;}public void run() {try {mTask.run();} finally {synchronized (this) {mDone = true;notifyAll();}}}public boolean postAndWait(Handler handler, long timeout) {if (!handler.post(this)) {return false;}synchronized (this) {if (timeout > 0) {final long expirationTime = SystemClock.uptimeMillis() + timeout;while (!mDone) {long delay = expirationTime - SystemClock.uptimeMillis();if (delay <= 0) {return false; // timeout}try {wait(delay);} catch (InterruptedException ex) {}}} else {while (!mDone) {try {wait();} catch (InterruptedException ex) {}}}}return true;}
}
由此可见,BlockingRunnable.postAndWait() 方法是阻塞操作,就是先将消息放入 Handler 所指向的线程,此处是指 ”android.display” 线程,由于该方法本身运行在 system_server 主线程。也就意味着 system_server 主线程会进入等待状态,直到 handler 线程执行完成后再唤醒 system_server 主线程。所以在 WindowManagerService 启动完成之后,SystemServer 主线程才能继续往下走。
WMS 所在的线程就是 DisplayThread 这个显示线程,这个线程不仅被 WMS 使用,DisplayManagerService、InputManagerService 也会使用。当实例化 WindowManagerService 时,它的成员变量 mH 就与 DisplayThread 产生了关联,后期可以通过 mH 这个 handler 投递事件到 DisplayThread 的 looper 队列中。mH 是 WMS 的内部类 final class H extends Handler {}。
2.5.2.4 构造方法
private WindowManagerService(Context context, InputManagerService inputManager,boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy,ActivityTaskManagerService atm, TransactionFactory transactionFactory) {installLock(this, INDEX_WINDOW);mGlobalLock = atm.getGlobalLock();mAtmService = atm;mContext = context;mAllowBootMessages = showBootMsgs;mOnlyCore = onlyCore;// 各种变量读取mLimitedAlphaCompositing = context.getResources().getBoolean(com.android.internal.R.bool.config_sf_limitedAlpha);mHasPermanentDpad = context.getResources().getBoolean(com.android.internal.R.bool.config_hasPermanentDpad);mInTouchMode = context.getResources().getBoolean(com.android.internal.R.bool.config_defaultInTouchMode);......mInputManager = inputManager;mDisplayManagerInternal =LocalServices.getService(DisplayManagerInternal.class);// Display设置mDisplayWindowSettings = new DisplayWindowSettings(this);mTransactionFactory = transactionFactory;mTransaction = mTransactionFactory.make();//PhoneWindowManager(继承于WindowManagerPolicy, 用来提供UI相关的一些行为)mPolicy = policy;// 在一个单独的task中执行动画和Surface操作的类mAnimator = new WindowAnimator(this);// 根Window容器mRoot = new RootWindowContainer(this);// 用来确定Window和Surface的位置mWindowPlacerLocked = new WindowSurfacePlacer(this);// 任务快照管理器(当App不可见时, 会将Task的快照以Bitmap形式存在缓存中)mTaskSnapshotController = new TaskSnapshotController(this);LocalServices.addService(WindowManagerPolicy.class, mPolicy);mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);// Keyguard处理器mKeyguardDisableHandler =KeyguardDisableHandler.create(mContext, mPolicy, mH);// PowerManager是控制设备电池状态的管理器mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);// PowerManagerInternal是PowerMananger的本地服务mPowerManagerInternal =LocalServices.getService(PowerManagerInternal.class);if (mPowerManagerInternal != null) {mPowerManagerInternal.registerLowPowerModeObserver(new PowerManagerInternal.LowPowerModeListener() {@Overridepublic int getServiceType() {return ServiceType.ANIMATION;}@Overridepublic void onLowPowerModeChanged(PowerSaveState result) {synchronized (mGlobalLock) {// 低电量模式发生变化时, 需要调整对应的动画final boolean enabled = result.batterySaverEnabled;
if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) {mAnimationsDisabled = enabled;dispatchNewAnimatorScaleLocked(null);}}}});// 获取是否允许动画mAnimationsDisabled =mPowerManagerInternal.getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;}mScreenFrozenLock =mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");mScreenFrozenLock.setReferenceCounted(false);// 获取IActivity.Stub.Proxy(new BinderProxy())mActivityManager = ActivityManager.getService();// 获取IActivityTaskManager.Stub.ProxymActivityTaskManager = ActivityTaskManager.getService();// ActivityManagerInternal是ActivityManager的本地服务mAmInternal =LocalServices.getService(ActivityManagerInternal.class);// ActivityTaskManagerInternal是ActivityTaskManager的本地服务mAtmInternal =LocalServices.getService(ActivityTaskManagerInternal.class);mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);AppOpsManager.OnOpChangedInternalListener opListener =new AppOpsManager.OnOpChangedInternalListener() {@Override public void onOpChanged(int op, String packageName) {updateAppOpsState();}};mAppOps.startWatchingMode(OP_SYSTEM_ALERT_WINDOW, null, opListener);mAppOps.startWatchingMode(AppOpsManager.OP_TOAST_WINDOW,null, opListener);// PackageManagerInternal是PackageManager的本地服务mPmInternal = LocalServices.getService(PackageManagerInternal.class);// 注册Package suspend/unsuspend广播final IntentFilter suspendPackagesFilter = new IntentFilter();suspendPackagesFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);suspendPackagesFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);context.registerReceiverAsUser(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {final String[] affectedPackages =intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);final boolean suspended =Intent.ACTION_PACKAGES_SUSPENDED.equals(intent.getAction());updateHiddenWhileSuspendedState(new ArraySet<>(Arrays.asList(affectedPackages)), suspended);}}, UserHandle.ALL, suspendPackagesFilter, null, null);// 获取并设置window scale设置final ContentResolver resolver = context.getContentResolver();mWindowAnimationScaleSetting = Settings.Global.getFloat(resolver,Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);......// 注册广播, 当DevicePolicyManager状态发生变化时设置keyguard属性是否可用IntentFilter filter = new IntentFilter();filter.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);mContext.registerReceiverAsUser(mBroadcastReceiver,UserHandle.ALL, filter, null, null);mLatencyTracker = LatencyTracker.getInstance(context);mSettingsObserver = new SettingsObserver(); ...... mSurfaceAnimationRunner = new SurfaceAnimationRunner(mPowerManagerInternal);mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);// Task定位控制器mTaskPositioningController = new TaskPositioningController(this,mInputManager, mActivityTaskManager, mH.getLooper());// View的拖/拉操作控制器mDragDropController = new DragDropController(this, mH.getLooper()); ......// 注册WindowManager的本地服务WindowManagerInternalLocalServices.addService(WindowManagerInternal.class, new LocalService());
}
2.2.5.5 onInitReady
public void onInitReady() {// 初始化PhoneWindowManagerinitPolicy();// 添加Watchdog monitorWatchdog.getInstance().addMonitor(this); // 调用SurfaceControl.openTransaction(), 启动一个事务openSurfaceTransaction();// 创建水印createWatermarkInTransaction();// 结束事务closeSurfaceTransaction("createWatermarkInTransaction"); // 显示模拟器显示层showEmulatorDisplayOverlayIfNeeded();
}
2.5.2.6 initPolicy
private void initPolicy() {UiThread.getHandler().runWithScissors(new Runnable() {@Overridepublic void run() {WindowManagerPolicyThread.set(Thread.currentThread(),Looper.myLooper());mPolicy.init(mContext, WindowManagerService.this,WindowManagerService.this);}}, 0);}
PhoneWindowManager 的初始化运行在 “android.ui” 线程。
2.5.2.7 displayReady
public void displayReady() {synchronized (mGlobalLock) {// 设置RootWindowContainer的Window列表的最大宽度if (mMaxUiWidth > 0) {mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth));}final boolean changed = applyForcedPropertiesForDefaultDisplay();mAnimator.ready();mDisplayReady = true;if (changed) {// 重新配置DiaplayContent属性reconfigureDisplayLocked(getDefaultDisplayContentLocked());}mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);}// 1.修改当前configuration 2.确保当前Activity正在运行当前configurationmActivityTaskManager.updateConfiguration(null);// 更新CircularDisplayMaskupdateCircularDisplayMaskIfNeeded();
}
2.5.2.8 systemReady
public void systemReady() {mSystemReady = true;mPolicy.systemReady();mRoot.forAllDisplayPolicies(DisplayPolicy::systemReady);mTaskSnapshotController.systemReady();// 是否支持色域mHasWideColorGamutSupport = queryWideColorGamutSupport();// 是否支持HDR渲染mHasHdrSupport = queryHdrSupport();UiThread.getHandler().post(mSettingsObserver::updateSystemUiSettings);UiThread.getHandler().post(mSettingsObserver::updatePointerLocation);// 获取IVrManager.Stub.Proxy, 并注册状态变化listenerIVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(Context.VR_SERVICE));if (vrManager != null) {final boolean vrModeEnabled = vrManager.getVrModeState();synchronized (mGlobalLock) {vrManager.registerListener(mVrStateCallbacks);if (vrModeEnabled) {mVrModeEnabled = vrModeEnabled;mVrStateCallbacks.onVrStateChanged(vrModeEnabled);}}}
}
2.6 总结
整个启动过程涉及3个线程:system_server 主线程,“android.display”,“android.ui”,整个过程是采用阻塞方式(利用 Handler.runWithScissors) 执行的。其中 WindowManagerService.mH 的 Looper 运行在 “android.display” 进程,也就意味着 WMS.H.handleMessage() 在该线程执行。 流程如下:
三 创建和添加Window
主要从系统和应用两个层次分析 Window 的添加过程,以及论述重要组件之间的关系:
1.SystemUI (如 StatusBar) 和 Activity 中 Window 的创建和添加
2.Activity / PhoneWindow / DecorView / StatusBar / ViewRootImpl 之间的关系
涉及代码如下:
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.javaframeworks/base/core/java/android/app/ContextImpl.java
frameworks/base/core/java/android/app/ActivityThread.java
frameworks/base/core/java/android/app/Activity.java
frameworks/base/core/java/android/view/WindowManagerImpl.java
frameworks/base/core/java/android/view/Window.java
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
frameworks/base/core/java/com/android/internal/policy/DecorView.javaframeworks/base/packages/SystemUI/AndroidManifest.xml
frameworks/base/packages/SystemUI/res/values/config.xml
frameworks/base/packages/SystemUI/res/layout/super_status_bar.xml
frameworks/base/packages/SystemUI/res/layout/status_bar.xml
frameworks/base/core/res/res/layout/screen_simple.xml
3.1 系统窗口的添加
SystemUI 包括很多子类, 状态栏 StatusBar 是最常见的一种。本文以 StatusBar 为例进行分析。
SystemUI 运行的进程名是 SystemUIApplication。
3.1.1 SystemUIApplication
frameworks/base/packages/SystemUI/AndroidManifest.xml
<applicationandroid:name=".SystemUIApplication"android:persistent="true"android:allowClearUserData="false"android:allowBackup="false"android:hardwareAccelerated="true"android:label="@string/app_label"android:icon="@drawable/icon"android:process="com.android.systemui"android:supportsRtl="true"android:theme="@style/Theme.SystemUI"android:defaultToDeviceProtectedStorage="true"android:directBootAware="true"android:appComponentFactory="androidx.core.app.CoreComponentFactory">...<service android:name="SystemUIService" android:exported="true"/>...
</application>
SystemUI 调用链:
SystemUIApplication.onCreate -> SystemUIService.onCreate -> SystemUIApplication.startServicesIfNeeded -> mServices[i].start() -> SystemBars.start
3.1.2 SystemBars.start
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java
public class SystemBars extends SystemUI {private SystemUI mStatusBar;@Overridepublic void start() {createStatusBarFromConfig();}private void createStatusBarFromConfig() {// 从config.xml中读取className-"com.android.systemui.statusbar.phone.StatusBar"final String clsName = mContext.getString(R.string.config_statusBarComponent);// 反射创建StatusBar对象Class<?> cls = mContext.getClassLoader().loadClass(clsName);mStatusBar = (SystemUI) cls.newInstance();mStatusBar.mContext = mContext;mStatusBar.mComponents = mComponents;// 将StatusBar注入系统UI的根组件if (mStatusBar instanceof StatusBar) {SystemUIFactory.getInstance().getRootComponent().getStatusBarInjector().createStatusBar((StatusBar) mStatusBar);}// 创建状态栏View, 并将其添加到WindowManagermStatusBar.start();}
}
SystemBars.start 做的事情:
1.读取配置文件中的属性 config_statusBarComponent,得到字符串 className 为 StatusBar 的字符串
2.利用反射创建 StatusBar 对象,并调用 StatusBar 的 start 方法用来创建状态栏 View 并将其添加到 WindowManager 中
3.1.3 StatusBar
3.1.3.1 StatusBar.start
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
public class StatusBar extends SystemUI implements DemoMode,ActivityStarter, OnUnlockMethodChangedListener,OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,ColorExtractor.OnColorsChangedListener, ConfigurationListener,StatusBarStateController.StateListener, ShadeController,ActivityLaunchAnimator.Callback, AmbientPulseManager.OnAmbientChangedListener,AppOpsController.Callback {protected WindowManager mWindowManager;protected IWindowManager mWindowManagerService;// 状态栏最外层Viewprotected StatusBarWindowView mStatusBarWindow;// CollapsedStatusBarFragment的根View(即StatusBarWindowView的childView)protected PhoneStatusBarView mStatusBarView;@Overridepublic void start() {......// 获取WindowManagerImplmWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);// 获取IWindowManager.Stub.ProxymWindowManagerService = WindowManagerGlobal.getWindowManagerService();......// 创建状态栏View, 并将其添加到WindowManagercreateAndAddWindows(result);......}......
}
3.1.3.2 StatusBar.createAndAddWindows
// 创建状态栏View, 并将其添加到WindowManager
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {// 根据布局文件super_status_bar.xml创建StatusBarWindowView. makeStatusBarView(result);mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);// 将StatusBarWindowView添加到WindowManager. mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
}// 获取状态栏高度, App就是借鉴这个方法
public int getStatusBarHeight() {if (mNaturalBarHeight < 0) {final Resources res = mContext.getResources();mNaturalBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);}return mNaturalBarHeight;
}
这里要提一下:
getStatusBarHeight 这个方法,App 开发中获取状态栏高度就是借鉴的这个方法,只不过是反射获取该属性。基本思路就是看源码中 StatusBar 对应的布局,然后读取其高度属性。
3.1.3.3 StatusBar.makeStatusBarView
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {......// 根据布局文件super_status_bar.xml创建StatusBarWindowViewinflateStatusBarWindow(context);mStatusBarWindow.setService(this);mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener());......// status_bar_container位置填充CollapsedStatusBarFragment// CollapsedStatusBarFragment对应的布局文件为status_bar.xmlFragmentHostManager.get(mStatusBarWindow).addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {// Fragment创建完成时, 初始化CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment;statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);PhoneStatusBarView oldStatusBarView = mStatusBarView;mStatusBarView = (PhoneStatusBarView) fragment.getView();mStatusBarView.setBar(this);mStatusBarView.setPanel(mNotificationPanel);mStatusBarView.setScrimController(mScrimController);mStatusBarView.setBouncerShowing(mBouncerShowing);if (oldStatusBarView != null) {float fraction = oldStatusBarView.getExpansionFraction();boolean expanded = oldStatusBarView.isExpanded();mStatusBarView.panelExpansionChanged(fraction, expanded);}......mStatusBarWindow.setStatusBarView(mStatusBarView);......}).getFragmentManager().beginTransaction().replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),CollapsedStatusBarFragment.TAG).commit();......// 创建导航栏(仅针对CarStatusBar)createNavigationBar(result);......
}
3.1.3.4 StatusBar.inflateStatusBarWindow
protected void inflateStatusBarWindow(Context context) {// 从super_status_bar.xml布局文件创建StatusBarWindowViewmStatusBarWindow = (StatusBarWindowView) mInjectionInflater.injectable(LayoutInflater.from(context)).inflate(R.layout.super_status_bar, null);
}
3.1.4 StatusBarWindowController.add
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
// Adds the status bar view to the window manager.
public void add(ViewGroup statusBarView, int barHeight) {mLp = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,barHeight,WindowManager.LayoutParams.TYPE_STATUS_BAR,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,PixelFormat.TRANSLUCENT);mLp.token = new Binder();mLp.gravity = Gravity.TOP;mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;mLp.setTitle("StatusBar");mLp.packageName = mContext.getPackageName();mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;mStatusBarView = statusBarView;mBarHeight = barHeight;mWindowManager.addView(mStatusBarView, mLp);mLpChanged.copyFrom(mLp);onThemeChanged();
}
总结 StatusBar.start 做的事情:
一句话总结就是创建状态栏 View 并将其添加到 WindowManager 中。
具体过程为:
- 根据布局文件 super_status_bar.xml 创建 StatusBarWindowView
- 上述布局文件中 id 为 status_bar_container 的位置填充 CollapsedStatusBarFragment
- 创建导航栏(仅针对 CarStatusBar )
- StatusBarWindowView 添加到 WindowManager
3.2 应用窗口添加
3.2.1 ActivityThread.performLaunchActivity
frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r,Intent customIntent) {...... try {......// 将Activity attach到Applicationactivity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback,r.assistToken); ......}......return activity;
}
3.2.2 Activity.attach
frameworks/base/core/java/android/app/Activity.java
final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,......) {attachBaseContext(context);......//创建PhoneWindowmWindow = new PhoneWindow(this, window, activityConfigCallback);mWindow.setWindowControllerCallback(this);mWindow.setCallback(this);mWindow.setOnWindowDismissedCallback(this);mWindow.getLayoutInflater().setPrivateFactory(this);......mUiThread = Thread.currentThread(); mMainThread = aThread;mInstrumentation = instr;mToken = token;...... //设置WindowManagerImplmWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//设置父Windowif (mParent != null) {mWindow.setContainer(mParent.getWindow());} // 获取本地创建的WindowManagerImpl//(这个和上面setWindowManager设置进来不是同一个实例)mWindowManager = mWindow.getWindowManager();......
}
3.2.3 Activity.setContentView
在 Activity 启动过程中我们知道执行完 attach 方法后,就要执行回调 Activity 的 onCreate 方法了,我们会在 onCreate 方法中使用 setContentView(layoutResID),来完成布局文件的加载,那么我们来看下 setContentView 这个方法的执行流程
public void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();
}
3.2.4 PhoneWindow.setContentView
@Override
public void setContentView(int layoutResID) {if (mContentParent == null) {installDecor();} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {......} else {// 将布局layoutResID添加到DecorView中id为R.id.content的位置mLayoutInflater.inflate(layoutResID, mContentParent);}mContentParent.requestApplyInsets();final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}mContentParentExplicitlySet = true;
}
3.2.5 ActivityThread.handleResumeActivity
@Override
public void handleResumeActivity(IBinder token, ......) {...... final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); final Activity a = r.activity; ...... //当Window还未添加到WindowManager, 且还未finish当前Activity或//未启动新Activity时, 需要先添加到WindowManagerboolean willBeVisible = !a.mStartedActivity;if (!willBeVisible) {willBeVisible = ActivityTaskManager.getService().willActivityBeVisible(a.getActivityToken()); }if (r.window == null && !a.mFinished && willBeVisible) {// 拿到PhoneWindowr.window = r.activity.getWindow();// 拿到DecorViewView decor = r.window.getDecorView();// 设置DecorView不可见decor.setVisibility(View.INVISIBLE);// 获取本地创建的WindowManagerImplViewManager wm = a.getWindowManager();// 设置Window各属性(如type为TYPE_BASE_APPLICATION)WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;l.softInputMode |= forwardBit;if (r.mPreserveWindow) {a.mWindowAdded = true;r.mPreserveWindow = false;// 获取DecorView的ViewPootImpl(每个View都有一个ViewRootImpl)ViewRootImpl impl = decor.getViewRootImpl();
// 通知子View已经被重建(DecorView还是旧实例,
// Activity是新实例, 因此需要更新callbacks)
// 1.通常情况下, ViewRootImpl通过
//WindowManagerImpl#addView->ViewRootImpl#setView
//设置Activity的callbacks回调
// 2.但是如果DecorView复用时, 需要主动告诉ViewRootImpl callbacks可能发生变化if (impl != null) {impl.notifyChildRebuilt();}}// 当Activity可见时, 添加DecorView到WindowManagerImpl//或回调Window属性变化方法if (a.mVisibleFromClient) {if (!a.mWindowAdded) {a.mWindowAdded = true;wm.addView(decor, l);} else {a.onWindowAttributesChanged(l);}}} else if (!willBeVisible) {r.hideForNow = true;}// Get rid of anything left hanging around.cleanUpPendingRemoveWindows(r, false /* force */);if (!r.activity.mFinished && willBeVisible &&r.activity.mDecor != null && !r.hideForNow) {// 调用Activity.onConfigurationChanged方法if (r.newConfig != null) {performConfigurationChangedForActivity(r, r.newConfig);r.newConfig = null;}// 更新DecorView的LayoutParamsWindowManager.LayoutParams l = r.window.getAttributes();if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)!= forwardBit) {l.softInputMode = (l.softInputMode &(~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) |forwardBit;if (r.activity.mVisibleFromClient) {ViewManager wm = a.getWindowManager();View decor = r.window.getDecorView();wm.updateViewLayout(decor, l);}}r.activity.mVisibleFromServer = true;mNumVisibleActivities++;// 设置Activity可见if (r.activity.mVisibleFromClient) {r.activity.makeVisible();}} r.nextIdle = mNewActivities;mNewActivities = r;Looper.myQueue().addIdleHandler(new Idler());
}
在 Activity 创建过程中,会创建和添加 PhoneWindow.
Activity 中 PhoneWindow 的创建和添加过程:
1.handleLaunchActivity 过程
- 创建 Activity 和 Application 实例,调用 Activity.attach 方法将 Activity attach 到 Application
- 在 Activity 中创建 PhoneWindow,PhoneWindow 又创建 DecorView。设置 WindowManagerImpl 到 PhoneWindow.
- 调用 Activity.onCreate -> setContentView(layoutResID) 将 layoutResId 添加到 DecorView 中 R.id.content 位置
2.handleResumeActivity 过程
- PhoneWindow 中的 DecorView 添加到 WindowManager
- 设置 DecorView 可见
3.3 PhoneWindow&DecorView详解
我们已经分析了 Activity 中 PhoneWindow 的创建和添加过程,在 Activity#onCreate 中会调用PhoneWindow#installDecor 创建 DecorView 和子 View。接下来我们来分析下 PhoneWindow 和 DecorView 的组成。
3.3.1 PhoneWindow
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
3.3.1.1 PhoneWindow.installDecor
// 创建DecorView及其子View
private void installDecor() {mForceDecorInstall = false;if (mDecor == null) {// 创建DecorViewmDecor = generateDecor(-1);mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);......} else {mDecor.setWindow(this);}if (mContentParent == null) {// 根据设置的Window相关属性, 设置PhoneWindow特性//给PhoneWindow的根DecorView添加子View, 并返回ContentViewmContentParent = generateLayout(mDecor); ...... // 对于R.layout.screen_simple没有该元素// 但对于R.layout.screen_action_bar包含该元素final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(R.id.decor_content_parent); // 如果根View为ActionBarOverlayLayout, 则设置// ActionBarOverlayLayout的title/icon/logo/menu等if (decorContentParent != null) {mDecorContentParent = decorContentParent;mDecorContentParent.setWindowCallback(getCallback());if (mDecorContentParent.getTitle() == null) {mDecorContentParent.setWindowTitle(mTitle);}final int localFeatures = getLocalFeatures();for (int i = 0; i < FEATURE_MAX; i++) {if ((localFeatures & (1 << i)) != 0) {mDecorContentParent.initFeature(i);}}mDecorContentParent.setUiOptions(mUiOptions);if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||(mIconRes != 0 && !mDecorContentParent.hasIcon())) {mDecorContentParent.setIcon(mIconRes);} else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&mIconRes == 0 && !mDecorContentParent.hasIcon()) {mDecorContentParent.setIcon(getContext().getPackageManager().getDefaultActivityIcon());mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;}if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||(mLogoRes != 0 && !mDecorContentParent.hasLogo())) {mDecorContentParent.setLogo(mLogoRes);}PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);if (!isDestroyed() && (st == null || st.menu == null)&& !mIsStartingWindow) {invalidatePanelMenu(FEATURE_ACTION_BAR);}} else {// 如果有title元素, 更新titlemTitleView = findViewById(R.id.title);if (mTitleView != null) {if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {final View titleContainer = findViewById(R.id.title_container);if (titleContainer != null) {titleContainer.setVisibility(View.GONE);} else {mTitleView.setVisibility(View.GONE);}mContentParent.setForeground(null);} else {mTitleView.setText(mTitle);}}}......}
}
3.1.1.2 PhoneWindow.generateDecor
protected DecorView generateDecor(int featureId) {......Context context;if (mUseDecorContext) {Context applicationContext =getContext().getApplicationContext();if (applicationContext == null) {context = getContext();} else {context = new DecorContext(applicationContext, getContext());if (mTheme != -1) {context.setTheme(mTheme);}}} else {context = getContext();}return new DecorView(context, featureId, this, getAttributes());
}
3.1.1.3 PhoneWindow.generateLayout
protected ViewGroup generateLayout(DecorView decor) {// Apply data from current theme.TypedArray a = getWindowStyle();mIsFloating =a.getBoolean(R.styleable.Window_windowIsFloating, false);int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)& (~getForcedWindowFlags());if (mIsFloating) {setLayout(WRAP_CONTENT, WRAP_CONTENT);setFlags(0, flagsToUpdate);} else {setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR,flagsToUpdate);}if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {requestFeature(FEATURE_NO_TITLE);} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {// Don't allow an action bar if there is no title.requestFeature(FEATURE_ACTION_BAR);} ...... final Context context = getContext();......WindowManager.LayoutParams params = getAttributes();...... // Inflate the window decor. int layoutResource;int features = getLocalFeatures();// System.out.println("Features: 0x" + Integer.toHexString(features));if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {layoutResource = R.layout.screen_swipe_dismiss;setCloseOnSwipeEnabled(true);} else if ((features & ((1 << FEATURE_LEFT_ICON) |(1 << FEATURE_RIGHT_ICON))) != 0) {if (mIsFloating) {TypedValue res = new TypedValue();getContext().getTheme().resolveAttribute(R.attr.dialogTitleIconsDecorLayout, res, true);layoutResource = res.resourceId;} else {layoutResource = R.layout.screen_title_icons;}// XXX Remove this once action bar supports these features.removeFeature(FEATURE_ACTION_BAR);// System.out.println("Title Icons!");} else if ((features & ((1 << FEATURE_PROGRESS) |(1 << FEATURE_INDETERMINATE_PROGRESS))) != 0&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {layoutResource = R.layout.screen_progress;// System.out.println("Progress!");} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {// Special case for a window with a custom title.// If the window is floating, we need a dialog layoutif (mIsFloating) {TypedValue res = new TypedValue();getContext().getTheme().resolveAttribute(R.attr.dialogCustomTitleDecorLayout, res, true);layoutResource = res.resourceId;} else {layoutResource = R.layout.screen_custom_title;}// XXX Remove this once action bar supports these features.removeFeature(FEATURE_ACTION_BAR);} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {// If no other features and not embedded, only need a title.// If the window is floating, we need a dialog layoutif (mIsFloating) {......} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {layoutResource = a.getResourceId(R.styleable.Window_windowActionBarFullscreenDecorLayout,R.layout.screen_action_bar);} else {layoutResource = R.layout.screen_title;}// System.out.println("Title!");} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {layoutResource = R.layout.screen_simple_overlay_action_mode;} else {// Embedded, so no decoration is needed.layoutResource = R.layout.screen_simple;// System.out.println("Simple!");}mDecor.startChanging();// DecorView添加子View. 从父到子依次是:DecorView -> //DecorCaptionView(不一定包含) -> root(即layoutResource对应的View)mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);// 获取R.id.content对应的ContentViewViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ...... // 仅最顶层Window执行if (getContainer() == null) {mDecor.setWindowBackground(mBackgroundDrawable);final Drawable frame;if (mFrameResource != 0) {frame = getContext().getDrawable(mFrameResource);} else {frame = null;}mDecor.setWindowFrame(frame);mDecor.setElevation(mElevation);mDecor.setClipToOutline(mClipToOutline);if (mTitle != null) {setTitle(mTitle);}if (mTitleColor == 0) {mTitleColor = mTextColor;}setTitleColor(mTitleColor);}mDecor.finishChanging();return contentParent;
}
3.3.2 DecorView
frameworks/base/core/java/com/android/internal/policy/DecorView.java
3.3.2.1 onResourcesLoaded
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {if (mBackdropFrameRenderer != null) {loadBackgroundDrawablesIfNeeded();mBackdropFrameRenderer.onResourcesLoaded(this, mResizingBackgroundDrawable, ......);}// 创建DecorCaptionView(即包含系统按钮如最大化,关闭等的标题)mDecorCaptionView = createDecorCaptionView(inflater);final View root = inflater.inflate(layoutResource, null);if (mDecorCaptionView != null) {// 从父到子依次是:DecorView -> DecorCaptionView -> rootif (mDecorCaptionView.getParent() == null) {addView(mDecorCaptionView,new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));}mDecorCaptionView.addView(root,new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));} else {// 从父到子依次是:DecorView -> rootaddView(root, 0,new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));}mContentRoot = (ViewGroup) root;initializeElevation();
}private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {DecorCaptionView decorCaptionView = null;for (int i = getChildCount() - 1; i >= 0 &&decorCaptionView == null; i--) {View view = getChildAt(i);if (view instanceof DecorCaptionView) {
// The decor was most likely saved from a relaunch - so reuse it.decorCaptionView = (DecorCaptionView) view;removeViewAt(i);}}final WindowManager.LayoutParams attrs = mWindow.getAttributes();final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||attrs.type == TYPE_APPLICATION ||attrs.type == TYPE_DRAWN_APPLICATION;final WindowConfiguration winConfig =getResources().getConfiguration().windowConfiguration;if (!mWindow.isFloating() && isApplication &&winConfig.hasWindowDecorCaption()) {if (decorCaptionView == null) {decorCaptionView = inflateDecorCaptionView(inflater);}decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);} else {decorCaptionView = null;} // Tell the decor if it has a visible caption.enableCaption(decorCaptionView != null);return decorCaptionView;
}
总结 PhoneWindow.installDecor:
该方法在 Activity#setContentView(layoutResID) 里调用,用来在 PhoneWindow 创建 DecorView 及其子 View。
PhoneWindow#installDecor 具体步骤:
1.generateDecor:创建 DecorView (是一个 FrameLayout )
2.generateLayout:根据设置的 Window 相关属性,设置 PhoneWindow 特性。给 PhoneWindow 的根 DecorView 添加子 View,并返回 ContentView
- 读取配置文件中的 Window 相关的属性< declare-styleable name=“Window”>
- 根据设置的 Window 各属性值,设置 PhoneWindow 的特性(例如requestFeature/setFlags/WindowManager.LayoutParams),以及选择对应的 layout (默认为 screen_simple)
- DecorView 添加子 View。从父到子依次是:DecorView -> DecorCaptionView (不一定包含) -> root (即上述 layout对应的 View)
- 返回 root (即 R.layout.screen_simple ) 中 R.id.content 对应的 ContentView (即一个 FrameLayout)
DecorCaptionView 即包含系统按钮如最大化,关闭等的标题,此处不做详细介绍。
frameworks/base/core/res/res/layout/screen_simple.xml
3.4 Activity&PhoneWindow&DecorView关系图
上图是根据源码总结出来的各个类的关系图。主要包含一下三部分:
1.StatusBar
状态栏 (系统 UI ),其根 View 为 StatusBarWindowView,通过 WindowManagerImpl.addView 添加到窗口管理器
2.DecorView
(1) 应用程序 View. 通过 WindowManagerImpl.addView 添加到窗口管理器. 一个页面对应一个 Activity,一个 Activity 包含一个 PhoneWindow,PhoneWindow 的根 View 即为 DecorView,DecorView 为 FrameLayout
(2) 根据创建 Activity 时设置的 Window 属性的不同,选择不同的 layout 布局,并将该 layout 布局添加到 DecorView 中。最简单的为 R.layout.screen_simple
(3) 上述 layout 布局 R.layout.screen_simple 为 LinearLayout,包含2个元素: @id/action_mode_bar_stub 和 @android:id/content。即标题栏和内容体。在 Activity#onCreate -> setContentView(layoutResID) 调用时,会将layoutResID 填充到 @android:id/content
3.NavigationBar
通常指的手机底部的虚拟按键。
上面我们已经知道 StatusBar 和 DecorView 都会添加到 WindowManagerImpl,通过窗口管理器进行管理。因此开发者可以定制状态栏和应用程序的 UI 样式及他们之间的显示关系。
下面以沉浸式状态栏实现为例:
我们实现这样的效果需要遵循几个步骤:
(1) 状态栏透明
(2) DecorView 占满整个屏幕
(3) NavigationBar 的控制
实现代码如下:
private static void transparentStatusBar(final Activity activity) {Window window = activity.getWindow();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {// Android 5.0及以上, 设置DecorView全屏和系统状态栏透明window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE |View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;window.getDecorView().setSystemUiVisibility(option);window.setStatusBarColor(Color.TRANSPARENT);} else {// Android 4.4 设置系统状态栏透明(不能实现DecorView全屏)// 由于Android 4.4不存在PhoneWindow#setStatusBarColor这个方法.// 因此设置透明状态栏需要在DecorView添加一个AlphaStatusBarViewaddStatusBarAlpha(activity, 0)window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);}
}// 仅仅用于Android 4.4
private static void addStatusBarAlpha(final Activity activity,final int alpha) {ViewGroup parent = (ViewGroup) activity.getWindow().getDecorView()View fakeStatusBarView = parent.findViewWithTag(TAG_ALPHA);if (fakeStatusBarView != null) {if (fakeStatusBarView.getVisibility() == View.GONE) {fakeStatusBarView.setVisibility(View.VISIBLE);}fakeStatusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0));} else {parent.addView(createAlphaStatusBarView(parent.getContext(), alpha));}
}// 仅仅用于Android 4.4
private static View createAlphaStatusBarView(final Context context,
final int alpha) {View statusBarView = new View(context);statusBarView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight()));statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0));statusBarView.setTag(TAG_ALPHA);return statusBarView;
}
系统 UI 的可见性 :
系统 UI (如 StatusBar 和 NavigationBar),可以在 Activity 中通过 DecorView#setSystemUiVisibility 控制。
(1) 设置 DecorView 全屏
View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
(2) 隐藏 NavigationBar
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
(3) 其他参考 View.SYSTEM_UI_FLAG_XXX
3.6 理解ViewRootImpl
ViewRootImpl 是一个视图层次结构的顶部,可以理解为一个 Window 中所有 View 的根 View 的管理者(但 ViewRootImpl 不是 View,只是实现了 ViewParent 接口),实现了 View 和 WindowManager 之间的通信协议,实现的具体细节在 WindowManagerGlobal 这个类中。
简单来说 ViewRootImpl 是 View 与 WindowManager 之间联系的桥梁,作用总结如下:
1.将 DecorView 传递给 WindowManagerSerive
2.完成 View 的绘制过程,包括 measure、layout、draw 过程
3.向 DecorView 分发收到的用户发起的 event 事件,如按键,触屏等事件。
其中,ViewRootImpl 中包含了两个需要重点关注的内部类:
final class ViewRootHandler extends Handler 用于向 DecorView 分发事件
static class W extends IWindow.Stub
W 是 ViewRootImp l的一个嵌入类,也是一个 Binder 服务。通过 mWindowSession.addToDisplay 函数传入 WMS,用来在 WMS 中通过 Binder 回调。
将Window传递给WMS
public void setView(View view, WindowManager.LayoutParams attrs,View panelParentView) {synchronized (this) {if (mView == null) {mView = 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();//见下节介绍if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {mInputChannel = new InputChannel();//生成InputChannel}......try {......//调用mWindowSession.addToDisplay通过binder调用到WMS//实现对Window的真正的添加,这里的mWindow为 W 对象res = mWindowSession.addToDisplay(mWindow, mSeq, ......);setFrame(mTmpFrame);} catch (RemoteException e) {......} finally {......}...... if (mInputChannel != null) {if (mInputQueueCallback != null) {mInputQueue = new InputQueue();mInputQueueCallback.onInputQueueCreated(mInputQueue);}//用于输入事件的接收mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());}......}}}
四 Window属性
之前我们分析了 Window、WindowManager 和 WMS 之间的关系,WMS 是 Window 的最终管理者,Window 好比是员工,WMS 是老板,为了方便老板管理员工则需要定义一些 “协议”,这些“协议”就是 Window 的属性,被定义在 WindowManager 的内部类 LayoutParams 中,了解 Window 的属性能够更好的理解 WMS 的内部原理。
Window 的属性有很多种,与应用开发最密切的有三种,它们分别是 Type (Window 的类型)、Flag (Window 的标志) 和 SoftInputMode(软键盘相关模式),下面分别介绍这三种 Window 的属性
4.1 Window类型
Window 的类型有很多种,比如应用程序窗口、系统错误窗口、输入法窗口、PopupWindow、Toast、Dialog 等等。总来来说分为三大类分别是:Application Window(应用程序窗口)、Sub Windwow(子窗口)、System Window(系统窗口),每个大类又包含了很多种类型,它们都定义在 WindowManager 的静态内部类 LayoutParams 中,接下来我们分别对这三大类进行讲解。
常见的 Window 类型如下:
完整的 Window 类型及解释, 可以参考源码 WindowManager。
当一个进程向 WMS 申请一个窗口时,WMS 会为窗口确定显示次序。为了方便窗口显示次序的管理,手机屏幕可以虚拟的用 X、Y、Z 轴来表示,其中 Z 轴垂直于屏幕,从屏幕内指向屏幕外,这样确定窗口显示次序也就是确定窗口在 Z 轴上的次序,这个次序称为 Z-Oder。Type 值是 Z-Oder 排序的依据,我们知道应用程序窗口的 Type 值范围为 1 到 99,子窗口 1000 到 1999 ,系统窗口 2000 到 2999,一般情况下,Type 值越大则 Z-Oder 排序越靠前,就越靠近用户。当然窗口显示次序的逻辑不会这么简单,情况会比较多,举个常见的情况:当多个窗口的 Type 值都是 TYPE_APPLICATION,这时 WMS 会结合各种情况给出最终的 Z-Oder,我们接下来分析下这个 Z-Oder 的次序是怎么确定的。
4.1.1 Z-Order的确定
在 Client 端,WindowManager.LayoutParams 的 x & y & type 用来确定 Window 的三维坐标位置。
public static class WindowManager.LayoutParams extendsViewGroup.LayoutParams implements Parcelable {public int x;// x坐标 public int y;// y坐标 public int type;// 类型(WMS根据type确定Z-Order)......
}
在 WMS 端,WindowState 表示一个窗口实例,其中的属性 mBaseLayer 和 mSubLayer 用来确定 Z-Order。
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
// 计算主序时乘以10000, 目的是为同类型的多个窗口预留空间和Z-Order调整
static final int TYPE_LAYER_MULTIPLIER = 10000;
// 计算主序时加1000, 目的是确定相同主序的子窗口相对于父窗口的上下位置
static final int TYPE_LAYER_OFFSET = 1000;
// 表示主序
final int mBaseLayer;
// 表示子序
final int mSubLayer;
WindowState(WindowManagerService service, Session s, ......) {......// 若当前窗口类型为子窗口if ((mAttrs.type >= FIRST_SUB_WINDOW &&mAttrs.type <= LAST_SUB_WINDOW)) {// 计算主序, 主序与父窗口一致,主序大的窗口位于主序小的窗口上面mBaseLayer = mPolicy.getWindowLayerLw(parentWindow) *TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;// 计算子序mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);......} else { // 若当前窗口类型不是子窗口// 计算主序mBaseLayer = mPolicy.getWindowLayerLw(this) *TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;// 子序为0mSubLayer = 0;......}......
}
4.1.2 确定主序
frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java
default int getWindowLayerLw(WindowState win) {return getWindowLayerFromTypeLw(win.getBaseType(),win.canAddInternalSystemWindow());
}default int getWindowLayerFromTypeLw(int type) {if (isSystemAlertWindowType(type)) {throw new IllegalArgumentException("......");}return getWindowLayerFromTypeLw(type, false);
}default int getWindowLayerFromTypeLw(int type,
boolean canAddInternalSystemWindow) {if (type >= FIRST_APPLICATION_WINDOW && type <=LAST_APPLICATION_WINDOW) {return APPLICATION_LAYER; // 2}switch (type) {case TYPE_WALLPAPER:// wallpaper is at the bottom, though the // window manager may move it.return 1;case TYPE_PRESENTATION:case TYPE_PRIVATE_PRESENTATION:case TYPE_DOCK_DIVIDER:case TYPE_QS_DIALOG:return APPLICATION_LAYER;case TYPE_PHONE:return 3;case TYPE_SEARCH_BAR:case TYPE_VOICE_INTERACTION_STARTING:return 4;case TYPE_VOICE_INTERACTION:// voice interaction layer is almost// immediately above apps.return 5;case TYPE_INPUT_CONSUMER:return 6;case TYPE_SYSTEM_DIALOG:return 7;case TYPE_TOAST:// toasts and the plugged-in battery thingreturn 8;case TYPE_PRIORITY_PHONE:// SIM errors and unlock. Not sure if this // really should be in a high layer.return 9;case TYPE_SYSTEM_ALERT:// like the ANR / app crashed dialogs// Type is deprecated for non-system apps. // For system apps, this type should be// in a higher layer than TYPE_APPLICATION_OVERLAY.return canAddInternalSystemWindow ? 13 : 10;case TYPE_APPLICATION_OVERLAY:return 12;case TYPE_DREAM:// used for Dreams (screensavers with TYPE_DREAM windows)return 14;case TYPE_INPUT_METHOD:// on-screen keyboards and other such input // method user interfaces go here.return 15;case TYPE_INPUT_METHOD_DIALOG:// on-screen keyboards and other such input// method user interfaces go here.return 16;case TYPE_STATUS_BAR:return 17;case TYPE_STATUS_BAR_PANEL:return 18;case TYPE_STATUS_BAR_SUB_PANEL:return 19;case TYPE_KEYGUARD_DIALOG:return 20;case TYPE_VOLUME_OVERLAY:// the on-screen volume indicator and// controller shown when the user// changes the device volumereturn 21;case TYPE_SYSTEM_OVERLAY:// the on-screen volume indicator and // controller shown when the user// changes the device volumereturn canAddInternalSystemWindow ? 22 : 11;case TYPE_NAVIGATION_BAR:// the navigation bar, if available// shows atop most thingsreturn 23;case TYPE_NAVIGATION_BAR_PANEL:// some panels (e.g. search) need to // show on top of the navigation barreturn 24;case TYPE_SCREENSHOT:// screenshot selection layer shouldn't go // above system error, but it should cover// navigation bars at the very least.return 25;case TYPE_SYSTEM_ERROR:// system-level error dialogsreturn canAddInternalSystemWindow ? 26 : 10;case TYPE_MAGNIFICATION_OVERLAY:// used to highlight the magnified portion of a displayreturn 27;case TYPE_DISPLAY_OVERLAY:// used to simulate secondary display devicesreturn 28;case TYPE_DRAG:// the drag layer: input for drag-and-drop is // associated with this window,// which sits above all other focusable windowsreturn 29;case TYPE_ACCESSIBILITY_OVERLAY:// overlay put by accessibility services to// intercept user interactionreturn 30;case TYPE_SECURE_SYSTEM_OVERLAY:return 31;case TYPE_BOOT_PROGRESS:return 32;case TYPE_POINTER:// the (mouse) pointer layerreturn 33;default:return APPLICATION_LAYER;}
}
从主序的确定过程可知:
- 从大的分类上来说,系统窗口 > 子窗口 > 应用窗口,整体上 type 和主序存在正相关。
- 但是在单个分类里,单个窗口的 type 大小和主序大小不存在正相关或负相关。
- 主序越大,窗口 Z-Order 越靠上面。
4.1.3 确定子序
default int getSubWindowLayerFromTypeLw(int type) {switch (type) {case TYPE_APPLICATION_PANEL:case TYPE_APPLICATION_ATTACHED_DIALOG:return APPLICATION_PANEL_SUBLAYER; // 1case TYPE_APPLICATION_MEDIA:return APPLICATION_MEDIA_SUBLAYER; // -2case TYPE_APPLICATION_MEDIA_OVERLAY:return APPLICATION_MEDIA_OVERLAY_SUBLAYER; // -1case TYPE_APPLICATION_SUB_PANEL:return APPLICATION_SUB_PANEL_SUBLAYER; // 2case TYPE_APPLICATION_ABOVE_SUB_PANEL:return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER; // 3}return 0;
}
窗口子序,用来描述子窗口相对于父窗口的位置:
- 子序 >0 表示位于父窗口上面,子序 <0 表示位于父窗口下面,子序 = 0 表示不是子窗口。
- 子序越大,子窗口相对于父窗口越靠上面。
从子序的确定过程可知:
- 子窗口的 type 大小和子序大小不存在正相关或负相关。
- 视频相关的子窗口类型 ( TYPE_APPLICATION_MEDIA 和 TYPE_APPLICATION_MEDIA_OVERLAY ) 位于父窗口的下面
- 其他类型子窗口位于父窗口上面
确定了主序和子序,之后的过程后面再分析,此处不细纠。
4.2 Window标志Flag
Window 的标志也就是 Flag,用于控制 Window 的显示,同样被定义在 WindowManager 的内部类 LayoutParams 中,一共有 20 多个,这里我们给出几个比较常用。
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
使用该标志后,当窗口可见时,即使设备处于打开状态但在锁屏状态下,仍允许在屏幕上锁屏。这主要是针对一些情况下需要保持屏幕亮着而又不能操作时很有用,比如让用户只观看某些特定内容不可操作,此时屏幕保持亮着而不会滑入待机状态。
FLAG_NOT_FOCUSABLE
仅当窗口无须获得输入焦点时,才应设置此标志。设置该标志意味着,当用户触摸并选择该窗口时,该窗口不会接收到任何输入事件。
FLAG_NOT_TOUCHABLE
使用该标志后,窗口将不会响应任何触摸事件。
FLAG_NOT_TOUCH_MODAL
指定窗口在处理触摸事件时,是否限制在当前窗口内。如果使用这个标志,则该窗口外的任何触摸事件都将被传递给下面的窗口。
FLAG_KEEP_SCREEN_ON
该标志确保屏幕保持开启状态,直到从当前窗口移除或清除FLAG_KEEP_SCREEN_ON标志。
FLAG_LAYOUT_NO_LIMITS
使用该标志可以使窗口超出屏幕尺寸而不会被截断。这个标志通常用于应用程序需要在特殊情况下覆盖全屏幕的场景。
FLAG_FULLSCREEN
指定窗口在全屏模式下呈现,隐藏状态栏和导航栏。比如在游戏和视频播放时使用。
FLAG_SHOW_WHEN_LOCKED
使用该标志后,即使设备处于锁屏状态,该窗口也能显示在锁屏界面之上。这个标志通常使用于闹钟、电话来电等应用场景。
FLAG_IGNORE_CHEEK_PRESSES
当用户在通话过程中将自己的脸靠近屏幕时,该标志告知系统不要响应触摸事件。这个标志通常用于防止带有接近检测芯片的设备因触摸而产生额外的误操作。
FLAG_TURN_SCREEN_ON
此标志允许屏幕在显示窗口时点亮,以确保接收到最新信息,例如来电提醒等。
设置 Window 的 Flag 有三种方法,第一种是通过 Window 的 addFlags 方法:
Window mWindow = getWindow();
mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
第二种通过 Window 的 setFlags 方法:
Window mWindow = getWindow();
mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN
,WindowManager.LayoutParams.FLAG_FULLSCREEN);
其实 Window 的 addFlags 方法内部会调用 setFlags 方法,因此这两种方法区别不大。
第三种则是给 LayoutParams 设置 Flag,并通过 WindowManager 的 addView 方法进行添加,如下所示:
WindowManager.LayoutParams mWindowLayoutParams =new WindowManager.LayoutParams();mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;WindowManager mWindowManager =(WindowManager) getSystemService(Context.WINDOW_SERVICE); TextView mTextView = new TextView(this);mWindowManager.addView(mTextView,mWindowLayoutParams);
4.3 软键盘相关模式
窗口和窗口的叠加是非常常见的场景,但如果其中的窗口是软键盘窗口,可能就会出现一些问题,比如典型的用户登录界面,默认的情况弹出的软键盘窗口可能会盖住输入框下方的按钮,这样用户体验会非常糟糕。
为了使得软键盘窗口能够按照期望来显示,WindowManager 的静态内部类 LayoutParams 中定义了软键盘相关模式,这里给出常用的几个:
- SoftInputMode 描述
- SOFT_INPUT_STATE_UNSPECIFIED 没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
- SOFT_INPUT_STATE_UNCHANGED 不会改变软键盘状态
- SOFT_INPUT_STATE_HIDDEN 当用户进入该窗口时,软键盘默认隐藏
- SOFT_INPUT_STATE_ALWAYS_HIDDEN 当窗口获取焦点时,软键盘总是被隐藏
- SOFT_INPUT_ADJUST_RESIZE 当软键盘弹出时,窗口会调整大小
- SOFT_INPUT_ADJUST_PAN 当软键盘弹出时,窗口不需要调整大小,要确保输入焦点是可见的
从上面给出的 SoftInputMode,可以发现,它们与 AndroidManifest 中 Activity 的属性 android:windowSoftInputMode 是对应的。因此,除了在 AndroidMainfest 中为 Activity 设置 android:windowSoftInputMode 以外还可以在 Java 代码中为 Window 设置 SoftInputMode
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
4.4 常见Window
App 开发中常见的涉及 Window 的地方:Activity / Dialog / PopupWindow / Toast / 输入法。
通过查看相关源码,得出如下结论:
Activity:创建 PhoneWindow,类型为 TYPE_BASE_APPLICATION
Dialog:创建 PhoneWindow,未指定类型
PopupWindow:未创建 Window,类型为 TYPE_APPLICATION_PANEL
Toast:类型为 TYPE_TOAST
输入法(IMM):类型为 TYPE_INPUT_METHOD
4.5 WindowInset
4.5.1 什么是WindowInsets?
WindowInsets源码解释为Window Content的一系列插值集合,可以理解为可以将其理解为不同的窗口装饰区域类型,比如一个Activity相对于手机屏幕需要空出的地方以腾给StatusBar、Ime、NavigationBar等系统窗口,具体表现为该区域需要的上下左右的宽高。
WindowInsets包括三类:SystemWindowInsets、StableInsets、WIndowDecorInsets
- SystemWindowInsets:全窗口下,被navigationbar、statusbar、ime或其他系统窗口覆盖的区域
- StableInsets:全窗口下,被系统UI覆盖的区域
- WIndowDecorInsets:系统预留属性
4.5.2 监听inset 变化
-
设置Insetscontroller变化监听
InsetsController.addOnControllableInsetsChangedListener(OnControllableInsetsChangedListener ...);
-
应用WindowInsets变化
getWindow().getDecorView().setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener ...);@Overridepublic WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {mImeVisible = insets.isVisible(ime());return v.onApplyWindowInsets(insets);}
开发者可以通过在自定义 View 中重写 onApplyWindowInsets()
方法或调用 setOnApplyWindowInsetsListener()
来监听 WindowInsets
的变化,通过对 View 添加 margin
或 padding
的方式处理解决冲突。
这两个方法是互斥的,当存在 OnApplyWindowInsetsListener
时不会执行 onApplyWindowInsets
:
不过开发者可以在也可 OnApplyWindowInsetsListener
手动调用 onApplyWindowInsets
使两个方法同时被执行。