Android源码阅读分析:从Activity开始(二)——加载布局
从Activity开始(二)——加载布局
(注:源代码为android-8.1)
0. 前言
本篇文章主要讲解了当Activity
创建后,如何加载布局。
如果想了解Activity
是如何被启动起来的,可以参考我之前的文章:Android源码阅读分析:从Activity开始(一)——启动流程。
1. 代码分析
通常情况下,我们在Android Studio里新建一个Activity
时,会自动生成如下代码:
public class MainActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}
在Activity
创建后,通常情况会调用setContentView
方法。查看Activity
的源码时,我们发现,setContentView
有三个同名重载方法,分别是
- setContentView(@LayoutRes int layoutResID)
- setContentView(View view)
- setContentView(View view, ViewGroup.LayoutParams params)
通常情况下,我们会使用第一个方法。下面对该方法进行跟踪。
(frameworks/base/core/java/android/app/Activity.java)public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar();}
getWindow
方法返回一个Window
类的对象。Window
类是一个抽象类。从上一篇文章中可知,Activity
创建时调用了attach
方法,在attach
方法中创建了一个PhoneWindow
对象。下面就查看PhoneWindow
类中的setContentView
方法。
(frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java)@Overridepublic void setContentView(int layoutResID) { // 创建或清空顶层容器 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;}
对于新建的Activity
,顶层容器肯定是空的,那么就会调用installDecor
方法。而后将用户定义的布局载入到mContentParent
中。这个mContentParent
是如何被创建的,我们在下文探讨。这里先看一下installDecor
方法的实现。
(frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java)private void installDecor() { ... if (mDecor == null) { // 产生新的DecorView mDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { mDecor.setWindow(this); } if (mContentParent == null) { // 产生新的顶层容器 mContentParent = generateLayout(mDecor); ... }}
DecorView
类继承自FrameLayout
,而frameLayout
则是ViewGroup
的子类。也就是说,DecorView
是布局的根节点,顾名思义,这个根节点的作用是对界面做一些装饰性布局。
而mContentParent
则通过方法则生成。下面跟踪generateLayout
方法。
(frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java)protected ViewGroup generateLayout(DecorView decor) { TypedArray a = getWindowStyle(); // 设置一系列PhoneWindow特征 ... int layoutResource; int features = getLocalFeatures(); // 通过判断features来决定需要载入的layoutResource ... mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 这里的findViewById会调用DecorView的findViewById // 也就是说,此处会获取DecorView加载布局内的Id为content的ViewGroup ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ... return contentParent;}
该方法主要的作用是应用现有主题数据,根据各个style
进行设置,首先是对PhoneWindow
的特征进行设置,之后调用了mDecor.onResourcesLoaded
方法,从这个方法的名字来看,应该是在DecorView
中做了一些有关于资源加载的操作。最后,该方法返回的contentParent
则是DecorView
加载布局内的Id为ID_ANDROID_CONTENT
的ViewGroup
。
下面我们跟踪一下mDecor.onResourcesLoaded
方法的代码看一看。
(frameworks/base/core/java/com/android/internal/policy/DecorView.java)void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { ... final View root = inflater.inflate(layoutResource, null); if (mDecorCaptionView != null) { if (mDecorCaptionView.getParent() == null) { addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); } else { // Put it below the color views. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; ...}
在这个方法中,通过载入布局资源,构建出DecorView
的根View
。
现在我们再来看一下所载入的布局资源。需要载入的布局资源在PhoneWindow.generateLayout
方法中,根据style
的不同,选择了不同的布局文件。这里选择最简单的R.layout.screen_simple
作为例子,该资源对应的布局文件为frameworks/base/core/res/res/layout/screen_simple.xml。
(frameworks/base/core/res/res/layout)<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>
这个布局的根节点是一个LinearLayout
,这意味着DecorView
的根View
是一个LinearLayout
。而Id为content
的View
是一个FrameLayout
,而从上文的分析中可以知道,这个FrameLayout
就是承载用户定义布局的容器。
2. 总结
本文简单的分析了一下Activity
的布局加载逻辑。
Activity
加载布局的方法调用逻辑比较简单。如下图所示:
布局的加载主要是在Activity
、PhoneWindow
、DecorView
这三个类中进行的。通过分析源码,我们可以清晰的看出,在Activity
中的布局结构如下图所示。
界面的一些装饰性布局,比如ActionBar
等,也是在DecorView
中,与content
同级。
在布局加载的调用逻辑中,最终将资源文件转换为布局的方法是LayoutInflater.inflate
方法。我会在后续文章中详细分析该方法是如何实现的:《Android源码阅读分析:从资源文件到控件布局——LayoutInflater分析》
更多相关文章
- Android 获取内外置存储卡方法
- Android Junit 单元测试、异步测试方法简介及异步测试框架指南
- Android动态加载jar、apk的实现
- Android打包成jar文件方法总结
- android的应用程序调用另一个应用程序的方法