Android setContentView 源码解析

文章可能篇幅过长,但会一个步骤一个步骤带大家来分析。
实现页面的几种方式
  • 继承 AppCompatActivity
  • 继承 Activity
Activity
public class Activity {
    ...
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    public Window getWindow() {
        return mWindow;
    }
    ...
}

这里的 mWindow 其实是 PhoneWindow ,在 Activity 的 attach 方法中初始化

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

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

第一次 mContentParent 肯定为 null ,所以会执行 installDecor 方法

private void installDecor() {
    ...
    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);
    }
    ...
}

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();
    }
    return new DecorView(context, featureId, this, getAttributes());
}

generateDecor 方法的意思就是 new DecorView 并返回

接着 installDecor 方法往下看
private void installDecor() {
    ...
    if (mContentParent == null) {
         mContentParent = generateLayout(mDecor);
    }
    ...
}

protected ViewGroup generateLayout(DecorView decor) {
    ...
    int layoutResource;
    int features = getLocalFeatures();
     if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        ...
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        ...
    }
    ...
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ...
    return contentParent;
}

generateLayout 方法有 3 点
1 加载系统的布局
2 将布局加载到创建的 DecorView 中
3 通过 DecorView 找到 id 为 com.android.internal.R.id.content 的 ViewGroup 返回,也就是 mContentParent

界面的组成
界面组成
AppCompatActivity
public class AppCompatActivity {
    ...
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
    ...
}
class AppCompatDelegateImpl {
    ...
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
    
    private void ensureSubDecor() {
        ...
        mSubDecor = createSubDecor();
        ...
    }
    
    private ViewGroup createSubDecor() {
        ...
         mWindow.setContentView(subDecor);
        ...
        return subDecor;
    }
    ...
}

ensureSubDecor 方法和上面的 generateLayout 类似,只不过 contentParent 是在 setContentView 中处理的

LayoutInflater 分析

经过上面的分析,不论是 Activity 还是 AppCompatActivity 最终都会执行 LayoutInflater.inflate 方法

public abstract class LayoutInflater {
    ... 
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
        }

        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

     public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            final View temp = createViewFromTag(root, name, inflaterContext, attrs);
            ...
        }
    }

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        ...
        View view = tryCreateView(parent, name, context, attrs);
        ...
    }

    public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context,
        @NonNull AttributeSet attrs) {
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }
        
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
        
        return view;
    }
    ...
}

LayoutInflater. inflate -> createViewFromTag -> tryCreateView

public class AppCompatActivity {
    ...
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }
    ...
}
public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else {
        if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
        }
    }
}

AppCompatActivity 在 onCreate 中设置了Factory,所以会执行 mFactory2.onCreateView 的方法

class AppCompatDelegateImpl {
    ...
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        ...
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }
    ...
}
public class AppCompatViewInflater {
    ...
    final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        ...
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ToggleButton":
                view = createToggleButton(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                // The fallback that allows extending class to take over view inflation
                // for other tags. Note that we don't check that the result is not-null.
                // That allows the custom inflater path to fall back on the default one
                // later in this method.
                view = createView(context, name, attrs);
        }
        ...
        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }
    }
    ...
}

可以看到当 API < 21时 View 被转换成为其他的 View,所以我们可以理解为 AppCompatActivity 会兼容低版本。

有兴趣的可以去做个小实验,分别继承 Activity 和 AppCompatActivity,将 TextView 打印出来。继承 Activity TextView 还是 TextView,而继承 AppCompatActivity 时 TextView 打印为 AppCompatTextView。
public abstract class LayoutInflater {
    ...
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        View view = tryCreateView(parent, name, context, attrs);

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
    }
    ...
}

经过刚才的分析,继承 AppCompatActivity View 会被创建成对应的 View,而Activity 是没有设置 Factory 的,所以 View 为空,将执行下面的判断。

if (-1 == name.indexOf('.')) {
    view = onCreateView(context, parent, name, attrs);
} else {
    view = createView(context, name, null, attrs);
}

如果没有'.',也就是说如果是系统控件会执行 onCreateView 方法
如果有'.',就是我们的自定义控件,如'com.xxx',将会执行 createView 方法

系统控件
protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
}

就是将系统的控件拼接上 android.view ,然后创建出来。

setContentView 源码大概就介绍到这里了,最后给大家做一个总结。
  1. Activity 和 AppCompatActivity 加载布局前都会创建一个 DecorView,并将系统布局加载到 DecorView 中,通过 DecorView 找到 id 为 android.id.content 的FrameLayout,最后通过 LayoutInflater 加载我们的 xml 布局。
  2. Activity 没有设置Factory ,AppCompatActivity 设置了 Factory。
  3. Activity 不会拦截 View,而 AppCompatActivity 会拦截 View,并将部分 View 转换成对应的 AppCompatView。
拦截小例子
public class V8BaseActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        inflateTest();
        super.onCreate(savedInstanceState);
    }

    private void inflateTest() {
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
            @Nullable
            @Override
            public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
                Log.d("View:", name);
                if (name.equals("Button")) {
                    TextView textView = new TextView(context);
                    textView.setText("test");
                    textView.setTextColor(Color.BLACK);
                    return textView;
                }
                return null;
            }

            @Nullable
            @Override
            public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
                return onCreateView(null, name, context, attrs);
            }
        });
    }
}
通过拦截方法将 Button 换成了 TextView
文章就介绍到这里了,如果有什么写得不对的,可以在下方评论留言,我会第一时间改正。

Github 源码链接

你可能感兴趣的:(Android setContentView 源码解析)