Window系列 (三) — Activity、Window、DecorView 的关系

一、概述

在 Android 中,常常会遇到 Activity、Window、DecorView 这几个概念,本文就来了解一下他们之间的关系。

版本: Android SDK 29
关联文章:

  1. 《Window系列 (一) — WindowManager 详解》

二、Activity、Window、DecorView、ViewRootImpl 之间的关系

Activity

  1. Activity 只负责生命周期的控制和事件的处理,并不负责视图控制,真正控制视图的是 Window。
  2. 一个 Activity 包含了一个Window,Window 才是真正代表一个窗口。

Window

  1. Window 是视图的承载器,内部持有一个 DecorView,而这个DecorView才是 view 的根布局。
  2. Window 是一个抽象类,实际在 Activity 中持有的是其子类 PhoneWindow。
  3. PhoneWindow 中有个内部类 DecorView,通过创建 DecorView 来加载 Activity.setContentView() 设置的 layout 布局。
  4. Window 通过 WindowManager 将 DecorView 加载其中,并将 DecorView 交给 ViewRootImpl,进行视图绘制以及其他交互。

DecorView

  1. DecorView 是 FrameLayout 的子类,它可以被认为是 Android 视图树的根视图。

ViewRootImpl

  1. 连接 DecorView 和 WindowManagerService 的纽带。
  2. View 的三大流程 (measure、layout、draw) 和事件分发等都是通过 ViewRootImpl 来执行的。

Activity、Window、DecorView、ViewRootImpl 四者之间的关系 如下图所示 (图片来源于网络):
在这里插入图片描述

三、源码分析

源码从下面三部分来分析:

  1. Activity 与 Window 的关联。
  2. DecorView 的创建及与 Window 的关联。
  3. DecorView 的显示。

1. Activity 与 Window 的关联

Activity

// Activity 
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, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    attachBaseContext(context);

    mFragments.attachHost(null /*parent*/);
	// 1.创建了PhoneWindow对象,在Activity中持有了Window。
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    // 2.将 Activity 作为参数传递给 Window,所以在 Window 中持有了 Activity 。
    mWindow.setCallback(this);
	// ...省略代码...
	// 3.设置 WindowManager,来关联 Window 和 DecorView。
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    // 4.在 Activity 中持有 WindowManager。
    mWindowManager = mWindow.getWindowManager();
    // ...省略代码...
}

在 Activity.attach() 中主要有4步操作:

  1. 创建一个 PhoneWindow,使 Activity 持有 Window。
  2. 将 Activity作为参数传递给 Window,此时 Window 与 Activity 就相互有了关联。
  3. 给 Window 设置一个 WindowManager,来关联 Window 和 DecorView。
  4. 通过 Window.getWindowManager()获取 WindowManager,使 Activity 持有 WindowManager 的引用。

2. DecorView 的创建 及 与Window的关联

在 Activity.attach() 方法中,生成了PhoneWindow实例,紧接着会执行 Activity.onCreate() 生命周期方法,在这个方法中我们会调用 Activity.setContentView() 来添加 DecorView 了。

Activity

// Activity 
public void setContentView(@LayoutRes int layoutResID) {
	// getWindow() 其实就是PhoneWindow,所以这里会触发 PhoneWindow.setContentView()方法。
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar(); //创建ActionBar
}

PhoneWindow

// PhoneWindow.class
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
    	// 1.mContentParent为空,创建一个DecroView。
    	// mContentParent 其实就是DecroView中id=com.android.internal.R.id.content的容器控件。
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    	// 2.mContentParent不为空,删除其中的View。
        mContentParent.removeAllViews();
    }
	// 3.将 layoutResID 布局文件加载并添加到 mContentParent 容器控件中。
 	mLayoutInflater.inflate(layoutResID, mContentParent);
   	// ...省略代码...
}

上面的逻辑主要分3步:

  1. 当添加布局时,先判断容器控件mContentParent是否存在,如果不存在,就先创建mContentParent。
  2. 如果mContentParent存在,则先将容器控件mContentParent内的元素清空。
  3. 然后将要添加的布局加载到容器控件mContentParent中显示。

PhoneWindow

// PhoneWindow.class
private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
    	// 1.创建DecorView
        mDecor = generateDecor(-1); 
        // ...省略代码...
    } else {
    	// 这里会将当前 Window 传入 DecorView,使 DecorView 与 Window 关联。
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
     	// 2.为DecorView设置布局格式,并返回mContentParent
        mContentParent = generateLayout(mDecor);
        // ...省略代码...
    }
}

protected DecorView generateDecor(int featureId) {
    // ...省略代码...
    // 创建一个DecorView 根视图 View。
    // 这里将 Window 当做参数传入DecorView,使 DecorView 与 Window 关联。
    return new DecorView(context, featureId, this, getAttributes());
}

这里将 Window 作为参数传入 DecorView,使 DecorView 与 Window 关联。

PhoneWindow

// PhoneWindow.class
protected ViewGroup generateLayout(DecorView decor) {
    // 从主题文件中获取样式信息
    TypedArray a = getWindowStyle();
    
    // 1.根据样式信息设置Feature特性
	// ...省略代码...

	// 2.根据不同的features加载不同的layout文件
    // Inflate the window decor.
    int layoutResource;
    int features = getLocalFeatures();
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
       layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);
    }
    // ...省略代码(条件判断获取layoutResource)...
    
    // 3.加载上面的 layoutResource 文件 
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
    // 4.获取 DecorView 中的 id=ID_ANDROID_CONTENT 的容器控件。
	// ID_ANDROID_CONTENT = com.android.internal.R.id.content;
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	// ...省略代码...
    return contentParent;
}

generateLayout() 方法主要分为4步:

  1. 根据样式信息设置Feature特性。
  2. 根据不同的features加载不同的layout文件。
  3. 加载上面的 layoutResource 文件到 DecorView中。
  4. 获取 DecorView 中的 id=ID_ANDROID_CONTENT 的容器控件并返回该控件。

DecorView

// DecorView.class
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
	// ...省略代码...
	// 加载 layoutResource 文件
    final View root = inflater.inflate(layoutResource, null);
	// ...省略代码...
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}

到这里, DecorView 的创建过程就结束了。

3. DecorView 的显示

从前面两步我们知道:

  1. Activity.attach() 的时候,创建了 PhoneWindow。
  2. 在 Activity.onCreate() 时,调用 Activity.setContent() 会创建DecorView。

此时的 DecorView 既没有显示,也还没有添加到 Window中 ,那么 DecorView 的显示又是在什么时候呢?答案是 Activity.onResume() 阶段。
这部分其实已经在 《Window系列 (一) — WindowManager 详解》 分析过了,这里将分析过程拷贝过来了。

源码分析:

当界面要与用户进行交互时,会调用 ActivityThread. handleResumeActivity() 方法。

ActivityThread

// ActivityThread.class
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
	// ...省略代码...

    // TODO Push resumeArgs into the activity for consideration
    // 1.将 Activity 回复到 RESUME 状态。
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
	
	// ...省略代码...
    final Activity a = r.activity;
	// ...省略代码...

    if (r.window == null && !a.mFinished && willBeVisible) {
    	// 2.获取在 Activity.attach() 方法中就创建了 PhoneWindow 对象。
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        // 这里使 Decor 不可见。
        decor.setVisibility(View.INVISIBLE);
        // 3.获取 Activity 中持有的 WindowManager。
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (r.mPreserveWindow) {
            a.mWindowAdded = true;
            r.mPreserveWindow = false;
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
            	//将 Activity.WindowAdded 标记为true,避免在 Activity.makeVisible() 是重复进行 Window 添加操作。
                a.mWindowAdded = true;
                // 4.将根 View(DecorView)通过 WindowManager 添加到 Window 中。
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    }
	// ...省略代码...
	
	// The window is now visible if it has been added, we are not
    // simply finishing, and we are not starting another activity.
    if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
        // ...省略代码...

        r.activity.mVisibleFromServer = true;
        mNumVisibleActivities++;
        if (r.activity.mVisibleFromClient) {
        	// 5.这个方法内部会是 DecorView 可见。
            r.activity.makeVisible();
        }
    }
}

public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest, String reason) {
    // 1.每一个 ActivityClientRecord 都代表着一个 Activity 。
    final ActivityClientRecord r = mActivities.get(token);
    
    // ...省略代码...
    try {
        r.activity.onStateNotSaved();
        r.activity.mFragments.noteStateNotSaved();
        checkAndBlockForNetworkAccess();
        if (r.pendingIntents != null) {
        	// 这里会触发 Activity.onNewIntent()方法。
            deliverNewIntents(r, r.pendingIntents);
            r.pendingIntents = null;
        }
        if (r.pendingResults != null) {
        	// 这里会触发 Activity.onActivityResult()方法。
            deliverResults(r, r.pendingResults, reason);
            r.pendingResults = null;
        }
        // 这里会触发 Activity.onResume()方法。
        r.activity.performResume(r.startsNotResumed, reason);

        r.state = null;
        r.persistentState = null;
        // 这里将当前 Activity 的生命周期状态设置为 ON_RESUME。
        r.setState(ON_RESUME);

        reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");
    } catch (Exception e) {
        // ...省略代码...
    }
    return r;
}

Activity

// Activity.class
void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    // 将 DecorView设置为可见。
    mDecor.setVisibility(View.VISIBLE);
}

这里主要分为5步:

  1. 执行performResumeActivity,将 Activity 的状态切换为 Resume 状态。
  2. 获取 DecorView 对象,并设置 DecorView 可见性为不可见。
  3. 获取 Activity 中持有的 WindowManager。
  4. 将 DecorView 通过 WindowManager 添加到 Window 中显示。
  5. Activity.makeVisible() 方法中,最终将 DecorView 设置为可见。

接下来的流程,请参考: 《Window系列 (一) — WindowManager 详解》


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值