Android源码分析之界面的构成和创建

前言

对于大部分Android开发而言界面都是我们开发必不可少的环节,但是大部分人对界面的理解还只停留在id为content的根布局中,我们自己的布局外到底套着多少布局?为什么在onCreate中无法通过getWidth()和 getMeasuredWidth()拿到view的尺寸?为什么View不能在子线程中更新,但是在onResume中和onResume之前可以在子线程中更新?这些疑问在阅读完本篇文章之后都会解开答案。

注意:下面的内容是建立在继承AppCompatActivity的基础上,Activity的结构类似但是简单一点
由于代码量太大,也为了方便阅读,下面的代码都是删减版,只有核心代码

界面的构成

废话不多说,直接上图:
在这里插入图片描述
这是我通过Android Studio的Layout Inspector抓取的手机上界面的构成图,看上去是不是有点懵B,别着急下面还有一张:
在这里插入图片描述
这张图里我用箭头加文字描述了每个部分是什么,注意我由从外到内的方式标了序号,按照我的序号看,而且每个层次我用的相同的颜色框起来的。简单介绍一下(上面的序号对应下面的序号):

  1. 从图上看DecorView是最外层的布局,这么说对也不对,因为DecorView是由PhoneWindow持有的,你可以说最外层是PhoneWindow,但是PhoneWindow不是继承View的,又不算是布局,所以也可以说最外层的布局是DecorView,这里不用纠结,知道这么一个情况就行了。DecorView下有statusBarBackground、navigationBarBackground和LinearLayout三个直接子布局。
  2. statusBarBackground是手机的状态栏,我们经常需要适配的沉浸式状态栏就是这里。
  3. navigationBarBackground是手机的导航栏,也就是下面的虚拟按键,这个有的手机有,有的没有,现在的全面屏手机基本都会有这个选项,但是大部分人用的是手势操作,一般不用去适配这个。
  4. Linearlayout下面包着一个FrameLayout,FrameLayout下面又包着一个decor_content_parent,decor_content_parent下面有action_bar_container和content两个直接子布局。
  5. action_bar_container就是系统自带的标题栏了,里面是一个ToolBar,但是我们一般不用。
  6. content可能是最多人了解的一个布局了,他是我们自己布局的父布局,setContentView(R.layout.activity_main)中的R.layout.activity_main就是被添加到这个布局中了。
  7. RelativeLayout是我们自己的布局,是我写的。button是我添加的一个按钮。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Button" />
</RelativeLayout>

这是原始的布局文件。
界面的构成讲完了,其实也没有那么复杂,以前在网上搜这个知识点,没有一个讲的全面的,果然还是要靠自己。

界面的创建

这里就来到愉(cao)快(dan)的阅读源码时间了,想了解界面的创建过程只能去读源码。我先大致把流程总结一下,后面我们在看源码,这样会好理解一点。
界面的创建涉及Activity的两个生命周期,在onCreate中创建视图,在onResume中展示视图。

在onCreate的过程中会创建PhoneWindow、WindowManager和DecorView,视图在解析之后添加到DecorView中,DecorView会保存在PhoneWindow中。

在onResume中DecorView会被WindowManager的ViewRootImpl进行绘制,也就是调用view的三大方法onMeasure、onLayout、onDraw。

简单来说就是这么个流程,但是里面的细节很多,看下面的内容建议你先冲杯咖啡。

onCreate-视图创建过程

故事的开头从performLaunchActivity开始,这是创建Activity开始的类,onCreate方法就是在这里调用的。

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        // 将目标Activity的类文件加载到内存中,并创建一个实例。由于所有Activity组件都是从Activity类
        // 继承下来的,因此,我们就可以将前面创建的Activity组件保存在Activity对象activity中
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        // 创建Application对象
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        // 使用ContextImpl对象appContext和ActivityClientRecord对象r来初始化Activity对象activity
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config,
                r.referrer, r.voiceInteractor, window, r.configCallback);
        // 调用成员变量mInstrumentation的callActivityOnCreate方法将Activity对象activity启动起来,
        // 会调用Activity的onCreate方法
        if (r.isPersistable()) {
            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
        } else {
            mInstrumentation.callActivityOnCreate(activity, r.state);
        }
        return activity;
    }

PhoneWindow创建

这里创建了Activity并且调用它的attach方法绑定了很多对象,最后调用Activity的onCreate方法,attach方法绑定的对象中有一个window对象,我们看一下他的attach方法:

    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) {
        attachBaseContext(context);

        // 初始化PhoneWindow对象(可以看到每一个Activity都有一个PhoneWindow)
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        // 创建WindowManager(实现类是WindowManagerImpl,另外一个是创建Dialog时)
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
    }

WindowManager创建

可以看到这里通过Window对象创建了PhoneWindow对象,接着设置PhoneWindow的各种参数,其中最重要的是WindowManager对象,我们看一下他的setWindowManager方法:

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        // 创建WindowManager的实现类WindowManagerImpl
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

因为WindowManager是系统服务,这里通过getSystemService获取了这个对象,然后又通过他的createLocalWindowManager方法又创建了一个WindowManager对象,我们看一下createLocalWindowManager方法:

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

这里直接new了一个WindowManagerImpl对象返回,这里我们也就知道WindowManager的实现类是WindowManagerImpl,这样的话每个Activity都会有一个WindowManager对象,这样怎么统一管理呢?其实WindowManager只是一个代理类,真正管事的是WindowManager中的WindowManagerGlobal:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

可以看出WindowManagerGlobal是一个单例类,这样即使有多个WindowManager对象最终也只有一个WindowManagerGlobal。我们看一下WindowManagerImpl的addView方法:

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

其实就是调用WindowManagerGlobal的addView方法。
这里我们也讲一下WindowManagerGlobal这个类,它里面缓存了一个app所有的DecorView和ViewRootImpl:

    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();

从这里也能看出他是真正对Window管理的类,他还有两个非常重要的方法getWindowManagerService()和getWindowSession() :

public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    if (sWindowManagerService != null) {
                        ValueAnimator.setDurationScale(
                                sWindowManagerService.getCurrentAnimatorScale());
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }

    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

这里是跨进程通信相关的东西了,getWindowManagerService获取的是WindowManagerService,这是系统用来管理窗口的类。而getWindowSession是调用WindowManagerService的openSession方法获取的Session,这个Session是干嘛的呢?其实他类似于WindowManagerImpl和WindowManagerGlobal的关系,WindowManagerImpl的方法都是调用WindowManagerGlobal来实现的,而Session的方法其本上都是调用的WindowManagerService的方法,这里贴出来几个:

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
    }
    
    @Override
    public void setInsets(IWindow window, int touchableInsets,
            Rect contentInsets, Rect visibleInsets, Region touchableArea) {
        mService.setInsetsWindow(this, window, touchableInsets, contentInsets,
                visibleInsets, touchableArea);
    }

    @Override
    public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
        mService.getWindowDisplayFrame(this, window, outDisplayFrame);
    }

其中的mService就是WindowManagerService。

DecorView创建

到这里PhoneWindow和WindowManager都已经创建完成,下面就到了DecorView的创建,这一步在onCreate方法的setContentView中。

       public void setContentView(@LayoutRes int layoutResID) {
        // getWindow获取的是PhoneWindow,所以这里是调用的PhoneWindow的setContentView方法
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

这里的getWindow方法获取的就是在attach中创建的PhoneWindow对象,我们看一下PhoneWindow的setContentView方法:

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        // 根据layout的id加载一个布局,然后通过findViewById(R.id.content)加载出布局中id为content
        // 的FrameLayout赋值给mContentParent,并且将该view添加到mDecor(DecorView)中
        if (mContentParent == null) {// 第一次是空
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            // 没有过度效果,并且不是第一次setContentView,那么要先移除盛放setContentView传递进来
            // 的View的父容器中的所有子view
            mContentParent.removeAllViews();
        }

        // 窗口是否需要过度显示
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {// 不需要过度,加载id为layoutResID的视图并且添加到mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        // 绘制视图
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

这个方法的信息量很大,首先如果是第一次调用该方法会先调用installDecor方法创建DecorView,然后通过LayoutInflater.inflate方法将我们自己的布局加到mContentParent中,这个mContentParent是在installDecor方法中初始化的,其实就是id为content的布局。

installDecor方法
    private void installDecor() {
        mForceDecorInstall = false;
        // 继承FrameLayout,是窗口顶级视图,也就是Activity显示View的根View,包含一个TitleView和一个ContentView
        if (mDecor == null) {// 首次为空
            // 创建DecorView(FrameLayout)
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {// 第一次setContentView时为空
            // 这个mContentParent就是后面从系统的frameworks\base\core\res\res\layout\目录下加载出来
            // 的layout布局(这个Layout布局加载完成后会添加到mDecor(DecorView)中)中的一个id为content的
            // FrameLayout控件,这个FrameLayout控件用来盛放setContentView传递进来的View
            mContentParent = generateLayout(mDecor);
            // 背景
            if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                mDecor.setBackgroundFallback(mBackgroundFallbackResource);
            }
        }
    }

这里还不是创建DecorView的具体地方,而是通过generateDecor去创建,在DecorView创建完成之后会通过generateLayout方法用被创建的DecorView对象去构造mContentParent,这两个就是我们想要的了。

我们先来看DecorView的创建:

generateDecor
    protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {// 从Activity的setContentView方法调用则为true
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {// 系统进程时没有Application的context,所以就用现有的context
                context = getContext();
            } else {// 应用有application的Context
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    // 设置主题
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

这里就是创建DecorView的地方,是通过new一个DecorView对象,DecorView的构造函数会把当前的PhoneWindow对象传过去。

接着看mContentParent的创建:

generateLayout
/**
     * 因为setContentView要初始化ActionBar,所以设置NoTittle要在setContentView之前
     * <p>
     * 这里返回的是frameworks\base\core\res\res\layout\目录下layout布局中id为content的FrameLayout布局
     * 用来放置setContentView中传递进来的View
     * <p>
     * 可能的Layout:
     * R.layout.screen_swipe_dismiss
     * R.layout.screen_title_icons
     * R.layout.screen_progress
     * R.layout.screen_custom_title
     * R.layout.screen_title
     * R.layout.screen_simple_overlay_action_mode
     * R.layout.screen_simple
     */
    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        // 根据Window的属性调用相应的requestFeature
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }


        // 获取Window的各种属性来设置flag和参数
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
                false)) {
            setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                    & (~getForcedWindowFlags()));
        }

        // Inflate the window decor.
        // 根据之前的flag和feature来加载一个layout资源到DecorView中,并把可以作为容器的View返回
        // 这个layout布局文件在frameworks\base\core\res\res\layout\目录下
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            /**
             * 这个就是screen_simple.xml布局中的代码,后面的就是id为content的FrameLayout布局
             *
             * <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>
             */
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        // 根据layoutResource(布局id)加载系统中布局文件(Layout)并添加到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        // contentParent是用来添加Activity中布局的父布局(FrameLayout),并带有相关主题样式,就是上面
        // 提到的id为content的FrameLayout,返回后会赋值给PhoneWindow中的mContentParent
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        // 设置mDecor背景之类
        if (getContainer() == null) {
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            }
            mDecor.setWindowBackground(background);

            final Drawable frame;
            if (mFrameResource != 0) {
                frame = getContext().getDrawable(mFrameResource);
            } else {
                frame = null;
            }
            mDecor.setWindowFrame(frame);

            mDecor.setElevation(mElevation);
            mDecor.setClipToOutline(mClipToOutline);

            if (mTitle != null) {
                setTitle(mTitle);
            }

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            setTitleColor(mTitleColor);
        }

        mDecor.finishChanging();

        return contentParent;
    }

这个方法首先加载了一个布局文件,这个布局文件是系统提供的,不同的参数会加载不同的布局,创建界面的布局是R.layout.screen_simple,看一下这个布局:

<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,这个东西其实是以前的ActionBar,现在我们系统都是用ToolBar,我们开发甚至连ToolBar都不想用,所以这个基本没用,和ViewStub平级的是一个FrameLayout,他的id是content,这里就有点奇怪了,这和上面的界面构成图不一样呀,明明还有好几个层级呢,其实这是我们继承AppCompatActivity造成的,原始加载的布局确实是这一个,只是在加载完之后AppCompatDelegateImpl替换了content,替换成了下面的布局:

<androidx.appcompat.widget.ActionBarOverlayLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/decor_content_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

    <include layout="@layout/abc_screen_content_include"/>

    <androidx.appcompat.widget.ActionBarContainer
            android:id="@+id/action_bar_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            style="?attr/actionBarStyle"
            android:touchscreenBlocksFocus="true"
            android:gravity="top">

        <androidx.appcompat.widget.Toolbar
                android:id="@+id/action_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:navigationContentDescription="@string/abc_action_bar_up_description"
                style="?attr/toolbarStyle"/>

        <androidx.appcompat.widget.ActionBarContextView
                android:id="@+id/action_context_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"
                android:theme="?attr/actionBarTheme"
                style="?attr/actionModeStyle"/>

    </androidx.appcompat.widget.ActionBarContainer>

</androidx.appcompat.widget.ActionBarOverlayLayout>

不信你Activity改成继承原来的Activity,这时候布局就会恢复原样
在这里插入图片描述
拿到布局之后就会调用DecorView的onResourcesLoaded方法将布局添加到DecorView中:

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        final View root = inflater.inflate(layoutResource, null);
   
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

这里使用的是inflate加载布局,然后直接addview。

根布局加载完成之后会从根布局中里取id为content的子布局返回赋值给mContentParent

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

到这里界面创建的过程讲完了,下面该讲显示了,不对呀?还有statusBarBackground和navigationBarBackground呢?没有看到他们的创建呀,别急他们在后面呢,他们并没有被写进布局里,而是在动态添加的。他们的添加过程在onResume过程里。

onResume-视图显示过程

视图的显示是从ActivityThread的handleResumeActivity方法中开始的,这里也是调用onResume调用的地方。

@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                                     String reason) {

        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);

        final Activity a = r.activity;

        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            // 获取DecorView,此处r.window是PhoneWindow
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            // ViewManager是WindowManagerImpl
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;

            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    // 将DecorView添加到窗口中,调用的是WindowManagerImpl中的addView方法
                    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) {
            if (r.activity.mVisibleFromClient) {
                // 显示Activity
                r.activity.makeVisible();
            }
        }
    }

首先performResumeActivity方法里没有上面需要关注的,就是这里调用了onResume方法,这里就不把代码贴出来了,**从这里也知道为什么在onCreate中无法通过getWidth()和 getMeasuredWidth()拿到view的尺寸了,因为调用onResume方法的时候view还没开始绘制呢。**后面会 获取Activity之前创建的DecorView并设置他的Visibility为View.INVISIBLE,然后在拿到Activity的WindowManager,这里是用的ViewManager 接收,因为WindowManager是继承ViewManager的,然后调用WindowManager的addView方法将DecorView添加进去。最后调用Activity的makeVisible方法设置DecorView为View.VISIBLE。

上面的方法主要就是WindowManager的addView方法,从前面WindowManager的创建我们可以知道他的实现类是WindowManagerImpl,那就是调用到下面的代码:

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

WindowManagerImpl又会调用到WindowManagerGlobal的addView方法:

 /**
     * 窗口显示时调用,添加视图到窗口进行显示(Activity显示或者Dialog显示)
     *
     * @param view         要显示的视图(Layout布局)
     * @param params       布局参数
     * @param display      显示参数对象(大小、分辨率等)
     * @param parentWindow 要显示的窗口(Activity的PhoneWindow或者Dialog的PhoneWindow)
     */
    public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {
        
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // 初始化ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

这里首先创建了一个ViewRootImpl,后面调用了他的setView方法并将DecorView传进去,看一下这个方法:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                try {
                    // 将mWindow发送给WindowManagerService,注册进去,以后WMS接收按键、Config等改变就会
                    // 通知mWindow,mWindow是一个Binder对象,其接收到消息就会做相应处理,比如发送给主线程
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } catch (RemoteException e) {
                    throw new RuntimeException("Adding window failed", e);
                }
            }
        }
    }

首先调用了requestLayout方法,这个方法就是绘制的主要方法。后面又通过跨进程的方式调用了WindowManagerService的addToDisplay方法将window信息和回调传过去。
先来看一下requestLayout方法:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            // 检测线程,如果不是在主线程抛出异常
            checkThread();
            // 标记已经处于强制布局的过程中了
            mLayoutRequested = true;
            // 往Handler发送一个重新布局和重绘的请求
            scheduleTraversals();
        }
    }

这里有一个很重要的checkThread方法,顾名思义就是检查线程是否是主线程的,我们确认一下:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

这里拿mThread和当前线程比较,如果不同就抛异常,mThread是在ViewRootImpl的构造函数中赋值的

mThread = Thread.currentThread()

因为ViewRootImpl是在主线程创建的所以mThread被赋的值就是主线程。
这里就解释了为什么在onResume和onResume之前在子线程更新ui没有抛异常的原因了。
检查完线程之后会通过scheduleTraversals方法发送一个布局请求:

  void scheduleTraversals() {
        // 当mTraversalScheduled为false,也就是没有重绘请求或者没有未执行完的重绘时才开始重绘
        if (!mTraversalScheduled) {
            // 一旦开始重回此处设置为True,当执行完毕后调用unscheduleTraversals函数,
            // 重新设置为false,避免同时存在多次绘制
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 将消息放入消息处理器中,最终调用doTraversal方法
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

mTraversalRunnable就是我们需要执行的消息:

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            // 执行View绘制流程
            performTraversals();
        }
    }

这里就到了performTraversals方法,这是执行绘制的最终方法也是最复杂的方法(有八百多行代码),单讲它就要一篇文章,我们就不仔细研究它了,讲一下大概流程:

    private void performTraversals() {
        // 调用每一个View的dispatchAttachedToWindow方法
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        
        // 通知View树观察者,窗口(Window)被添加了
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
        dispatchApplyInsets(host);
        
        // 1.第一步:测量
        // Ask host how big it wants to be
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

        // 2.第二步:布局
        performLayout(lp, mWidth, mHeight);

        // 3.第三步:绘制
        performDraw();
    }

八百多行的代码被我压缩成这几行,首先是调用DecorView的 dispatchAttachedToWindow方法,后面会调用ViewTreeObserver的回调,这里也有很多东西,放开了讲就太多了,以后再说吧。在接着就是dispatchApplyInsets,前面说的状态栏就在这里添加的,再往后就是三大流程了。我们先说状态栏是怎么被添加的。

首先我们说的这个状态栏有两层,系统有一个显示时间通知等等的view,而我们添加的状态栏是它之下的一个带有颜色方块view而已,他只是填充在界面上,并不会影响系统的view。
从界面构成图我们可以看到状态栏是在DecorView下面的,说明状态栏是DecorView中添加的,如果你在DecorView中搜索statusbar最终应该会找到下面的代码:

    public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
            new ColorViewAttributes(SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
                    Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
                    Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
                    com.android.internal.R.id.statusBarBackground,
                    FLAG_FULLSCREEN);

    public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES =
            new ColorViewAttributes(
                    SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
                    Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
                    Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
                    com.android.internal.R.id.navigationBarBackground,
                    0 /* hideWindowFlag */);

这里是状态栏和导航栏的属性,被定义在这里,他们只在一个地方使用了:

    private final ColorViewState mStatusColorViewState =
            new ColorViewState(STATUS_BAR_COLOR_VIEW_ATTRIBUTES);
    private final ColorViewState mNavigationColorViewState =
            new ColorViewState(NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES);

我们看一下ColorViewState这个类:

    private static class ColorViewState {
        View view = null;
        int targetVisibility = View.INVISIBLE;
        boolean present = false;
        boolean visible;
        int color;

        final ColorViewAttributes attributes;

        ColorViewState(ColorViewAttributes attributes) {
            this.attributes = attributes;
        }
    }

里面保存了一个状态栏的所有信息,view参数其实就是我们要添加到布局中的东西,也可以称他为状态栏。
那么在哪里创建的呢?ViewRootImpl的dispatchApplyInsets会去调用DecorView的dispatchApplyWindowInsets方法,接着调用onApplyWindowInsets方法:

    @Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        final WindowManager.LayoutParams attrs = mWindow.getAttributes();
        mFloatingInsets.setEmpty();
        if ((attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
            // For dialog windows we want to make sure they don't go over the status bar or nav bar.
            // We consume the system insets and we will reuse them later during the measure phase.
            // We allow the app to ignore this and handle insets itself by using
            // FLAG_LAYOUT_IN_SCREEN.
            if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) {
                mFloatingInsets.top = insets.getSystemWindowInsetTop();
                mFloatingInsets.bottom = insets.getSystemWindowInsetBottom();
                insets = insets.inset(0, insets.getSystemWindowInsetTop(),
                        0, insets.getSystemWindowInsetBottom());
            }
            if (mWindow.getAttributes().width == WindowManager.LayoutParams.WRAP_CONTENT) {
                mFloatingInsets.left = insets.getSystemWindowInsetTop();
                mFloatingInsets.right = insets.getSystemWindowInsetBottom();
                insets = insets.inset(insets.getSystemWindowInsetLeft(), 0,
                        insets.getSystemWindowInsetRight(), 0);
            }
        }
        mFrameOffsets.set(insets.getSystemWindowInsets());
        insets = updateColorViews(insets, true /* animate */);
        insets = updateStatusGuard(insets);
        if (getForeground() != null) {
            drawableChanged();
        }
        return insets;
    }

这里进过一系列计算之后会调用updateColorViews方法:

    WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
        boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset);
        boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset);
        int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset);
        updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
                mWindow.mNavigationBarColor, mWindow.mNavigationBarDividerColor, navBarSize,
                navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
                0 /* sideInset */, animate && !disallowAnimate, false /* force */);

        boolean statusBarNeedsRightInset = navBarToRightEdge
                && mNavigationColorViewState.present;
        boolean statusBarNeedsLeftInset = navBarToLeftEdge
                && mNavigationColorViewState.present;
        int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
                : statusBarNeedsLeftInset ? mLastLeftInset : 0;
        updateColorViewInt(mStatusColorViewState, sysUiVisibility,
                calculateStatusBarColor(), 0, mLastTopInset,
                false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
                animate && !disallowAnimate,
                mForceWindowDrawsStatusBarBackground);
        return insets;
    }

这里两次调用了updateColorViewInt方法,而且传入的参数分别是状态栏和导航栏的ColorViewState,我们要找的应该就在这,我们到这个方法里看看:

private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
                                    int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin,
                                    boolean animate, boolean force) {
        View view = state.view;
        if (view == null) {
            if (showView) {
                state.view = view = new View(mContext);
                setColor(view, color, dividerColor, verticalBar, seascape);
                view.setTransitionName(state.attributes.transitionName);
                view.setId(state.attributes.id);
                visibilityChanged = true;
                view.setVisibility(INVISIBLE);
                state.targetVisibility = VISIBLE;

                FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(resolvedWidth, resolvedHeight,
                        resolvedGravity);
                if (seascape) {
                    lp.leftMargin = sideMargin;
                } else {
                    lp.rightMargin = sideMargin;
                }
                addView(view, lp);
                updateColorViewTranslations();
            }
        }
    }

在这里会创建状态栏View并设置他的颜色、id等属性,最后通过addView添加到DecorView中。

最后就是三大流程了。

    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);
        }
    }
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                               int desiredWindowHeight) {
        try {
            // host是DecorView,安排自己的位置
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        }finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }

无非就是调用view的measure、layout和draw方法,performDraw方法我没有贴出来,因为里面涉及了硬件绘制软件绘制等等的东西嵌套的有点多,总之最终还是调用draw方法。到这里界面就绘制完成了,还记的前面performResumeActivity中调用的Activity的makeVisible方法吗,经过这一系列的调用之后最终视图的展示出来。

总结

  • PhoneWindow:Window的子类,主要负责了DecorView的创建和setContentView中布局文件的解析和添加。
  • DecorView:界面的根布局,是一个继承FrameLayout的ViewGroup。
  • WindowManager:管理一个app的所有Window,缓存着所有的Window和ViewRootImpl,会把View的绘制请求转发到ViewRootImpl中。
  • ViewRootImpl:真正实现绘制的地方,所有View的根(虽然他是所有View的根,但是他并不是一个View,只是实现了ViewParent接口),
    DecorView也在他的管辖之下,所有的绘制请求都会到这里处理。
发布了65 篇原创文章 · 获赞 24 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/shanshui911587154/article/details/105048964
今日推荐