Window
代表窗口的概念,在Android里面我们看到的页面几乎都跟Window
有关。为了对它有些概念,我们就从添加一个Window
开始吧:
private void addWindow() {
//1:先获取WindowManager,这是一个Interface,它的实现是WindowManagerImpl
//WindowManager继承了ViewManager,ViewManager只有三个方法:addView、updateViewLayout、removeView
//感觉好熟悉啊!
WindowManager windowManager = getWindowManager();
FrameLayout frameLayout = new FrameLayout(this);
frameLayout.setBackgroundColor(Color.parseColor("#ff0000"));
//添加View的时候需要设置LayoutParams,具体的我们之后再讲
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_APPLICATION);
params.height = 100;
params.width = 100;
params.format = PixelFormat.RGBA_8888;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.gravity = Gravity.CENTER;
//调用WindowManager的添加View的方法
windowManager.addView(frameLayout, params);
}
接下来我们就可以直接去看WindowManagerImpl
的addView
方法的实现了:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowManagerImpl
其实啥也没干,直接把工作交给了mGlobal
,这个mGlobal
是WindowManagerGlobal
的实例,WindowManagerGlobal
对外提供一个单例。在WindowManagerGlobal
里面,addView的实现主要分为以下几步:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//1、检查一些参数是不是合法
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
//2、布局参数也需要一些调整,最重要的的是token(这是一个省份证,主要用于在跨
// 进程的时候表明window的身份,这是一个很重要的参数,后面会讲)的赋值
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
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) {
........
//3、同一个View不能添加到两个Window里面,
//如果当前View已经添加到Window里面,假如正在移除,里面调用移除操作
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
//WindowManager.LayoutParams的成员变量type表示的是Window的类别
//Android里面一共有三种Window,分别是应用Window,子window和系统Window
//每个Window都有对应的z-ordered,层级大的会覆盖在层级小的上
//应用Window的层级范围是1-99,一般对应的是一个Activity
//子Window的层级范围是1000-1999,它不能单独存在,需要嵌套在其他Window(不能是子Window)里面
//系统window的层级范围是2000-2999,系统Window需要声明权限
//4、如果是子Window,那么就需要找到他的父View了
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
//5、创建ViewRootImpl对象,这个类是View的层次结构的最高层,负责把View和WindowMAnager联系起来
//WindowManagerGlobal的内部的大多数实现都是由ViewRootImpl来完成的
root = new ViewRootImpl(view.getContext(), display);
//给View设置布局参数
view.setLayoutParams(wparams);
//mViews是WindowManagerGlobal的成员变量,保存了所有添加到Window的View
//由于WindowManagerGlobal是单例形式,这个所有代表的是整个进程
mViews.add(view);
//mRoots保存了所有的对应ViewRoot对象
mRoots.add(root);
//mParams保存了所有的对应的布局参数
mParams.add(wparams);
//WindowManagerGlobal还有一个比较重要的成员变量mDyingViews,保存了所有正在被移除的View
// do this last because it fires off messages to start doing things
try {
//6、最后调用ViewRoot的setView方法
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
经过上面的123456步,就把实现交给了ViewRootImpl
来实现了,我移除了一些代码后,它的执行如下:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
........
//1、在把Window加到WindowManager前先执行一次布局,确保可以接受到系统事件前relayout
//这是一个很重要的方法,下面会详细解析
requestLayout();
.....
try {
//mWindowSession的实现类是Session,这个类负责连接ViewRootImpl和WindowManagerService,并且
//在WindowManagerService里每个进程只有一个Session对象,这是一个跨进程请求
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
......
}
}
}
这里需要简单先看1:requestLayout方法的执行:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//执行线程检测,如果我们在子线程操作UI,这里就会抛出异常
checkThread();
mLayoutRequested = true;
//继续执行
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//卡片阻断,位于这个卡片之后的同步消息将不会执行,知道这个卡片被移除,异步消息正常执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//这个类其实有点像Handler,这里也可以把它当做Handler来理解,mTraversalRunnable里面只做了一件事doTraversal();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除卡片,这里主要是为了确保doTraversal先执行,防止被前面的消息堵塞
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//继续执行
performTraversals();
}
}
performTraversals是一个巨长巨长的方法,但是我们可以从哪里找到几个有意思的执行:
private void performTraversals() {
//重新布局,这里是跨进程,最终是交给WindowManagerService,给以调整Window的状态
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
//执行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//执行layout方法
performLayout(lp, mWidth, mHeight);
//执行draw
performDraw();
}
到这里关于View的展示就基本结束了,接下来我们就要继续去关注对Window的管理了。 mWindowSession的addToDisplay方法,里面执行很简单,调用WindowManagerService
的addWindow方法,接下来就需要去看看这个方法的具体实现了:
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
int[] appOp = new int[1];
//权限检查,对于系统级别的window需要我们手动添加权限,app级别和子window一般都自带权限
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
......
//window类别,有application window、有sub window、有system window
final int type = attrs.type;
//使用锁机制,由此看来,如果多进程同时操作addWindow会按照到达的先后顺序去执行
//mWindowMap持有了所有添加的window
synchronized(mWindowMap) {
//获取手机屏幕(展示window的屏幕),如果没有就返回失败
final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
if (displayContent == null) {
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
//检查当前应用(确切来说是进程)是否可以在屏幕上显示window
if (!displayContent.hasAccess(session.mUid)
&& !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
//判断该window是否已被加过,一个Window不能重复添加
if (mWindowMap.containsKey(client.asBinder())) {
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
//sub window需要一个父window parentWindow
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
//查询当前的父window是否已经添加,从mWindowMap里面读取
parentWindow = windowForClientLocked(null, attrs.token, false);
if (parentWindow == null) {
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
//从这里看就能知道, sub window不能嵌套
if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
&& parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
}
AppWindowToken atoken = null;
final boolean hasParent = parentWindow != null;
// Use existing parent window token for child windows since they go in the same token
// as there parent window so we can apply the same policy on them.
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
// If this is a child window, we want to apply the same type checking rules as the
// parent window type.
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
boolean addToastWindowRequiresToken = false;
if (token == null) {
//应用进程需要token来表明window的身份,从这里可以看出,dialog不能传Application 的context
if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
Slog.w(TAG_WM, "Attempted to add application window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
//输入键盘window
if (rootType == TYPE_INPUT_METHOD) {
Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_VOICE_INTERACTION) {
Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_WALLPAPER) {
Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_DREAM) {
Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_QS_DIALOG) {
Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_TOAST) {
//应用在版本大于25的时候,不再允许随便添加Toast
if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
parentWindow)) {
Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
//系统window会自动创建token不需要
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
token = new WindowToken(this, binder, type, false, displayContent,
session.mCanAddInternalSystemWindow);
} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
//应用token也是AppWindowToken
atoken = token.asAppWindowToken();
if (atoken == null) {
Slog.w(TAG_WM, "Attempted to add window with non-application token "
+ token + ". Aborting.");
return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
} else if (atoken.removed) {
Slog.w(TAG_WM, "Attempted to add window with exiting application token "
+ token + ". Aborting.");
return WindowManagerGlobal.ADD_APP_EXITING;
}
}......继续一系列判断
//这个可以代表一个window
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
//跨进程通信,一般都会添加死亡监听
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
Slog.w(TAG_WM, "Adding window client " + client.asBinder()
+ " that is dead, aborting.");
return WindowManagerGlobal.ADD_APP_EXITING;
}
.....
//开始添加到页面, 准备绘制的surface
win.attach();
//将添加的window保存起来
mWindowMap.put(client.asBinder(), win);
boolean imMayMove = true;
//token也保存一份window
win.mToken.addWindow(win);
........
//对View的层次进行管理
displayContent.assignWindowLayers(false /* setLayoutNeeded */);
}
return res;
}
这样,Window的添加过程基本就是这么一个过程,当然里面还有一些Window怎么去具体的层次管理,以及怎么去绘制到Surface,请原谅小弟才疏学浅,可能以后会写吧。
removeView,updateViewLayout,操作都差不多,就不再赘言了。
总结:
1、Window是一个虚化的概念,真正操作的对象还是View
2、View需要依附于Window去展示
3、ViewRootImpl负责把View和WindowManager联系起来
4、WindowManager负责Window的管理