Android窗口机制:二、DecorView与SubDecor的创建加载。(源码版本SDK31)

Android 窗口机制 SDK31源码分析 总目录

本篇文章介绍一下DecorView与SubDecor的创建初始化流程。

了解一组概念,Window、PhoneWindow、DecorView、SubDecor.

Window

从前面的介绍中我们知道,每个Activity都会持有一个Window实例,那么Window究竟是什么呢?我们看一下源码以及介绍:

Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.
The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.
public abstract class Window {
    ...
	private WindowManager mWindowManager;
    ...
        
    Convenience for setContentView(View, ViewGroup.LayoutParams) to set the screen content from a layout resource. The 		resource will be inflated, adding all top-level views to the screen.
    Params:
    layoutResID – Resource ID to be inflated.
    See Also:
    setContentView(View, ViewGroup.LayoutParams)
    public abstract void setContentView(@LayoutRes int layoutResID);
    ...
                                              
}

顶级窗口外观和行为策略的抽象基类,这个类的实例应该作为顶级视图被添加到窗口管理器,它提供了标准的 UI 策略,例如背景、标题区域、默认键处理等。

此抽象类的唯一现有实现是 PhoneWindow,你应该在需要 Window 时对其进行实例化。

显而易见,Window是一个抽象基类,它提供一系列窗口的方法,比如设置布局、背景、标题等等,它唯一的子类即为PhoneWindow。

可以看到Window内部会持有WindowManager类型的全局变量mWindowManager

PhoneWindow

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ...
    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    ...
}

PhoneWindow作为Window的唯一实现类,内部持有了DecorView。

我们在Activity中,通过getWindow()方法获取到的就是PhoneWindow的实例。

DecorView

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    ...
    private final int mFeatureId;

    private final Rect mDrawingBounds = new Rect();

    private final Rect mBackgroundPadding = new Rect();
	...
}

显然DecorView是一个FrameLayout,它将作为最顶级的View,由PhoneWindow负责添加。

SubDecor

这个玩意儿是什么呢?如果你看大多数Android窗口分析的话,应该是没有这个东西的,但是现在自己去看代码的话应该能看到这个View。

//AppCompatDelegateImpl 里面创建SubDecor 的方法
private ViewGroup createSubDecor() {
}

因为现在的源码setContentView的操作委托给了AppCompatDelegateImpl去操作,在这里面会创建一个SubDecor,类型是ViewGroup,添加到DecorView里面。所以如果是较新的Android版本的话,我们自己的布局其实是添加到SubDecor里面的。

那上面这些东西是如何进行关联呢?下面从AppCompatActivitysetContentView开始进行源码流程跟踪。setContentView方法有几个重载方法,分别可以传入布局ID或者View或者布局ID+LayoutParams,因为个人习惯使用ViewBinding,所以调用setContentView的话传进去的直接是View了,所以我们就分析接收View的这个重载方法。

AppCompatActivity    
public void setContentView(View view) {
    getDelegate().setContentView(view);
}

调用了getDelegate().setContentView(view),而getDelegate()获取的就是AppCompatDelegateImpl,所以这里其实就是调用到了AppCompatDelegateImpl的setContentView(view)方法。

AppCompatDelegateImpl
public void setContentView(View v) {
    //构建SubDecor
    ensureSubDecor();
    //将我们传进入的View添加进SubDecor
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    contentParent.addView(v);
    //通知内容改变
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

这里首先调用ensureSubDecor方法,构建SubDecor。之后将我们传进来的View添加到SubDecorid为content的ViewGroup中。

所以我们看一下ensureSubDecor方法,看一下SubDecor是如何构建出来的。

AppCompatDelegateImpl
private void ensureSubDecor() {
    ...
    //构建SubDecor 布局
    mSubDecor = createSubDecor();
    //设置标题到mSubDecor布局  getTitle() 方法获取到的就是在AndroidManifest.xml 中给Activity设置的label, 通过这里设置到了Android原生主题的标题
    CharSequence title = getTitle();
    if (!TextUtils.isEmpty(title)) {
        if (mDecorContentParent != null) {
            mDecorContentParent.setWindowTitle(title);
        } else if (peekSupportActionBar() != null) {
            peekSupportActionBar().setWindowTitle(title);
        } else if (mTitleView != null) {
            mTitleView.setText(title);
        }
    }
	//根据主题的宽高,比如windowFixedWidthMajor等,重新设置给mSubDecor布局容器。
    applyFixedSizeWindow();
	...
}

上面的注释应该写的比较明确了,通过createSubDecor方法构建出了SubDecor,下面都是对SubDecor的一些设置,所以我们看一下createSubDecor方法。

AppCompatDelegateImpl
private ViewGroup createSubDecor() {
    ...
    //确保已经存在Window对象,否则抛异常
    ensureWindow();
	//安装DecorView
    mWindow.getDecorView();
    //根据各种有无工具栏等设置subDecor布局
    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;
    ...     //列出其中一个布局,没有标题栏的
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
    ...
	//获取subDecor内的容器
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
        R.id.action_bar_activity_content);
	//获取DecorView内的容器
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
		...
        //兼容:将原来的DecorView内部容器的id设置为NO_ID,将subDecor内部容器id设置为content。所以对于外层调用这来说通过content都可以获取到底层的容器。
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);
		...
    }
    //将subDecor添加到DecorView之中
    mWindow.setContentView(subDecor);
	...
    return subDecor;
}

注释的已经很清楚了吧,我们挑重点来看。首先是调用了ensureWindow方法,该方法主要判断Window对象是否存在,不存在则会抛出异常。接下来调用Window的getDecorView方法,去获取DecorView。之后就是使用LayoutInflater填充SubDecor,这里省略了很多判断布局,总之会根据主题设置有无标题等相关的进行布局填充,比如上述列出填充的布局为abc_screen_simple,该布局没有标题,有一个id为action_bar_activity_content的FramLayout,再下来将它的id更换为content。之后将SubDecor通过Window添加到DecorView之中。

简单分析一下我们需要关注的重点:

  1. DecorView是如何安装的,看getDecorView方法。
  2. SubDecor是如何安装到DecorView的,看setContentView方法。

好,接下来看一下Window的getDecorView方法。通过上面分析我们知道Window的唯一实现类为PhoneWindow,所以我们看PhoneWindow对应的方法。

    PhoneWindow
    public final @NonNull View getDecorView() {
        ...
        installDecor();
        ...
        return mDecor;
    }
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //构建DecorView
            mDecor = generateDecor(-1);
        ...
        if (mContentParent == null) {
            //填充DecorView布局
            mContentParent = generateLayout(mDecor);
        ...
    }
    protected DecorView generateDecor(int featureId) {
         ...
         //直接new 
         return new DecorView(context, featureId, this, getAttributes());
    }
    protected ViewGroup generateLayout(DecorView decor) {
        ...
		//设置flag相关,所以这儿就是为什么falg的设置要在调用setContentView之前
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }
        ...
        if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
                false)) {
            setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                    & (~getForcedWindowFlags()));
        }
		//和前面SubDecor一样,根据主题设置宽高等属性
        ...
        if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
            if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
            a.getValue(R.styleable.Window_windowFixedWidthMajor,
                    mFixedWidthMajor);
        }
        ...

        // DecorView 布局填充  
        int layoutResource;
        ...	
            //这里列出一个最简单的布局 一个title 一个id为content的Framelayout
            layoutResource = R.layout.screen_simple;
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
		//ID_ANDROID_CONTENT 其实就是R.id.content
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
    }        

注释应该也非常明显了,内部核心调用了installDecor进行DecorView的构建,installDecor内部调用了两个核心的方法,一个是generateDecor;一个是generateLayout

根据上面所说,大家应该知道DecorView其实是一个FrameLayout,所以generateDecor方法其实直接new了一个DecorView返回了,并赋值给了mDecor变量。

generateLayout其实就是根据主题各种判断去选择系统的布局,最终加载到了DecorView之中。那么DecorView就创建完成了。

那么DecorView和SubDecor是如何关联的呢?还记得在createSubDecor方法的最后调用了mWindow.setContentView(subDecor)吗?接下来我们看看这个方法干了什么吧。

	PhoneWindow   
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //在getDecorView方法中已经给mContentParent赋值了,所以走这里
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            //将SubDecor添加进DecorView的布局之中
            mContentParent.addView(view, params);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //回调通知表示完成界面改变
            cb.onContentChanged();
        }
        ...
    }

显而易见,将SubDecor给添加到了DecorView之中。之后通知Activity界面已经完成改变,因为Activity实现了Window.Callback接口,是一个空实现,所以可以通过重写该方法来监听布局内容的改变。

 public void onContentChanged() {
 }

所以总结一下,调用onCreate时,我们的Activity已经持有了Window对象,且通过调用setContentView方法,我们会现在PhoneWindow中根据主题构建DecorView,之后因为compat等原因,会构建SubDecor添加到DecorView之中,而我们设置的布局则会添加到SubDecor之中,并且被添加的布局id为content。比如通过getWindow().getDecorView()获取到的对象,就是PhoneWindow中的DecorView。

那么Activity是合适拥有了PhoneWindow呢?上面说到的CallBack是合适设置到PhoneWindow中呢?可以继续关注下一篇文章:Window与Window Manager的创建加载。

创作不易,如有帮助一键三连咯🙆‍♀️。欢迎技术探讨噢!

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pumpkin的玄学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值