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对象。

相关推荐
baidu_247438612 小时前
Android 自定义竖直的虚线
android
传奇开心果编程3 小时前
【Jetpack Compose娓娓道来】 第25课:ViewModel就是UI界面的大脑
android·kotlin·移动开发·jetpack compose·编程语言理论·编程框架·声明式ui
马立杰3 小时前
PHP 未授权 RCE 应急响应:CVE-2026-109618 CVE-2026-109619 排查与加固实战
android·开发语言·php
NamoAmitabha1283 小时前
安装“编译器环境”(Android Studio)--deepseek--Cocos --Creator3.8.8
android·ide·android studio
聚美智数4 小时前
文档转换 API‑文档转 PDF‑PDF 转文档‑异步文档处理‑文件格式迁移‑转换结果回调推送 API 接口介绍
android·java·pdf
booboosui4 小时前
Android 上的无人回合:只放开 2 类工具,宁可空手
android
事圆则缓6 小时前
Android 性能排查实战:怎么看内存抖动、CPU 占用和 FPS
android
mmsx6 小时前
Android 三类「看着无关」的故障:大事务、UI 阻塞与 ViewModel 作用域
android·ui
换元不配限6 小时前
Android 休眠与唤醒完全指南:从手机到 IoT 设备的实战解析
android·物联网·智能手机·alarmmanager·wakelock·workmanager·休眠和唤醒
通信侠8 小时前
android 16触摸提示音Back虚拟按键无声
android