Android Activity 启动流程 二:setContentView

简介: Android Activity 启动流程 二:setContentView

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。

@TOC

在这里插入图片描述

- > 上 篇,Activity创建后,还只是调用了onCreate方法,页面并没有展示出来,还需要调用setContentView方法,加载页面布局,并进行渲染,最后展示。

一、概览

本源码基于Android 12
看代码前,我们先上一张Activity,Window, DecorView三者之间的关系图
在这里插入图片描述

DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。
在该布局下面,有标题view和内容view两个子元素。

Activity setContentView 核心就是PhoneWindow的setContentView方法,其主要干了两件事:
1.完成DecorView的创建与加载,这个DecorView会在后面onresume后添加到window中
2.将MainActivity的布局加载到DecorView内的一个ViewGroup中

创建DecorView,即installDecor方法,其内部用到了两个核心的方法:
1.generateDecor方法创建出DecorView对象
2.generateLayout方法完成这个DecorView对象的布局加载,并完成了MainActivity的父容器的赋值(即contentParent变量)

先上一张流程图
在这里插入图片描述

二、setContentView()

我们跟踪一下源码,看看这个方法是怎么做的

    public void setContentView(View view) {
   
   
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

这里window即为 PhoneWindow,
window的初始化是在 Acticity 创建的时候初始化, 在Acticity对象创建后,会调用attach方法


    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    final void attach(Context context, ActivityThread aThread, ...) {
   
   
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    }

PhoneWindow.java


    @Override
    public void setContentView(int layoutResID) {
   
   
        根view 为空,则初始 mDecor view
        if (mContentParent == null) {
   
   
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
   
   
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
   
   
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
   
   

            // 将布局文件添加到 mContentParent
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
   
   
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

    调用installDecor()进行DecorView的初始化
    private void installDecor() {
   
   
        mForceDecorInstall = false;
        if (mDecor == null) {
   
   
            // 创建出一个DecorView并返回
            mDecor = generateDecor(-1);

        } else {
   
   
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
   
   
            //对mContentParent进行赋值,作为Activity布局的父容器,
            mContentParent = generateLayout(mDecor);

        }
    }

首先判断了mContentParent是否为null,如果为空则执行installDecor()方法,同时初始化一个mContentParent,这个就是Activity布局的父容器

三、inflate

LayoutInflater.java

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
   
   
        final Resources res = getContext().getResources();
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
   
   
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource);
        try {
   
   
            return inflate(parser, root, attachToRoot);
        } finally {
   
   
        }
    }

    private @Nullable
    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root, boolean attachToRoot) {
   
   
        try {
   
   
            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
            Method inflater = clazz.getMethod(layout, Context.class, int.class);
            View view = (View) inflater.invoke(null, mContext, resource);

            if (view != null && root != null) {
   
   
                XmlResourceParser parser = res.getLayout(resource);
                try {
   
   
                    AttributeSet attrs = Xml.asAttributeSet(parser);
                    advanceToRootNode(parser);
                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);

                    if (attachToRoot) {
   
   
                        root.addView(view, params);
                    } else {
   
   
                        view.setLayoutParams(params);
                    }
                } finally {
   
   
                    parser.close();
                }
            }

            return view;
        } catch (Throwable e) {
   
   
        } finally {
   
   
        }
        return null;
    }

布局就是这么添加进mContentParent中的。
在这里插入图片描述

但是,view还是没有显示出来的,此时代码所做的事情仅仅只是加载了布局,并没有开始view的测量、布局、绘制工作。
对应方法是onMeasure, onLayout, onDraw,这些操作在后面

四、view的绘制展示

每一个Activity组件都有一个关联的Window对象,用来描述一个应用程序窗口。每一个应用程序窗口内部又包含一个View对象,用来描述应用程序窗口的视图。
我们再看下图:
在这里插入图片描述

Activity#onResume()之后才是布局由不可见变为可见的,我们看源码

4.1 Activity.onResume

ActivityThread.java
下面这个方法是在Activity onCreate创建后调用的,handleResumeActivity,不清楚的可以看前面app启动文章.


    @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
   
   


        // 这个方法会调用 activity的 onResume 方法
        if (!performResumeActivity(r, finalStateRequest, reason)) {
   
   
            return;
        }

        final Activity a = r.activity;

        // window 未被添加进 windowmanager
        if (r.window == null && !a.mFinished && willBeVisible) {
   
   

            // window
            r.window = r.activity.getWindow();

            // decorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);

            ViewManager wm = a.getWindowManager();

            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;

        } else if (!willBeVisible){
   
   
        }

        if (a.mVisibleFromClient) {
   
   
            if (!a.mWindowAdded) {
   
   

                // DecorView 添加到 window
                wm.addView(decor, l);
            } else {
   
   
                a.onWindowAttributesChanged(l);
            }
        }

        if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
   
   

            使布局可见
            if (r.activity.mVisibleFromClient) {
   
   
                r.activity.makeVisible();
            }
        }

        r.nextIdle = mNewActivities;
    }

在上面的代码中,会先调用Activity的onResume, 然后再是view的绘制,最后将DecorView 设置 可见;

4.2 WindowManager addView

WindowManagerImpl.java


    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
   
   
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

这里也是一个空壳代码,调用WindowManagerGlobal

WindowManagerGlobal.java

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
   
   


        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;


        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);

            // do this last because it fires off messages to start doing things
            try {
   
   
                // //调用ViewRootImpl.setView方法,把DecorView作为参数传递进去
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
   
   
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
   
   
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

在方法内部,会通过跨进程方式向WMS(WindowManagerService)发起一个调用,从而将DecorView最终加到Window上,在这个过程中,ViewRootImpl、DecorView和WMS会彼此关联。
最后,WMS调用ViewRootImpl.performTraversals 方法开始View的测量、布局、绘制。

4.3 ViewRootImpl

一个 Window 对应着一个 ViewRootImpl 和 一个 VIew。这个 View 就是被 ViewRootImpl 操作的.

从上面代码,我们可以看到,ViewRootImpl的初始化是在WindowManagerGlobal的addView中

ViewRootImpl.java


/**
 * We have one child
 */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
   
   
        synchronized (this) {
   
   
            if (mView == null) {
   
   
                mView = view;

                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // 刷新布局的操作,触发view的measure -> layout -> draw 操作
                requestLayout();


                try {
   
   
                    //将 View 添加到 WMS 中
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, ...);

                } catch (RemoteException e) {
   
   

                } finally {
   
   

                }

                // Set up the input pipeline. 设置了一系列的输入通道
                CharSequence counterSuffix = attrs.getTitle();
                mSyntheticInputStage = new SyntheticInputStage();
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);

            }
        }
    }

首先会调用requestLayout方法来刷新布局,然后将 View 添加到 WMS 中,最后是view事件的处理;
view事件的处理,最后还是会回到了 PhoneWindow 中的 DecorView 来处理,剩下的就是从 DecorView 开始将事件层层传递给内部的子 View 中了
这里就不展开

ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
   
   

    }
    @Override
    public void requestLayout() {
   
   
        if (!mHandlingLayoutInLayoutRequest) {
   
   
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    final class TraversalRunnable implements Runnable {
   
   
        @Override
        public void run() {
   
   
            doTraversal();
        }
    }

    void doTraversal() {
   
   
        if (mTraversalScheduled) {
   
   

            performTraversals();

        }
    }

requestLayout()最终会调用到performTraversals,在这个方法中会调用 View 的 measure() ,layout() ,draw() 方法。
我们看下面源码


private void performTraversals() {
   
   

    final View host = mView;

    if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
            || mForceNextWindowRelayout) {
   
   


        try {
   
   
            if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
   
   

                performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),
                        !mFirst, INVALID_DISPLAY /* same display */);
            }


        } catch (RemoteException e) {
   
   
        }

        if (!mStopped || wasReportNextDraw) {
   
   
                //View 的测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);


                if (measureAgain) {
   
   
                    //View 的测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }

                layoutRequested = true;
            }
        }
    } else {
   
   

    }


    if (didLayout) {
   
   
        // View 的布局
        performLayout(lp, mWidth, mHeight);


    }


    if (!cancelDraw) {
   
   
        // View 的绘制
        performDraw();
    } else {
   
   
    }

    mIsInTraversal = false;
}

4.4 addWindow & makeVisible

com.android.server.wm.Session.java


    @Override
    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
   
   
        return mService.addWindow(this, window, attrs, viewVisibility, displayId,
                UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
                outActiveControls);
    }

Activity.java


    把DecorView的状态设置为可见,那么布局也就可见了
    void makeVisible() {
   
   
        if (!mWindowAdded) {
   
   
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

五、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

相关文章
|
3月前
|
Android开发 开发者
Android UI设计: 请解释Activity的Theme是什么,如何更改应用程序的主题?
Android UI设计: 请解释Activity的Theme是什么,如何更改应用程序的主题?
44 1
|
3月前
|
数据库 Android开发 开发者
Android基础知识:请解释Activity的生命周期。
Android基础知识:请解释Activity的生命周期。
45 2
|
6月前
|
存储 SQL 人工智能
Android Activity启动流程一:从Intent到Activity创建
Android Activity启动流程一:从Intent到Activity创建
|
6月前
|
SQL 人工智能 移动开发
Android应用启动流程:从启动到可交互的过程解析
Android应用启动流程:从启动到可交互的过程解析
|
2月前
|
Android开发
[Android 四大组件] --- Activity
[Android 四大组件] --- Activity
22 1
|
3月前
|
Android开发
Android基础知识:什么是Fragment?与Activity的区别是什么?
Android基础知识:什么是Fragment?与Activity的区别是什么?
293 54
|
4月前
|
XML 安全 Java
Android Studio App开发入门之活动Activity中为活动补充附加信息讲解及实战(附源码 超详细必看)
Android Studio App开发入门之活动Activity中为活动补充附加信息讲解及实战(附源码 超详细必看)
36 0
|
4月前
|
Android开发
Android Studio App开发入门之在活动之间传递消息(附源码 超详细必看)(包括显示和隐式Intent,向上一个和下一个Activity发送数据)
Android Studio App开发入门之在活动之间传递消息(附源码 超详细必看)(包括显示和隐式Intent,向上一个和下一个Activity发送数据)
46 0
|
4月前
|
Android开发
Android Studio APP开发入门之活动Activity中启停活动页面的讲解及实战(附源码,包括Activity的启动结束、生命周期、跳转等)
Android Studio APP开发入门之活动Activity中启停活动页面的讲解及实战(附源码,包括Activity的启动结束、生命周期、跳转等)
43 0
|
5月前
|
Android开发
安卓activity管理器
安卓activity管理器
27 0