深刻剖析Window

本文是Android视图层源码分析系列第一篇文章。主要来理清Window的地位以及做用。java

Android中全部的视图(View)都是经过Window来呈现的,无论是ActivityDialog仍是Toast,它们的视图实际上都是附加在Window上的,所以Window实际是View的直接管理者。本文就从源码来分析一下Window,理清Window是如何组织视图(View)以及ActivityPhoneWindow的工做原理。本文不会去讨论Window的详细使用。android

分析以前,咱们先找一个切入点,如下面这段代码为例:git

WindowTestActivity.javagithub

// example 1
val simpleTv = getSimpleTextView()
windowManager.addView(simpleTv, getSimpleWindowLayoutParams()) 

//example 2
window.addContentView(getSimpleTextView(), ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
复制代码

即咱们直接经过windowManager.addViewwindow.addContentView()来添加了一个View。这两个方法都是Activity直接提供的方法,也是咱们惟一与Window交互的几个方法之一,那:bash

  1. 这两个方法有什么关系与不一样呢?
  2. View究竟添加到了哪里呢?
  3. windowwindowManager有什么关系呢?

下面咱们将从源码一点一点弄清这些问题。先来看一下windowManager.addView(contentView, layoutParams),为了下面方便叙述,咱们把被addview叫作contentView微信

经过WindowManager添加一个View

WindowManager实例的建立

WindowManager是一个接口,在看windowManager.addView()以前咱们先来看一下Activity的WindowManager的实例是谁。app

追踪Activity的源码发现WindowManager实际上是经过Window来获取的(它实际上是Window的成员变量)源码分析

mWindowManager = mWindow.getWindowManager();
复制代码

WindowWindowManager是在什么地方赋值的呢?实际上是在Activity attch时:布局

//Activity.java
final void attach(...){
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    ...
    mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken,..);
}
复制代码

mWindow.setWindowManager()内部实际上是构造了一个WindowManagerImpl:ui

public void setWindowManager(WindowManager wm, IBinder appToken...) {
    ...
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
复制代码

ActivityPhoneWindowWindowManager实例是WindowManagerImpl

而且在Activity.attach方法中也能够看出ActivityWindow的实例时PhoneWindow(PhoneWindow实际上是Window的惟一实现类,是针对于app客户端(相对于Android系统)的一个Window实体)。

WindowManagerImpl其实只是一个简单的装饰类,全部操做直接转发到了WindowManagerGlobal, 所以windowManager.addView()源码的追踪能够直接看WindowManagerGlobal.addView():

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View panelParentView = null;
    ...

    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    root.setView(view, wparams, panelParentView);
}
复制代码

parentWindow这个参数其实就是Activitywindow(PhoneWindow)。而WindowManagerGlobal.addView()作的主要事情是:

  1. 构造了一个(root)ViewRootImpl
  2. 分别把contentView相关对象放入到mViews/mRoots/mParams集合中。(若是contentView被移除,那么这3个集合相关对象也会被移除)
  3. root.setView(contentView..)会经过IPC调用到WindowManagerService来在window中显示contentView

因此windowManager.addView()作的事情是:

contentView建立一个ViewRootImpl对象,并把contentView相关对象放入到mViews/mRoots/mParams集合中维护起来,而后调用ViewRootImpl.setView(..)方法来显示contentView

因此到这里能够用下面这张图总结一下Activity/Window/WindowManager之间的关系:

通过上面的分析咱们还知道 : 经过windowManager.addView(contentView)来显示视图实际上是和ActivityWindow有着密切的联系的(显示一个视图必需要有Window)。那Activity的视图是怎么显示的呢?

咱们继续看一下(其实Activity的视图也是经过windowManager.addView(contentView)的方式来显示的):

Activity的视图的显示

追踪Activity.setContentView(..)源码能够看到:

getWindow().setContentView(contentView);
复制代码

即咱们的contentView实际上是设置给了Window(PhoneWindow):

PhoneWindow的视图层级

PhoneWindow.java

public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    ...
    mContentParent.addView(view, params);
}
复制代码

即咱们Activity的根布局View实际上是添加到了PhoneWindow的mContentParent成员变量中中。那mContentParent是什么呢?看一下PhoneWindow.installDecor(),这个方法也很长,所以只截取最重要的部分看一下:

private void installDecor() {
    mDecor = generateDecor(-1);  // decor 的实例时DecorView,它继承自FrameLayout
    ...
    mDecor.setWindow(this);  //DecorView 绑定一个window
    ...
    mContentParent = generateLayout(mDecor); // mContentParent 会被add到 decor view中
    ...  
}
复制代码

根据上面的注释,咱们能够先这样理解PhoneWindow/DecorView/mContentParent的关系:

PhoneWindow里存在一个DecorView(mDecor)成员变量,能够把它理解为一个FrameLayout,它包含一个mContentParent的子View, mContentParentActivity的根布局contentView的父View

mContentParent是一个什么样的布局/View呢?继续看一下generateLayout(mDecor):

protected ViewGroup generateLayout(DecorView decor) {

     int layoutResource; //mContentParent的布局文件

    int features = getLocalFeatures();

    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        ....各类 if else
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
    }

    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //会把这个布局文件inflate出的view,添加到DecorView中

    //经过 findViewById 来获取 ContentParent。 这个id其实就来自 layoutResource 所指向的布局文件
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

}
复制代码

上面我作了一些注释,能够理解为mContentParent就是DecorView的子View。layoutResource根据当前ActivityTheme的设置,会对应到许多不一样的布局文件,R.layout.screen_toolbar是给Activity设置默认 Theme是所对应的布局文件:

<com.android.internal.widget.ActionBarOverlayLayout
    android:id="@+id/decor_content_parent"
    ...>
    <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:gravity="top">
        <Toolbar
            android:id="@+id/action_bar"
            ... />
        <com.android.internal.widget.ActionBarContextView
            android:id="@+id/action_context_bar"
            ..../>
    </com.android.internal.widget.ActionBarContainer>
</com.android.internal.widget.ActionBarOverlayLayout>
复制代码

看一个具体的Android Layout Inspectot分析:

ContentFrameLayoutsupport v7的类,能够把它理解为FrameLayout

PhoneWindow的视图层级能够用下图表示

通过上面的分析: PhoneWindow的视图层级其实就是DecorView的视图层级。DecorView就是Activity视图的根View

因此Activity的视图显示的过程其实就是DecorView的视图显示的过程。那DecorView如何显示呢?

它的显示原理也是使用windowManager.addView() :

DecorView的显示

Activity Resume时,DecorView会添加到WindowManager中:

ActivityThread.java

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ...
    final Activity a = r.activity;
    ...
    wm.addView(decor, l);
}
复制代码

即在Activity ResumeDecorView添加到WindowManager中,进而经过WindowManagerService来显示成功。因此Activity.onResume()用户才能够看到Activity的视图。

总结

总结一下到目前为止所分析的点:

  1. 视图(View)的显示离不开Window
  2. WindowManager属于Window,负责管理WindowView的显示。在Window中显示View咱们应使用它的接口
  3. 一个Window能够有多个子View,每一个子View都对应一个ViewRootImpl
  4. ViewRootImpl会经过IPC来与WindowManagerService交互,来实现View的显示

它们之间的关系以下图:

下一篇文章将继续分析ViewRootImpl.setView(..)所引发的WindowManagerService的操做,即Window是怎么在屏幕上展现内容的。

欢迎关注个人Android进阶计划看更多干货

欢迎关注个人微信公众号:susion随心

参考文章:

  • 《Android开发艺术探索》
相关文章
相关标签/搜索