Android Activity 启动流程 二:setContentView

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

在这里插入图片描述

- > 上 篇,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学习专栏]

相关文章
|
2月前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
94 6
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
2月前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
Android面试高频知识点(4) 详解Activity的启动流程
28 3
|
2月前
|
缓存 前端开发 Android开发
Android实战之如何截取Activity或者Fragment的内容?
本文首发于公众号“AntDream”,介绍了如何在Android中截取Activity或Fragment的屏幕内容并保存为图片。包括截取整个Activity、特定控件或区域的方法,以及处理包含RecyclerView的复杂情况。
24 3
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
26 2
|
2月前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
44 3
|
2月前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
18 0
|
3月前
|
Android开发 开发者
Android面试之Activity启动流程简述
每个Android开发者都熟悉的Activity,但你是否了解它的启动流程呢?本文将带你深入了解。启动流程涉及四个关键角色:Launcher进程、SystemServer的AMS、应用程序的ActivityThread及Zygote进程。核心在于AMS与ActivityThread间的通信。文章详细解析了从Launcher启动Activity的过程,包括通过AIDL获取AMS、Zygote进程启动以及ActivityThread与AMS的通信机制。接着介绍了如何创建Application及Activity的具体步骤。整体流程清晰明了,帮助你更深入理解Activity的工作原理。
56 0
|
18天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
23天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。