Android 绘制流程(从Activity创建 -> 内容展示)

1,886 阅读9分钟

Android 绘制流程(从Activity创建 -> 内容展示)

如果说,岁月是一首歌,那么我们便是歌者,纵使孤独,仍会固执高歌

背景

刚入门Android开发时,水水对启动一个“页面”,到页面“显示”执行过程的印象:

  • StartActivity 启动新的Activity
  • Activity执行onCreate生命周期
  • setContentView 加载Layout布局
  • onResume 应用进入前台显示

通常会把需要显示的内容,写进xml布局文件中,然后Activity创建后,进入前台就会展示。

再后来了解到ActivityThread,PhoneWindow,ViewRootImpl,DecorView 这些UI绘制过程中的关键组成,本次作为新晋职场老油条,水水将对Android绘制流程中从Activity创建到页面显示中的关键类,关键方法进行整理性说明。

说明

源代码基于 Android api 29/ androidx 1.1.0

关键类 :ActivityThread

ActivityThread就是平时常说的Android主线程或UI线程,ActivityThread中的main方法可以看做为App的入口。关于ActivityThread及App启动,可以看这篇。 本文中关于启动时的UI绘制流程,在ActivityThread中主要关注一下两个重要方法。即在LaunchActivityItem生命周期事件之后,ActivityThread中执行的流程,该过程中会执行两个 关键方法:

  • ActivityThread&performLaunchActivity
  • ActivityThread&handleResumeActivity 本文会从两个方法展开,分析从Activity创建 -> 内容展示究竟创建了哪些类,并执行了哪些方法。

关键方法 performLaunchActivity:

顾名思义这是一个执行Launch(启动)Activity的方法,进入代码:

ActivityThread.java -> performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
        // 创建Activity所属的ContextImpl
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            // 通过Instrumentation创建Activity
            // Instrumentation中最终newInstance反射创建Activity
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            ......
        }

        try {
                ......
                // 关键方法执行, Activity的attach方法
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
                ......
                
                activity.mCalled = false;
                // 执行Instrumentation 中的callActivityOnCreate,最终执行Activity的onCreate生命周期
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }

        } catch (SuperNotCalledException e) {
        ......

        return activity;
}

Instrumentation中的callActivityOnCreate方法:

Instrumentation.java -> callActivityOnCreate
public void callActivityOnCreate(Activity activity, Bundle icicle,
            PersistableBundle persistentState) {
        prePerformCreate(activity);
        // 执行Activity的performCreate
        activity.performCreate(icicle, persistentState);
        postPerformCreate(activity);
}

Activity中的prePerformCreate方法:

@UnsupportedAppUsage
    final void performCreate(Bundle icicle, PersistableBundle persistentState) {
        .....
        // 执行自身onCreate生命周期
        if (persistentState != null) {
            onCreate(icicle, persistentState);
        } else {
            onCreate(icicle);
        }
       ......
    }

阶段小结

在performLaunchActivity方法中,可以看到:

  • 执行了Activity所属Context的真正执行类ContextImpl的创建
  • 创建完了Activity之后,在Instrumentation中通过newInstance方式,反射创建了Activity
  • 创建完Activity之后,在onCreate方法执行之前先调用了关键方法:attach
  • 在执行完attach方法后,通过层层调用,最终执行Activity的onCreate生命周期方法

执行流程: performLaunchActivity - > Activity创建 -> Activtiy.attach -> Activity.onCreate

深入attach方法,看看究竟做了什么操作:

Activity.java -> attach
@UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,
            ...... {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        
        // 执行了PhoneWindow的创建,并将其赋值给Activity中的mWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        // 将当前线程赋值给mUiThread,即 ActivityThread(主线程)  == UIThread
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mAssistToken = assistToken;
        ..... 
        //设置WindowManager

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        ......
    }

从上面的代码中,可以看到 PhoneWindow在Activity的attach方法中被创建,并赋值给Activity中的mWindow变量。PhoneWindow作为Android中Window的唯一实现类。Window可以看做Android显示系统中,在Framework层对于窗口的抽象,而PhoneWindow为这个抽象的具体实现类。关于窗口和Android显示系统,可以看这篇。

再到onCreate方法:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

程序会在onCreate方法中执行setContentView方法

深入setContentView,简单看看究竟发生了什么:

AppCompatDelegateImpl 顾名思义就是AppCompatActivity(兼容性Activity)的委托类的实现
AppCompatDelegateImpl.java -> setContentView
@Override
public void setContentView(int resId) {
    // 创建SubDecor,以及DecorView,并SubDecor对于DecorView的完成替换
    // 这里的SubDecor,顾名思义就是一个替补性质的ViewGroup
    ensureSubDecor();
    // 获取mSubDecor中的R.id.content对应的ViewGroup
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    // 清空ViewGroup中的子View
    contentParent.removeAllViews();
    // resId对应的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

ensureSubDecor()方法:

AppCompatDelegateImpl.java -> ensureSubDecor
private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        // 创建SubDecor
        mSubDecor = createSubDecor();
            ......
    }
}
AppCompatDelegateImpl.java -> createSubDecor
private ViewGroup createSubDecor() {
        // 获取设置的App 主题,并获取主题中关于TITLE,ACTION_BAR等属性的值
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }

        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
        } 
        ......
        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
        a.recycle();

        // Now let's make sure that the Window has installed its decor by retrieving it
        ensureWindow();
        // 获取DecorView,最终执行类为PhoneWindow,调用PhoneWindow中的installDecor方法
        mWindow.getDecorView();

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;

        // 根据上设置的Window属性,加载对应的layout布局并赋值给subDecor
        if (!mWindowNoTitle) {
            if (mIsFloating) {
                ......
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);
                mHasActionBar = mOverlayActionBar = false;
            } else if (mHasActionBar) {
               
                ......
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);
                ......
            }
        } else {
            if (mOverlayActionMode) {
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }
          .......
        // 将subDecor中的R.id.title对应的View赋值给mTitleView,在AppCompat包下的AppCompatActivity或Dialog中title即是这个View,Activity下的setTitle方法会通过委托的形式,调用AppCompatDelegateImpl中的方法。

        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }
        ......
        // Make the decor optionally fit system windows, like the window's decor
        ViewUtils.makeOptionalFitsSystemWindows(subDecor);
        ......
        // 这里的contentView 对应subDecor中的contentView
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        // 这里的windowContentView对应DecorView中的R.id.content对应的View
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // 如果有些子view已经add到windowContentView,需要进行合并
            while (windowContentView.getChildCount() > 0) {
                // 移除DecorView中原先contentView的子View,并将移除的child子View,add到subDecor的contentView上,完成merge合并操作
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }

            // 将原先的DecorView中的contentView,设值为NO_ID
            windowContentView.setId(View.NO_ID);
            // 将merge之后的subDecor中的contentView,设置id为R.id.content,完成替换前的最后一步操作
            contentView.setId(android.R.id.content);

            // The decorContent may have a foreground drawable set (windowContentOverlay).
            // Remove this as we handle it ourselves
            if (windowContentView instanceof FrameLayout) {
                ((FrameLayout) windowContentView).setForeground(null);
            }
        }

        // Now set the Window's content view with the decor
        // 最后将subDecor这个View通过setContentView传入PhoneWindow,本质上就是讲SubDecor通过addView,加到PhoneWindow中的contentParent上,完成替换。
        mWindow.setContentView(subDecor);
        
        ......

        return subDecor;
    }

AppCompatActivity 和 Activity在setContentView,在setContentView的过程中,都会创建DecorView,区别点在于:AppCompatActivity中,还会根据给Window设置的属性,创建一个subDecor替换性质的ViewGroup,这个ViewGroup的创建及布局选择,会依据AppCompat包下的主题,比如常见的Theme.AppCompat.Light.DarkActionBar。

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

再创建两者之后,通过PhoneWindow中的findViewById(android.R.id.content)找到原本DecorView中的content位置,并将原本content中的子View移除并add到subDecor上,并给subDecor和DecorView中的content重新设置ID,subDecor的ID将设置R.id.content,最后将其add到PhoneWindow中的contentParent上即完成整个替换的过程。

关于mWindow.getDecorView(),即在PhoneWindow中创建DecorView,详细的介绍有很多,可以看看这篇

performLaunchActivity小结:

在performLaunchActivity的过程中,先后执行了

1.Activity创建

2.Activity的attch

  • 创建了PhoneWindow以及对应的WindowManager

3.Activity的onCreate

  • 执行setContentView,完成了DerView的创建

在performLaunchActivity过程中,完成Activity中设置resId对应的布局文件的从DecorView到最后子View的加载(过程中涉及xml解析,递归以及反射,详细细节可以看看这篇,但到Activity的onCreate位置,视图还并未显示,需要再执行performResumeActivity方式。

关键方法handleResumeActivity:

ActivityThread.java -> handleResumeActivity
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ......
        // performResumeActivity最终会执行Activity中的onResume生命周期方法
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
       ......
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    // WindowManager.addView方法
                    wm.addView(decor, l);
                }
                ......
            }

        ......
        Looper.myQueue().addIdleHandler(new Idler());
}

可以看到在DecorView被add到Window上之前,会先执行onResume生命周期,当然指的是在Activity首次启动的过程中,mWindowAdded为false的情况下。

WindowManager有对应的实现类WindowManagerImpl,当然WindowManagerImpl也并不是最终的方法执行类,WindowManagerGlobal才是,并且它是一个单例。再来看WindowManagerGlobal中的addView方法:

WindowManagerGlobal.java -> addView
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ......

        生命ViewRootImpl
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ......

            // 创建ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            try {
                // 调用ViewRootImpl的setView方法
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

ViewRootImpl.java -> setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                // 将DecorView赋值到mView变量
                mView = view;
                ......
                mAttachInfo.mDisplayState = mDisplay.getState();
                // 执行requestLayout
                requestLayout();
                ......
            }
        .......
}

以上,可以看到performResumeActivity,在addView方法中,主要去创建了Window对应的ViewRootImpl,在创建完ViewRootImpl之后将DecorView传入其中,最后执行ViewRootImpl的requestlayout方法。

ViewRootImpl是什么?

ViewRootImpl是DecorView的父类,平常在代码中的执行绘制或者是刷新api时,往往从子View一层一层往上,经历若干个ViewGroup到达DecorView,DecorView则会将调用它的父类方法即ViewRootImpl中的相对应绘制或刷新api,而这些api最终往往会走到ViewRootImpl中的scheduleTraversals,将刷新事件发送至Choreographer中,过程中choreography会申请VSync刷新信号,并在信号到来之际执行doframe方法,再返回到ViewRootImple中执行performTraversals。关于Choreography编舞者及刷新机制可以看这篇

ViewRootImpl.java -> performTraversals
private void performTraversals() {
        // 这里的mView,就是通过setView方法传入的DecorView
        final View host = mView;
        .....
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        .....
        performLayout(lp, mWidth, mHeight);
        .....
        performDraw();
        
}

这里的对应了DecorView即ViewGroup中的measure,layout,draw三个绘制过程中的重要方法。

ViewRootImpl.java -> performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            // 执行DecorView.measure
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        // 判断DecorView是否为空
        final View host = mView;
        if (host == null) {
            return;
        }
       ......
       // 执行DecorView的layout方法
       host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
       if (numViewsRequestingLayout > 0) {
           ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
          if (validLayoutRequesters != null) {
              // 执行DecorView的layout方法
              host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
          }
       ......
            
 }

performDraw()涉及内容很多,这里不做展开了。

小结

在handleResumeActivity之前,DecorView已经被创建但未绘制,在handleResumeActivity中,先执行了onResume生命周期,后创建了ViewRootImpl作为DecorView的父类,配置Choreography进行刷新以及ViewTree的绘制工作。

由此可见:

在Activity首次创建的过程中,在执行onResume生命周期时,控件树已经被创建,即我们可以通过findViewById获取到UI控件,但这个时期,内容并未展示,而是去向系统申请刷新信号,当信号到来之时,才进行绘制(measure,layout,draw)工作。

总结

在performLaunchActivity到handleResumeActivity的过程中,先后执行了

1.Activity创建

2.Activity的attch

  • 创建了PhoneWindow以及对应的WindowManager

3.Activity的onCreate

  • 执行setContentView,完成了DerView的创建 4.Activity的onResume

5.WindowMananger.addView

  • 创建ViewRootImpl,并将DecorView传入
  • 通过Choreography申请刷新信号 6.VSync刷新信号到来
  • ViewRootImpl执行performTraversals
  • ViewTree控件树,执行measure,layout,draw方法

其中draw方法会根据是否开启硬件加速有多种实现机制。

我是超级水的Zheng滴水,若写的有不对的地方,欢迎指出! 以上,谢谢!

参考文章

ActivityThread的理解和APP的启动过程

Android的UI显示原理之Surface的创建

Android应用setContentView与LayoutInflater加载解析机制源码分析

“终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解!