笔沫

有梦为马,随处可栖

  • 主页
  • 技术
  • 随想
  • 音乐
所有文章 其他

笔沫

有梦为马,随处可栖

  • 主页
  • 技术
  • 随想
  • 音乐

Activity布局加载流程源码分析(I)

2017-12-29

最近阅读Android源码,似乎有点发现新大陆的感觉。以前经常接触Android知识,在阅读源码中,开始变得豁然开朗。前两天才写完两篇博文Activity启动流程源码分析(应用中)和Activity启动流程源码分析(Launcher中),今天,就急不可耐的想写写Activity布局加载流程,其实,也就是想趁热打铁,好好梳理梳理这部分知识。

在开始梳理之前,我们需要了解一些概念,如:

  • Window: 是一个抽象类,表示是一个窗口。Android系统中的界面,也都是以窗口的形式存在的。
  • PhoneWindow: 是Window类具体实现类,Activity中布局加载逻辑主要就是在此类中完成的。
  • WindowManager: 是Window的管理类,管理着Window的添加、更新和删除。
  • WindowManagerService(AMS):是系统窗口管理服务类,具体管理着系统各种各样的Window.
  • DecorView:是Window的顶级View,主要负责装载各种View。

一、Activity布局加载分析

我们知道,设置Activity布局内容,主要是在Activity的onCreate()中调用setContentView()方法,下面让我们来看看此方法

1
2
3
4
5
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);//核心代码
initActionBar();
}

这里主要调用了getWindow().setContentView()方法,我们来看看Activity中getWindow()

1
2
3
public Window getWindow() {
return mWindow;
}

由此知mWindow是Activity一个属性变量,在前面Activity启动流程介绍中,我们知道在Activity启动前都会先调用attach(),而这mWindow就是在attach初始化的时候赋值的,我们来看看Activity的attach源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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) {
attachBaseContext(context);
mFragments.attachActivity(this);
mWindow = PolicyManager.makeNewWindow(this);//核心代码
......
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}

这里我们来关注一下PolicyManager.makeNewWindow(this)方法,创建Window,我们来看看PolicyManager类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
static {
try {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();//反射初始化Policy
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
} catch (InstantiationException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
}
}
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context); //核心方法
}
.......
}

由上易知,这里主要是通过反射初始化Policy,然后利用设计模式里氏替换原则调用Policy的makeNewWindow()方法,我们继续来看Policy中的方法

1
2
3
4
5
6
7
8
9
public class Policy implements IPolicy {
........
public PhoneWindow makeNewWindow(Context context) {
return new PhoneWindow(context);//核心代码
}
......
}

我们可以发现mWindow其实就是PhoneWindow,在Activity中getWindow().setContentView()方法,就是调用PhoneWindow中的setContentView方法,所以我们这里来看看PhoneWindow中的setContentView()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null){
installDecor();//1.安装装饰器
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);//2.填充我们的布局文件
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}

从注释2,我们知道布局填充器mLayoutInflater向mContentParent填充我们的布局内容,而mContentParent是一个ViewGroup,它是怎么赋值的呢?这里我们要来看注释1,当mContentParent为空时,会安装装饰器,我们继续来看phoneWindow中installDecor()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//1.生成装饰器
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//2.对填充我们布局的ViewGroup赋值
mDecor.makeOptionalFitsSystemWindows();
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
.......
} else {
mTitleView.setText(mTitle);//设置Activity的title
}
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
if (mActionBar != null) {
.......mActionBar的处理
}
}
}
}

首先,我们先来看看注释1装饰器的生成方法generateDecor()

1
2
3
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}

这里主要就是装饰器DecorView的初始化,我们再来看一下DecorView的源码

1
2
3
4
5
6
7
8
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
......
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
}
.......

DecorView类包括内容还有许多,这里就不介绍了,我们只需知道DecorView是PhoneWindow的内部类,DecorView继承于FrameLayout,实现RootViewSurfaceTaker接口。下面我们来看一下mContentParent的生成,即generateLayout(mDecor)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
protected ViewGroup generateLayout(DecorView decor) {
.......//初始化一些window属性
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
//通过判断Activity的不同feature加载不同的系统默认布局
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
//带进度系统布局
layoutResource = com.android.internal.R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.R.layout.screen_custom_title;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
//无Titile系统布局
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
} else {
layoutResource = com.android.internal.R.layout.screen_action_bar;
}
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = com.android.internal.R.layout.screen_simple;//1.一般系统布局
}
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));//2.向装饰View加入系统布局View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//3.获取我们Activity能填充的内容ViewGroup
......
}
mDecor.finishChanging();
return contentParent;
}

这里我们来看一下,系统默认的几种Activity的头部布局xml文件,布局文件的源码位置为:android4.1.1_r1\frameworks\base\core\res\res\layout,我们挑两个文件来看一下:

第一个screen_title.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

第二个,screen_simple.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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" />
<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>

通过对比,我们发现这两个布局文件都有一个共同id为@android:id/content的FrameLayout,其实这也就是我们Activity布局填充容器。我们还发现,这两个布局父布局都是一个线性布局LinearLayout,并且方向都是垂直的,这也验证了我们Activity内容布局一般都是状态栏的下边的模式。我们再来看后面的代码

1
2
3
4
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));//2.向装饰View加入系统布局View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//3.获取我们Activity能填充的内容ViewGroup

这里向装饰器添加了系统布局View,并从系统布局View中获取了Activity填充内容的容器ViewGroup。其中ID_ANDROID_CONTENT就是com.android.internal.R.id.content,通过(ViewGroup)findViewById(ID_ANDROID_CONTENT)就获取了布局文件中的FrameLayout,即Activity内容填充布局的ViewGroup。这样我们再回到PhoneWindow的setContentView方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);//核心代码
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}

现在mContentParent已经赋完值了,再通过布局填充器mLayoutInflater的inflate()方法,这样我们就把Activity的布局文件添加到装饰器上了。然而,现在虽然装饰器DecorView上已经有了Activity布局内容,但是是什么时候添加到Window上的呢?这里就需要了解Activity的启动流程,在Activity的启动流程最后几步会执行ActivityThread中handleLaunchActivity()方法,我们接着此方法继续分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
.......
Activity a = performLaunchActivity(r, customIntent);//1.创建Activity实例
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward);//2.调用Activity onResume方法
.......
} else {
.......
}
}

在注释1处,已经建立Activity的实例,并且执行Activity生命周期的attach()和onCreate()方法。我们知道setContentView()也就在onCreate()方法中调用的,所以这个时候,我们Activity布局文件内容已经装入了装饰器DecorView中,接下来就是把DecorView和Window关联起来,所以下面我们继续来看handleResumeActivity()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
ActivityClientRecord r = performResumeActivity(token, clearHide);//1.执行Activity的onResume方法
if (r != null) {
final Activity a = r.activity;
........
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(a.getActivityToken());//2.Activity显示可见
} catch (RemoteException e) {
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);//3.通过WindowManager将DecorView加入Window,从而显示Window,Activity变为可见。
}
} else if (!willBeVisible) {
r.hideForNow = true;
}
cleanUpPendingRemoveWindows(r);
if (!r.activity.mFinished && willBeVisible&& r.activity.mDecor != null && !r.hideForNow) {
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);//4.更新DecorView,更新Activity界面
}
}
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
.......
} else {
.......
}
}

在注释1处,调用performResumeActivity(token, clearHide)方法,实际上就是调用activity生命周期的onResume()方法。注释2处,通过Binder跨进程通信,调用ActivityManagerService中willActivityBeVisible()获取显示Activity的控制开关,从而在注释3处,通过WindowManager添加装饰器DecorView到Window,然后,再调用相关View的绘制流程,这样一个有布局的Activity就被加载出来了。

到这里,Activity布局加载流程就是梳理完了。

注:源码采用android-4.1.1_r1版本,建议下载源码然后自己走一遍流程,这样更能加深理解。

二、参考文档

Binder通信机制原理解析

Activity启动流程源码分析(应用中)

Activity启动流程源码分析(Launcher中)

赏

感谢认可,么么哒

支付宝
微信
  • 技术
  • Android
  • Android框架源码解析

扫一扫,分享到微信

微信分享二维码
Activity布局加载流程源码分析(II)
Activity启动流程源码分析(Launcher中)
© 2019 笔沫
Hexo Theme Yilia by Litten
  • 所有文章
  • 其他

tag:

  • 技术
  • Android
  • Activity的启动模式
  • Android注解
  • Java反射机制
  • Java动态代理
  • Android基础
  • MVP
  • NDK
  • JNI C/C++
  • 开源框架
  • AS Gradle优化
  • 博客搭建
  • 设计模式
  • Fragment
  • Rxjava
  • Rxandroid
  • 响应式编程
  • 随想
  • 你本是一个肉体,是什么驱使你前行
  • 原创
  • 笔沫拾光
  • Java
  • Java基础
  • 王阳明心学
  • 中国历史
  • 人生的意义
  • 演讲
  • 执着的人是幸福的
  • 郭川
  • Android框架源码解析
  • 加解密算法
  • Binder通信机制
  • 开源框架源码解析
  • LeakCanary框架源码分析
  • Java集合类
  • 转载
  • Http文件断点续传
  • Logger框架源码解析
  • Android应用程序入口源码解析
  • DecorView绘制流程
  • Android消息机制源码解析
  • Activity启动流程
  • Butterknife框架源码解析
  • 我之存在,因为有你
  • 霍华德*舒尔茨
  • Android相机
  • Camera
  • 悬浮窗
  • WindowManager

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 风光摄影
  • 星空摄影
  • 人像摄影
  • 学习站点