Activity启动流程(四):从setContentView到View树创建与Window挂载

书接上回,已经到 Activity resume 了,但是resume被调用,只代表activity进入到了resumed生命周期状态,并不代表它的窗口已经完成添加。更不代表屏幕上已经出现了第一帧画面。

Activity中的onCreate()onStart()onResume()描述的是生命周期状态的推进,开发者也会在这些回调中初始化页面、恢复数据和准备交互。

那真正的窗口可以显示在界面上,需要几个阶段。view 树 -----> window 树 ------> SurfaceFlinger 图层

setContentView()

→ App进程中创建View树

WindowManager.addView()

→ ViewRootImpl连接View树与WMS

→ WMS创建WindowState并加入WindowContainer树

performTraversals()

→ measure/layout/draw

→ 绘制结果写入Surface对应的Buffer

SurfaceFlinger

→ 合成各窗口图层并输出到屏幕

一、setContentView

首先,上面 app 进程在按照 system_server 发过来的 ClientTransaction 执行应用的生命周期时,刚执行到 onCreate调用阶段。

在多数普通activity中,开发者会在onCreate中调用setcontentview,为当前Activity设置页面内容。

frameworks/base/core/java/android/app/Activity.java

java 复制代码
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);//返回的是 Activity 创建时生成的 PhoneWindow。
    //因此真正设置页面内容的工作由PhoneWindow.setContentView()完成。
}

frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

java 复制代码
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    }
    mLayoutInflater.inflate(layoutResID, mContentParent);
}

PhoneWindow.setContentView()首先通过installDecor()创建或准备窗口最外层的DecorView,并找到用于承载开发者页面的mContentParent。随后,LayoutInflater将开发者提供的XML布局解析成View对象,并加入mContentParent,最终形成以DecorView为根节点的View树。

这里可以通过dumpsys activity top 查看顶层activity的信息,如下

此时页面内容仍然只是App进程中的一组View对象。WMS虽然已经知道对应Activity的存在,但尚未为其创建WindowState,SurfaceFlinger中也还没有可供合成的应用窗口内容

二、addWindow

app进程在按照system_server 发过来的ClientTransaction执行应用的生命周期时,刚执行到Activity.onResume()之后

回到r.activity.performResume(r.startsNotResumed, reason); 会继续执行下面的 wm.addView(decor, l);

frameworks/base/core/java/android/app/ActivityThread.java

java 复制代码
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
     ......
     wm.addView(decor, l); 
     //decor是view树的根节点
    //WindowManagerGlobal将为它创建ViewRootImpl
    //ViewRootImpl负责连接App端View树和WMS端窗口
}

View树关心measure、layout、draw;WMS关心Display、窗口层级、焦点、Insets、输入通道及Surface。ViewRootImpl就是两个世界之间的桥梁。

frameworks/base/core/java/android/view/WindowManagerImpl.java

java 复制代码
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
            mContext.getUserId());
}

frameworks/base/core/java/android/view/WindowManagerGlobal.java

java 复制代码
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    root = new ViewRootImpl(view.getContext(), display);
    root.setView(view, wparams, panelParentView, userId);
}

addToDisplayAsUser()中的mWindow不是PhoneWindow,而是ViewRootImpl内部实现了IWindow接口的Binder对象。WMS以这个IWindow作为客户端窗口标识,并创建对应的WindowState

frameworks/base/core/java/android/view/ViewRootImpl.java

java 复制代码
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
            mAttachInfo.mDisplayCutout, inputChannel,
            mTempInsets, mTempControls);
}

frameworks/base/services/core/java/com/android/server/wm/Session.java

java 复制代码
public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, int userId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
    mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
            outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
            outInsetsState, outActiveControls, userId);
}

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

java 复制代码
public int addWindow(Session session, IWindow client, int seq,
        LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
        int requestUserId) {
    int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
            appOp); // 检查权限 检查调用方是否有权添加这种类型的窗口
    final WindowState win = new WindowState(this, session, client, token, parentWindow,
            appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
            session.mCanAddInternalSystemWindow);//在WMS中为客户端窗口创建WindowState记录
    displayPolicy.getLayoutHint(win.mAttrs, token, outFrame, outContentInsets,
            outStableInsets, outDisplayCutout); // 这里得到的是初步布局,后续relayout()仍可能继续调整
    win.attach();
    mWindowMap.put(client.asBinder(), win);
    win.mToken.addWindow(win);
}

WindowState创建后,WMS将它添加到对应WindowToken的子节点中。由于WindowToken自身已经处在WindowContainer层级中,这个窗口也由此正式进入WMS管理的窗口树。

三、首次Traversal:View树如何产生第一帧内容

WMS.addWindow()成功之后,App端的ViewRootImpl已经与WMS端的WindowState建立了联系。但此时只能证明窗口已经登记,View树还没有按照WMS提供的窗口尺寸完成测量,布局和绘制,因此仍然没有可供SurfaceFlinger合成的有效画面

这里从frameworks/base/core/java/android/view/ViewRootImpl.java 的

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,

int userId) 开始 在执行addToDisplayAsUser之前 还需要执行requestLayout();

这里会发一个异步的任务

frameworks/base/core/java/android/view/ViewRootImpl.java

java 复制代码
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    requestLayout();
}

public void requestLayout() {
    scheduleTraversals();
}

void scheduleTraversals() {
    // 避免重复预约Traversal,将短时间内的多个布局请求合并到后续一帧处理
    // 标记需要重新布局,并通过Choreographer预约一次Traversal 
    //此处只是预约,不会立即执行performTraversals()
    mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}

requestLayout()虽然写在addToDisplayAsUser()前面,但它只负责预约Traversal。当前主线程会继续同步执行WMS.addWindow();等setView()流程结束、Choreographer调度到对应回调时,才真正执行doTraversal()

java 复制代码
void doTraversal() {
    performTraversals();
    ├── 与WMS协商窗口Frame、Insets和Surface
    ├── performMeasure():确定各View的尺寸
    ├── performLayout():确定各View的位置
    └── performDraw():将View树绘制到Surface对应的Buffer
}
//源码会根据窗口尺寸、Insets和可见性等状态决定是否重新测量,并不代表每次Traversal都严格按固定顺序各执行一次。

View树不是直接交给SurfaceFlinger。App先把View树绘制成Buffer,SurfaceFlinger看到的是Layer和Buffer,而不是Button、TextView等View对象。

相关推荐
三少爷的鞋3 小时前
Kotlin 2026:裁员、AI、Rust——黄金时代结束了吗?Jake Wharton 为你解答
android
alexhilton4 小时前
从零开始的架构测试套件
android·kotlin·android jetpack
finhaz5 小时前
台电x98plus在安卓x86的使用
android·平板·安卓x86
事圆则缓5 小时前
遗留项目改造实战指南:从可读性、代码质量到性能和 AI 约束
android·ai·代码规范
mmsx6 小时前
Android 手簿 ADB 无线调试全攻略:USB 转 WiFi 一键连接
android·前端
律宏阔6 小时前
Android WebRTC + H.265 适配记录
android
mmsx6 小时前
MapLibre 实战 08|GPS 点漂了几百米才被发现:GCJ-02 纠偏原理与"转两次"陷阱
android·前端
春夏与冬6 小时前
Android : apktool
android