应用 Activity 界面 布局层次 分析
从一个简单 Activity 界面布局入手, 大致分析了 布局层次结构。
发现 Activity 的 setContentView ,而它最终 是 调用 PhoneWindow 的同名方法.
因此,本文从 源码 角度,进一步剖析 PhoneWindow.setContentView,
目的是深入了解 DecorView 是在哪里创建的, 它对应的布局资源是哪个, 如何获取布局资源.
这里,我们不涉及如何 将 应用的 布局资源,设置到 DecorView中 以如何添加到Window中.
重点是介绍 android.content 的父布局, 即 DecorView 的 布局资源.
1.PhoneWindow.setContentView
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
package com.android.internal.policy;
public class PhoneWindow extends Window implements MenuBuilder.Callback {
ViewGroup mContentParent;
public void setContentView(int layoutResID) {
if (mContentParent == null) {//(1)
installDecor();
}
}
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor); //(2)
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);//(5)
}
}
protected DecorView generateDecor(int featureId) {
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) { //(2)
TypedArray a = getWindowStyle();
...
// Inflate the window decor.
int layoutResource;//(3)
if () {
} else if (...){
...
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);//(3)
}
...
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//(4)
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);// com.android.internal.R.id.content;
return contentParent;
}
分析这几段关键代码:
(1) setContentView 判断的 mContentParent 正是 android.id.content , 即我们要设置的内容
(2) generateLayout 先读取当前主题的 window 属性值(getWindowStyle), 例如是否为 浮动窗口, 设置标志Flag等.
(3) 根据当前主题的设置, 获取对应的布局文件id
(4) 将(3) 获取到的布局文件, 传入 DecorView 对象,即作为 DecorView 的布局资源文件.
(5) 经过(4), 已经将布局资源设置给 DecorView, 可以从它里面读取出 content 的父布局:decor_content_parent
2. DecorView.onResourcesLoaded 设置 资源文件
上述 1-(3)、1-(4) 中可知 DecorView 的资源加载 是由 PhoneWindow 中调起的.
DecorView 的 onResourcesLoaded 负责加载
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
final View root = inflater.inflate(layoutResource, null);
...
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); // ViewGroup 继承 ViewManager
mContentRoot = (ViewGroup) root;
}
3. 进一步剖析 DecorView 的资源文件
decor_content_parent 布局id 所在的xml文件 即为 DecorView 的 xml
从源码上搜索, decor_content_parent 在两个 xml 文件上定义:
screen_action_bar.xml 和 screen_toolbar.xml。
而这两个文件又在 主题(themes.xml) 中 被引用.
因此, decor_content_parent 间接地 由 主题决定, 对应了 1-(3) .
其中 screen_action_bar 在下面中被引用:
/frameworks/base/core/res/res/values/themes.xml
<style name="Theme">
<item name="windowActionBarFullscreenDecorLayout">@layout/screen_action_bar</item>
...
screen_toolbar 在下面中被引用:
//frameworks/base/core/res/res/values/themes_material.xml
<style name="Theme.Material">
<item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item>
...
<style name="Theme.Material.Light" parent="Theme.Light">
<item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item>
3.1 DecorView 对应的 布局文件 源码
即screen_action_bar.xml 和 screen_toolbar.xml
(1) /frameworks/base/core/res/res/layout/screen_action_bar.xml
<com.android.internal.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:splitMotionEvents="false"
android:theme="?attr/actionBarTheme">
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.internal.widget.ActionBarContainer
android:id="@+id/action_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
android:touchscreenBlocksFocus="true"
android:keyboardNavigationCluster="true"
android:gravity="top">
<com.android.internal.widget.ActionBarView
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/actionBarStyle" />
<com.android.internal.widget.ActionBarContextView
android:id="@+id/action_context_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
style="?attr/actionModeStyle" />
</com.android.internal.widget.ActionBarContainer>
<com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/actionBarSplitStyle"
android:visibility="gone"
android:touchscreenBlocksFocus="true"
android:keyboardNavigationCluster="true"
android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>
(2) /frameworks/base/core/res/res/layout/screen_toolbar.xml
<com.android.internal.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:splitMotionEvents="false"
android:theme="?attr/actionBarTheme">
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.internal.widget.ActionBarContainer
android:id="@+id/action_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
android:touchscreenBlocksFocus="true"
android:keyboardNavigationCluster="true"
android:gravity="top">
<Toolbar
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:navigationContentDescription="@string/action_bar_up_description"
style="?attr/toolbarStyle" />
<com.android.internal.widget.ActionBarContextView
android:id="@+id/action_context_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
style="?attr/actionModeStyle" />
</com.android.internal.widget.ActionBarContainer>
</com.android.internal.widget.ActionBarOverlayLayout>
4. 调用栈
在Android Studio 使用 API31 在线调试,
可以看到 setContentView 的调用栈如下所示
(和上面的源码分析稍有差异,
因为这个示例的Activity 使用到了AndroidX类库,但是 PhoneWindow 的逻辑是一样的 ):
generateLayout:2443, PhoneWindow (com.android.internal.policy)
installDecor:2726, PhoneWindow (com.android.internal.policy)
getDecorView:2135, PhoneWindow (com.android.internal.policy)
initViewTreeOwners:219, AppCompatActivity (androidx.appcompat.app)
setContentView:194, AppCompatActivity (androidx.appcompat.app)
onCreate:9, MainActivity2 (com.example.androidapi31fwdebug)
performCreate:8051, Activity (android.app)
performCreate:8031, Activity (android.app)
callActivityOnCreate:1329, Instrumentation (android.app)
performLaunchActivity:3606, ActivityThread (android.app)
handleLaunchActivity:3790, ActivityThread (android.app)
execute:103, LaunchActivityItem (android.app.servertransaction)
executeCallbacks:135, TransactionExecutor (android.app.servertransaction)
execute:95, TransactionExecutor (android.app.servertransaction)
handleMessage:2202, ActivityThreadMethodAndArgsCaller (com.android.internal.os)
main:982, ZygoteInit (com.android.internal.os)
网友评论