一、PhoneWindow和WindowManager的关联
1、WindowManager是一个接口类,继承自接口ViewManager,ViewManager中定义了3个方法,分别用来添加、更新和删除View,如下所示:
frameworks/base/core/java/android/view/ViewManager.java
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
2、WindowManager也继承了这些方法,这些方法传入的参数都是View类型,说明Window也是以View的形势存在的。WindowManager在继承ViewManager的同时,还加入了很多功能,包括Window的类型和层级相关的常量、内部类以及一些方法。
frameworks/base/core/java/android/view/ViewManager.java
public Display getDefaultDisplay();//获得WindowManager所管理的屏幕
public void removeViewImmediate(View view);//这个方法返回前会执行View.onDetachedFromWindow()来完成View相关的销毁工作
3、Window是一个抽象类,具体实现类为PhoneWindow,PhoneWindow是在Activity的attach方法中被创建的:
frameworks/base/core/java/android/app/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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);//创建PhoneWindow
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);//设置回调
...代码省略...
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//为PhoneWindow设置WindowManager实例。
...代码省略...
}
4、PhoneWindow的setWindowManager方法是在父类Window中实现的:
/frameworks/base/core/java/android/view/Window.java
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);//如果wm为null,那么就获取已经存在的WindowManager对象实例。
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);//创建WindowManagerImpl的实例
}
5、WindowManagerImpl的createLocalWindowManager方法如下所示:
frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();//获取WindowManagerGlobal单例对象
private final Context mContext;
private final Window mParentWindow;//这个会指向PhoneWindow实例
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;//将PhoneWindow实例赋给mParentWindow
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
//创建实例对象并传入当前PhoneWindow的引用,使得WindowManagerImpl可以操作PhoneWindow
return new WindowManagerImpl(mContext, parentWindow);
}
...代码省略...
}
6、WindowManager的关联类图如下所示:
WindowManager的关联类图结合图片和上面的代码分析我们可以知道,PhoneWindow继承自Window,Window通过setWindowManager方法与WindowManager发生关联。WindowManager继承自接口ViewManager,WindowManagerImpl是WindowManager接口的实现类,但是具体的功能最终都是委托给WindowManagerGlobal来实现的。
二、Window的属性
了解Window的属性能够更好的理解WMS的内部原理,Window的属性有很多种,与应用开发最密切的有3种,分别是Type(Window的类型),Flag(Window的标记)和SoftInputMode(软键盘相关模式)。
1、Window的类型
Window的类型有很多种,比如应用程序窗口、系统错误窗口、输入法窗口、PopupWindow、Toast、Dialog等。总的来说Window分为三大类型,分别是Application Window(应用程序窗口)、Sub Window(子窗口)、System Window(系统窗口),每个大类型中又分很多小类型,它们都定义在WindowManager的静态内部类LayoutParams中,下面简单介绍一下Window的三大类型。
1)应用程序窗口
Activity就是一个典型的应用程序窗口,应用程序窗口包含的类型如下所示:
frameworks/base/core/java/android/view/WindowManager.java
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
public static final int FIRST_APPLICATION_WINDOW = 1;
public static final int TYPE_BASE_APPLICATION = 1;//应用程序窗口的基础值,其他窗口的值都要大于这个值
public static final int TYPE_APPLICATION = 2;//普通的应用程序窗口类型
public static final int TYPE_APPLICATION_STARTING = 3;//应用程序启动窗口类型,用户系统在应用程序窗口启动前显示的窗口
public static final int TYPE_DRAWN_APPLICATION = 4;
public static final int LAST_APPLICATION_WINDOW = 99;//应用程序窗口的结束值
}
}
应用程序窗口的Type值的范围为1~99。
2)子窗口
子窗口不能单独存在,需要依附于其他窗口才行,PopupWindow就属于子窗口。子窗口的类型定义如下所示:
frameworks/base/core/java/android/view/WindowManager.java
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
public static final int FIRST_SUB_WINDOW = 1000;//子窗口类型初始值
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
public static final int LAST_SUB_WINDOW = 1999;//子窗口类型结束值
}
}
子窗口的Type值的范围为1000~1999。
3)系统窗口
Toast、输入法窗口、系统音量条窗口、系统错误窗口都属于系统窗口。系统窗口的类型定义如下所示:
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
public static final int FIRST_SYSTEM_WINDOW = 2000; //系统窗口类型初始值
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW; //系统状态栏窗口
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1; //搜索条窗口
@Deprecated //use TYPE_APPLICATION_OVERLAY instead
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2; //通话窗口
@Deprecated //use TYPE_APPLICATION_OVERLAY instead
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3; //系统Alert窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4; //锁屏窗口
@Deprecated//use TYPE_APPLICATION_OVERLAY instead
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5; //Toast窗口
@Deprecated //use TYPE_APPLICATION_OVERLAY instead
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
@Deprecated //use TYPE_APPLICATION_OVERLAY instead
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;//系统弹窗
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;//锁屏弹窗
@Deprecated//use TYPE_APPLICATION_OVERLAY instead
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16;
public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;//系统导航栏
public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;//音量
public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
public static final int TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22;
public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;
public static final int TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW+34;
public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;
public static final int TYPE_PRESENTATION = FIRST_SYSTEM_WINDOW + 37;
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;//很多废弃的系统弹窗类型都可以使用这个进行替换
public static final int LAST_SYSTEM_WINDOW = 2999; //系统窗口类型的结束值
}
}
系统窗口的类型值有接近40个,系统窗口的Type值范围为2000~2999。
4)窗口的显示次序
当一个进程向WMS申请一个窗口的时候,WMS会为窗口确定显示次序。为了方便窗口显示次序的管理,手机屏幕可以虚拟地用X、Y、Z轴来表示,其中Z轴垂直于屏幕,从屏幕内指向屏幕外,这样窗口的显示次序其实就是窗口在Z轴上的次序,这个次序称为Z-Oder。Type值是Z-Order排序的依据,我们知道应用程序窗口的Type值范围为1-99,子窗口为1000-1999,系统窗口为2000-2999,在一般情况下,Type值越大则Z~Order排序越靠前,窗口越靠近用户。
不过窗口显示次序的逻辑并不仅仅依靠窗口的Type,情况是比较多的;最常见的情况,当多个窗口的Type都是Type_APPLICATION,这时WMS还需要结合各种情况来计算最终的Z-Oder。
2、Window的标记
Window的标记也就是Flag,用于控制Window的显示,同样被定义在WindowManager的静态内部类LayoutParams 中,一共有20多个,这里介绍几个比较常用的。
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;//只要窗口可见,就允许在开启状态的屏幕上锁屏
public static final int FLAG_DIM_BEHIND = 0x00000002;
@Deprecated
public static final int FLAG_BLUR_BEHIND = 0x00000004;
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;//窗口不能获得输入焦点,设置该标记的同时,FLAG_NOT_TOUCH_MODAL也会被设置
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;//窗口不接受任何触摸事件
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;//将该窗口区域外的触摸事件传递给其他的Window,而自己只会处理窗口区域内的触摸事件
@Deprecated
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;//只要窗口可见,屏幕就会一直亮着
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;//允许窗口超过屏幕之外
public static final int FLAG_FULLSCREEN = 0x00000400;//隐藏所有的屏幕装饰窗口,比如在游戏、播放器中的全屏显示
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
@Deprecated
public static final int FLAG_DITHER = 0x00001000;
public static final int FLAG_SECURE = 0x00002000;
public static final int FLAG_SCALED = 0x00004000;
public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;//当用户的脸贴近屏幕时(比如打电话),不会去响应此事件
public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;//窗口可以在锁屏的窗口之上显示
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;//窗口显示时将屏幕点亮
@Deprecated
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
public static final int FLAG_SPLIT_TOUCH = 0x00800000;
public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;
public static final int FLAG_SLIPPERY = 0x20000000;
public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;
public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
}
}
设置Window的Flag有3中方法。
1)通过Window的setFlags方法。
Window mWindow = getWindow();
mWindow.setFlag(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
2)通过Window的addFlags方法。
Window mWindow = getWindow();
mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
其实addFlags方法内部也是调用setFlags方法来实现的,因此和第1种方法区别不大。
3)通过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);
3、软键盘相关模式
窗口和窗口的叠加是十分常见的场景,但如果其中的窗口是软键盘窗口,可能就会出现一些问题,比如典型的用户登录界面,默认情况弹出的软键盘窗口可能会盖住输入框下方的按钮,这样用户体验会非常糟糕。为了使得软键盘窗口能够按照期望来显示,WindowManager的静态内部类LayoutParams种定义了软键盘的相关模式。
public static final int SOFT_INPUT_MASK_STATE = 0x0f;
public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;//没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;//不会改变软键盘的状态
public static final int SOFT_INPUT_STATE_HIDDEN = 2;//当用户进入该窗口时,软键盘默认隐藏
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;//当窗口获取焦点时,软键盘总是被隐藏
public static final int SOFT_INPUT_STATE_VISIBLE = 4;
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;//当软键盘弹出时,窗口会调整大小
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;//当软键盘弹出时,窗口会调整大小
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
- SoftInputMode与AndroidManifest种Activity的属性android:windowSoftInputMode是对应的。
- 还可以在Java代码中为Window设置SoftInputMode,如下所示:
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
三、Window的操作
1、窗口的添加过程
WindowManager对Window进行管理,说到管理那就离不开对Window的添加、更新和删除操作,在这里我们把它们称为Window的操作。对于Window的操作,最终都是交由WMS来进行处理的。窗口的操作分为两大部分,一部分是WindowManager处理,另一部分是WMS处理。上面我们介绍过Window的三个大类,应用程序窗口、子窗口、系统窗口。对于不同类型的窗口WindowManager的添加过程会有所不同,但是对于WMS处理的部分,添加过程基本上是一样的。
image.png2、系统窗口StatusBar的添加过程
1)上面我们介绍过Window的三大类型,也说过不同类型的Window的添加过程也不尽相同,接下来我们以系统窗口StatusBar为例,讲述一下系统窗口的添加过程。
StatusBar是SystemUI的重要组成部分,具体就是指系统状态栏。
StatusBar的addStatusBarWindow方法源码如下所示:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
private void addStatusBarWindow() {
makeStatusBarView();//构建视图
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
mRemoteInputController = new RemoteInputController(mHeadsUpManager);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
2)addStatusBarWindow方法首先调用makeStatusBarView构建状态栏视图,然后再调用mStatusBarWindowManager的add方法,将状态栏视图和状态栏高度传进去,StatusBarWindowManager的add方法如下所示:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
public void add(View 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.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
mLp.gravity = Gravity.TOP;
mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("StatusBar");
mLp.packageName = mContext.getPackageName();
mStatusBarView = statusBarView;
mBarHeight = barHeight;
mWindowManager.addView(mStatusBarView, mLp);//将状态栏视图添加到WindowManager中
mLpChanged = new WindowManager.LayoutParams();
mLpChanged.copyFrom(mLp);
}
3)StatusBarWindowManager的add方法首先创建LayoutParams,
并配置了包括Width、Height、Type、Flag、Gravity、SoftInputMode等StatusBar视图的属性。特别是设置了窗口类型为TYPE_STATUS_BAR,明确了StatusBar视图的窗口类型是状态栏。然后调用WindowManager的addView方法将状态栏视图添加到WindowManager中,关于mWindowManager的addView方法,具体是在WindowManagerImpl中实现的。
frameworks/base/core/java/android/view/WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
4)WindowManagerImpl的addView又进一步调用WindowManagerGlobal的addView方法。
frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {
private final ArrayList<View> mViews = new ArrayList<View>();//当前存在的View列表
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();//当前存在的ViewRootImpl列表
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();//当前存在的布局参数列表
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...代码省略...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);//如果当前窗口要作为子窗口显示,需要父窗口根据WindowManager.LayoutParams类型的wparams参数对子窗口进行相应调整
} else {
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...代码省略...
root = new ViewRootImpl(view.getContext(), display);//创建ViewRootImpl实例对象
view.setLayoutParams(wparams);
mViews.add(view);//将要添加的view保存到View列表中
mRoots.add(root);//将新创建的root保存到ViewRootImpl列表中
mParams.add(wparams);//将窗口的参数保存到布局参数列表中
try {
root.setView(view, wparams, panelParentView);//将窗口和窗口参数设置到ViewRootImpl中
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
}
5)WindowManagerGlobal中维护了和Window操作相关的3个关键列表,在窗口的添加、更新和删除过程中都会涉及这3个列表,他们分别是View列表,ViewRootImpl列表,布局参数列表。addView方法首先会对传入的参数view、params、display进行检查,另外如果parentWindow不为空,还需要父窗口根据WindowManager.LayoutParams类型的wparams参数对view进行相应调整。
一切准备就绪会创建ViewRootImpl对象实例,在将view、root、mparams保存到对应的数据列表中后,便会调用ViewRootImpl的setView方法将窗口和窗口参数设置到ViewRootImpl中。ViewRootImpl肩负了很多职责,主要有以下几点:
- View树的根并管理View树
- 触发View的测量、布局和绘制
- 输入事件的中转站
- 管理Surface
- 负责与WMS进行进程间通信
了解了ViewRootImpl的职责以后,继续来着看ViewRootImpl的setView的方法:
frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...代码省略...
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
...代码省略...
}
5)setView方法中有很多逻辑,这里只截取了最关键的一小部分,主要就是调用了mWindowSession的addToDisplay方法,mWindowSession是IWindowSession类型的,它是一个Binder对象,用于进行进程间通信,IWindowSession是Client端的代理,它的Server端的实现为Session,此前的代码逻辑都是运行在本地进程的,而Session的addToDisplay方法则运行在WMS所在的进程(SystemServer)中。
ViewRootImpl与WMS通信
6)从上图可以看出,本地进程的ViewRootImpl要想和WMS进行通信需要经过Session,那么Session为何包含在WMS中呢?继续看Session的addToDisplay方法。
frameworks/base/services/core/java/com/android/server/wm/Session.java
@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);
}
7)addToDisplay方法会进一步调用WMS的addWindow方法,并将自身作为参数传了进去,每个应用程序进程都会对应一个Session,WMS会用ArrayList来保存这些Session,这就是为什么WMS包含Session的原因。这样剩下的工作就交给WMS来处理,在WMS中会为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS会将它所管理的Surface交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。
系统窗口StatusBar的添加过程的时序图3、Activity的添加过程
1)无论是StatusBar还是Activity,又或者其他类型从窗口,它的添加过程在WMS处理部分中基本是类似的,只不过会在权限和窗口显示次序等方面会有些不同。但是在WindowManager处理部分会有所不同,这里以最典型的应用程序窗口Activity为例,Activity在启动过程中,如果Activity所在的进程不存在则会创建新的进程,创建新的进程之后就会运行代表主线程的实例ActivityThread,ActivityThread管理着当前应用程序进程的线程,这在Activity的启动过程中运用的很明显,当界面要与用户进行交互的时候,会调用ActivityThread的handleResumeActivity方法,如下所示:
frameworks/base/core/java/android/app/ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...代码省略...
r = performResumeActivity(token, clearHide, reason);//方法内部最终会调用Activity的onResume方法
...代码省略...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();//获取WindowManager实例
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;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);//调用WindowManager的addView,该方法会进一步调用WindowManagerImpl方法的addView方法
} else {
a.onWindowAttributesChanged(l);
}
}
...代码省略...
}
ActivityThread的handleResumeActivity方法主要做了以下几点:
- 调用performResumeActivity方法,该方法最终会调用Activity的onResume方法
- 获取WindowManager的实例对象
- 将Activity的视图内容mDecor添加到WindowManager中
- WindowManager的addView方法进一步调用WindowManagerImpl方法的addView方法,进一步调用WindowManagerGlobal的setView方法,进一步调用ViewRootImpl的setView方法,进一步调用Session的addToDisplay方法,进一步调用WMS的addWindow方法。Activity窗口的添加过程和StatusBar窗口的添加过程非常相似。
4、Window的更新过程
1)Window的更新过程和Window的添加过程是相似的,需要调用ViewManager的updateViewLayout方法,updateViewLayout方法在WindowManagerImpl中实现。
frameworks/base/core/java/android/view/WindowManagerImpl.java
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
2)WindowManagerImpl进一步调用WindowManagerGlobal的updateViewLayout方法。
frameworks/base/core/java/android/view/WindowManagerGlobal.java
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);//将更新的参数设置到View中
synchronized (mLock) {
int index = findViewLocked(view, true);//从已经存在的View列表中获取窗口的索引
ViewRootImpl root = mRoots.get(index);//根据索引得到窗口的ViewRootImpl
mParams.remove(index);
mParams.add(index, wparams);//更新存在的窗口的布局参数
root.setLayoutParams(wparams, false);//调用ViewRootImpl的setLayoutParams方法将更新的参数设置到ViewRootImpl中
}
}
frameworks/base/core/java/android/view/ViewRootImpl.java
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
...代码省略...
scheduleTraversals();
}
}
3)ViewRootImpl的setLayoutParams的最后会调用scheduleTraversals方法。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//编舞者
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
scheduleTraversals方法中最关键的就是名为“编舞者”的Choreographer,该对象用户接受显示系统的VSync信号(垂直同步信号),在下一帧渲染的时候控制执行一些操作。Choreographer的postCallback方法用户发起添加回调,这个添加的回调mTraversalRunnable将在下一帧被渲染的时候执行。
4)继续来看下ViewRootImpl类中和mTraversalRunnable相关的代码。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
5)TraversalRunnable的run方法进一步调用 ViewRootImpl的doTraversal方法。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
6)doTraversal方法中又调用了performTraversals方法,performTraversals方法使得ViewTree开始View的工作流程,如下所示。
private void performTraversals() {
...代码省略...
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...代码省略...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...代码省略...
performLayout(lp, mWidth, mHeight);
...代码省略...
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
以上代码主要做了以下几个关键点:
- relayoutWindow方法内部调用IWindowSession的relayout方法来更新Window视图,和上面StatusBar窗口、Activity窗口原理一样,最终也是调用WMS的relayoutWindow方法。
- performMeasure方法调用View的messure方法进行View的测量;performLayout方法调用View的layout方法进行View的布局;performDraw方法调用View的draw方法进行View的绘制;这样就完成了View的工作流程。
performTraversals方法既更新了Window的视图,又执行了Window中View的工作流程,这样就完成了Window的更新。
四、小结
本篇文章我们学习了WindowManager的关联类、Window的属性和Window的操作,了解了Window、WindowManager和WMS之间的关系,也知道了Window的操作分为两大部分并且最终都是由WMS处理的。
网友评论