3、ViewRootImpl的诞生:ActivityThread.handleResumeActivity中ViewRootImpl的创建与关联
上一章我们聊完了Activity的启动流程,最后卡在了handleResumeActivity这个方法里。今天我们就从这里切入,看看ViewRootImpl到底是怎么冒出来的。
说实话,我早年看源码时,一直以为ViewRootImpl是在setContentView里创建的。后来被一次线上崩溃狠狠教育了一顿——空指针异常,原因是ViewRootImpl还没初始化就去操作DecorView。从那以后,我才老老实实把这块流程啃完。
3.1 handleResumeActivity 的入口
先找到入口。在ActivityThread里,handleResumeActivity是处理Activity恢复的核心方法。它会在Activity的onResume之后被调用。
// ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest,
boolean isForward, String reason) {
// 1. 执行Activity的onResume
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r != null) {
final Activity a = r.activity;
// 2. 获取DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 3. 获取WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
// 4. 关键:将DecorView添加到WindowManager
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
}
注意看第4步。wm.addView(decor, l)这行代码,就是ViewRootImpl诞生的导火索。这里的wm实际上是WindowManagerImpl的实例。
核心要点:ViewRootImpl不是在Activity的构造函数里创建的,也不是在setContentView里创建的。它是在handleResumeActivity中,通过WindowManager.addView触发的。
3.2 WindowManagerImpl.addView 做了什么
我们跟进去看看WindowManagerImpl.addView的实现。这个类其实是个代理,真正的逻辑在WindowManagerGlobal里。
// WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow);
}
嗯,很简洁。直接委托给了mGlobal,也就是WindowManagerGlobal的单例。为什么要用单例?因为一个应用里所有窗口的管理都集中在这里,包括Activity的窗口、Dialog的窗口、PopupWindow的窗口等等。
3.3 WindowManagerGlobal.addView 创建 ViewRootImpl
好戏来了。我们看WindowManagerGlobal.addView的核心逻辑:
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 参数检查
if (view == null) throw new IllegalArgumentException("view must not be null");
if (display == null) throw new IllegalArgumentException("display must not be null");
// 创建ViewRootImpl
ViewRootImpl root;
synchronized (mLock) {
// 1. 创建ViewRootImpl实例
root = new ViewRootImpl(view.getContext(), display);
// 2. 设置参数
view.setLayoutParams(wparams);
// 3. 保存到列表
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// 4. 关键:执行ViewRootImpl的setView方法
root.setView(view, wparams, panelParentView);
}
这里有三件事值得注意:
- 创建ViewRootImpl:new了一个新实例
- 保存引用:把view、root、params分别存到三个ArrayList里
- 调用setView:这才是真正的重头戏
我个人习惯:调试窗口相关问题时,我经常在mViews、mRoots、mParams这三个列表上打断点。它们能清晰反映出当前应用有多少个窗口,每个窗口的状态是什么。
3.4 ViewRootImpl 的构造函数
我们看看ViewRootImpl在构造时做了什么:
// ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mContext = context;
mDisplay = display;
// 1. 创建IWindowSession
mWindowSession = WindowManagerGlobal.getWindowSession();
// 2. 创建AttachInfo
mAttachInfo = new View.AttachInfo(mWindowSession, mDisplay, this, mHandler, this);
// 3. 创建Choreographer
mChoreographer = Choreographer.getInstance();
// 4. 创建Surface
mSurface = new Surface();
// 5. 初始化硬件加速相关
mIsAnimating = false;
mStopped = false;
mLastTraversalFinished = false;
}
这里我重点说两个东西:
第一个是mWindowSession。它是ViewRootImpl和WMS(WindowManagerService)通信的桥梁。通过WindowManagerGlobal.getWindowSession()获取,内部会通过Binder调用到WMS。
第二个是mAttachInfo。这个对象包含了窗口的各种信息,比如Display、WindowToken等。它会被传递给DecorView,后续整个View树的绘制都依赖它。
我曾经踩过的坑:在自定义View的构造函数里调用getViewRootImpl(),返回的是null。原因很简单——ViewRootImpl还没创建呢。ViewRootImpl是在Activity resume之后才创建的,所以不要在View的构造阶段依赖它。
3.5 root.setView 做了什么
创建完ViewRootImpl后,紧接着调用了root.setView(view, wparams, panelParentView)。这个方法才是真正把View和Window系统关联起来的关键。
// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mWindowAttributes = attrs;
// 1. 请求布局
requestLayout();
// 2. 通过Binder向WMS添加窗口
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
// 3. 设置View的Parent为ViewRootImpl
view.assignParent(this);
}
}
}
这里有三步:
- requestLayout():发起第一次布局请求,这会触发View树的measure、layout、draw流程
- addToDisplay:通过Binder告诉WMS,我要创建一个窗口。WMS会分配WindowToken,并返回窗口的尺寸信息
- assignParent:把ViewRootImpl设置为DecorView的Parent。这样DecorView就知道自己的父节点是谁了
注意这个细节:view.assignParent(this)这行代码,意味着ViewRootImpl成为了DecorView的"父View"。但ViewRootImpl本身不是View,它是一个控制器。这种设计在Android里很常见——用组合代替继承。
3.6 关联关系总结
到这里,整个关联关系就清晰了。我画个表格帮你梳理:
| 组件 | 角色 | 关联方式 |
|---|---|---|
| ActivityThread | 应用主线程 | 调用handleResumeActivity |
| WindowManagerImpl | 窗口管理器代理 | 委托给WindowManagerGlobal |
| WindowManagerGlobal | 全局窗口管理器 | 创建ViewRootImpl并调用setView |
| ViewRootImpl | View树控制器 | 持有DecorView引用,负责绘制和事件分发 |
| DecorView | 根View | 被ViewRootImpl管理,通过assignParent建立关联 |
| WMS | 窗口管理服务 | 通过IWindowSession与ViewRootImpl通信 |
说白了,整个流程就是:
ActivityThread → WindowManager → ViewRootImpl → WMS
ViewRootImpl就像是一个"翻译官",把上层的View操作翻译成WMS能理解的窗口操作,同时把WMS的事件分发给View树。
3.7 为什么不在setContentView里创建?
你可能会问:为什么Google不把ViewRootImpl的创建放在setContentView里?那样不是更直观吗?
我个人理解,原因有两个:
- 生命周期管理:ViewRootImpl需要和WMS建立连接,这涉及到跨进程通信。如果在
onCreate里就创建,但Activity可能还没准备好显示,会造成资源浪费 - 窗口可见性:只有Activity真正resume了,窗口才应该被显示。在
handleResumeActivity里创建,时机刚刚好
一个小技巧:如果你想在Activity启动时尽早获取ViewRootImpl,可以在onResume之后通过View.getViewRootImpl()获取。但要注意判空,因为某些情况下可能还没创建完成。
好了,这一章的内容就到这里。ViewRootImpl的诞生,说白了就是handleResumeActivity → WindowManager.addView → ViewRootImpl创建 → setView关联 这条链路。下一章我们会深入ViewRootImpl的performTraversals方法,看看View树第一次绘制时到底发生了什么。