Android的窗口系统是比较难啃的一块内容了,这是UI架构很重要的一部分,数据结构比较多,细节比较多,面试的时候,面试官问我一个问题,r.token与app.token有什么区别,在一个月以前我是不能回答这个问题的,作为一个应用开发工程师转行到系统做Framework,最大的感觉就是Android系统博大精深,很多人可以单独搞定一个app,但是系统就不行了,以前看framework代码侧重流程,疏忽了不少细节。因为整个系统很大,学习起来要有一套方式方法,现在主要采取各个模块(比如WMS,每个模块仍然要细分)逐个击破,先高度抽象,总结架构,然后梳理流程,把握主线,重点函数,重点分析,深究细节,一边理解,一遍记录理解,画出类图和流程图,难于理解的地方,开启调试,细细打磨。本篇文章主要介绍窗口相关数据结构和抽象概念理解,关于 窗口部分的博客计划如下。
- 窗口抽象概念理解和WMS相关数据结构
- WMS的布局系统,计算与管理窗口的位置、层次
- WMS的创建过程和启动过程
- 窗口的创建和删除
- 窗口大小的计算
- 窗口的切换过程
- 窗口的动画框架
- 屏幕旋转
本篇文章主要讨论窗口坐标系统、Z序确定,Choreographer、WindowToken、WindowState、WindowManagerService中的关键成员等知识。
一、窗口布局
窗口的布局一般有两种,一种是平铺式的布局,一种是层叠式的布局
平铺窗口布局
平铺式的布局设计简单,现在还有一些终端系统仍然是单窗口的平铺布局,手机上采用的是层叠式布局,层叠式布局是一个三维的空间,将手机的水平方向作为X轴,竖直方向作为Y轴,还有一根垂直与屏幕从里朝外方向的虚拟的Z轴,所有窗口 (WindowState) 按照顺序排列在Z轴上,如下图。
层叠式布局
1.1、窗口主序的确定
WindowState是WMS中事实的窗口,而不是Window,包含了一个窗口的所有的属性,其中一个属性为mLayer,表示窗口在Z轴的位置,mLayer值越小,窗口越靠后,mLayer值越大,窗口越靠前,最前面的一个窗口就作为焦点窗口,可以接收触摸事件。因为窗口的切换,切换后的Z序(窗口的显示次序称为 Z 序)就可能不同,所以mLayer的值不是固定不变的。mLayer是通过WindowState的另一个成员变量mBaseLayer的值计算得到,mBaseLayer的值是固定不变的,只和窗口类型有关。mBaseLayer(称为主序)是WindowState的构造方法中赋值。
mBaseLayer = mPolicy.windowTypeToLayerLw(
attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER
+ WindowManagerService.TYPE_LAYER_OFFSET;
windowTypeToLayerLw根据窗口的类型type,返回2开始的整数值
public int windowTypeToLayerLw(int type) {
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
return 2;
}
switch (type) {
case TYPE_WALLPAPER:
return 2;
// wallpaper is at the bottom, though the window manager may move it.
case TYPE_PHONE:
return 3;
.....
case TYPE_TOAST:
// toasts and the plugged-in battery thing
return 8;
.....
case TYPE_BOOT_PROGRESS:
return 30;
case TYPE_POINTER:
// the (mouse) pointer layer
return 31;
}
Log.e(TAG, "Unknown window type: " + type);
return 2;
可以看到系统中的窗口种类比较多,case个数都到31了。比如应用程序的窗口是2,电话类型的窗口是3,Toast窗口是8...,因为系统中同类型的窗口比较多,所以对返回的整数值乘以10000(WindowManagerService.TYPE_LAYER_MULTIPLIER),在加上1000(WindowManagerService.TYPE_LAYER_OFFSET),相当把Z轴划分成了31个值域,不同类型的窗口的Z轴位置都是处于两个不相交的值域之中,互相不打扰。OK,通过mBaseLayer,我们知道了窗口是如何排序的,但是,假设系统中现在有5个应用程序的窗口,按照上面的规则,计算出来的主序应该相同,如何排序? 其实窗口的Z轴位置除了跟mBaseLayer有关与之外,还跟窗口在堆栈中的位置有关,后面会详细介绍窗口位置的计算。
1.2、窗口子序的确定
除了mBaseLayer,还有SubLayer(称为子序),SubLayer值是用来描述一个窗口是否属于另外一个窗口的子窗口,或者说SubLayer值是用来确定子窗口和父窗口之间的相对位置的。
SubLayer.png
一个Activity中有三个子窗口WindowState1、WindowState2、WindowState3,WindowState1WindowState2在窗口A的前面,WindowState3在A的后面,这几个兄弟窗口为什么可以这样排序呢,这就是mSubLayer的作用,子序越大,则相对其他兄弟窗口越靠前,反之,越靠后,如果为负数,就处在父窗口的后面,如窗口A中的WindowState3,子序是根据窗口类型调用subWindowTypeToLayerLw确定的。
mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
public int subWindowTypeToLayerLw(int type) {
switch (type) {
case TYPE_APPLICATION_PANEL:
case TYPE_APPLICATION_ATTACHED_DIALOG:
return APPLICATION_PANEL_SUBLAYER;//返回值是1
case TYPE_APPLICATION_MEDIA:
return APPLICATION_MEDIA_SUBLAYER;//返回值是-2
case TYPE_APPLICATION_MEDIA_OVERLAY:
return APPLICATION_MEDIA_OVERLAY_SUBLAYER;//返回值是-1
case TYPE_APPLICATION_SUB_PANEL:
return APPLICATION_SUB_PANEL_SUBLAYER;//返回值是2
case TYPE_APPLICATION_ABOVE_SUB_PANEL:
return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;//返回值是3
}
Log.e(TAG, "Unknown sub-window type: " + type);
return 0;
}
1.3、窗口的组成
窗口的组成
这一部分很简单,Activity内部有PhoneWindow对象,PhoneWindow持有DecorView,DecorView继承Framelayout,具有标题部分和内容部分,我们写的各种View都在它的内容部分。
二、与窗口相关的数据结构
准确的说,这里先说一下与WMS相关的数据结构,因为与窗口相关的数据结构很多,动画的部分,绘图的部分都是相关的,先学习一小部分。
类的成员省去
2.1、Choreographer的作用
WMS使用Choreographer负责所有的窗口动画和屏幕旋转动画,墙纸动画的渲染,用法跟Handler有点类似,他们都是在后期某个时机传入Runable对象,但是他们的回调时机不一样,Handler的处理时机取决与消息队列的处理情况,各个Message的时间都可能不同,而Choreograpoher的回调时机是下一次的VSYNC(垂直刷新同步),如果在当前时机没有处理完成就会造成失帧,造成造成失帧的原因往往是因为我们布局写的太过复杂,导致16ms内没有完成。 Android性能优化第(四)篇---Android渲染机制有介绍。
2.2、WindowToken的作用
你可以把WindowToken理解成是一个显示令牌,无论是系统窗口还是应用窗口,添加新的窗口的时候必须使用这个令牌向WMS表明自己的身份,添加窗口的时候会创建WindowToken,销毁窗口的时候移除WindowToken(removeWindowToken方法)。
WMS使用WindowToken将同一个应用组件(Activity,InputMethod,Wallpaper,Dream)的窗口组织在一起,换句话说,每一个窗口都会对应一个WindowToken,并且这个窗口中的所有子窗口将会对应同一个WindowToken,就如下面这个图的关系,假设窗口A是播放器中的一个窗口,除了主窗口外,还有三个子窗口,这些窗口的WindowToken都是一样的。
WindowToken和WindowState是1对多的关系.png
WindowToken的定义如下
class WindowToken {
// The window manager!
final WindowManagerService service;
// The actual token.
final IBinder token;
// The type of window this token is for, as per WindowManager.LayoutParams.
final int windowType;
// Set if this token was explicitly added by a client, so should
// not be removed when all windows are removed.
final boolean explicit;
// For printing.
String stringName;
// If this is an AppWindowToken, this is non-null.
AppWindowToken appWindowToken;
// All of the windows associated with this token.
final WindowList windows = new WindowList();
// Is key dispatching paused for this token?
boolean paused = false;
// Should this token's windows be hidden?
boolean hidden;
// Temporary for finding which tokens no longer have visible windows.
boolean hasVisible;
// Set to true when this token is in a pending transaction where it
// will be shown.
boolean waitingToShow;
// Set to true when this token is in a pending transaction where its
// windows will be put to the bottom of the list.
boolean sendingToBottom;
WindowToken(WindowManagerService _service, IBinder _token, int type, boolean _explicit) {
service = _service;
token = _token;
windowType = type;
explicit = _explicit;
}
void removeAllWindows() {
for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
WindowState win = windows.get(winNdx);
if (DEBUG_WINDOW_MOVEMENT) Slog.w(TAG_WM, "removeAllWindows: removing win=" + win);
win.mService.removeWindowLocked(win);
}
windows.clear();
}
...
}
简单介绍一下各个成员的意思,实际的Token是WindowToken的Ibinder类型的token成员,windowType表示窗口的类型,explicit表示token创建是通过显式创建的还是通过隐式创建的,为true就是显式创建的。appWindowToken专门为Activity用的,windows这个成员,里面存放的是WindowState,表示WindowToken相同的所有窗口都在一起。hidden表示Activity组件是否是处于不可见状态。
如何向WMS声明一个令牌呢,其实任意一个Binder对象都可以作为令牌。声明令牌是通过WMS的addWindowToken函数,从而获取WMS帮忙添加窗口的许可。
WindowManagerService.java
final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();
@Override
public void addWindowToken(IBinder token, int type) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addWindowToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
WindowToken wtoken = mTokenMap.get(token);
if (wtoken != null) {
Slog.w(TAG_WM, "Attempted to add existing input method token: " + token);
return;
}
wtoken = new WindowToken(this, token, type, true);
mTokenMap.put(token, wtoken);
if (type == TYPE_WALLPAPER) {
mWallpaperControllerLocked.addWallpaperToken(wtoken);
}
}
}
声明WindowToken需要android.Manifest.permission.MANAGE_APP_TOKENS权限,然后进入同步代码块中,先尝试从mTokenMap中取WindowToken,如果没有,就创建WindowToken,并把它放到mTokenMap这个表中,对于墙纸类型的窗口,需要单独处理,调用mWallpaperControllerLocked.addWallpaperToken(wtoken),将WindowToken加到mWallpaperTokens表中。外部要添加窗口,首先调用WMS的addWindowToken方法申请令牌(WindowToken)。现在看下Activity中添加一个窗口(Dialog)令牌是怎么样的。 Activity的创建是由SystemServer进程的ActivityStackSupervisor的realStartActivityLocked方法调用scheduleLaunchActivity方法发起的。其中的参数r.appToken就是添加窗口所需要的令牌。
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {
...
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
....
}
appToken是在ActivityRecord的构造函数中初始化的。
appToken = new Token(this, service);
Token的定义
static class Token extends IApplicationToken.Stub {
private final WeakReference<ActivityRecord> weakActivity;
private final ActivityManagerService mService;
Token(ActivityRecord activity, ActivityManagerService service) {
weakActivity = new WeakReference<>(activity);
mService = service;
}
//窗口绘制完成的时候调用
@Override
public void windowsDrawn() {
synchronized (mService) {
ActivityRecord r = tokenToActivityRecordLocked(this);
if (r != null) {
r.windowsDrawnLocked();
}
}
}
//窗口可见的时候调用
@Override
public void windowsVisible() {
synchronized (mService) {
ActivityRecord r = tokenToActivityRecordLocked(this);
if (r != null) {
r.windowsVisibleLocked();
}
}
}
//窗口不可见的时候调用
@Override
public void windowsGone() {
synchronized (mService) {
ActivityRecord r = tokenToActivityRecordLocked(this);
if (r != null) {
if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "windowsGone(): " + r);
r.nowVisible = false;
return;
}
}
}
//取出一个Activity
private static final ActivityRecord tokenToActivityRecordLocked(Token token) {
if (token == null) {
return null;
}
ActivityRecord r = token.weakActivity.get();
if (r == null || r.task == null || r.task.stack == null) {
return null;
}
return r;
}
...
}
当 app.thread.scheduleLaunchActivity执行之后,来到ActivityThread::ApplicationThread
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
...
sendMessage(H.LAUNCH_ACTIVITY, r);
}
将token赋值给ActivityClientRecord,接下来,应用就会拿着这个token作为令牌去添加窗口(ActivityThread的handleResumeActivity中),细心的会看到,token和ActivityRecord在数量上是一对一的关系。之后这个Token是怎么传递给WMS的呢?
Token的传递上.png
在Activity被创建出来之后,会调用Activity的attach方法,attach方法将token传给了Activity,Activity调用Window的setWindowManager方法,将token传给了Window,保存在Window的IBinder类型的成员变量mAppToken中。
![Uploading image_126547.png . . .]
Token的传递下.png
之后ActivityThread发起addView,调用到了Window的adjustLayoutParamsForSubWindow方法,这个方法就将Window中mAppToken赋值给传进来的参数wp,之后WindowManagerGlobal调用ViewRootImpl的setView将参数wparams中的token传给WMS。
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
WindowToken token = mTokenMap.get(attrs.token);
AppWindowToken atoken = null;
if (token == null) {
...
token = new WindowToken(this, attrs.token, -1, false);
addToken = true;
}
}
总结:WindowToken对于客户端来说只是一个令牌,一个可以添加窗口的许可,WindowToken对于WMS来说,是一个WindowToken的实例对象,存储在mTokenMap中。WindowToken将同一个应用组件(Activity,InputMethod,Wallpaper,Dream)的窗口组织在一起。看完WindowToken的添加,你们可以分析一下AppWindowToken是怎么添加的。
2.3、WindowState的作用
WindowState用于窗口管理,这个是WMS中事实的窗口,包含了一个窗口的所有的属性,WindowState对象都存放在mWindowMap里面,,mWindowMap是所有窗口的一个全集,如果梳理WindowState的一些增加、移动和删除等操作,会更加理解这个类。前面也接触了一点,WindowState的构造函数会根据用户传进来的Window Type计算出窗口的mBaseLayer,mSubLayer和mLastLayer值,分别对应于主窗口,主窗口上弹出的子窗口(如输入法),以及动画时分别对应的ZOrder值。WindowState 构造函数中还会生成IWindowId.Stub 对象和DeathRecipient对象来分别监听Focus和窗口死亡的信息,生成一个WindowStateAnimator 负责整个Window的动画,并在内部将windowToken, appWindowToken等关联起来。WindowState的属性很多,看看它的构造函数。
WindowState.java
final class WindowState implements WindowManagerPolicy.WindowState {
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
mService = service;
mSession = s;
mClient = c;
mAppOp = appOp;
mToken = token;
mOwnerUid = s.mUid;
mWindowId = new IWindowId.Stub() {
....
};
mAttrs.copyFrom(a);
mViewVisibility = viewVisibility;
mDisplayContent = displayContent;
mPolicy = mService.mPolicy;
mContext = mService.mContext;
DeathRecipient deathRecipient = new DeathRecipient();
mSeq = seq;
mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Window " + this + " client=" + c.asBinder()
+ " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
try {
c.asBinder().linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
mDeathRecipient = null;
mAttachedWindow = null;
mLayoutAttached = false;
mIsImWindow = false;
mIsWallpaper = false;
mIsFloatingLayer = false;
mBaseLayer = 0;
mSubLayer = 0;
mInputWindowHandle = null;
mWinAnimator = null;
return;
}
mDeathRecipient = deathRecipient;
WindowState appWin = this;
while (appWin.isChildWindow()) {
appWin = appWin.mAttachedWindow;
}
WindowToken appToken = appWin.mToken;
while (appToken.appWindowToken == null) {
WindowToken parent = mService.mTokenMap.get(appToken.token);
if (parent == null || appToken == parent) {
break;
}
appToken = parent;
}
mRootToken = appToken;
....
mWinAnimator = new WindowStateAnimator(this);
mWinAnimator.mAlpha = a.alpha;
mRequestedWidth = 0;
mRequestedHeight = 0;
mLastRequestedWidth = 0;
mLastRequestedHeight = 0;
mXOffset = 0;
mYOffset = 0;
mLayer = 0;
mInputWindowHandle = new InputWindowHandle(
mAppToken != null ? mAppToken.mInputApplicationHandle : null, this,
displayContent.getDisplayId());
}
}
解释一下各个变量的意思,老罗总结的详细,下面解释摘自老罗的博客。
-
mSession:指向一个类型为Session的Binder本地对象,使用参数s来初始化,表示当前所创建的WindowState对象是属于哪一个应用程序进程的,Session是进程唯一的。
-
mClient:指向一个实现了IWindow接口的Binder代理对象,它引用了运行在应用程序进程这一侧的一个类型为W的Binder本地对象,使用参数c来初始化,通过它可以与运行在应用程序进程这一侧的Activity组件进行通信。
-
mToken:指向一个WindowToken对象,使用参数token来初始化,通过它就可以知道唯一地标识一个窗口。
-
mAttrs:指向一个WindowManager.LayoutParams对象,使用参数a来初始化,通过它就可以知道当前当前所创建的WindowState对象所描述的窗口的布局参数。
-
mViewVisibility:这是一个整型变量,使用参数viewVisibility来初始化,表示当前所创建的WindowState对象所描述的窗口视图的可见性。
-
mAlpha:这是一个浮点数,使用参数a所描述的一WindowManager.LayoutParams对象的成员变量alpha来初始化,表示当前所创建的WindowState对象所描述的窗口的Alpha通道。
-
mLayer,mBaseLayer和mSubLayer上面已经说过
-
mAttachedWindow:指向一个WindowState对象,用来描述一个子窗口的父窗口。
-
mLayoutAttached:这是一个布尔变量,用来描述一个子窗口的视图是否是嵌入在父窗口的视图里面的。对于非子窗口来说,这个值固定为false;对于子窗口来说,这个值只有子窗口的类型是非对话框时,它的值才会等于true,否则都等于false。
-
mIsImWindow:这是一个布尔变量,表示当前所创建的WindowState对象所描述的窗口是否是一个输入法窗口或者一个输入法对话框。
-
mIsWallpaper :这是一个布尔变量,表示当前所创建的WindowState对象所描述的窗口是否是一个壁纸窗口。
-
mSurface:指向一个mSurface对象,用来描述窗口的绘图表面。
-
mRequestedWidth:这是一个整型变量,用来描述应用程序进程所请求的窗口宽度。
-
mRequestedHeight:这是一个整型变量,用来描述应用程序进程所请求的窗口高度。
-
mLastRequestedWidth:这是一个整型变量,用来描述应用程序进程上一次所请求的窗口宽度。
-
mLastRequestedHeight:这是一个整型变量,用来描述应用程序进程上一次所请求的窗口高度。
-
mXOffset:这是一个整型变量,用来描述壁纸窗口相对屏幕在X轴上的偏移量,对其它类型的窗口为说,这个值等于0。
-
mYOffset:这是一个整型变量,用来描述壁纸窗口相对屏幕在Y轴上的偏移量,对其它类型的窗口为说,这个值等于0。
-
mAnimLayer:这是一个整型变量,用来描述窗口的Z轴位置值,它的值可能等于mLayer的值
-
mLastLayer:这是一个整型变量,用描述窗口上一次所使用的mAnimLayer的值
2.4、WMS中的集合解释
-
保存所有的Session,上面说过 final ArraySet<Session> mSessions = new ArraySet<>();
-
保存WindowState,给出一个键,能够快速找出对应的窗口 final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
-
保存WindowToken final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();
-
保存已经完成启动的应用。 final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
-
保存尺寸正在改变的窗口,当改变完成后,需要通知应用。 final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
-
保存动画结束的窗口 final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
-
保存需要释放Surface的窗口。 final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
-
保存需要强行关闭的窗口,以释放内存。 final ArrayList<WindowState> mForceRemoves = new ArrayList<>();
-
保存失去焦点的窗口,等待获得焦点的窗口进行显示 ArrayList<WindowState> mLosingFocus = new ArrayList<>();
-
保存等待绘制的窗口 ArrayList<WindowState> mWaitingForDrawn = new ArrayList<>();
好了,与WMS相关的难以理解的数据结构都写在这个,后面遇到重要的数据结构,比如PhoneWindowManager是窗口策略类,继承了WindowManagerPolicy,主要进行窗口大小计算,DisplayContent等,再在到这补充,下一篇写WMS的布局系统,计算与管理窗口的位置、层次。