美文网首页
应用 Activity 界面 布局层次 分析(2) - setC

应用 Activity 界面 布局层次 分析(2) - setC

作者: 行走中的3卡 | 来源:发表于2022-11-25 15:47 被阅读0次

应用 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, ActivityThreadH (android.app) dispatchMessage:106, Handler (android.os) loopOnce:201, Looper (android.os) loop:288, Looper (android.os) main:7829, ActivityThread (android.app) invoke:-1, Method (java.lang.reflect) run:548, RuntimeInitMethodAndArgsCaller (com.android.internal.os)
main:982, ZygoteInit (com.android.internal.os)

相关文章

网友评论

      本文标题:应用 Activity 界面 布局层次 分析(2) - setC

      本文链接:https://www.haomeiwen.com/subject/kprxfdtx.html