Android-Framework: Activity、Window、View三者之间的关系

本文详细探讨了Android中Activity、Window和View三者的关系,通过源码分析了setContentView()的执行过程,包括AppCompat层的实现、PhoneWindow的getDecorView()以及View是如何显示在Activity上的。通过一系列步骤揭示了DecorView、ContentParent的创建以及ViewRootImpl在显示过程中的作用。

Android-Framework: Activity、Window、View三者之间的关系

前言

ActivityView是大家都比较熟悉一个是Android的四大组件、一个是用于展示各种控件的View,相对于前两者 Window 在日常开发中比较陌生,今天我们这篇文章就将这三个问题讲清楚。

源代码版本:

彻底弄明白setContentView()

要想弄清楚 Window对象是什么使用创建的,就要从最简单的代码入手,一步步得往下查看源码才能解开这个迷惑,那我们就开始吧

首先看以下代码:

class MyTestViewActivity : AppCompatActivity(){
   
   

    override fun onCreate(savedInstanceState: Bundle?) {
   
   
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_test_view) 

		//打印View所以父View
		LayoutUtils.printAllViewLayout(ll_first_content)       
    }
}

activity_my_test_view.xml 布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/ll_first_content"
    android:orientation="vertical"/>

这里我们有一个 继承与 AppCompatActivityMyTestViewActivity 里面只是调用了 setContentView() 将自定义布局文件传入其中,在 onCreate() 我们调用 printAllViewLayout() 将 自定义布局 LinearLayout 的所有父View 打印出来。

printAllViewLayout() 如下:

fun printAllViewLayout(view: View?) {
   
   
    if (view == null) {
   
   
        return
    }
    //打印view所有的parent
    var parent = view.parent
    while (parent != null) {
   
   
        Log.d("printAllViewLayout: ","parent : $parent")
        
        parent = parent.parent
    }
}

上述代码运行之后打印如下信息:

printAllViewLayout:: parent : androidx.appcompat.widget.ContentFrameLayout
printAllViewLayout:: parent : androidx.appcompat.widget.FitWindowsLinearLayout
printAllViewLayout:: parent : android.widget.FrameLayout
printAllViewLayout:: parent : android.widget.LinearLayout
printAllViewLayout:: parent : com.android.internal.policy.PhoneWindow$DecorView
printAllViewLayout:: parent : android.view.ViewRootImpl

这里我们看到打印出很多父View 类型、 ContentFrameLayoutFitWindowsLinearLayoutFrameLayoutLinearLayout 以及 DecorViewViewRootImpl 这些都是什么时候添加的呢?又起到什么作用呢?我们带着这两个问题从源码的角度进行一旦究竟吧

首先我们需要从 setContentView() 的具体实现开始分析

AppCompat 层实现

//AppCompatActivity 实现
@Override
public void setContentView(@LayoutRes int layoutResID) {
   
   
    getDelegate().setContentView(layoutResID);
}

//AppCompatDelegate 实现类 AppCompatDelegateImpl 
@Override
public void setContentView(int resId) {
   
   
	//创建DecorView
    ensureSubDecor();
    //加载自定义布局 并添加到 contentParent 中
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mOriginalWindowCallback.onContentChanged();
}

AppCompatActivitysetContentView() 是有其代理类实现,AppCompatDelegateImpl 类中 setContentView() 只做了两件事

  • 执行 ensureSubDecor() 创建自定义ViewGroup对象 :mSubDecor
  • 加载自定义布局并添加到contentParent中 (此时的contentParent 是AppCompatDelegateImpl类中自定义的ViewGroup)
ensureSubDecor()

我们继续往下看 ensureSubDecor() 具体是怎么实现的

private void ensureSubDecor() {
   
   
    if (!mSubDecorInstalled) {
   
   
        mSubDecor = createSubDecor();
        //省略不重要代码
    }
}

ensureSubDecor() 中很显然只有一句比较重要的代码 调用了 createSubDecor() 而 mSubDecorInstalled 只是一个标志位用于标识是否已经初始化过了,当执行完 createSubDecor() 会置成 true 因此保证只会初始化一次。

我们接着往下看 createSubDecor() 具体做了什么

private ViewGroup createSubDecor() {
   
   
    //省略不重要代码...
    
    // 确保window已经加载了 DecorView 如果没有则进行创建并添加到window上
    mWindow.getDecorView();
    
    //根据不同的 theme 加载不同的layout 布局文件 
    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;
    //存在 windowTitle 情况下加载 带有actionBar 的布局文件
    if (!mWindowNoTitle) {
   
   
        if (mIsFloating) {
   
   
            // If we're floating, inflate the dialog title decor
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_dialog_title_material, null);
            // Floating windows can never have an action bar, reset the flags
            mHasActionBar = mOverlayActionBar = false;
        } else if (mHasActionBar) {
   
   
            // Now inflate the view using the themed context and set it as the content view
            subDecor = (ViewGroup) LayoutInflater.from(themedContext).inflate(R.layout.abc_screen_toolbar, null)}
    } else {
   
   
        if (mOverlayActionMode) {
   
   
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple_overlay_action_mode, null);
        } else {
   
   
        	//没有windowTitle 默认情况下加载此布局
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        }
    }
 
    // 将上述创建的自定义ViewGroup 添加到DecorView 中的 ContentParent 中
    mWindow.setContentView(subDecor);
   
    // 返回自定义ViewGroup
    return subDecor;
}

上述createSubDecor() 只做了三件大事,具体是:

  1. 调用WindowgetDecorView()创建DecorView、创建 ContentParent ViewGroup 并加入到DecorView中
  2. AppCompat 层创建自定义样式 ViewGroup并设置相关适配、监听等
  3. 调用WindowsetContentView() 将创建的ViewGroup 添加到 ContentParent 中

接下来我们先一件一件拆开来分析,首先我们来看第一件事 也就是 getDecorView() , Window 是一个抽象类,而 Window 的唯一实现类 是 com.android.internal.policy.PhoneWindow所以我们就看一下 PhoneWindow 是怎么实现的。

PhoneWindow 的 getDecorView()实现

PhoneWindowgetDecorView() 很显然这个方法是个单例的,只会创建一个 DecorView对象,而且具体实现是在 installDecor()

@Override
public final @NonNull View getDecorView() {
   
   
    if (mDecor == null || mForceDecorInstall) {
   
   
        installDecor();
    }
    return mDecor;
}

//安装 DecorView 布局
private void installDecor() {
   
   
    if (mDecor == null) {
   
   
        //创建 DecorView
        mDecor = generateDecor(-1);
    }
    if (mContentParent == null) {
   
   
        //创建 ContentParent 布局
        mContentParent = generateLayout(mDecor);
    }
    //省略无关代码
}

installDecor() 方法比较长,但实际只干了两件事:

  1. 如果mDecor 为空则调用 generateDecor() 创建 DecorView 对象
  2. 如果 mContentParent 为空则调用 generateLayout() 创建ContentParent 对象
generateDecor() 创建DecorView

generateDecor() 直接创建了一个新的 DecorView对象

protected
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值