上一篇流程现已履行到 ViewRootImpl::setView办法了,也就意味着运用进程的逻辑到了结尾,剩下的将由 SystemService进程来处理。
回顾一下运用进程的相关调用链:
LaunchActivityItem::execute
ActivityThread::handleLaunchActivity
ActivityThread::performLaunchActivity
Instrumentation::newActivity --- 创立Activity
Activity::attach --- 创立Window
Window::init
Window::setWindowManager
Instrumentation::callActivityOnCreate
Activity::performCreate
Activity::onCreate
ResumeActivityItem::execute
ActivityThread::handleResumeActivity
ActivityThread::performResumeActivity
Activity::performResume
Instrumentation::callActivityOnResume
Activity::onResume
WindowManagerImpl::addView --- 创立ViewRootImpl
WindowManagerGlobal::addView
ViewRootImpl::setView --- 与WMS通信 addView
别的还留下了一个疑问:
分明是addWindo流程,可是到了WindowManagerImpl就变成了addView,传递的也是DecoreView,再到和WMS同信的时分,参数里连DecoreView都不剩了,这怎么能叫addWindow流程呢?
本篇将介绍WindowManagerService是怎么处理剩下逻辑的,文末也会回答这个问题。
1. SystemServer进程处理
上篇看过这张图,在 system_service 要做的最重要的便是WindowState创立和挂载,也是本篇需求剖析的流程。
1.1 WindowManagerService::addWindow办法概览
接上篇知道 Session::addToDisplayAsUser 办法最终调用的是WindowManagerService::addWindow,先看一下这个办法。
# WindowManagerService
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
......
// 权限检查
int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
appOp);
if (res != ADD_OKAY) {
return res;
}
// 父窗口,运用Activity逻辑是没有父窗口的。
WindowState parentWindow = null;
......
synchronized (mGlobalLock) {
......
// 窗口现已增加,直接return
if (mWindowMap.containsKey(client.asBinder())) {
ProtoLog.w(WM_ERROR, "Window %s is already added", client);
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
......
ActivityRecord activity = null;
// 1. 是否为hasParent
final boolean hasParent = parentWindow != null;
// 2. 处理token
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
......
if (token == null) {
......
if (hasParent) {
// Use existing parent window token for child windows.
token = parentWindow.mToken;
} else if (mWindowContextListenerController.hasListener(windowContextToken)) {
// Respect the window context token if the user provided it.
final IBinder binder = attrs.token != null ? attrs.token : windowContextToken;
final Bundle options = mWindowContextListenerController
.getOptions(windowContextToken);
token = new WindowToken.Builder(this, binder, type)
.setDisplayContent(displayContent)
.setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
.setRoundedCornerOverlay(isRoundedCornerOverlay)
.setFromClientToken(true)
.setOptions(options)
.build();
} else {
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
token = new WindowToken.Builder(this, binder, type)
.setDisplayContent(displayContent)
.setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
.setRoundedCornerOverlay(isRoundedCornerOverlay)
.build();
}
} else if (rootType >= FIRST_APPLICATION_WINDOW
&& rootType <= LAST_APPLICATION_WINDOW) {
} else if......// 疏忽其他各种创立对 token的处理
// 3. 创立WindowState
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
......
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
// 4. 调整window的参数
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), callingUid,
callingPid);
win.setRequestedVisibilities(requestedVisibilities);
// 5. 验证Window是否能够增加,主要是验证权限
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != ADD_OKAY) {
// 假如不满足则直接return
return res;
}
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
// 6.打开工作输入通道
win.openInputChannel(outInputChannel);
}
......
// 7. 窗口增加进容器
win.attach();
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
......
win.mToken.addWindow(win);
displayPolicy.addWindowLw(win, attrs);
......
// 8.1 处理窗口焦点切换
boolean focusChanged = false;
if (win.canReceiveKeys()) {
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
false /*updateInputWindows*/);
if (focusChanged) {
imMayMove = false;
}
}
if (imMayMove) {
displayContent.computeImeTarget(true /* updateImeTarget */);
}
// 9. 分配层级
win.getParent().assignChildLayers();
// 8.2 更新inut焦点
if (focusChanged) {
displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
false /*updateInputWindows*/);
}
// 8.3 更新input窗口
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
// 窗口增加log
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
+ ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
......
}
Binder.restoreCallingIdentity(origId);
return res;
}
1.2 小结-addWindow主要处理了以下9个工作
-
- 假如是SubWindow判别当时是否有parentWindow (当时是Activity的逻辑,没有父窗口)
-
- 获取token —-DisplayContent.mTokenMap中获取
- 2.1 获取失败则创立token
- 2.2 获取成功将token转换为ActivitRecord
- 2.3 对token做校验,该回来报错就回来
-
- 依据token等信息创立WindowState
-
- 调整window的参数 ——-DisplayPolicy::adjustWindowParamsLw
-
- 验证Window是否能够增加 —–res = displayPolicy.validateAddingWindowLw
-
- 打开input工作输入通道—–WIndowState::openInputChannel 这样Activity能接收到input工作
-
- 增加窗口进入容器
- 7.1 通知WindowState现已增加 —–WindowState::attach
- 7.2 将WindowState存入WMS的mWindowMap—-mWindowMap.put(client.asBinder(), win);
- 7.3 将WindowState增加进WindowState.mToken调集,同一个类型的界面,进行排序 —- WindowToken::addWindow
-
- 3个当地都是对焦点窗口相关的处理
-
- 分配层级———- win.getParent().assignChildLayers();
当然这个办法实际上做的事肯定不止这些,仅仅依据我的个人理解整理出了比较重要的9个工作处理。
当时剖析的addWindow主流程,其间和当时盯梢相关的别离为2,3,7,下面详细剖析一下这3块,其间 3和7 别离对应 WindowState的创立与挂载,其他的后续有时间再独自剖析。
2. 主流程介绍
2.1 Token相关
这儿的Token指的其实是WindowToken,由于 【WindowContainer窗口树】介绍过,WindowState的父节点大部分情况是WindowToken,并且在上一篇看到dump发动应前后的窗口树差异,明确知道WindowStateWindowState是挂载到 ActivityRecord 下的, 而 ActivityRecord 是 WindowToken 的子类。
在剖析WindowState的创立和挂载前,需求先给它找到它的父节点 — WindowToken。这也是 WindowManagerService::addWindow办法中比较靠前履行的逻辑。
这一末节只关心 addWindow 办法中和WindowToken相关的代码
# WindowManagerService
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
......
// 1. 界说父窗口
WindowState parentWindow = null;
......
// 2. 界说ActivityRecord
ActivityRecord activity = null;
// 是否为hasParent(当时场景为false)
final boolean hasParent = parentWindow != null;
// 3. 获取token
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
......
if (token == null) {
......
if (hasParent) {
// Use existing parent window token for child windows.
// 比方PopupWindow在这运用父窗口的token
token = parentWindow.mToken;
} else if (mWindowContextListenerController.hasListener(windowContextToken)) {
......
} else {
// 4. WindowToken
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
token = new WindowToken.Builder(this, binder, type)
.setDisplayContent(displayContent)
.setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
.setRoundedCornerOverlay(isRoundedCornerOverlay)
.build();
}
} else if (rootType >= FIRST_APPLICATION_WINDOW
&& rootType <= LAST_APPLICATION_WINDOW) {
// 转换成ActivityRecord
activity = token.asActivityRecord();
......
} else if......// 疏忽其他各种创立对 token的处理
......
}
针对注释里的数字,再做更详细的解说:
-
- 首要界说了一个父窗口的变量 parentWindow ,当时是没有父窗口的,比方起了一个popupWindow, 那parentWindow的值便是对应的Window。
-
- 界说了 ActivityRecord,能够看到办法末尾假如 窗口类型是运用类型, 就会将token转换成ActivityRecord。 由于 ActivityRecord 是 WindowToken的子类,asActivityRecord这个办法是界说在窗口容器基类–WindowContainer下的,可是只要 ActivityRecord 重新了这个办法。
-
- 经过 DisplayContent::getWindowToken 办法来获取到token,
首要现已知道parentWindow是没有的,由于当时是Activity下的addWindow,只要子窗口才有parentWindow。那么token的赋值便是后边那句“attrs.token”,这个attrs依据传参调用知道是Window的参数,可是之前如同的剖析并没有看到对attrs.token的赋值。
可是上一篇 在剖析WindowManagerGlobal::addView办法的时分在adjustLayoutParamsForSubWindow 办法的注释上提过一下token,现在细心看一下这个办法。
# Window
// 运用Token
private IBinder mAppToken;
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
......
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; // activity的window在这儿设置token
}
......
}
mContainer唯一赋值的当地在Window::setContainer办法,当时没调,所以wp.token最终的值是为 mAppToken,mAppToken的赋值在给Window设置WindowManager的时分赋值,也便是setWindowManager办法,这儿的token便是ActivityRecord的token。
能够再回到 上一篇2.1.1 末节看一下 setWindowManager 办法
-
- 假如第三步没有获取到token,就需求创立一个 WindowToken 对象进行赋值了, 由于假如没有一个WindowToken那当时办法后续的逻辑就没法履行了, WindowState就没有挂载的父节点了。
那什么场景下会走到这呢? 现在一向运用窗口也便是Activity是在第三步就拿到了token的, 可是体系窗口,比方状态栏,是需求在这一步创立WindowToken的,并且是依据窗口类型创立的,由于创立出来的WindowToken是需求挂载到窗口树的,所以会依据WindowType来找到自己需求挂载的方位。具体的能够看 WindowContainer窗口层级-3-实例剖析下的体系窗口挂载。
2.1.1 WindowToken小结
在WMS模块中这个token非常重要,尽管上面流程现已介绍了token的由来,可是再重新理一遍。 首要我们知道在Activity发动流程的时分AMS会创立ActivityRecord,ActivityRecord的父类的WindowToken,ActivityRecord构成办法的时分就会创立一个匿名token,所以ActivityRecord内部是有一个token的,直接将ActivityRecord理解为token,也没什么问题。
然后在ActivityThread::performLaunchActivity办法调用Activity::attach办法时就将token传递了进去,在Activity::attach经过window::setWindowManager办法将token设置给了Window的成员变量mAppToken。 然后在调用Window::adjustLayoutParamsForSubWindow办法的时分,又将mAppToken设置给了Window的LayoutParams.token。
再回到WMS::addWindow办法中,获取到token后下面有一大堆代码,其实是对token ==null的处理,像体系window,或许子window就会履行,那么当时逻辑,是不需求履行的。
拿到WindowToken后,WindowState才能有挂载的父节点,addWindow办法后边才能够正常履行。
2.2 WindowState的创立
addWindow的流程是要将WindowState挂在到窗口树中,所以肯定是要创立一个WindowState的。
# WindowManagerService
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
......// WindowToken相关处理
// 创立WindowState
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
......
}
这儿留意几个参数,然后直接看WindowState的构造办法
# WindowState
@NonNull WindowToken mToken;
// The same object as mToken if this is an app window and null for non-app windows.
// 与mToken相同的对象(假如这是运用程序窗口),而关于非运用程序窗口为null
// 说人话便是运用窗口才有
ActivityRecord mActivityRecord;
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
PowerManagerWrapper powerManagerWrapper) {
......
mToken = token;
mActivityRecord = mToken.asActivityRecord();
......
}
创立WindowState有2个重要的参数client,和token。这个client看姓名也知道代表着客户端,那便是APP进程了,依据之前的剖析知道便是ViewRootImpl的内部类W,然后其也持有着ViewRootImpl的软引证,另一个参数token上面刚刚剖析过。
WindowState 今后会经常看到,不过当时只要知道在 WindowManagerService::addWindow 会创立出一个WindowState对象即可。
2.3 WindowState的挂载
# WindowManagerService
// ViewRootImpl和WindowState的map
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
......
// 窗口现已增加,直接return
if (mWindowMap.containsKey(client.asBinder())) {
// 打印log
ProtoLog.w(WM_ERROR, "Window %s is already added", client);
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
......// WindowToken相关处理
......// WindowState的创立
// WindowState的挂载
win.attach();
// 1. 存进map
mWindowMap.put(client.asBinder(), win);
......
// 2. 挂载
win.mToken.addWindow(win);
......
}
-
- 在看挂载前先看一下 mWindowMap这个数据结构,key是一个IBinder,value是 WindowState,这边将新创立的WindowState作为value增加到了map中,前面说过client是运用端ViewRootImpl下的 “W”这个类,也便是说在WMS中运用端的这个ViewRootImpl和为其创立的WindowState现已被记录在mWindowMap中了。
在履行WMS::addWindow办法开端的时分就会尝试经过clent从mWindowMap获取值,假如获取到了阐明现已履行过addWindow,值进行return,不履行后边逻辑。
-
- 这儿是窗口的挂载,“win.mToken”这儿的mToken刚刚看到是创立WindowState的时分传递的token,也便是 ActivityRecord (WindowToken)。
也便是说调用的是ActivityRecord::addWindow办法进行挂载的
# ActivityRecord
@Override
void addWindow(WindowState w) {
super.addWindow(w);
......
}
直接调用其父类办法,ActivityRecord父类是WindowToken
# WindowToken
void addWindow(final WindowState win) {
ProtoLog.d(WM_DEBUG_FOCUS,
"addWindow: win=%s Callers=%s", win, Debug.getCallers(5));
if (win.isChildWindow()) {
// Child windows are added to their parent windows.
return;
}
// This token is created from WindowContext and the client requests to addView now, create a
// surface for this token.
// 真正增加进子容器
if (!mChildren.contains(win)) {
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", win, this);
addChild(win, mWindowComparator);
mWmService.mWindowsChanged = true;
// TODO: Should we also be setting layout needed here and other places?
}
}
其实看到这个办法就完毕了,由于结合【WindowContainer窗口层级】和【Activity发动流程】的流程就知道现在WindowState现已被增加到层级树中了,挂在到对应的ActivityRecord下。
当然这儿需求留意WindowToken::addWindow最终也是调用父类WindowContainer::addChild将WindowState增加到自己的孩子中,这儿传递了一个mWindowComparator。
2.3.1 addWindow是次序 WindowToken下的mWindowComparator
# WindowToken下的mWindowComparator
private final Comparator<WindowState> mWindowComparator =
(WindowState newWindow, WindowState existingWindow) -> {
......
return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
};
protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
WindowState existingWindow) {
// 便是比较两个窗口的mBaseLayer
return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
}
这儿的newWindow和existingWindow当然是一个当时需求增加进容器的WindowState和上一个存在的WindowState
-
- 对 WindowToken 的子窗口进行比较排序,1和-1表明相对次序
-
- 回来true,则是1,表明刺进在后边。 也便是newWindow的mBaseLayer大于原来的
-
- 回来false,则是-1,表明刺进在签名,也便是newWindow的mBaseLayer小于原来的
简略来说便是比较mBaseLayer来判别当时新增加的是放在哪个方位。 正常情况都是按顺增加,也便是后增加的在最上面。
3. 总结
addWindow流程在system_service进程这边主要便是在 WindowManagerService::addWindow 办法处理的,这个办法也是WMS的核心办法。 先是保证能获取到一个WindowToken,然后进行WindowState的创立和挂载。
最后来解说下之前在上一篇留下的疑问,由于在WMS中并没有Window,有的是WindowState,而WMS经过App层传来的token和client创立WindowState的能映射到对应的Activity和Window,以及ViewRootImpl。 所以addWindow流程在运用进程最后仅仅经过 ViewRootImpl::setView 办法中触发跨进程调用时,传递一个 “W”到system_service进程,WMS那儿并不联系运用这边的Window和View。 WMS那儿假如有什么工作,只要经过这个“W”就能够和运用进程进行通信了。