认识窗口

该文章为窗口层级结构系列文章的总结,从头回看这方面内容的时分我自己也有了一些新的感悟,希望经过本次总结能让大家再次对窗口有一个全面的认识。

一般来说,屏幕上最起码包括三个窗口,StatusBar窗口、Activity窗口以及NavigationBar窗口:

认识窗口

咱们想要了解窗口,可以依照从小到大,从表到里的次序进行:

1)、微观视点,探求单个窗口内部的组成。

2)、微观视点,探求WMS怎样办理屏幕上显现的诸多窗口。

3)、底层视点,探求SurfaceFlinger怎样办理窗口,和WMS在这方面有何联络。

至于窗口,咱们先了解它为一块在屏幕上可以显现图画的区域。

1 微观下的Window —— View层级结构

窗口自身作为View目标的容器仅仅一个笼统的概念,真实可见的则是View目标,那么咱们探求单个窗口的表现方法,其实便是探求组成窗口的View层级结构。

这儿咱们写一个单选按钮组:

    <RadioGroup
        android:id="@+id/radio_group"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:layout_marginStart="20dp"
        android:background="@color/material_dynamic_neutral70">
        <RadioButton
            android:id="@+id/radio_button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="A" />
        <RadioButton
            android:id="@+id/radio_button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="B" />
        <RadioButton
            android:id="@+id/radio_button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="C" />
    </RadioGroup>

看起来是这姿态:

认识窗口

这现已是一个简单的View层级结构了,父View为RadioGroup,其间有3个RadioButtion的子View,那么它的层级结构便是:

认识窗口

我之前习气用树状图的方法:

认识窗口

不过似乎仍是榜首种表现方法更好,可以愈加直观的反映上下级的联系,后边描绘层级结构的时分我依然用树状图的一些叫法。

Android对此层级结构的树立供给了哪些支撑呢?

1.1 每一个UI元素都是一个View

比方这儿的单选按钮RadioButton类,它的承继联系为:

RadioButtion -> CompoundButton -> Button -> TextView -> View

RadioGroup类的承继联系为:

RadioGroup -> LinearLayout -> ViewGroup -> View

就像每一个类的基类是Object相同,每一个用来组成Activity界面的UI元素,其基类都是View类,因而我个人喜爱称这些由UI元素搭建的组织架构为View层级结构,层级结构里的每一个节点都是一个View。

1.2 父View和子View

以刚刚的单选按钮组为例,这儿是一个RadioGroup包括了3个RadioButtion,父View为RadioGroup,子View为3个RadioButtion。

回看RadioGroup,它是一种特别的View,承继自ViewGroup,ViewGroup顾名思义,便是一种可以包括其它View的特别View,它有一个View数组类型的成员变量mChildren:

// frameworks\base\core\java\android\view\ViewGroup.java
	// Child views of this ViewGroup
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    private View[] mChildren;

用来保存它包容的子View。

已然父View有一个mChildren用来拿到子View目标,那么子View也应该有一个成员变量用来保存父View的引证,实践上也的确如此,View中也界说了一个ViewParent类型的成员变量mParent用来指向当时View的父View:

// frameworks\base\core\java\android\view\View.java
	/**
     * The parent this view is attached to.
     * {@hide}
     *
     * @see #getParent()
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    protected ViewParent mParent;

ViewParent是一个接口类,界说一个可以作为父View的类的通用接口,这儿看到ViewGroup就完成了这个接口类:

// frameworks\base\core\java\android\view\ViewGroup.java
public abstract class ViewGroup extends View implements ViewParent, ViewManager

那么为什么不让View来完成ViewParent接口,而让ViewGroup来完成呢?不难想到,关于参加树立View层级结构的View来说,其实可以分为两类,一类就像RadioButtion相同,它们是View层级结构中的最小单位,或许叫做View层级结构中的叶节点,无法作为父View再去包容其它子View。而关于RadioGroup,它们作为父View可以包容其它子View,在View层级结构中充当中心节点(当然也可以作为叶节点)。

终究说下怎样向ViewGroup中增加子View,用的是ViewGroup.addChild办法:

    /**
     * Adds a child view with the specified layout parameters.
     *
     * <p><strong>Note:</strong> do not invoke this method from
     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
     *
     * @param child the child view to add
     * @param index the position at which to add the child or -1 to add last
     * @param params the layout parameters to set on the child
     */
    public void addView(View child, int index, LayoutParams params)

终究会将child参数代表的子View加入到当时VIewGroup.mChildren中,一同将子View的mParent指向当时ViewGroup。

LayoutInflater解析xml文件,其实也是实例化xml中界说的View,然后经过ViewGroup.addChild办法将子View增加到父View中,以这种办法将xml文件中界说的View层级结构树立起来。

1.3 根View

关于一切组成同一个Activity界面的UI元素,它们都在同一个层级结构中,天然它们都有一个一同的根View,那这个根View是谁?

咱们新建一个Activity,一般来说都在其onCreate办法中去加载其布局,比方像这样:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pip);
    }

那经过解析这个 R.layout.activity_pip 对应的xml文件所生成的View便是根View吗,依然拿这儿的demo进行验证,我自己界说的actiivty_pip.xml是一个LInearLayout,里边包括了两个单选按钮组。需求留意的是这个LinearLayout有一个不为“match_parent”的自界说高度,而且设置了一个布景色,其层级结构为:

认识窗口

看下实践状况:

认识窗口

关于它没有覆盖到的区域,仍有一个白色布景,阐明这个LinearLayout并不是根View。

假如想要找到根View,就需求去跟踪Activity.setContentView流程,这个流程我之前有总结过文档,这儿就不再详细叙说,直接放结论,是DecorView。

1.4 View层级结构的生成

看下View层级结构生成的流程,即Activity.setContentView办法流程,只说要害部分。

1)、创立DecorView实例。

认识窗口

2)、依据Activity恳求的feature(比方是否需求标题、图标)来决议DecorView的直接子View的布局类型,我这儿的是 R.layout.screen_simple

// frameworks/base/core/res/res/layout/screen_simple.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

详细为一个LinearLayout包括一个ViewStub和FrameLayout。

接着将这个xml实例化为一个View层级结构,将该层级结构的根节点作为子View加入到DecorView中:

认识窗口

3)、接着经过LayoutInflater.inflate解析咱们经过Activity.setContentView传入的xml文件,指定其父View为上一步解析的screen_simple.xml中界说的ID为:

    /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

的View,将咱们自界说的布局作为子View增加到该View上,生成终究的层级结构:

认识窗口

用”Legacy Layout Inspector“插件看是:

认识窗口

4)、回看咱们之前的Activity界面:

认识窗口

能看到顶部和底部别离还有一个区域,假如咱们的Activity不要求隐藏状况栏和导航栏,那么会创立两个View:

  • 状况栏布景区域,com.android.internal.R.id.statusBarBackground。
  • 导航栏布景区域,com.android.internal.R.id.navigationBarBackground。

当状况栏窗口和导航栏窗口盖在咱们的Activity窗口上时,这两个区域便是它们的布景。

DecorView将这两个View作为子View加入其间,和寄存咱们自界说布局的LinearLayout(内部层级被折叠)同级,如图所示:

认识窗口

而且对LinearLayout设置内外边距,然后防止咱们的自界说区域伸延到这些为体系窗口预留的布景区域中,以防咱们希望显现的内容被这些体系窗口覆盖。

1.5 ViewRootImpl —— 实践的根节点?

ViewRootImpl,不管是开发App仍是做Framework,都少不了跟它打交道,从姓名上来看,它似乎是作为根View的人物所存在,那么事实上怎样呢?

不管是Activity窗口仍对错Activity窗口,终究都要经过ViewRootImpl.setView向WMS注册窗口:

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                // ......
                try {
                    // ......
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                            mTempControls);
                    // ......
                }
                // ......
                view.assignParent(this);
                // ......
            }
        }
    }

值得留意的是以下几点:

1)、将View类型的传参view赋值给View类型的成员变量mView,这儿的传参view是一个窗口的View层级结构的尖端View,关于Activity窗口来说,便是DecorView,而关于非Activity窗口,如状况栏和导航栏,这些窗口的View层级结构彻底由它们自界说,不会像Activity窗口那样再由Framework给它们外面再套几个layout,这儿的传参view便是这些窗口的自界说View层级结构的尖端View。

后续ViewRootImpl就可以经过其成员变量mView拿到尖端View。

2)、接着经过IWindowSession.addToDisplayAsUser向WMS增加窗口。

3)、终究调用View.assignParent办法,将传参View的mParent指向当时ViewRootImpl:

    @UnsupportedAppUsage
    void assignParent(ViewParent parent) {
        if (mParent == null) {
            mParent = parent;
        }
        // .......
    }

那么关于Activity窗口来说,其DecorView还有一个父View,ViewRootImpl?

可是再看ViewRootImpl的界说:

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {

ViewRootImpl并不是一个View目标,因而它并没有一个可视的内容。

ViewRootImpl也不是一个ViewGroup,因而没有mChildren来保存其子View,所以它才新建了一个mView成员变量来保存其仅有的子View,关于Activity窗口来说,ViewRootImpl的仅有子View便是DecorView。

那么把ViewRootImpl视为真实的根View,虽也未尝不可,可是需求留意,咱们之前说的以DecorView为根节点的View层级结构,它的每一个节点都是一个可视的View,而ViewRootImpl仅仅一个笼统的概念,相比于DecorView这种“实体”根节点,它是一个虚拟的根节点。这不由让人考虑,ViewRootImpl为啥要这么取名?

那就要看看ViewRootImpl扮演了怎样的人物,上面分析过,ViewRootImpl保存了View层级结构的尖端View的引证(为数不多的保存尖端View引证的当地之一),那么许多需求遍历View层级结构的流程都会从ViewRootImpl发起。比方,经典的measure流程,便是经过ViewRootImpl.performMeasure办法发起:

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

ViewRootImpl先调用其直接子View的measure办法,再由该子View调用子子View的measure办法,顺次类推,终究整个View层级结构中的一切View的measure办法都可以得到调用。

除此之外,layout流程和draw流程,Input事情的传递以及Insets的更新,大部分需求遍历View层级结构的流程,起点都是在ViewRootImpl。

尽管如此,关于Activity窗口,我个人依然倾向于称DecorView为的View层级结构的根View。

1.6 Freeform的完成

这儿依据Freeform的完成办法,来验证一下咱们上面的分析内容。

这儿只关怀Freeform是怎样为Activity增加包括最大化按钮和退出按钮的标题栏。

假如让咱们自己写一个这个方法的界面,也十分简单,只需求一个LInearLayout包括两个Button即可:

认识窗口

可是假如想要为每一个进入Freeform的Activity都自动设置这样一个标题栏,就得靠Framework去统一处理。

google的做法如下:

1)、实例化一个DecorCaptionView,它经过xml文件界说:

 <com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:descendantFocusability="beforeDescendants" >
     <LinearLayout
             android:id="@+id/caption"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="end"
             android:background="@drawable/decor_caption_title"
             android:focusable="false"
             android:descendantFocusability="blocksDescendants" >
         <Button
                 android:id="@+id/maximize_window"
                 android:layout_width="32dp"
                 android:layout_height="32dp"
                 android:layout_margin="5dp"
                 android:padding="4dp"
                 android:layout_gravity="center_vertical|end"
                 android:contentDescription="@string/maximize_button_text"
                 android:background="@drawable/decor_maximize_button_dark" />
         <Button
                 android:id="@+id/close_window"
                 android:layout_width="32dp"
                 android:layout_height="32dp"
                 android:layout_margin="5dp"
                 android:padding="4dp"
                 android:layout_gravity="center_vertical|end"
                 android:contentDescription="@string/close_button_text"
                 android:background="@drawable/decor_close_button_dark" />
     </LinearLayout>
 </com.android.internal.widget.DecorCaptionView>

有一个LInearLayout,包括了两个按钮:

认识窗口

2)、实例化一个DecorCaptionView后,咱们原先的Activity窗口层级结构是(疏忽详细的细节部分):

认识窗口

接着DecorView将DecorCaptionView作为子View增加进来,接着将原先的仅有子View移除,而且将其作为子View增加到DecorCaptionView中,即:

认识窗口

2 微观下的Window —— WindowContainer层级结构

上一节以微观的视点去探求一个窗口的组成,即View层级结构。

这一节以微观的视点,把窗口当作WMS窗口体系下的最小单位,去探求WMS是怎样办理窗口的。

2.1 窗口 —— WindowState类

/** A window in the window manager. */
public class WindowState extends WindowContainer<WindowState> implements
    WindowManagerPolicy.WindowState, InsetsControlTarget {

在WMS窗口体系中,一个WindowState目标就代表了一个窗口。

这儿看到,WindowState承继自WindowContainer。

2.2 窗口容器类 —— WindowContainer类

/**
 * Defines common functionality for classes that can hold windows directly or through their
 * children in a hierarchy form.
 * The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime
 * changes are made to this class.
 */
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
        implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable {

WindowContainer界说了可以直接或许直接以层级结构的方法持有窗口的类的通用功用。

从类的界说和称号,可以看到WindowContainer是一个容器类,可以包容WindowContainer及其子类目标。假如有一个容器类想作为WindowState的容器,那么这个容器类需求承继WindowContainer或其子类。

持续看下,WindowContainer为了可以作为一个容器类,供给了哪些支撑:

    /**
     * The parent of this window container.
     * For removing or setting new parent {@link #setParent} should be used, because it also
     * performs configuration updates based on new parent's settings.
     */
    private WindowContainer<WindowContainer> mParent = null;
	// ......
    // List of children for this window container. List is in z-order as the children appear on
    // screen with the top-most window container at the tail of the list.
    protected final WindowList<E> mChildren = new WindowList<E>();
  • mParent,保存的是当时WindowContainer的父WindowContainer的引证,可以类比于View.mParent。
  • mChidlren,保存的是当时WindowContainer作为一个容器,所包括的子WindowContainer,可以类比于ViewGroup.mChildren。它是一个列表,列表的次序反映了子容器在Z轴上的次序,越接近队尾层级越高。

这两个成员变量为生成WindowContainer层级结构供给了支撑,就像依据View.mParent和ViewGroup.mChildren创立View层级结构那样。

容器这个概念十分重要,了解了它才干了解WindowContainer层级结构的组织方法。为了了解容器这个概念,可以把WindowContainer比作一个仓库,这儿举几个比方便利了解:

1)、当时WindowContainer现已连续增加了两个子容器,WindowContainer_A以及WindowContainer_B,持续增加WindowContainer_C,就可以看作是像当时仓库栈顶入栈一个WindowContainer_C:

认识窗口

这儿用仓库来表明一个WindowContainer也是由于仓库比较可以直观的反映WindowContainer内部的Z轴次序,WindowContainer层级结构和View层级结构是有相似之处的,ViewGroup也可以看作是View的容器,可是我个人感觉View层级结构中的Z轴这个概念略微弱了一点,所以之前没有用仓库的表现方法。

仓库的表现方法虽好,可是当层级结构略微复杂一点的话,画起来就十分麻烦,而且呈现出来的方法也不行直观,这儿转为我个人比较习气的层级结构图

认识窗口

这个图其实也能反映层级次序,这儿是WindowContainer_C高于WindowContainer_B高于WindowContainer_A。

2)、略微扩展一点,当时WindowContainer有两个子WindowContainer,一同这两个子WindowContainer也别离有各自的子WindowContainer。

认识窗口

转为层级结构图:

认识窗口

有两点需求留意:

  • 坐落同一个父容器中的WindowContainer可以直接进行比较。在WindowContainer_B中,很明显的可以看到WindowContainer_G高于WindowContainer_F高于WindowContainer_E。
  • 坐落不同父容器的两个WindowContainer不能直接进行比较。这儿假如咱们想比较WindowContainer_G和WindowContainer_D谁的层级高,就需求比较它们两个的父容器谁的层级高。这儿看到WindowContainer_A和WindowContainer_B都坐落同一个父容器中,所以可以得出WindowContainer_B高于WindowContainer_A,进而WindowContainer_G高于WindowContainer_D。假如WindowContainer_A和WindowContainer_B也没有处于同一个父容器中,那么就得持续沿着层级结构图往上找,直到找出处于同一个节点下的两个父容器。

此刻回看WindowState类的界说:

public class WindowState extends WindowContainer<WindowState>

有了新的发现,即WindowState自身也是一个容器,不过这儿指明晰WindowState作为容器类可以持有的容器类型限定为WindowState类型,即WindowState作为父容器,其间只能寄存WindowState类型的WindowContainer。

事实上也的确如此,Android是有父窗口和子窗口的概念的,比方翻开一个Message的弹窗:

认识窗口

检查PopupWindow的信息:

 Window #6 Window{193e28f u0 PopupWindow:b98815a}:
    // ......
    mParentWindow=Window{bfc77c9 u0 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity} mLayoutAttached=true

父窗口为Activity对应的窗口。

再检查层级信息:

          #0 6b8748a com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
           #0 3c3dfc1 PopupWindow:a760a0c type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

转为WindowContainer层级结构图为:

认识窗口

这儿表明WindowState姓名的方法一般是哈希值加上包名或类名或窗口称号。

2.3 WindowState的容器 —— WindowToken、ActivityRecord和WallpaperWindowToken

WindowContainer是可以直接或许直接持有WindowState的容器类的通用基类,直接持有WindowState的容器类暂时不提,必定许多,那么可以直接持有WindowState的类有哪些呢?

答案是WindowToken(除了上面说到的子窗口的概念),而且WindowToken衍生了两个子类,ActivityRecord和WallpaperWindowToken,用来对窗口进行更精密的分类。

2.3.1 App窗口和非App窗口

首要窗口可以分为App窗口和非App窗口:

  • App窗口,当App创立Activity、Dialog或许PopupWindow的时分,体系会为自动为其创立一个对应的窗口,是一般的App窗口。
  • 非App窗口,是体系为了特定意图而运用的特别类型的窗口,可以了解为体系窗口,如状况栏、导航栏以及壁纸窗口那些。

详细怎样区分,则是看窗口的类型,即界说在WindowManager.LayoutParams中的成员变量type:

        @WindowType
        public int type;

窗口类型从FIRST_APPLICATION_WINDOW(1)到LAST_APPLICATION_WINDOW(99)的窗口为App窗口。

窗口类型从FIRST_SYSTEM_WINDOW(2000)到LAST_SYSTEM_WINDOW(2999)的窗口为体系窗口。

其实除此之外只要一种特别类型的窗口,子窗口,窗口类型从FIRST_SUB_WINDOW(1000)到LAST_SUB_WINDOW(1999)。这个类型的窗口有必要依附于一个父窗口,比方刚刚说到的PopupWindow。这类窗口归于App窗口仍对错App窗口,彻底看其父窗口归于哪类。

那么以此分类,非App窗口的容器为WindowToken,App窗口的容器为ActivityRecord(子窗口的容器为父窗口,WindowState)。

2.3.2 进一步区分

非App窗口中一种特别的窗口,壁纸窗口,由于其是作为Activity的布景存在的,因而它的层级应该始终低于App窗口。而除壁纸窗口之外的非App窗口,层级则高于App窗口。

那么咱们结合窗口类型以及层级联系,可以对窗口进一步区分:

1)、App之上的窗口,父容器为WindowToken,如StatusBar:

       #0 WindowToken{ef15750 android.os.BinderProxy@4562c02} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
        #0 d3cbd49 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

认识窗口

2)、App窗口,父容器为ActivityRecord,如Message相关Activity的窗口:

         #0 ActivityRecord{bc0999c u0 com.google.android.apps.messaging/.main.MainActivity} t14} type=standard mode=fullscreen override-mode=undefined requested-bou
nds=[0,0][0,0] bounds=[0,0][720,1612]
          #0 adb45c7 com.google.android.apps.messaging/com.google.android.apps.messaging.main.MainActivity type=standard mode=fullscreen override-mode=undefined req
uested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

认识窗口

3)、App之下的窗口,父容器为WallpaperWindowToken,如ImageWallpaper窗口:

        #0 WallpaperWindowToken{145ca49 token=android.os.Binder@a727850} type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=
[0,0][720,1612]
         #0 ae9c947 com.android.systemui.ImageWallpaper type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

认识窗口

2.4 ActivityRecord的容器 —— Task

按理说这一步应该持续分析WindowToken的父容器了,可是ActivityRecord作为一种特别的WindowToken,它自己有一个特别的父容器,Task。

Task类的界说为:

class Task extends WindowContainer<WindowContainer> {

再次温习一下google关于Task的阐明:

使命(Task)是用户在履行某项作业时与之互动的一系列 Activity 的调集。这些 Activity 依照每个 Activity 翻开的次序排列在一个回来仓库中。例如,电子邮件运用或许有一个 Activity 来显现新邮件列表。当用户选择一封邮件时,体系会翻开一个新的 Activity 来显现该邮件。这个新的 Activity 会增加到回来仓库中。假如用户按回来按钮,这个新的 Activity 即会完成并从仓库中退出。

大多数Task都从Launcher上发动。当用户轻触Launcher中的图标(或主屏幕上的快捷办法)时,该运用的Task就会转到前台运转。假如该运用没有Task存在(运用最近没有运用过),则会创立一个新的Task,而且该运用的“主”Activity 将会作为仓库的根 Activity 翻开。

在当时 Activity 发动另一个 Activity 时,新的 Activity 将被推送到仓库顶部并取得焦点。上一个 Activity 仍保留在仓库中,但会停止。当 Activity 停止时,体系会保留其界面的当时状况。当用户按回来按 钮时,当时 Activity 会从仓库顶部退出(该 Activity 毁掉),上一个 Activity 会康复(界面会康复到上一个状况)。仓库中的 Activity 永久不会从头排列,只会被送入和退出,在当时 Activity 发动时被送入仓库,在用户运用回来按钮脱离时从仓库中退出。因而,回来仓库依照“后进先出”的目标结构运作。图 1 凭借一个时间轴直观地显现了这种行为。该时间轴显现了 Activity 之间的进展以及每个时间点的当时回来仓库。

认识窗口

图 1. 有关Task中的每个新 Activity 怎样增加到回来仓库的图示。当用户按回来按钮时,当时 Activity 会毁掉,上一个 Activity 将康复。

假如用户持续按回来,则仓库中的 Activity 会逐个退出,以显现前一个 Activity,直到用户回来到主屏幕(或Task开端时运转的 Activity)。移除仓库中的一切 Activity 后,该Task将不复存在。

WMS中的Task类的概念和以上阐明根本共同,用来办理ActivityRecord。

仍是上面的Message,状况是:

        #3 Task=14 type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
         #0 ActivityRecord{bc0999c u0 com.google.android.apps.messaging/.main.MainActivity} t14} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
          #0 adb45c7 com.google.android.apps.messaging/com.google.android.apps.messaging.main.MainActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

认识窗口

接着再发动Message的别的一个界面:

        #3 Task=14 type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
         #1 ActivityRecord{357651e u0 com.google.android.apps.messaging/.ui.appsettings.ApplicationSettingsActivity} t14} type=standard mode=fullscreen override-mod
e=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
          #0 e611691 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.ApplicationSettingsActivity type=standard mode=fullscreen ov
erride-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
         #0 ActivityRecord{bc0999c u0 com.google.android.apps.messaging/.main.MainActivity} t14} type=standard mode=fullscreen override-mode=undefined requested-bou
nds=[0,0][0,0] bounds=[0,0][720,1612]
          #0 adb45c7 com.google.android.apps.messaging/com.google.android.apps.messaging.main.MainActivity type=standard mode=fullscreen override-mode=undefined req
uested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

认识窗口

新的ActivityRecord被置于栈顶,盖在之前的ActivityRecord上。

Task的嵌套

Task是支撑嵌套的,从Task类的界说也可以看出来(Task是WindowContainer的容器,而非只能包容ActivityRecord)。

1)、看下当只要一个Launcher的状况:

        #2 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2400]
         #0 Task=50 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2400]
          #0 ActivityRecord{b864ccc u0 com.tcl.android.launcher/.uioverrides.TclQuickstepLauncher} t50} type=home mode=fullscreen override-mode=undefined requested-
bounds=[0,0][0,0] bounds=[0,0][1080,2400] 

认识窗口

这儿的示意图疏忽了ActivityRecord的下一级,WindowState。

只看到这儿或许无法了解Task嵌套的含义安在。

2)、接着发动Message:

       #1 DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2400]
        #3 Task=61 type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2400]
         #0 ActivityRecord{fac1d1c u0 com.google.android.apps.messaging/.ui.ConversationListActivity} t61} type=standard mode=fullscreen override-mode=undefined req
uested-bounds=[0,0][0,0] bounds=[0,0][1080,2400]
        #2 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2400]
         #0 Task=50 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2400]
          #0 ActivityRecord{b864ccc u0 com.tcl.android.launcher/.uioverrides.TclQuickstepLauncher} t50} type=home mode=fullscreen override-mode=undefined requested-
bounds=[0,0][0,0] bounds=[0,0][1080,2400]

认识窗口

此刻并没有产生Task的嵌套,新发动的Message对应的Task#61并没有发动到Task#1中,而是并入了DefaultTaskDisplayArea中。

3)、假如咱们此刻再切换到NexusLauncher:

        #5 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #1 Task=47 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
          #0 ActivityRecord{31ce9e6 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t47} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 Task=50 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2400]
          #0 ActivityRecord{b864ccc u0 com.tcl.android.launcher/.uioverrides.TclQuickstepLauncher} t50} type=home mode=fullscreen override-mode=undefined requested-
bounds=[0,0][0,0] bounds=[0,0][1080,2400]    

认识窗口

看到新发动的Task#47,嵌套到了Task#1中,也便是作为子节点,增加到了容器Task#1中。

为什么刚刚发动的Message的Task,Task#61,没有加入到Task#1中,而NexusLauncher对应的Task#47,却加入到了Task#1中呢?这儿要说下ActivityRecord的几种类型:

    /** The current activity type of the configuration. */
    private @ActivityType int mActivityType;
    /** Activity type is currently not defined. */
    public static final int ACTIVITY_TYPE_UNDEFINED = 0;
    /** Standard activity type. Nothing special about the activity... */
    public static final int ACTIVITY_TYPE_STANDARD = 1;
    /** Home/Launcher activity type. */
    public static final int ACTIVITY_TYPE_HOME = 2;
    /** Recents/Overview activity type. There is only one activity with this type in the system. */
    public static final int ACTIVITY_TYPE_RECENTS = 3;
    /** Assistant activity type. */
    public static final int ACTIVITY_TYPE_ASSISTANT = 4;
    /** Dream activity type. */
    public static final int ACTIVITY_TYPE_DREAM = 5;

大部分一般App的Activity,都是ACTIVITY_TYPE_STANDARD,如注释所说,规范的Activity类型, 没有什么特别的。

而关于Home/Launcher类型的App,它们的Activity都是ACTIVITY_TYPE_HOME类型,关于这类App对应的Task,体系会在开机的时分创立一个该类型的Task,作为该类型的RootTask,用来统一办理该类型的Task。因而,假如咱们又发动了第三个Launcher,该Launcher对应的Task,也会作为子节点增加到Task#1中。

可是不会呈现一个Task的子容器既有Task,又有ActivityRecord的状况(至少现在不会)。

2.5 Task的容器 —— TaskDisplayArea

/**
 * {@link DisplayArea} that represents a section of a screen that contains app window containers.
 *
 * The children can be either {@link Task} or {@link TaskDisplayArea}.
 */
final class TaskDisplayArea extends DisplayArea<WindowContainer>

TaskDisplayArea代表了屏幕上的一个包括App类型的WindowContainer的区域。它的子节点可以是Task,或许是TaskDisplayArea。可是现在在代码中,我看到创立TaskDisplayArea的当地只要一处,该处创立了一个名为“DefaultTaskDisplayArea”的TaskDisplayArea目标,除此之外并没有发现其他当地有创立TaskDisplayArea目标的当地,天然也没有找到有关TaskDisplayArea嵌套的痕迹。

因而现在可以说,TaskDisplayArea寄存Task的容器。

在手机的近期使命列表界面将一切App都清掉后,检查一下此刻的TaskDisplayArea状况:

       #0 DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1920,1200]
        #5 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1920,1200]
         #0 Task=8 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1920,1200]
          #0 ActivityRecord{5e73ff4 u0 com.tcl.android.launcher/.Launcher t8} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1920,1200]
           #1 ca4f5c4 com.tcl.android.launcher/com.tcl.android.launcher.Launcher type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1920,1200]
           #0 5784a50 com.tcl.android.launcher/com.tcl.android.launcher.Launcher type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1920,1200]
        #4 Task=2 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1920,1200]
        #3 Task=3 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1920,1200]
        #2 Task=4 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1920,1200]
        #1 Task=5 type=undefined mode=split-screen-primary override-mode=split-screen-primary requested-bounds=[0,0][950,1200] bounds=[0,0][950,1200]
        #0 Task=6 type=undefined mode=split-screen-secondary override-mode=split-screen-secondary requested-bounds=[970,0][1920,1200] bounds=[970,0][1920,1200]

认识窗口

预期应该是DefaultTaskDisplayArea的子容器,只要Launcher运用对应的Task#1,可是却多Task#2、Task#3、Task#4、Task#5和Task#6这几个Task,而且这几个Task并没有持有任何一个ActivityRecord目标。正常来说,App端调用startActivity后,WMS会为新创立的ActivityRecord目标创立Task,用来寄存这个ActivityRecord。当Task中的一切ActivityRecord都被移除后,这个Task就会被移除,比方Message对应的Task。

可是关于Task#1之类的Task来说并不是这样。这些Task是WMS发动后就由TaskOrganizerController创立的,这些Task并没有和某一详细的App联络起来,因而当它里边的子WindowContainer被移除后,这个Task也不会被毁掉。比方分屏Task#5和Task#6:

        #1 Task=5 type=undefined mode=split-screen-primary override-mode=split-screen-primary requested-bounds=[0,0][950,1200] bounds=[0,0][950,1200]
        #0 Task=6 type=undefined mode=split-screen-secondary override-mode=split-screen-secondary requested-bounds=[970,0][1920,1200] bounds=[970,0][1920,1200]

这两个Task由TaskOrganizerController发动,用来办理体系进入分屏后,需求跟从体系进入分屏形式的那些App对应的Task,也便是说这些App对应的Task的父容器会从DefaultTaskDisplayArea变为Task#3和Task#2。

Task的调度

Task的调度其实也十分简单,这儿拿一个实践的比方阐明。

1)、我先从Launcher发动Message,接着经过adb发动我自己写的DemoApp,那么此刻这三个Task的次序从高到低别离是:DemoApp -> Message -> Launcher,如图所示:

认识窗口

2)、接着我在DemoApp界面下点击Back键,此刻的行为是:

认识窗口

DemoApp地点的Task#64,跑到了TaskDisplayArea的栈底,其它Task次序不变,那么终究是Message呈现在了前台。

这是由于咱们重写了Activity的onBackPressed办法:

    @Override
    public void onBackPressed() {
        moveTaskToBack(true);
    }

当接收到Back事情的时分,将Activity地点的Task移动到终究,详细的行为便是移动到父容器的栈底,也便是TaskDisplayArea的栈底。

3)、这儿咱们更改onBackPressed办法的内容:

    @Override
    public void onBackPressed() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }

在接收到Back键时发动Launcher,那么当咱们再次点击Back键时,行为是:

认识窗口

看到Launche地点的Task#50,以及RootTask,Task#1,一同移动到了前台,而其它Task的次序不变。

4)、终究能看到,相同的操作,而且肉眼看到了相同的行为,可是TaskDisplayArea内部的Task的次序却不相同:一个是将前台App移动到栈底,一个是将后台App移动到前台,所以详细问题仍是要详细分析。

2.6 DisplayArea层级结构

可以包容非App窗口的父容器为DisplayArea.Tokens:

    /**
     * DisplayArea that contains WindowTokens, and orders them according to their type.
     */
    public static class Tokens extends DisplayArea<WindowToken> {

包容App窗口的父容器则是Task,包容Task的则是TaskDisplayArea,TaskDisplayArea也是DisplayArea的一个子类:

final class TaskDisplayArea extends DisplayArea<WindowContainer>

那么咱们接下来研究的目标便是DisplayArea。

DisplayArea自身也十分复杂,有许多子类:

认识窗口

这儿只说一下比较重要的几个类:

1)、能直接包容WindowToken的容器,除了刚刚说的DisplayArea.Tokens类和TaskDisplayArea(严谨点说TaskDisplayArea是Task的容器,Task才是ActivityRecord的容器)之外,还有一个DisplayArea.Tokens类的子类,界说在DisplayContent中的内部类ImeContainer,专门用来包容输入法窗口。这三个类由于可以包容WindowToken,所以在DisplayArea层级结构中作为叶节点存在(最小单位,无法再包容其它DisplayArea目标),就像WindowState是作为WindowContainer层级结构的最小单位相同。

2)、DisplayContent,代表一个屏幕,作为DisplayArea层级结构的根节点所存在。

2.6.1 窗口层级值

咱们都知道状况栏窗口的层级是比一般的App窗口层级高的,可是为什么是这样的,不知道大家有没有想过。

看一个办法,WindowManagerPolicy.getWindowLayerFromTypeLw:

    /**
     * Returns the layer assignment for the window type. Allows you to control how different
     * kinds of windows are ordered on-screen.
     *
     * @param type The type of window being assigned.
     * @param canAddInternalSystemWindow If the owner window associated with the type we are
     *        evaluating can add internal system windows. I.e they have
     *        {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}. If true, alert window
     *        types {@link android.view.WindowManager.LayoutParams#isSystemAlertWindowType(int)}
     *        can be assigned layers greater than the layer for
     *        {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} Else, their
     *        layers would be lesser.
     * @param roundedCornerOverlay {#code true} to indicate that the owner window is rounded corner
     *                             overlay.
     * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
     */
    default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,
            boolean roundedCornerOverlay) {
        // Always put the rounded corner layer to the top most.
        if (roundedCornerOverlay && canAddInternalSystemWindow) {
            return getMaxWindowLayer();
        }
        if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
            return APPLICATION_LAYER;
        }
        switch (type) {
            case TYPE_WALLPAPER:
                // wallpaper is at the bottom, though the window manager may move it.
                return  1;
            case TYPE_PRESENTATION:
            case TYPE_PRIVATE_PRESENTATION:
            case TYPE_DOCK_DIVIDER:
            case TYPE_QS_DIALOG:
            case TYPE_PHONE:
                return  3;
            case TYPE_SEARCH_BAR:
            case TYPE_VOICE_INTERACTION_STARTING:
                return  4;
            case TYPE_VOICE_INTERACTION:
                // voice interaction layer is almost immediately above apps.
                return  5;
            case TYPE_INPUT_CONSUMER:
                return  6;
            case TYPE_SYSTEM_DIALOG:
                return  7;
            case TYPE_TOAST:
                // toasts and the plugged-in battery thing
                return  8;
            case TYPE_PRIORITY_PHONE:
                // SIM errors and unlock.  Not sure if this really should be in a high layer.
                return  9;
            case TYPE_SYSTEM_ALERT:
                // like the ANR / app crashed dialogs
                // Type is deprecated for non-system apps. For system apps, this type should be
                // in a higher layer than TYPE_APPLICATION_OVERLAY.
                return  canAddInternalSystemWindow ? 13 : 10;
            case TYPE_APPLICATION_OVERLAY:
                return  12;
            case TYPE_INPUT_METHOD:
                // on-screen keyboards and other such input method user interfaces go here.
                return  15;
            case TYPE_INPUT_METHOD_DIALOG:
                // on-screen keyboards and other such input method user interfaces go here.
                return  16;
            case TYPE_STATUS_BAR:
                return  17;
            case TYPE_STATUS_BAR_ADDITIONAL:
                return  18;
            case TYPE_NOTIFICATION_SHADE:
                return  19;
            case TYPE_STATUS_BAR_SUB_PANEL:
                return  20;
            case TYPE_KEYGUARD_DIALOG:
                return  21;
            case TYPE_VOLUME_OVERLAY:
                // the on-screen volume indicator and controller shown when the user
                // changes the device volume
                return  22;
            case TYPE_SYSTEM_OVERLAY:
                // the on-screen volume indicator and controller shown when the user
                // changes the device volume
                return  canAddInternalSystemWindow ? 23 : 11;
            case TYPE_NAVIGATION_BAR:
                // the navigation bar, if available, shows atop most things
                return  24;
            case TYPE_NAVIGATION_BAR_PANEL:
                // some panels (e.g. search) need to show on top of the navigation bar
                return  25;
            case TYPE_SCREENSHOT:
                // screenshot selection layer shouldn't go above system error, but it should cover
                // navigation bars at the very least.
                return  26;
            case TYPE_SYSTEM_ERROR:
                // system-level error dialogs
                return  canAddInternalSystemWindow ? 27 : 10;
            case TYPE_MAGNIFICATION_OVERLAY:
                // used to highlight the magnified portion of a display
                return  28;
            case TYPE_DISPLAY_OVERLAY:
                // used to simulate secondary display devices
                return  29;
            case TYPE_DRAG:
                // the drag layer: input for drag-and-drop is associated with this window,
                // which sits above all other focusable windows
                return  30;
            case TYPE_ACCESSIBILITY_OVERLAY:
                // overlay put by accessibility services to intercept user interaction
                return  31;
            case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
                return 32;
            case TYPE_SECURE_SYSTEM_OVERLAY:
                return  33;
            case TYPE_BOOT_PROGRESS:
                return  34;
            case TYPE_POINTER:
                // the (mouse) pointer layer
                return  35;
            default:
                Slog.e("WindowManager", "Unknown window type: " + type);
                return 3;
        }
    }

这个办法回来给定窗口类型对应的层级值。允许你操控不同类型的窗口在屏幕上的排序办法。

看办法内容很简单,对每一种类型的窗口,都规矩了一个层级值。层级值反映了这些窗口在Z轴上的排序办法,层级值越高在Z轴上的方位也就越高。可是层级值的巨细和窗口类型对应的那个值的巨细并没有联系,窗口类型值大的窗口,对应的层级值不一定大。

别的,大部分层级值都仅有对应一种窗口类型,比较特别的是:

  • 层级值2,一切App窗口会被归类到这一个层级。
  • 层级值3,该办法中没有说到的窗口类型会被归类到这一个层级。

这儿看到状况栏窗口对应的窗口层级值是17,而一般的App窗口的窗口层级值是2,天然状况栏窗口就会比App窗口层级值高。

别的这儿也能看到,窗口类型的数值和窗口层级值不是一个正相关的联系,即窗口类型值大的窗口,它在Z轴上的层级并不一定就高,窗口在Z轴的排序详细要看该窗口的类型值转换后得到的窗口层级值。

2.6.2 DisplayArea层级结构的生成

接下来持续分析窗口层级值是怎样作用的,可是仍有一些内容需求提前了解,即DisplayArea依照详细作用,又可以分为以下几种类型:

   /**
     * The value in display area indicating that no value has been set.
     */
    public static final int FEATURE_UNDEFINED = -1;
    /**
     * The Root display area on a display
     */
    public static final int FEATURE_SYSTEM_FIRST = 0;
    /**
     * The Root display area on a display
     */
    public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST;
    /**
     * Display area hosting the default task container.
     */
    public static final int FEATURE_DEFAULT_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
    /**
     * Display area hosting non-activity window tokens.
     */
    public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2;
    /**
     * Display area for one handed feature
     */
    public static final int FEATURE_ONE_HANDED = FEATURE_SYSTEM_FIRST + 3;
    /**
     * Display area that can be magnified in
     * {@link Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW}. It contains all windows
     * below {@link WindowManager.LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY}.
     */
    public static final int FEATURE_WINDOWED_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 4;
    /**
     * Display area that can be magnified in
     * {@link Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN}. This is different from
     * {@link #FEATURE_WINDOWED_MAGNIFICATION} that the whole display will be magnified.
     * @hide
     */
    public static final int FEATURE_FULLSCREEN_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 5;
    /**
     * Display area for hiding display cutout feature
     * @hide
     */
    public static final int FEATURE_HIDE_DISPLAY_CUTOUT = FEATURE_SYSTEM_FIRST + 6;
    /**
     * Display area that the IME container can be placed in. Should be enabled on every root
     * hierarchy if IME container may be reparented to that hierarchy when the IME target changed.
     * @hide
     */
    public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7;
    /**
     * Display area for one handed background layer, which preventing when user
     * turning the Dark theme on, they can not clearly identify the screen has entered
     * one handed mode.
     * @hide
     */
    public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8;
    /**
     * The last boundary of display area for system features
     */
    public static final int FEATURE_SYSTEM_LAST = 10_000;

看一下这些Feature的含义都是什么:

  • FEATURE_ROOT,一个屏幕上的根DisplayArea,对应DisplayContent。
  • FEATURE_DEFAULT_TASK_CONTAINER,包容默认Task容器的DisplayArea,对应TaskDisplayArea。
  • FEATURE_WINDOW_TOKENS,包容非activity窗口的DisplayArea,对应DisplayArea.Tokens。
  • FEATURE_ONE_HANDED,用于单手形式的DisplayArea,对应名为“OneHanded”的DisplayArea。
  • FEATURE_WINDOWED_MAGNIFICATION,在ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW形式下可以对窗口的某些区域进行放大的DisplayArea,对应名为“WindowedMagnification”的DisplayArea。
  • FEATURE_FULLSCREEN_MAGNIFICATION,在ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN形式下可以对整个屏幕进行放大的DisplayArea,对应名为“FullscreenMagnification”的DisplayArea。
  • FEATURE_HIDE_DISPLAY_CUTOUT,隐藏DisplayCutout的DisplayArea,对应名为“HideDisplayCutout”的DisplayArea。
  • FEATURE_IME_PLACEHOLDER,寄存输入法窗口的DisplayArea,对应名为“ImePlaceholder”的DisplayArea。
  • FEATURE_ONE_HANDED_BACKGROUND_PANEL,包容单手布景图层的DisplayArea,防止用户敞开暗黑形式后,无法分辨屏幕是否现已进入了单手形式,对应名为“OneHandedBackgroundPanel”的DisplayArea。

其间DisplayContent作为DisplayArea根节点,TaskDisplayArea和DisplayArea.Tokens(它还有一个ImeContainer子类)作为叶节点,其它类型的DisplayArea作为中心节点。

举个比方来阐明一下DisplayArea层级结构的生成规矩。

1)、假如咱们现在有一个可以包容窗口层级值等于17的DisplayArea.Tokens,那么可以将它命名为“Leaf:17:17”,代表它是一个DisplayArea叶节点,而且可以包容的窗口层级值的最大值和最小值都是17,即它只能包容窗口层级值为17的窗口:

认识窗口

2)、由于它是作为叶节点存在的,自身只作为一个容器存在,假如咱们想要让它具有一些其它的功用,如可以包容输入法,那么就需求为它增加一个FEATURE_IME_PLACEHOLDER类型的父容器,子容器会承继父容器的特性:

认识窗口

3)、同理,假如咱们想要让它具有界说的一切功用,那么就需求为它增加每一个feature对应的节点。终究也别忘了根节点,这样才干构成一个完成的DisplayArea层级结构,即从根节点到叶节点:

认识窗口

4)、再来考虑这样一种状况,假如有两个相邻的Leaf,“Leaf:16:16”和“Leaf:17:17”,它们本来别离是有一个ImePlaceholder类型的父容器:

认识窗口

可是看到它们都的父容器都是ImePlaceholder类型的,代表它们都希望具有同一种功用,那么这儿可以把它们进行兼并,就像这样:

认识窗口

究竟已然可以重复利用,那就没有必要创立额定的节点。

留意这儿有必要要是相邻的节点才行,假如是这样:

认识窗口

这儿的“Leaf:15:15”和“Leaf:17:17”的父节点是无法兼并的,就只能为它们别离创立一个FullscreenMagnification类型的父容器。

5)、了解了以上内容,现在咱们可以来分析一下DisplayArea的生成规矩。

由于总共有37个窗口层级值,那么最初或许就会有37个独立而且完好的DisplayArea层级结构:

认识窗口

这儿的每一列都代表了一个独立而且完好的DisplayArea层级结构,每一个有色彩的格子都代表一个DisplayArea节点,从根节点DisplayContent一直到叶节点Leaf。

Leaf地点的层级结构代表了它所包容的窗口所具有的一些功用。由于窗口都是有特定的用处,所以必定不是一切的窗口都能作为输入法窗口,那么关于大部分层级结构,需求将删去ImePlaceholder类型的节点。相同,有一些窗口也不希望具有放大功用,那么它们的层级结构就或许需求删去WindowMagnification或许FullscreenMagnification类型的节点。

依据Android对各种窗口层级值可以具有的功用界说:

Feature.mName Feature.mID Feature.mWindowLayers
WindowedMagnification FEATURE_WINDOWED_MAGNIFICATION 0~31
HideDisplayCutout FEATURE_HIDE_DISPLAY_CUTOUT 016,18,2023,26~35
OneHandedBackgroundPanel FEATURE_ONE_HANDED_BACKGROUND_PANEL 0~1
OneHanded FEATURE_ONE_HANDED 023,2635
FullscreenMagnification FEATURE_FULLSCREEN_MAGNIFICATION 014,1723,2627,2931,33~35
ImePlaceholder FEATURE_IME_PLACEHOLDER 15~16

咱们需求删去一些节点,删去后的状况是(这儿把ImeContainer和TaskDisplayArea也标记了出来):

认识窗口

空出的格子代表了该层级结构不需求的节点,由于该节点被删去,所以需求将该节点的父节点与该节点的子节点进行衔接,即将有色彩的格子上移来填补表里的空白格子,衔接后的状况是:

认识窗口

终究,如咱们上面所说的,兼并那些可以兼并的相邻节点的父节点,终究得到:

认识窗口

依然是一个格子代表一个节点,转为树状图是:

认识窗口

终究再在每一个叶节点上,增加它们对应的窗口,得到终究的WindowContainer层级结构图:

认识窗口

终究再提一下,咱们将窗口分为App之上的窗口、App窗口以及App之下的窗口,基准便是TaskDisplayArea。

2.7 WindowContainer层级结构根节点 —— RootWindowContainer

回看上面的WindowContainer层级结构图,能看到根节点为RootWindowContainer,而非DisplayArea层级结构的根节点DisplayContent,究竟DisplayContent只代表一块屏幕,而Android是支撑多屏幕的,打开来说便是包括一个内部屏幕(内置于手机或平板电脑中的屏幕)、一个外部屏幕(如经过 HDMI 衔接的电视)以及一个或多个虚拟屏幕。

那么就需求一个DisplayContent的容器呈现,这个人物天然也便是RootWindowContainer了,从姓名也能看出来,它是作为WindowContainer层级结构的根节点所存在的:

/** Root {@link WindowContainer} for the device. */
class RootWindowContainer extends WindowContainer<DisplayContent>
        implements DisplayManager.DisplayListener

那么假如咱们在开发者选项里经过“Simulate secondary displays”敞开另一个虚拟屏幕,此刻的状况是:

ROOT type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
  #1 Display 0 name="Built-in screen" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][720,1612] bounds=[0,0][720,1612]
  #0 Display 2 name="Overlay #1" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][720,480] bounds=[0,0][720,480]

转为层级结构图:

认识窗口

“Root”即RootWindowContainer,“Display 0”代表默认屏幕,“Display 2”是咱们敞开的虚拟屏幕。

2.8 小结

在代码查找WindowContainer的承继联系,可以得到:

认识窗口

除了不参加WindowContainer层级结构的WindowingLayerContainer之外,其他类都在本文有提及,用类图总结为:

认识窗口

除了WindowState可以显现图画外,大部分的WindowContainer,如WindowToken、TaskDisplayArea是不会有内容显现的,都仅仅一个笼统的容器概念,说白了便是一个收纳用的容器。极端点说,WMS假如只为了办理窗口,WMS也可以不创立这些个WindowContainer类,直接用一个WindowState的列表办理屏幕上的一切窗口。可是为了更有逻辑地办理屏幕上显现的窗口,比方哪些窗口归于同一个Activity?哪些Activity归于同一个App?怎样将App窗口和非App窗口清楚得分离隔?因而仍是需求创立各种各样的窗口容器类,即WindowContainer及其子类,来对WindowState进行分类,而且每一种WindowContainer一般都由别的一种专门的WindowContainer来办理,然后对窗口进行层次化的办理。这种层次化办理确保了每一个WiindowContainer都会在其对应的父容器中进行调整而不会越界。这个一点很重要,比方我点击HOME键回到Launcher,此刻TaskDisplayArea就会把Launcher对应的Task,移动到它所对应的仓库顶部,而这个调整仅限于TaskDisplayArea内部(由于TaskDisplayArea是Task的容器),这就确保了再怎样调整Launcher,Launcher的窗口也永久不或许高于StatusBar窗口,也不会低于Wallpaper窗口。

别的这个层级结构树立起来后,对窗口的遍历也是依据这个层级结构。比方产生转屏后,DisplayContent会依据新的屏幕方向从头计算Configuration,然后依照这个层级结构的次序,顺次把这个新计算的Configuration发给每一个WindowContainer,终究每一个WindowContainer都能依据新的Configuration更新自己的Configuration,而非每一个WindowContainer都需求从头去计算一次Configuration,而且大部分的更新其实都是直接拿从父容器传过来的Configuration赋值给自己的Configuration(相当于彻底承继了父容器的Configuration,当然会有一些WindowContainer会显式的声明自己有共同的策略,不会彻底承继父容器的Configuration,但这仅仅少量)。这表现容器的一个特性,即承继特性,比方你把一个盒子翻转,那么盒子里的小盒子也会跟着翻转,而不是每一个小盒子再去考虑要不要翻转。再举一个比方便是,分屏形式下,咱们只需求计算好分屏Task的bounds,Task里的ActivityRecord以及ActivityRecord里的WindowState,会自动承继Task的Configuration,变成和Task相同的巨细(大盒子缩小后,里边的小盒子会跟从大盒子缩小)。

3 Window的底层完成 —— Layer层级结构

3.1 Layer根底概念

之前咱们或许会以为窗口便是屏幕上一块显现图画的区域,这个当然不错,可是反之不相同树立,即屏幕上一块显现图画的区域不一定便是一个窗口,可是它一定是一个Layer。至于Layer,则一个比窗口更底层的完成,代表屏幕上一块显现内容的区域:

认识窗口

SurfaceFlinger假如要将一切Layer进行合成,那么需求知道每一个Layer的两个根本信息:

  • 显现边框/鸿沟,即该Layer在屏幕上的显现方位(x和y)和巨细(w和h)。

  • 显现内容,即该Layer所制作图画的图形数据,保存在Layer具有的图形缓冲区中。

这两个信息需求WMS和App这两方别离供给:

  • WMS作为窗口办理服务,每一个窗口要显现多大,显现在哪儿,天然是由它来决议。

  • App决议它界说的每一个界面的详细制作内容,这个也不必多说。

SurfaceFlinger拿到从WMS和App传过来的数据,将一切Layer合成送显。

那么还有一个问题,处于不同的进程的App、WMS以及SurfaceFlinger,经过什么办法来进行信息传递?

认识窗口

1)、当App进入前台时,WMS会向SurfaceFlinger恳求为App创立Layer。

2)、SurfaceFlinger创立Layer后,回来给WMS一个该Layer对应的SurfaceControl目标。WMS持有这些SurfaceControl,而且后续可以经过SurfaceControl向SurfaceFlinger供给窗口元数据,用来操控App在屏幕上的外观。

3)、WMS将SurfaceControl发送给App,App会依据SurfaceControl创立BufferQueue和Surface。Surface持有一组图形缓冲区,而且链接了BufferQueue中的图画出产者BufferQueueProducer,后续由它们来向SurfaceFlinger供给制作了内容的图形缓冲区。

总的来说,SurfaceFlinger消费Surface出产的图画缓冲区,并运用WMS经过SurfaceControl供给的窗口元数据,来对Layer进行合成,因而Layer是Surface(包括 BufferQueue)和SurfaceControl(包括显现边框等窗口元数据)的组合。

3.2 Layer层级结构

再来看一段SurfaceFlinger的信息:

+ EffectLayer (Task=16#0) uid=1000
  Region TransparentRegion (this=0 count=0)
  Region VisibleRegion (this=0 count=0)
  Region SurfaceDamageRegion (this=0 count=0)
      layerStack=   0, z=       11, pos=(0,0), size=(  -1,  -1), crop=[  0,   0,   0,   0], cornerRadius=0.000000, isProtected=0, isTrustedOverlay=0, isOpaque=0, invalidate=0, dataspace=De
fault, defaultPixelFormat=Unknown/None, backgroundBlurRadius=0, color=(-1.000,-1.000,-1.000,1.000), flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00], mBounds=[0.00, 0.00, 1200.00, 1920.00], l
ayerId=0
      parent=DefaultTaskDisplayArea#0
      zOrderRelativeOf=none
      activeBuffer=[   0x   0:   0,Unknown/None], tr=[0.00, 0.00][0.00, 0.00] queued-frames=0, mRefreshPending=0, metadata={taskId:16}, cornerRadiusCrop=[0.00, 0.00, 0.00, 0.00],  shadowRa
dius=0.000,
+ ContainerLayer (1c7dd1d ActivityRecordInputSink com.android.settings/.Settings#0) uid=1000
  Region TransparentRegion (this=0 count=0)
  Region VisibleRegion (this=0 count=0)
  Region SurfaceDamageRegion (this=0 count=0)
      layerStack=   0, z=-2147483648, pos=(0,0), size=(  -1,  -1), crop=[  0,   0,  -1,  -1], cornerRadius=0.000000, isProtected=0, isTrustedOverlay=0, isOpaque=0, invalidate=1, dataspace=
Default, defaultPixelFormat=Unknown/None, backgroundBlurRadius=0, color=(0.000,0.000,0.000,1.000), flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00], mBounds=[0.00, 0.00, 1200.00, 1920.00], la
yerId=0
      parent=ActivityRecord{8e62b92 u0 com.android.settings/.Settings t16}#0
      zOrderRelativeOf=none
      activeBuffer=[   0x   0:   0,Unknown/None], tr=[0.00, 0.00][0.00, 0.00] queued-frames=0, mRefreshPending=0, metadata={}, cornerRadiusCrop=[0.00, 0.00, 0.00, 0.00],  shadowRadius=0.00
0,
+ ContainerLayer (ActivityRecord{8e62b92 u0 com.android.settings/.Settings t16}#0) uid=1000
  Region TransparentRegion (this=0 count=0)
  Region VisibleRegion (this=0 count=0)
  Region SurfaceDamageRegion (this=0 count=0)
      layerStack=   0, z=        0, pos=(0,0), size=(  -1,  -1), crop=[  0,   0,  -1,  -1], cornerRadius=0.000000, isProtected=0, isTrustedOverlay=0, isOpaque=0, invalidate=1, dataspace=De
fault, defaultPixelFormat=Unknown/None, backgroundBlurRadius=0, color=(0.000,0.000,0.000,1.000), flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00], mBounds=[0.00, 0.00, 1200.00, 1920.00], laye
rId=0
      parent=Task=16#0
      zOrderRelativeOf=none
      activeBuffer=[   0x   0:   0,Unknown/None], tr=[0.00, 0.00][0.00, 0.00] queued-frames=0, mRefreshPending=0, metadata={}, cornerRadiusCrop=[0.00, 0.00, 0.00, 0.00],  shadowRadius=0.00
0,
+ ContainerLayer (5cadaf2 com.android.settings/com.android.settings.Settings#0) uid=1000
  Region TransparentRegion (this=0 count=0)
  Region VisibleRegion (this=0 count=0)
  Region SurfaceDamageRegion (this=0 count=0)
      layerStack=   0, z=        0, pos=(0,0), size=(  -1,  -1), crop=[  0,   0,  -1,  -1], cornerRadius=0.000000, isProtected=0, isTrustedOverlay=0, isOpaque=0, invalidate=1, dataspace=De
fault, defaultPixelFormat=Unknown/None, backgroundBlurRadius=0, color=(0.000,0.000,0.000,1.000), flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00], mBounds=[0.00, 0.00, 1200.00, 1920.00], laye
rId=0
      parent=ActivityRecord{8e62b92 u0 com.android.settings/.Settings t16}#0
      zOrderRelativeOf=none
      activeBuffer=[   0x   0:   0,Unknown/None], tr=[0.00, 0.00][0.00, 0.00] queued-frames=0, mRefreshPending=0, metadata={}, cornerRadiusCrop=[0.00, 0.00, 0.00, 0.00],  shadowRadius=0.00
0,
+ BufferStateLayer (com.android.settings/com.android.settings.Settings#0) uid=1000
  Region TransparentRegion (this=0 count=0)
  Region VisibleRegion (this=0 count=1)
    [  0,   0, 1200, 1920]
  Region SurfaceDamageRegion (this=0 count=1)
    [  0, 286, 1200, 1824]
      layerStack=   0, z=        0, pos=(0,0), size=(1200,1920), crop=[  0,   0,  -1,  -1], cornerRadius=0.000000, isProtected=0, isTrustedOverlay=0, isOpaque=1, invalidate=0, dataspace=De
fault, defaultPixelFormat=RGBA_8888, backgroundBlurRadius=0, color=(0.000,0.000,0.000,1.000), flags=0x00000102, tr=[1.00, 0.00][0.00, 1.00], mBounds=[0.00, 0.00, 1200.00, 1920.00], layerId
=0
      parent=5cadaf2 com.android.settings/com.android.settings.Settings#0
      zOrderRelativeOf=none
      activeBuffer=[1200x1920:1200,RGBA_8888], tr=[1.00, 0.00][0.00, 1.00] queued-frames=0, mRefreshPending=0, metadata={dequeueTime:505108725337, windowType:1, ownerPID:6640, ownerUID:100
0}, cornerRadiusCrop=[0.00, 0.00, 0.00, 0.00],  shadowRadius=0.000,
      *BufferQueueDump mIsBackupBufInited=0, mAcquiredBufs(size=1)
       [00] handle=0xb400007e3a327d80, fence=0xb400007e3a281c80, time=0x759ae697c5, xform=0x00
       FPS ring buffer:
       (0) 13:19:15.488 fps=1.94  dur=1544.49       max=1494.64       min=16.54

这是当从Launcher发动Settings运用后,Settings中一切Layer的信息,转为SurfaceFlinger层级结构图:

认识窗口

比照Java层WindowContainer层级结构图:

认识窗口

能看到:

1)、WMS侧中每一级的WindowContainer,在SurfaceFlinger侧中都有一个对应的Layer,这是由于每创立一个WindowContainer,都会为其创立一个SurfaceControl目标。那么换句话说,在SurfaceFlinger侧的每一个Layer,大部分状况下在WMS侧都能找到一个对应的的surfaceControl,这也便是为什么说WMS可以经过SurfaceControl来操控SurfaceFlinger中Layer的显现。

2)、这儿有一个名为”ContainerLayer (1c7dd1d ActivityRecordInputSink com.android.settings/.Settings#0)“的Layer,这个类是用来阻拦输入事情向Activity发送的,可以看下它创立的当地:

// frameworks\base\services\core\java\com\android\server\wm\ActivityRecordInputSink.java
	private SurfaceControl createSurface(SurfaceControl.Transaction t) {
        SurfaceControl surfaceControl = mActivityRecord.makeChildSurface(null)
                .setName(mName)
                .setHidden(false)
                .setCallsite("ActivityRecordInputSink.createSurface")
                .build();
        // Put layer below all siblings (and the parent surface too)
        t.setLayer(surfaceControl, Integer.MIN_VALUE);
        return surfaceControl;
    }
  • 经过ActivityRecord.makeChildSurface,将该SurfaceControl对应的Layer的父Layer设置为了ActivityRecord。
  • 经过Transaction.setLayer,将该SurfaceControl对应的Layer的层级设置为最低 —— Integer.MIN_VALUE(-2147483648)。

那么结果便是该Layer的信息所显现的那样;

      z=-2147483648
      parent=ActivityRecord{8e62b92 u0 com.android.settings/.Settings t16}#0

所以ActivityRecord这一级WindowContainer对应的Layer,除了有一个WindowState对应的子Layer以外,还有一个ActivityRecordInputSink对应的子Layer。

别的由于该Layer的层级值是负数,所以我这边在画层级结构图的时分,将它放在了父Layer上面,即父Layer上面的子Layer,层级值为负数,父Layer下面的子Layer,层级值为正数(SurfaceFlinger的信息,越往下的Layer层级值越高,而之前贴的WindowContainer的信息则是越靠上层级值越高)。

3)、SurfaceFlinger中,WindowState这一级下面,还有一个Layer:”BufferStateLayer (com.android.settings/com.android.settings.Settings#0)“。那么这儿就需求介绍一下Layer类的承继联系:

认识窗口

  • Layer,一切Layer的父类。
  • EffectLayer,代表了一个有纯色或许暗影作用的Layer,比方完成Dim作用的时分,增加的便是这个Layer,别的一提,Task对应的WindowContainer也是这种Layer,阐明Task也是可以显现一些内容的(ShadowRadius)。
  • ContainerLayer,代表了一个容器类Layer,这种Layer没有缓冲区,仅仅用来作为其他Layer的容器,比方ActivityRecord和WindowState对应的Layer,都是这种类型。
  • BufferLayer,是带着图形缓冲区的Layer,即真实显现App制作内容的Layer。其间又分BufferQueueLayer和BufferStateLayer,由于BufferQueueLayer运用旧版的API,支撑的功用有限,如今现已弃用,现在用的是BufferStateLayer。BufferStateLayer对应Java层WindowSurfaceController目标持有的SurfaceControl目标,会在relayoutWindow流程中创立,当窗口制作完成后进行显现。

将Layer层级结构和WindowContainer层级结构进行比较后可以知道,Layer层级结构的树立是以WindowContainer层级结构为根底,向下又延申了一层BufferStateLayer,假如你现已了解了WindowContainer层级结构,那么了解Layer层级结构起来就十分简单。

别的留意这仅仅一般规矩,实践上体系运转进程中或许会创立许多Layer交叉其间,如动画的Leash、完成暗影作用的DimLayer以及旋转用到的RotationLayer等等,关于这些特别的Layer则需求详细场景详细分析。

接下来介绍几种可以层级结构改变的状况。

3.3 Leash

以一个从Launcher发动Settings的动画进程为例来阐明一下Leash的用法,由所以App从后台移动到前台,因而动画的主体是Task。

3.3.1 动画前

动画前Task#16相关的层级结构是:

认识窗口

Task#16的Layer层级结构,咱们上面也阐明过了,这儿额定加入了它的父Layer,DefaultTaskDisplayArea。

此刻没有BufferStateLayer,由于此刻Settings还在后台,窗口并没有制作。

3.3.2 动画中

动画进程中Task#16相关的层级结构是:

认识窗口

这儿多了两个Layer:

  • BufferStateLayer,窗口制作完成后才开端的动画,所以动画进程中BufferStateLayer现已在了。
  • 一个名为”animation-leash of app_transition“的Layer,且该Layer插在了DefaultTaskDisplayArea和Task中心。

Leash的这种行为有点像咱们之前分析Freeform的时分DecorCaptionView的做法:

1)、先创立一个Leash,然后指定该Leash对应的Layer的父Layer为动画的主体(这个比方是Task)的父Layer(DefaultTaskDisplayArea)。

2)、接着将Task对应的Layer从原父Layer(DefaultTaskDisplayArea)上剥离,变为Leash的子Layer(将一个子Layer从原父Layer重定向到一个新的父Layer上,称为reparent操作)。

然后完成在DefaultTaskDisplayArea和Task中插进一个Leash的操作。

留意此刻履行动画的目标是Leash,而非Task。但由于Task的父Layer是Leash,所以Leash移动,Task会跟着移动,咱们可以看到的BufferStateLayer也会跟着移动。

3.3.3 动画后

动画完毕后Task#16相关的层级结构是:

认识窗口

动画完毕,Leash现已完成了它的使命,可以撤了,可是在撤之前需求将绑定在Leash上的Task从头reparent回原父Layer上(DefaultTaskDisplayArea)。

3.3.4 小结

上面的比方大致阐明晰Leash的运转机制,这其间有3个Layer参加,一个是动画主体(Task),一个是动画主体的父Layer(DefaultTaskDisplayArea),还有一个是Leash。

Leash的作用即是代替动画的主体去履行动画:

1)、创立Leash,将Leash绑定到动画主体的父Layer上,动画主体则reparent到Leash上。

2)、对Leash进行动画,而非动画主体。

3)、动画完毕后,将动画主体reparent回原父Layer上,移除Leash。

至于为什么要搞Leash这么一个东西出来,我个人以为是,假如直接对动画主体履行动画,那么就会触及一个康复的操作。比方说对Activity间的切换,假如不必Leash直接对Activity进行操作,那么状况是:旧的Activity切走的时分,或许会有一个向左平移的动画,后续假如咱们想要这个旧的Activity从头呈现,那么咱们不只需求将它显现出来,还需求将它从之前的动画完毕方位向右平移,这样它才干呈现在屏幕上。而且咱们的动画越复杂,康复需求的过程也就越多。

假如咱们把动画主体reparent到Leash上,然后对Leash进行相同的动画,不只能到达相同的作用,而且不管咱们对Leash怎样进行动画,只要在动画完毕后将动画主体reparent回本来的父Layer上,就可以康复原状,而不必像上面相同考虑要做多少相反的操作才干康复原状。

终究,Leash理论上可以被运用到任何希望进行动画的Surface上,比方这儿举的比方是运用到了Task上,由于这个动画产生于App间的切换进程中。假如是Activity间的切换,那么Leash就可以运用到ActivityRecord,相同WindowState也是可以的,首要看动画的主体是整个App?仍是单个Activity?仍是某个窗口?

3.4 DimLayer和相对层级

DimLayer的本质是一个EffectLayer,默认是一个纯黑Layer,咱们可以经过设置Layer的透明度来施加暗影作用,或许设置Layer含糊半径来施加含糊作用。

来看一个实践运用的比方,Files:

认识窗口

此刻Files有两个窗口,子窗口盖在主窗口上,而且此刻子窗口声明晰一个特别窗口标志位:

        /** Window flag: everything behind this window will be dimmed.
         *  Use {@link #dimAmount} to control the amount of dim. */
        public static final int FLAG_DIM_BEHIND        = 0x00000002;

该窗口标志位的作用是,将声明晰该标志位的窗口的后边一切内容都变暗,因而终究的作用便是如上图所示,即主窗口被蒙上了一层暗影作用。

此刻的窗口层级结构图是:

认识窗口

在这个层级结构图中,只要可见的Layer才被标记了色彩,此刻可见Layer的Z轴次序是:子窗口 > DImLayer > 主窗口。

主窗口和子窗口对应的两个BufferStateLayer不必多说,这儿首要看下DimLayer,相关信息如下:

+ EffectLayer (Dim Layer for - Task=36#0) uid=1000
      z=       -1
      color=(0.000,0.000,0.000,0.600)
      parent=Task=36#0
      zOrderRelativeOf=f19c0b8 com.google.android.apps.nbu.files/com.google.android.apps.nbu.files.home.HomeActivity#0
  • alpha值为0.6,上面咱们也说到过,EffectLayer默认是一个纯黑的Layer,咱们需求设置它的透明度来到达变暗的作用(透明度为1则会彻底遮住后边的内容)。
  • DimLayer的父Layer是Task#36,这个跟上层的完成有联系,现在是Task和DisplayArea支撑创立这么一个DimLayer。
  • 这儿设置了DimLayer的相对层级为子窗口WindowState这一级对应的ContainerLayer,ContainerLayer (f19c0b8 com.google.android.apps.nbu.files/com.google.android.apps.nbu.files.home.HomeActivity#0)。设置一个Layer的相对层级,相当所以将该Layer从其本来的父Layer上剥离,这儿是 EffectLayer (Task=36#0),然后将这个相对层级的Layer作为父Layer,这儿是 ContainerLayer (f19c0b8 com.google.android.apps.nbu.files/com.google.android.apps.nbu.files.home.HomeActivity#0),从头绑定到这个父Layer上面。所以这儿可以看到尽管DimLayer的父Layer是 EffectLayer (Task=36#0),可是在实践上的层级结构中,DimLayer是作为了 ContainerLayer (f19c0b8 com.google.android.apps.nbu.files/com.google.android.apps.nbu.files.home.HomeActivity#0) 的子Layer。
  • 当然只设置相对层级还不行,还需求将DimLayer的层级值设置为-1,这样它才不会把子窗口也盖住,而且可以遮住层级比子窗口低的Layer。

3.4.1 DimLayer层级值从-1修正为100

这儿咱们手动修正DimLayer的完成,将其层级值从-1修正为100,那么显现作用为:

认识窗口

此刻暗影作用不只覆盖了主窗口,连子窗口也覆盖了,层级结构图为:

认识窗口

很显然,当设置EffectLayer的层级值为100后,它就变成了子窗口中层级值最高的Layer,已然比子窗口对应的BufferStateLayer层级值还高,天然会把整个Activity都遮住,契合预期。

3.4.2 DimLayer相对层级从子窗口修正为主窗口

这儿咱们手动修正DimLayer的完成,将其相对层级的目标从子窗口修正为主窗口,那么显现作用为:

认识窗口

此刻暗影作用消失,层级结构图为:

认识窗口

将EffectLayer的相对层级的目标从子窗口修正为主窗口,它就变成了子窗口中层级值最低的Layer,天然无法再遮挡任何Layer,而且被全屏的主窗口彻底遮挡然后无法显现,契合预期。

3.4.3 小结

相对层级的呈现,使得Layer的层级次序调整愈加灵活,而且后续假如不需求相对层级了,也不需求履行繁琐的”先将当时Layer从相对层级Layer上移除,再从头绑定回原父Layer“的操作,直接设置一下层级值就可以将相对层级Layer移除了。

别的你也可以依据需求,为DimLayer设置不同的相对层级目标,然后完成将某个子窗口变暗,或许将某个Activity变暗,或许将整个App变暗。

4 总结

这儿我依照自己的了解画了一下Android架构图,首要是对看下Framework这块:

认识窗口

  • 顶层是依据ApplicationFramework层的Apps层,ApplicationFramework是运转在App进程的Framework代码,如四大组件,各种Manager。在这一层,屏幕上的一块显现区域,典型代表是Activity,可是Activity究竟是一个综合性比较强的概念,详细到内容显现这块仍是由Window类担任,Window则是包容View目标的容器。咱们要了解屏幕的内容是怎样组成的,首要需求知道的便是组成屏幕的最小单位 —— Window,的内部构造,即咱们介绍的榜首部分,View层级结构。

  • 接着是体系服务,其间体系服务既有依据Java的WMS之类(我习气叫做JavaFramework),也有依据C++的SurfaceFlinger之类(我习气叫做NativeFramework):

    • JavaFramework,在这一层,屏幕上的一块显现区域,典型代表是WindowState,当然愈加通用的结构是WindowContainer。WMS作为办理窗口的服务,它需求把屏幕上的一切窗口依照一定的规矩体系化的组织起来,即咱们介绍的第二部分,WindowContainer层级结构。这样SurfaceFlinger就只需求操心合成Layer的相关事务就行了,不需求去关怀怎样去树立如此巨大的一个层级结构。
    • NativeFramework,在这一层,屏幕上的一块显现区域,典型代表是BufferStateLayer,当时愈加通用的结构是Layer。SurfaceFlinger对Layer进行合成需求依据Layer层级结构,而Layer层级结构的根本结构便是WindowContainer层级结构,别的WMS也可以经过SurfaceControl来在Layer层级结构里进行一些部分的调整。