Android源码初探之视图窗口层级关系

写在最前面

Android源码初探 —— 初次相遇,便无法自拔!
以下源码均源于 Android API 24

结论预览

Android源码初探之视图窗口层级关系_第1张图片
View(2).png

导火索

每次我们创建一个 Activity 时,都会通过调用 setContentView(@LayoutRes int layoutResID)设置布局文件。所以,我们第一步在Activity源码中找到 setContentView方法。

Activity

setContentView 源码:

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
}
public Window getWindow() {
        return mWindow;
}
public void attach( ...){
        ......
        mWindow = new PhoneWindow(this, window);
        ......
}

从代码中看出,调用了 mWindow.setContentView 方法,mWindow是一个 Window 类型,但是实现的是PhoneWindow。我们先看看 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{ public abstract void setContentView(@LayoutRes int layoutResID); }

Window 是一个抽象类,它的 setContentView 也是一个抽象方法。而且,我们从注释中可以看到:

Window 用于顶级窗口外观和行为策略的抽象基类。 此类的实例应该用作添加到窗口管理器的顶级视图。 它提供标准UI策略,如背景,标题区域,默认键处理等。
且只存在唯一实现类 PhoneWindow

所以,我们知道 Window 是Android 视图窗口的顶级。而且,我们明白 PhoneWindow 是 Window 的实现类,那么,我们看一下 PhoneWindow 中 setContentView 的实现。

PhoneWindow

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ......
    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
   ......
}

从上面代码中,我们看到最终是通过mLayoutInflater(布局加载器)将我们自定义的布局加载到了mContentParent。从定义中我们发现 mContentParent 是一个 ViewGroup 类型,且其注释说明

mContentParent 在Window的内容区展示,且 mContentParent 是 mDecor本身或者是mDecor的一个子元素

mDecor 是什么呢?那句话什么意思呢?

DecorView

我们从代码中看到 当 mContentParent = null 时,调用了 installDecor() 方法。我们看installDecor()源码。

//###########################################################
//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;

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            ......
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            .......
        }
        ......        
    }

    protected DecorView generateDecor(int featureId) {
        ......
        return new DecorView(context, featureId, this, getAttributes());
    }
    ......
}

//#######################################################
//DecorView 源码
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
DecorView(Context context, int featureId, PhoneWindow window,
            WindowManager.LayoutParams params) {
        super(context);
        ......
    }
}


从上面代码中,我们发现 mDecor 是一个 DecorView 类型(注释:DecorView 是 Window中的顶级View),且DecorView 本身继承至 FrameLayout 。我们看到调用PhoneWinow#generateLayout(DecorView decor) 对 mContentParent 进行了赋值。我们打开 PhoneWinow#generateLayout(DecorView decor) 源码。

DecorView 与 mContentParent 的关系

//PhoneWindow 类
protected ViewGroup generateLayout(DecorView decor) {
      // Apply data from current theme.
      ......
      //根据主题样式设置 DecorView 的布局,样式
      //颜色,标题等
      if (...) {
           layoutResource = R.layout.XXXX;
      }
      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
      ......
      //ID_ANDROID_CONTENT 特定的ID值
      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      return contentParent;
}

// Window 类 
public View findViewById(@IdRes int id) {
     //getDecorView()  返回前面的 mDecor
     return getDecorView().findViewById(id);
}

通过 generateLayout(DecorView decor)找到了 Window类的findViewById(@IdRes int id)。通过代码,我们可以很清楚的看到** mContentParent 是从 mDecor 布局中来的,且其ID为R.id.content根据 mDecor 布局文件的不同,有无标题titleBar,mContentParent 是 mDecor本身或者是 mDecor 的一个子元素**。这也就解释了之前的问题。

小结 :DecorView 是顶级 View,内部有 titlebar 和 contentParent 两个子元素,而我们自己设置的布局则是contentParent 里面的一个子元素。

Android源码初探之视图窗口层级关系_第2张图片
View(1).png

DecorView 与 Window 的关系

从 DecorView 的注释中,我们可以猜想到:DecorView 最终被添加到了Window 上,DecorView 是 Window 上的顶级 View。
那么 DecorView是怎么添加到 Window 上的?什么时候添加上去的呢?
首先,我们需要了解 Activity组件的启功过程,大家可以看下老罗的这篇文章 Android应用程序启动过程源代码分析
Activity组件在启动的过程中,会调用ActivityThread类的成员函数handleLaunchActivity,用来创建以及首次激活Activity组件,并完成了上面所述的DecorView创建动作,然后继续调用 ActivityThread#handleResumeActivity方法。我们看一下 handleResumeActivity 的代码:

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
      ActivityClientRecord r = mActivities.get(token);
      ......      
      if (r != null) {
            final Activity a = r.activity;
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                ......
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
            }
      }
      ......
}

上面代码中,先获取 该Activity a 相关的 Decor对象、Window对象 以及 WindowManager对象。WindowManager 是一个接口类,其实现类为 WindowManagerImpl 。所以,调用了 WindowManagerImpl#addView 方法,然后将 DecorView 添加到了 Window上(当然,其过程不止这些,不比如调用 WindowManagerGlobal类、ViewRootImpl类等,这里不展开讲了,大家可以自己去看下源码)。
经过上面的层层分析,我们得到了一个 Android 的窗口层级关系图,如下:

Android源码初探之视图窗口层级关系_第3张图片
View(2).png

补充: 添加 DecorView 到 Window

DecorView 与 Window 的关系 提到,在ActivityThread#handleResumeActivity方法中调用了**WindowManagerImpl#addView 方法,我们看下WindowManagerImpl#addView **的代码:
第一步:

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

我们继续查看 WindowManagerGlobal#addView代码:
第二步:

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
 ...
 ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
 view.setLayoutParams(wparams);
 mViews.add(view);
 mRoots.add(root);
 mParams.add(wparams);
 root.setView(view, wparams, panelParentView);
 ...
}

这个方法里创建一个ViewRootImpl,并将之前创建的DecoView作为参数传入。我们接着看ViewRootImpl 代码:
第三步:

/**
* 视图层次结构的顶部,在View和WindowManager之间实现所需的协议。
* 这实现大部分是{@link WindowManagerGlobal}的内部详细信息
*/
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
      ......
      public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
           synchronized (this) {
           if (mView == null) {
                 ............. 
                 //请求布局 
                 requestLayout();
                 .............
           }
     }
    @Override
    public void requestLayout() {
        ......
        // 执行测量操作
         //执行布局操作
         //执行绘制操作
    }
}

ViewRootImpl是个ViewParent,它里面主要对DecorView进行了测量,布局,绘制等操作。当然,里面代码很多很复杂,我这里是简写流程,大家可以自己看下源码。
第四部:
通过IWindowSession 和 IWindow 接口 进行 WindowManager 和WindowManagerService的交互,实现Window和DecorView的绑定。
大家可以通过下面的文章进一步了解:
Activity中UI框架基本概念
Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析

Android源码初探之视图窗口层级关系_第4张图片
Paste_Image.png

结束语

看源码学习,可以让我们对Android 的各种知识了解的更深更透彻,(●'◡'●)!
文章中如有错误,欢迎大家指正!也欢迎大家一起讨论学习!:)

你可能感兴趣的