自定义View(8)View的绘制流程

安卓UI的重点之一就是View的绘制流程,经常出现在面试题中。熟悉View的绘制流程,不仅能轻松通过View相关的面试,也可以让我们更加方便的使用自定义View以及官方View。此篇先以常见面试题为切入点,说明自定义View的重要性,然后又以getMeasuredHeight值的获取作为问题点,带着问题从源码角度分析View的绘制流程。

1. 面试题介绍

1.1 Android 基础与底层机制

1. 数据库的操作类型有哪些,如何导入外部数据库?
2. 是否使用过本地广播,和全局广播有什么差别?
3. 是否使用过IntentService,作用是什么,AIDL解决了什么问题?(小米)
4. Activity、Window、View三者的差别,Fragment的特点?(360)
5. 描述一次网络请求的流程(新浪)
6. Handler、Thread和HandlerThread的差别(小米)
7. 低版本SDK实现高版本API(小米)
8. launch mode 应用场景(百度、小米、乐视)
9. touch 事件流程传递(小米)
> 10. view 绘制流程(百度)
11. 什么情况导致内存泄露(美团)
12. ANR定位和修正
13. 什么情况导致OOM (乐视、美团)
14. Android Service 与Activity 之间通信的几种方式
15. Android 各个版本API的区别
16. 如何保证一个后台服务不被杀死,比较省电的方式是什么?(百度)
17. RequestLayout、onLayout、onDraw 、DrawChild 区别与联系(猎豹)
18. Invalidate() 和 postInvalidate() 的区别及使用(百度)
19. Android 动画框架实现原理

2. 不同位置获取 getMeasuredHeight 的值

public class MainActivity extends AppCompatActivity {
    private TextView mTextView;
    private String TAG = "view8";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.text_view);
        Log.e(TAG, "onCreate: " + "height1 = " + mTextView.getMeasuredHeight());

        mTextView.post(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "onCreate: " + "height2 = " + mTextView.getMeasuredHeight());
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e(TAG, "onCreate: " + "height3 = " + mTextView.getMeasuredHeight());
    }
}

在这里插入图片描述
从上面代码和运行结果可知,在Activity onCreate 和 onResume 的时候都无法获取到 getMeasuredHeight 值,而使用 mTextView.post(new Runnable())方式可以获取到值,为何如此呢?

3. View 的绘制流程

3.1 View的添加流程 (是如何被添加到屏幕窗口上)

3.1.1 创建顶层布局容器DecorView

//View8/app/src/main/java/com/example/view8/MainActivity.java
// 这里主要是以默认继承的 AppCompatActivity 源码分析,如果是继承 Activity,
// 则直接进到PhoneWindow 的 setContentView,但基本流程都差不多
public class MainActivity extends AppCompatActivity {  
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_main);   // onCreate中主要就是操作了这一行
-------->
//.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat/1.6.1/ace9a78b961165396147e8691faa18c1b0e48e20/appcompat-1.6.1-sources.jar!/androidx/appcompat/app/AppCompatActivity.java
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
-------->
//.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat/1.6.1/ace9a78b961165396147e8691faa18c1b0e48e20/appcompat-1.6.1-sources.jar!/androidx/appcompat/app/AppCompatDelegate.java
    public abstract void setContentView(View v);
-------->
//.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat/1.6.1/ace9a78b961165396147e8691faa18c1b0e48e20/appcompat-1.6.1-sources.jar!/androidx/appcompat/app/AppCompatDelegateImpl.java
    public void setContentView(View v) {
        ensureSubDecor();

    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();

    private ViewGroup createSubDecor() {
        mWindow.getDecorView();  // 这里的mWindow就是PhoneWindow
-------->
//Android/Sdk/sources/android-33/com/android/internal/policy/PhoneWindow.java
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(-1);

    protected DecorView generateDecor(int featureId) {
        return new DecorView(context, featureId, this, getAttributes());   // 在这里new DecorView

3.1.2 在顶层布局中加载基础布局ViewGroup

//Android/Sdk/sources/android-33/com/android/internal/policy/PhoneWindow.java
    private void installDecor() {
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

    protected ViewGroup generateLayout(DecorView decor) {
        int layoutResource;
        // 通过不同的条件(主题),对 layoutResource 进行初始化,然后传入 onResourcesLoaded
            // 假设 layoutResource 走了这个,如果走了其他的,布局中也会有FrameLayout,只是上面的东西不一样
            layoutResource = R.layout.screen_simple; 
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        // ID_ANDROID_CONTENT = com.android.internal.R.id.content,也就是 layoutResource 中的 FrameLayout 布局
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  
        return contentParent;  // 将 FrameLayout 布局 返回
-------->
//Android/Sdk/sources/android-33/com/android/internal/policy/DecorView.java 
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
            // 解析layoutResource,执行了addView,添加到 mDecor 里
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-------->
<!-- Android/Sdk/platforms/android-33/data/res/layout/screen_simple.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

3.1.3 将ContentView添加到基础布局中的FrameLayout中

// 如果 MainActivity extends Activity 
//Android/Sdk/sources/android-33/com/android/internal/policy/PhoneWindow.java
    public void setContentView(int layoutResID) {
    // 将我们创建的 R.layout.activity_main 布局,放到 mContentParent 即系统创建的 FrameLayout 布局中
            mLayoutInflater.inflate(layoutResID, mContentParent); 

// 如果 MainActivity extends AppCompatActivity 
//.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat/1.6.1/ace9a78b961165396147e8691faa18c1b0e48e20/appcompat-1.6.1-sources.jar!/androidx/appcompat/app/AppCompatDelegateImpl.java
    public void setContentView(int resId) {
        // 将我们创建的 R.layout.activity_main 布局,放到 mContentParent 即系统创建的 FrameLayout 布局中
        LayoutInflater.from(mContext).inflate(resId, contentParent);

在这里插入图片描述

3.2 View的绘制流程

上面已经走了一遍View的添加流程,即创建DecorView,然后将我们的布局R.layout.activity_main添加进去,但是还没有走View的测量,所以还是拿不到getMeasuredHeight值的。

“虽然View的绘制流程不是直接从ActivityThread的handleMessage方法开始,但handleMessage方法在处理Activity生命周期事件时扮演着关键角色。例如,当Activity进入RESUMED状态时,它可能会触发一个消息来请求重新绘制UI。这个请求最终会被ActivityThread捕获并处理,从而间接影响到View的绘制。”

相关推荐

  1. Android中View绘制流程

    2024-07-19 00:54:02       40 阅读
  2. 定义View

    2024-07-19 00:54:02       48 阅读
  3. Android View绘制流程详解

    2024-07-19 00:54:02       46 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-19 00:54:02       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-19 00:54:02       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-19 00:54:02       57 阅读
  4. Python语言-面向对象

    2024-07-19 00:54:02       68 阅读

热门阅读

  1. GNN论文粗读

    2024-07-19 00:54:02       23 阅读
  2. 介绍一些编程语言— Mojo 语言

    2024-07-19 00:54:02       20 阅读
  3. RNN与CNN:昔日辉煌与今日应用的深度透视

    2024-07-19 00:54:02       19 阅读
  4. nvide shortcuts table

    2024-07-19 00:54:02       22 阅读
  5. style-components使用手册

    2024-07-19 00:54:02       18 阅读
  6. MySQL中的幻读究竟是怎么回事?

    2024-07-19 00:54:02       17 阅读
  7. 大语言模型-基础及拓展应用

    2024-07-19 00:54:02       21 阅读
  8. 算法刷题笔记 字符串哈希(C++实现)

    2024-07-19 00:54:02       23 阅读
  9. ubuntu 网络 通讯学习笔记2

    2024-07-19 00:54:02       19 阅读