setContentView 布局加载流程

Android知识总结

一、继承 Activity 的流程

整体流程图

1.1、PhoneWindow 的创建

首先ActivityThreadperformLaunchActivity方法中进行activity的attach进行参数绑定

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo; //获取AcitivityInfo对象
        if (r.packageInfo == null) { //获取APK文件的描述类LoadeApk
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

        ComponentName component = r.intent.getComponent(); //创建要启动Activity的上下文环境
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader(); //用类加载器来创建该Activity的实例
            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) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);//创建Application

            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
            if (localLOGV) Slog.v(
                    TAG, r + ": app=" + app
                    + ", appName=" + app.getPackageName()
                    + ", pkg=" + r.packageInfo.getPackageName()
                    + ", comp=" + r.intent.getComponent().toShortString()
                    + ", dir=" + r.packageInfo.getAppDir());

            if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                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,
                        r.assistToken); //初始化Activity

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                checkAndBlockForNetworkAccess();
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); //调用Activity的onCreate
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
                r.activity = activity;
            }
            r.setState(ON_CREATE);
            synchronized (mResourcesManager) {
                mActivities.put(r.token, r);
            }

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }

        return activity;
    }

我们再看Activity#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, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        //创建抽象类 window 的实现类 PhoneWindow,对View进行管理
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this); //设置回调,向 Activity 分发点击或状态改变等事件
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mAssistToken = assistToken;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }
        //给 Window 设置 WindowManager 对象
        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();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
        mWindow.setPreferMinimalPostProcessing(
                (info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);

        setAutofillOptions(application.getAutofillOptions());
        setContentCaptureOptions(application.getContentCaptureOptions());
    }

1.2、setContentView 流程

首先进入Activity#setContentView方法

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

上面通过getWindow()获取Window的实现类PhoneWindow

   //在attach方法里面通过 PhoneWindow 创建
    private Window mWindow;

    public Window getWindow() {
        return mWindow;
    }

然后调用PhoneWindow#setContentView进行渲染视图

    private DecorView mDecor;
    ViewGroup mContentParent;

主要目的是创建 DecorView 拿到 Content

    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            //创建 DecorView 
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //移除 ViewGroup 中所有的 View
            mContentParent.removeAllViews();
        }

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

1.2.1、创建 DecorView

    private void installDecor() {
        mForceDecorInstall = false;
        //当mDecor为空时创建 DecorView
        if (mDecor == null) {
            //创建 DecorView
            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 为空时,用系统提供的layout初始化
            mContentParent = generateLayout(mDecor);
            ...
            }
        }
    }

mDecor == null时,调用PhoneWindow#generateDecor进行初始化

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

接下来看generateLayout里面怎么初始化 mContentParent

 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
 protected ViewGroup generateLayout(DecorView decor) {
        //设置主题
        TypedArray a = getWindowStyle();
        ....
        //系统提供的xml文件
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        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 {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
       // 添加到 DecorView(FrameLayout)里面
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource););
        //获取 contentParent (FrameLayout)
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      .....
        return contentParent;
    }

再看DecorViewonResourcesLoaded方法,主要初始化根布局

    ViewGroup mContentRoot;

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

        mDecorCaptionView = createDecorCaptionView(inflater);
        //获取布局的 View
        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();
    }

1.2.2、渲染到 mContentParent

执行LayoutInflater里的inflate 方法

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

    //resource: 要加载的XML布局资源的ID
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        //获取Resources, Resources是安卓的资源管理类
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: /"" + res.getResourceName(resource) + "/" ("
                  + Integer.toHexString(resource) + ")");
        }
        //通过反射获取编译后 View
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        //解析xml
        XmlResourceParser parser = res.getLayout(resource);
        try {
            //添加到 root (mContentParent) 跟布局中
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
        boolean attachToRoot) {
        if (!mUseCompiledView) {
            return null;
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");

        // Try to inflate using a precompiled layout.
        String pkg = res.getResourcePackageName(resource);
        String layout = res.getResourceEntryName(resource);

        try {
            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
            Method inflater = clazz.getMethod(layout, Context.class, int.class);
            View view = (View) inflater.invoke(null, mContext, resource);

            if (view != null && root != null) {
                // We were able to use the precompiled inflater, but now we need to do some work to
                // attach the view to the root correctly.
                XmlResourceParser parser = res.getLayout(resource);
                try {
                    AttributeSet attrs = Xml.asAttributeSet(parser);
                    advanceToRootNode(parser);
                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);

                    if (attachToRoot) {
                        root.addView(view, params);
                    } else {
                        view.setLayoutParams(params);
                    }
                } finally {
                    parser.close();
                }
            }

            return view;
        } catch (Throwable e) {
            if (DEBUG) {
                Log.e(TAG, "Failed to use precompiled view", e);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        return null;
    }

二、继承 AppCompatActivity 的流程

进入AppCompatActivity#setContentView方法

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

getDelegate()获取AppCompatDelegate的实现类,具体相当于代理的作用

    private AppCompatDelegate mDelegate;

    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

最终会调用AppCompatDelegate的实现类AppCompatDelegateImpl里面的setContentView

    @Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

    public void setContentView(int resId) {
        //步骤一
        // 创建并初始化DecorView
        ensureSubDecor();
        // 找到subDecor中的content id
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        // 移除content中的所有view
        contentParent.removeAllViews();
        //步骤二
        // 通过LayoutInflater加载用户定义的布局,内部实现是通过获得类名反射出每一个View,我们可以通过setFactory2()接管创建View的过程来自定义LayoutInflater
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        // 内容改变回调
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

    @Override
    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

    @Override
    public void addContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.addView(v, lp);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

createSubDecor方法, 根据应用的样式主题(Theme)设置根布局DecorView的样式, 并执行初始化. 当未含有AppCompatTheme_windowActionBar属性时, 则认为主题未设置, 并抛出异常IllegalStateException.

2.1、步骤一

    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            //创建mSubDecor
            mSubDecor = createSubDecor();

            // If a title was set before we installed the decor, propagate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                if (mDecorContentParent != null) {
                    mDecorContentParent.setWindowTitle(title);
                } else if (peekSupportActionBar() != null) {
                    peekSupportActionBar().setWindowTitle(title);
                } else if (mTitleView != null) {
                    mTitleView.setText(title);
                }
            }
            applyFixedSizeWindow();
            onSubDecorInstalled(mSubDecor);
            mSubDecorInstalled = true;
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!mIsDestroyed && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }
    private ViewGroup createSubDecor(){
        //设置属性主题
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }

        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
        a.recycle();
        //1、从Activity 拿 PhoneWindow
        ensureWindow();
        // 2、获取DecorView
        mWindow.getDecorView();
        ...
        //  通过LayoutInflater inflate subDecor的视图,这里有好几种,我挑了其中一个继续分析
        LayoutInflater inflater = LayoutInflater.from(mContext);
        subDecor = (ViewGroup) inflater.inflate(R.layout.abc_dialog_title_material, null);
        //我们得到了DecorView,而DecorView里面已经有了content内容,现在这里新建了subDecor,所以需要把content的视图migrate,之后再重新设置 content id 
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(R.id.action_bar_activity_content);
        ....
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // 也许已经有Views加载到windowContentView中,需要把这些视图重新添加到contentView中,并设置id
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }
            //将原始的 content id 置为 NO_ID
            windowContentView.setId(View.NO_ID);
            //subDecerView R.id.action_bar_activity_content --> 置为 content
            contentView.setId(android.R.id.content);
        }
        // 用subDecor填充Window的内容视图
        mWindow.setContentView(subDecor);
        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
            @Override
            public void onAttachedFromWindow() {}

            @Override
            public void onDetachedFromWindow() {
                dismissPopups();
            }
        });

        return subDecor;
    }
  • 1
    private void ensureWindow() {
        // We lazily fetch the Window for Activities, to allow DayNight to apply in
        // attachBaseContext
        if (mWindow == null && mHost instanceof Activity) {
            attachToWindow(((Activity) mHost).getWindow());
        }
        if (mWindow == null) {
            throw new IllegalStateException("We have not been given a Window");
        }
    }
  • 2
    执行PhoneWindow#getDecorView方法
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            //初始化 DecorView
            installDecor();
        }
        return mDecor;
    }

下面具体流程Activity的流程

private void installDecor() {
  ...
}

2.2、步骤二, View 创建

执行LayoutInflater#inflate方法

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

标签

    private static final String TAG_MERGE = "merge";
    private static final String TAG_INCLUDE = "include";
    private static final String TAG_1995 = "blink";
    private static final String TAG_REQUEST_FOCUS = "requestFocus";
    private static final String TAG_TAG = "tag";

初始化布局

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
              ...
                if (TAG_MERGE.equals(name)) {
                    //root == null || attachToRoot == true 抛出异常
                    if (root == null || !attachToRoot) {
                        throw new InflateException(" can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 通过反射创建View  --- 布局的rootView
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // 从 xml 中解析我们布局中根布局设值得大小
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // 设置View在父布局下Params
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // 遍历子节点
                    rInflateChildren(parser, temp, attrs, true);
                    //如果Root存在并且attachToRoot为true,即与父View绑定
                    //这里在解析的同时,就会将其添加至父View上
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    //如果父Viewwe为null或者没有绑定父View都会将当前解析的View返回,大小有自己确定
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            }
            return result;
        }
    }
  • 总结:由上可的分为三种情况
LayoutInflate的参数的作用
// 方式一:将布局添加成功
View view = inflater.inflate(R.layout.inflate_layout, ll, true);

// 方式二:报错,一个View只能有一个父亲(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, ll, true); // 已经addView
ll.addView(view);

// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);

// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
// 但是子View(Button)有效,因为Button是出于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);

由上面可得当父布局 root != null,的时候我们设置的布局才有效

调用createViewFromTag方法创建 View

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        try {
            View view = tryCreateView(parent, name, context, attrs);
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        //(1)、是否是sdk里面的控件,如:LinearLayout
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        //(2)、自定义View 
                        //如:name = androidx.constraintlayout.widget.ConstraintLayout
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } 
        ...
    }
  • (1)
    会执行PhoneLayoutInflater里面的 onCreateView方法
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };

    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }

接下来执行LayoutInflater#createView方法

    public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
          // 具体实现见下:
    }
  • (2)
    public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Objects.requireNonNull(viewContext);
        Objects.requireNonNull(name);
        Constructor constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class clazz = null;

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                //获取全类名,通过反射创建 View 对象
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                }
                //获取构造方法
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                                mContext.getClassLoader()).asSubclass(View.class);

                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, viewContext, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                }
            }

            Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = viewContext;
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            try {
                //执行构造方法,创建 View
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) {
                    // Use the same context when inflating ViewStub later.
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                //返回构造的view
                return view;
            } finally {
                mConstructorArgs[0] = lastContext;
            }
            ...
        } 
    }

接下来看inflate中调用的rInflateChildren方法

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                     // include 不能作为根结点
                    throw new InflateException(" cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                // merge 只能作为根结点
                throw new InflateException(" must be the root element");
            } else {
                //创建 View
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

上面方法所用到的标签

    //优化布局,必须作为rootView
    private static final String TAG_MERGE = "merge";
    //不能作为 root_View
    private static final String TAG_INCLUDE = "include";
    private static final String TAG_1995 = "blink";
    private static final String TAG_REQUEST_FOCUS = "requestFocus";
    private static final String TAG_TAG = "tag";
    private static final String ATTR_LAYOUT = "layout";
  • include 布局
    private void parseInclude(XmlPullParser parser, Context context, View parent,
            AttributeSet attrs) throws XmlPullParserException, IOException {
        int type;
        //判断 Include标签是否在 ViewGroup容器之内,因为 include 标签只能存在于 ViewGroup 容器之内
        if (!(parent instanceof ViewGroup)) {
            throw new InflateException(" can only be used inside of a ViewGroup");
        }
        //提取出 include 的 thme 属性,如果设置了 them 属性,那么include 包裹的View 设置的 theme 将会无效
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        final boolean hasThemeOverride = themeResId != 0;
        if (hasThemeOverride) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();

            //如果这个属性是指向主题中的某个属性,我们必须设法得到主题中layout 的资源标识符
            //先获取 layout 属性(资源 id)是否设置
        int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
        if (layout == 0) {
            //如果没直接设置布局的资源 id,那么就检索?attr/name这一类的 layout 属性
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            if (value == null || value.length() <= 0) {
                throw new InflateException("You must specify a layout in the"
                    + " include tag: ");
            }

            //从  ?attr/name 这一类的属性中,获取布局属性  
            layout = context.getResources().getIdentifier(
                value.substring(1), "attr", context.getPackageName());

        }
       //这个布局资源也许存在主题属性中,所以需要去主题属性中解析
        if (mTempValue == null) {
            mTempValue = new TypedValue();
        }
        if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
            layout = mTempValue.resourceId;
        }

        if (layout == 0) {
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            throw new InflateException("You must specify a valid layout "
                + "reference. The layout ID " + value + " is not valid.");
        }

        final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
            (ViewGroup) parent, /*attachToRoot=*/true);
        if (precompiled == null) {
            final XmlResourceParser childParser = context.getResources().getLayout(layout);

            try {
                final AttributeSet childAttrs = Xml.asAttributeSet(childParser);

                while ((type = childParser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                    // Empty.
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(getParserStateDescription(context, childAttrs)
                            + ": No start tag found!");
                }

                final String childName = childParser.getName();

                if (TAG_MERGE.equals(childName)) {
                    // The  tag doesn't support android:theme, so
                    //解析 Meger 标签
                    rInflate(childParser, parent, context, childAttrs, false);
                } else {
                    //根据 name名称来创建View
                    final View view = createViewFromTag(parent, childName,
                        context, childAttrs, hasThemeOverride);
                    final ViewGroup group = (ViewGroup) parent;
                    //获取 View 的 id 和其 Visiable 属性
                    final TypedArray a = context.obtainStyledAttributes(
                        attrs, R.styleable.Include);
                    //include 布局的 id
                    final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                    final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                    a.recycle();
                    //设置 include 包裹内容的通用 Params
                    ViewGroup.LayoutParams params = null;
                    try {//include 的 params 
                        params = group.generateLayoutParams(attrs);
                    } catch (RuntimeException e) {
                        // Ignore, just fail over to child attrs.
                    }
                    if (params == null) { //子视图的 params 
                        params = group.generateLayoutParams(childAttrs);
                    }
                    //include设置了params用include的,否则用子视图的
                    view.setLayoutParams(params);

                    // 解析子标签
                    rInflateChildren(childParser, view, childAttrs, true);
                    //如果不等于 View.NO_ID 重新设置 ID
                    if (id != View.NO_ID) {
                        //将include的id设置给view
                        view.setId(id);
                    }
                    // 加载include内容时,需要直接设置其 可见性
                    switch (visibility) {
                        case 0:
                            view.setVisibility(View.VISIBLE);
                            break;
                        case 1:
                            view.setVisibility(View.INVISIBLE);
                            break;
                        case 2:
                            view.setVisibility(View.GONE);
                            break;
                    }
                    //添加至父容器中
                    group.addView(view);
                }
            } finally {
                childParser.close();
            }
        }
        LayoutInflater.consumeChildElements(parser);
    }
解析 Include 标签
整体流程图

版权声明:
作者:ht
链接:https://www.techfm.club/p/44777.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>