美文网首页
Android View之布局加载流程

Android View之布局加载流程

作者: 昊空_6f4f | 来源:发表于2020-07-07 16:23 被阅读0次

概述

我们经常使用setContentView方法在Activity中显示页面布局,那我们编写的layout又是如何加载到Activity呢?下面我们通过阅读核心源码分析。

在进行分析之前,我们先来看一张包含关系图:

下面根据这张关系图逐步分析,最后再对这张关系图进行总结

Activity的创建

Activity都是配置在AndroidManifest.xml中,但它是如何创建的呢?追踪到入口类ActivityThread的performLaunchActivity方法:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       ...
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
          ...            
        }

上面的代码中,有一句关键代码:
activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass());

Activity是由Instrumentation类中的newActivity方法创建的,关键源码如下:

public Activity newActivity(ClassLoader cl, String className,
         Intent intent)
         throws InstantiationException, IllegalAccessException,
         ClassNotFoundException {
     String pkg = intent != null && intent.getComponent() != null
             ? intent.getComponent().getPackageName() : null;
     return getFactory(pkg).instantiateActivity(cl, className, intent);
 }

newActivity方法又调用了AppComponentFactory的instantiateActivity方法,关键源码如下:

public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
         @Nullable Intent intent)
         throws InstantiationException, IllegalAccessException, ClassNotFoundException {
     return (Activity) cl.loadClass(className).newInstance();
 }

看到这里就明白了AndroidManifest.xml配置的Activity是通过反射创建的

PhoneWindow的创建

在ActivityThread的performLaunchActivity方法中,有以下一段代码:

Window window = null;
    if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
        window = r.mPendingRemoveWindow;
        r.mPendingRemoveWindow = null;
        r.mPendingRemoveWindowManager = null;
    }
    appContext.setOuterContext(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);

注意activity.attach这句代码,该方法传递了一个Window对象,attach方法中有一句关键代码:
mWindow = new PhoneWindow(this, window, activityConfigCallback);
由源码可知,当Activity创建完后,会为当前的Activity创建一个PhoneWindow对象

DecorView的创建

DecorView的创建,我们从Activity的setContentView的源码开始分析:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

由源码可知,Activity的setContentView实际是调用了PhoneWindow的setContentView方法,跟踪源码:

 @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 == null时会调用installDecor方法,跟踪源码:

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
                ...
       }
       ....    

installDeco中会先generateDecor方法,关键代码如下:

 protected DecorView generateDecor(int featureId) {
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
    }

generateDecor方法会创建一个DecorView对象并返回,追溯源码可知DecorView是一个FrameLayout,到这里我们就明白DecorView是何时创建的

布局加载

在PhoneWindow的installDecor()方法中有以下一段关键代码:

if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        mDecor.makeOptionalFitsSystemWindows();
            ...
   }            

当mContentParent为null时会调用到generateLayout(mDecor)方法,追踪源码:

protected ViewGroup generateLayout(DecorView decor) {
    ...
    // Inflate the window decor.

        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_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 {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

       ...
       
        return contentParent;
    }
}

由源码注释可知generateLayout方法的核心功能是完成DecorView的布局加载,根据不同的主题样式会加载不同的系统默认布局,这里我们以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>

screen_simple.xml布局包含了一个ViewStub和FrameLayout,那这个布局是如何被加载到DecorView呢?generateLayout方法中有以下一句代码:

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

跟踪到DecorView的onResourcesLoaded方法:

 void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        if (mBackdropFrameRenderer != null) {
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        }

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

由源码可知,layoutResource通过LayoutInflate加载后,调用DecorView的addView方法将布局挂载到DecorView上,但是到这里也只是完成了DecorView布局加载,那我们自己编写的layout布局又是在何时加载呢?我们继续跟踪源码,在mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);后执行了以下代码:

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

在DecorView加载完默认布局后,会查找Id为ID_ANDROID_CONTENT的控件(默认布局中的FrameLayout控件)并作为generateLayout方法的返回值,那我们编写的布局是加载到这个控件上吗?我们回到setContentView方法中,可观察到以下代码:

mLayoutInflater.inflate(layoutResID, mContentParent);

到这里我们知道了,我们自己编写的布局是被加载到DecorView中Id为ID_ANDROID_CONTENT的控件上

总结

至此,我们来总结一下最开始的包含关系图,当Activity创建后会创建出一个PhoneWindow对象,当在Activity中调用setContentView时,实际上是调用了PhoneWindow的setContentView方法,此时PhoneWindow会创建根布局DecorView并根据主题样式为DecorView加载对应的默认系统布局,在默认的系统布局中包含了一个Id为ID_ANDROID_CONTENT的控件,而我们编写的布局就是加载到这个控件中的。

相关文章

网友评论

      本文标题:Android View之布局加载流程

      本文链接:https://www.haomeiwen.com/subject/yylaqktx.html