Android窗口机制核心:Activity、Window、DecorView、ViewRootImpl的真实关系

368 阅读3分钟

Android窗口机制核心:Activity、Window、DecorView、ViewRootImpl的真实关系

你为什么需要理解这个?

  • 为什么有时候findViewById返回null?
  • 为什么有些UI操作必须在特定生命周期后才能执行?
  • 为什么自定义View的触摸事件有时候收不到?
  • 为什么有些布局在不同机型上显示不一致?

答案都在这四个组件的协作机制里。


四个组件的真实职责

Activity - 生命周期管理者

class MainActivity : AppCompatActivity() {
    // 职责1: 管理生命周期
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 职责2: 设置内容视图
        setContentView(R.layout.activity_main)
        // 职责3: 处理业务逻辑
        setupClickListeners()
    }
}

真实作用:生命周期管理 + 业务逻辑容器,不直接参与UI绘制。

Window - 窗口策略制定者

// Activity内部会创建PhoneWindow
public class Activity {
    private Window mWindow;
    
    final void attach(...) {
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowManager(...)
    }
}

真实作用:窗口策略制定者,管理窗口属性(全屏、状态栏等),持有DecorView。

DecorView - 顶层ViewGroup

// PhoneWindow.java
private DecorView mDecor;

@Override
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor(); // 创建DecorView
    }
    return mDecor;
}

真实作用:最顶层ViewGroup,包含系统UI(状态栏、导航栏)+ 你的内容区域。

ViewRootImpl - 绘制引擎

// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
    root.setView(view, wparams, panelParentView); // 开始绘制流程
}

真实作用:连接Java层和Native层,负责实际的测量、布局、绘制。


创建流程源码分析

完整的创建链路

ActivityThread.performLaunchActivity()
    ↓
Activity.attach() // 创建PhoneWindow
    ↓  
Activity.onCreate() // 调用setContentView
    ↓
PhoneWindow.setContentView()
    ↓
PhoneWindow.installDecor() // 创建DecorView
    ↓
ActivityThread.handleResumeActivity()
    ↓
WindowManagerImpl.addView() // 添加到WindowManager
    ↓
ViewRootImpl.setView() // 开始绘制流程

关键源码位置

1. Activity创建Window(Activity.java:7009)

final void attach(Context context, ActivityThread aThread, ...) {
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setCallback(this);
}

2. 创建DecorView(PhoneWindow.java:367)

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor(-1); // 创建DecorView
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor); // 设置布局主题
    }
}

3. 添加到WindowManager(WindowManagerImpl.java:94)

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

4. ViewRootImpl接管绘制(ViewRootImpl.java:809)

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    mView = view; // 持有DecorView引用
    requestLayout(); // 触发首次绘制
}

实际开发中的应用

问题1:findViewById为什么返回null?

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // ❌ 错误:DecorView还没创建
        val button = findViewById<Button>(R.id.button)
        
        setContentView(R.layout.activity_main)
        
        // ✅ 正确:DecorView已创建,内容已inflate
        val button2 = findViewById<Button>(R.id.button)
    }
}

问题2:获取View宽高为什么是0?

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    
    val textView = findViewById<TextView>(R.id.textView)
    
    // ❌ 错误:ViewRootImpl还没开始测量
    Log.d("Width", textView.width.toString()) // 输出0
    
    // ✅ 正确:等待ViewRootImpl完成测量
    textView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            textView.viewTreeObserver.removeOnGlobalLayoutListener(this)
            Log.d("Width", textView.width.toString()) // 输出真实宽度
        }
    })
}

问题3:状态栏颜色设置无效?

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    // ✅ 正确:通过Window设置系统UI
    window.statusBarColor = ContextCompat.getColor(this, R.color.primary)
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    
    setContentView(R.layout.activity_main)
}

问题4:自定义触摸事件分发

class CustomDecorView : DecorView {
    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        // 在DecorView层面拦截所有触摸事件
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                // 处理全局手势
                handleGlobalGesture(ev)
            }
        }
        return super.dispatchTouchEvent(ev)
    }
}

调试和实用技巧

1. 通过adb查看View层次

# 导出当前界面的View层次结构
adb shell uiautomator dump
adb pull /sdcard/window_dump.xml

2. 代码中打印View层次

fun printViewHierarchy(view: View, depth: Int = 0) {
    val indent = "  ".repeat(depth)
    Log.d("ViewHierarchy", "$indent${view.javaClass.simpleName} - ${view.id}")
    if (view is ViewGroup) {
        for (i in 0 until view.childCount) {
            printViewHierarchy(view.getChildAt(i), depth + 1)
        }
    }
}

// 使用
override fun onResume() {
    super.onResume()
    printViewHierarchy(window.decorView)
}

3. 获取真实的内容区域

// 获取不包含系统UI的内容区域
val contentView = findViewById<ViewGroup>(android.R.id.content)
val rect = Rect()
contentView.getWindowVisibleDisplayFrame(rect)
Log.d("ContentArea", "Top: ${rect.top}, Bottom: ${rect.bottom}")

4. 监听View树变化

// 监听DecorView的attach状态
window.decorView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
    override fun onViewAttachedToWindow(v: View?) {
        Log.d("DecorView", "已附加到WindowManager")
        // 此时可以安全操作View
    }
    
    override fun onViewDetachedFromWindow(v: View?) {
        Log.d("DecorView", "已从WindowManager分离")
    }
})

总结

记住这个简单的关系链:

Activity(业务逻辑) → Window(窗口策略) → DecorView(顶层容器) → ViewRootImpl(绘制引擎)

实际开发中:

  • onCreate之前不要操作View
  • onResume之后才能获取View尺寸
  • 系统UI相关设置通过Window
  • 性能问题多从ViewRootImpl的绘制流程入手