Android Activity setContentView()源码分析

Android Activity setContentView()源码分析

在安卓开发中,我们写一个Activity时,通常会继承自Activity 或AppCompatActivity,在重写他们的onCreate()方法中通过setContentView(int layoutResID)设置我们的布局文件,然后布局文件就会被加载显示到我们的页面中,今天就来探究一下,setContentView到底都做了什么.

这里需要说明,我们自己的Activity 继承系统 Avtivity 或者 AppCompatActivity的时候,它们的流程会有些许不同,下面我们就进行分析.

本篇基于Android 源码31 及 androidx.appcompat:appcompat:1.3.0分析

setContentView(int layoutResID)在什么时候被调用?

首先,我们知道setContentView()是在onCreate()中调用的.onCreate()是由系统调用,下面我们看看系统是如何调用的.这里不会对AMS进行过多展开分析,只是大概了解下调用时机.

当我们启动一个Activiy时,系统会在ActivityThread中调用performLaunchActivity().下面拿出源码 忽略一些流程,只说大概流程

​ 1.mInstrumentation 会根据Activity 相关信息创建Activity对象

​ 2.activity绑定相关信息(下面用到时,再说)

​ 3.mInstrumentation 调用 callActivityOnCreate()

​ 4.mInstrumentation 调用Activity performCreate()

​ 5. 最终再Activity performCreate()方法中,我们看到,这里调用了Activity的OnCreate()

ActivityThread: 
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //... 忽略代码
     
     	//创建出Activity 对象
        Activity  activity = mInstrumentation.newActivity(...);
         
     	//... 忽略代码

            if (activity != null) {
                //....
                
             	//activity 绑定相关信息
                activity.attach(...);
                
                //....
                
                //调用Activity onCreate
				mInstrumentation.callActivityOnCreate(activity, r.state);
               
                //....
                
            }
     
    	 //....
     
        return activity;
    }


Instrumentation:
  public void callActivityOnCreate(Activity activity, Bundle icicle,
            PersistableBundle persistentState) {
        prePerformCreate(activity);
        activity.performCreate(icicle, persistentState);
        postPerformCreate(activity);
    }

Activity:
	  final void performCreate(Bundle icicle, PersistableBundle persistentState) {
        // ...
        if (persistentState != null) {
            onCreate(icicle, persistentState);
        } else {
            onCreate(icicle);
        }
      	//...
    }

了解了onCreate是怎么被调用的,下面我们就开始分析onCreate被调用时 setContentView()的流程

extends Activity

首先,我们看到Activity中setContentView()会调用.

 Activity:
 public void setContentView(@LayoutRes int layoutResID) {
        //此处调用了getWindow() 的setContentView()
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
  }


 public Window getWindow() {
        return mWindow;
  }

这里调用了Window对象的setContentView(),Window是一个抽象类,那我么找找他赋值的地方,通过搜索,我们看到mWindow赋值的地方在attach调用的时候,这就说明,在我们调用onCreate()之前,mWindow已经创建成功,他是实例对象是PhoneWindow.

  	
	@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
      	// ....
        
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        
        //....
    }

那么,其实Activity setContentView 调用的是PhoneWindow 的setContentView().

而这里我们需要关心的有两步操作:

1.根据mContentParent判断去初始化DecroView和mContentParent.

2.将页面xml添加到mContentParent中.

    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
        	//初始化DecorView和mContentParent
            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 {
        	//将我们页面xml 加载到mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

可以看到,最终我们页面设置的xml布局文件,会被加载到mContentParent中去.而这个mContentParent就是再installDecor()中创建的.

	PhoneWindow:
	
	private void installDecor() {
       	// ...
        if (mDecor == null) {
        	//这里初始换创建了DecorView
            mDecor = generateDecor(-1);
        } 
        // ...
        
        if (mContentParent == null) {
        	//这里就是给mContentParent 初始化
            mContentParent = generateLayout(mDecor);    
         } 
        
        // ...
    }

	//这里返回new 一个DecorView
 	protected DecorView generateDecor(int featureId) {
        // ...
        return new DecorView(context, featureId, this, getAttributes());
    }

generateDecor() 直接是返回new 出来的一个DecorView

generateLayout(),是根据Window的style 去设置一些Flag 以及初始化一些PhoneWindow的默认属性

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

       	//.....

        // Inflate the window decor.
		
    	//定义一个xml资源id,根据window的一些默认特征选择使用哪个xml
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            
        } 
    	
    
    		//.....
    	
    
    	else {
           	//这里我们就分析这个布局文件
            layoutResource = R.layout.screen_simple;
            
        }

        mDecor.startChanging();
    	//调用DecorView的onResourcesLoaded()将资源文件加载到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
		
    	//获取DecorView中 id为android.R.id.content 的ViewGroup 最后将他返回赋值给mContentParent
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
     	
    	// ....

        mDecor.finishChanging();
		//返回赋值给 mContentParent
        return contentParent;
    }

接下来,我们看看DecorView onResourcesLoaded, 它将xml填充成View,在添加到DecorView中.

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
      
		// ....
       
        //这里使用LayoutInflate 将传递进来的xml填充成View
        final View root = inflater.inflate(layoutResource, null);
        
      	//...

        // 将View 最后添加到DecorView中
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        
        //...
    }

接下来我们先看看DecorView 添加的View ,screen_simple.xml 布局是什么样子,一个ViewStub (ActionBar) ,一个FrameLayout 就是上面我们获取的contentParent

<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">
       //一个ActionBar 	
    <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" />
     //这里我们看到,上面获取    contentParent 的控件就是此控件          
    <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>

至此,DecorView 和mCotentParent 都已初始化完毕.这里就再返回到PhoneWindow的setContentView 第二步,这时mContentParent已经初始化成功,利用LayoutInflate最后将Activity 传进来的布局xml填充到mContentParent(也就是DecorView)中,至此,setContentView流程结束.

注意:此时只是将xml填充到了DecroView中,还并未在屏幕中绘制,处于不可见状态.

此时,Activity 结构应该是如此:

在这里插入图片描述

继承自Activity页面 setContentView 流程图

在这里插入图片描述

extends AppCompatActivity

AppCompatActivity 的流程会与Activity有少许不同,但他也只是在Activity上做了一层包装,接下来我们就看看

AppCompatActivity 调用时,不是直接通过Window 调用setContentView,而是通过一个AppCompatDelegate去调用

 AppCompatActivity :	
	@Override
    public void setContentView(@LayoutRes int layoutResID) {
        initViewTreeOwners();
        //通过AppCompatDelegate setContentView
        getDelegate().setContentView(layoutResID);
    }

	//此方法再 AppCompatActivity 构造函数中也已被调用过,mDelegate再构造中已被初始化
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

AppCompatDelegate:

 	@NonNull
    public static AppCompatDelegate create(@NonNull Activity activity,
            @Nullable AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, callback);
    }

最终实际调用 AppCompatDelegateImpl.setContentView()

​ 此时核心步骤有两步:

​ 1.创建并初始化subDecor

​ 2.获取subDecor中id为android.R.id.content的 ViewGroup 并将页面传递进来的xml 布局填充进去.

   @Override
    public void setContentView(int resId) {
    	//创建SubDecor --> 里面调用createSubDecor()
        ensureSubDecor();
        //获取subDecor中id为android.R.id.content的 ViewGroup 并将页面传递进来的xml 布局填充进去
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
  

先看第一步,创建初始化subDecor,核心步骤

  1. 通过PhoneWindow 初始化DecorView 和 mContentParent (此步骤和继承Activity是流程相同)
  2. 根据window特质 选择对应xml填充subDecor
  3. 获取DecorView中 android.R.id.content 控件windowContentView,获取subDecor中R.id.action_bar_activity_content控件contentView,并将windowContentView的id清除,将contentView 的id 设置为android.R.id.content
  4. 将subDecor 通过PhoneWindow 添加到DecorView中
 private ViewGroup createSubDecor() {
     	//获取主题相关配置
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
            //此处可以看到,当我们使用AppcompatActivty时,这里会校验我们使用的主题是不是AppCompat主题,不是的话就会抛异常
            a.recycle();
            throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }
		
     	//根据主题,设置window的一些特征标志
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
        } 
     
     	//.....
     
        a.recycle();

       	//1.此处和Activity不住差不多,就是通过PhoneWindow 创建DecorView 和 mContentParent
        ensureWindow();
        mWindow.getDecorView();

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

			
      	// 2.根据window的一些特征标志 选择subDecor填充的xml布局并填充
         subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        

    	// ....

       	
     	//3.获取刚填充布局的R.id.action_bar_activity_content 控件
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
		//获取Decorview中id为android.R.id.conten控件
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
           	//循环遍历 将DecorVierw 中windowContentView子View 取出全部添加到subDecor中
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }

            //将原 DecorView id 为android.R.id.content 控件的 id清除
            windowContentView.setId(View.NO_ID);
            //将subDecor中id 为R.id.action_bar_activity_content 的控件id 改为android.R.id.content,用以后面获取添加Activity的布局
            contentView.setId(android.R.id.content);

           	//...
        }

        // Now set the Window's content view with the decor
     	//4.最后将subDecor通过PhoneWindow 添加到DecorView中的mContentParent中
        mWindow.setContentView(subDecor);

       	// ...
		//最后返回subDecor
        return subDecor;
    }

步骤3中xml布局

abc_screen_simple.xml
<androidx.appcompat.widget.FitWindowsLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/action_bar_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <androidx.appcompat.widget.ViewStubCompat
        android:id="@+id/action_mode_bar_stub"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/abc_action_mode_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
	//此处引入下面的布局, action_bar_activity_content 这个布局就是AppcompatActivity 的xml的父布局,上面不管最终选择哪个布局,
	//最终布局里面都包含有这块
    <include layout="@layout/abc_screen_content_include" />

</androidx.appcompat.widget.FitWindowsLinearLayout>


abc_screen_content_include.xml:

<merge xmlns:android="http://schemas.android.com/apk/res/android">
	//放置Activity xml的父布局
    <androidx.appcompat.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />

</merge>


至此,subDecor 创建并初始化成功,也添加到了DecorView中,此时返回AppCompatDelegateImpl.setContentView()步骤2中,获取id为android.R.id.content的控件,也就是我们最后改的subDecor 的ContentFrameLayout,将页面传递的xml布局加载到ContentFrameLayout,至此 setContentView流程结束.

此时AppCompatActivity结构图如下:

在这里插入图片描述

流程图:

在这里插入图片描述

结尾

本篇,仅分析了setContentView()流程,此时,并未绘制到屏幕上,后续会再分析绘制流程.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值