setContentView源码阅读(安卓8.0)

  • 概述
  • 1.Activity的setContentView
  • 代码详情
  • 总结步骤
  • 2.AppCompatActivity的setContentView
  • 4. AppCompatViewInflater源码分析


概述

Activity 和AppCompatActivity 里面的setContentView 的代码是不同的,AppCompatActivity 对setContentView 又做了一定的判断和修改。
不同版本的setContentView 实现的代码可能有一些差异,我们要查看源码需要下载一下各个版本的安卓源码,只在Androidstudio中查看有些代码是看不到的,这里我已安卓8.0 为例子

1.Activity的setContentView

代码详情

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

进入setContentView:发现是这个样子滴

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

在进入 setContentView()发现一个问题

public abstract void setContentView(@LayoutRes int layoutResID);

Window类里面的setContentView是一个抽象的,看不到他里面的方法。对于这种方法,我们一般的做法是查看他的实现类,看实现类里面的方法。所以我们回去查看 getWindow() 方法

public Window getWindow() {
        return mWindow;
    }

我们直接搜索 mWindow = 查看他初始化的代码可以看到

Android中setContentView很慢 android studio setcontentview_android


这时可以看出new 了一个PhoneWindow 我们进入PhonWindow查看这时我们发现PhoneWindow 是红的我们进入到源码搜索PhoneWindow

Android中setContentView很慢 android studio setcontentview_加载_02


打开后查看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.
        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 {
         // 把我们自己的布局layoutId加入到mContentParent,我们set进来的布局原来是放在这里面的
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

进入installDecor()方法可以看到很多很长的代码大部分代码是进行判断可以查看到两个主要的方法

private void installDecor() {
 if (mDecor == null) {
            mDecor = generateDecor(-1);//初始化DecorView
          .........
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//初始化ViewGroup

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
            ........

先看generateDecor(-1); 初始化DecorView 看在这里做了什么

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) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        //上面的是判断context  在最后new了一个DecorView
        return new DecorView(context, featureId, this, getAttributes());
    }

进入DecorView 发现他的 实质是一个FrameLayout 所以说是创建了一个FrameLayout
接下来我们看generateLayout(mDecor); 各种各样的适配的源代码快给我看懵了其实作用是有的但是我们需要抓主要矛盾 这里在没进来就可以看出是要创建一个ViewGroup所以直接查看这个源码

int layoutResource;
这是要加载布局的资源id根据这个来查代码
可以看到是根据不同的模式 加载各种各样的资源id
我们简单的描述一下其他代码的作用

protected ViewGroup generateLayout(DecorView decor) {
	//判断Activity是否是浮动的
  mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
  .....
  //判断Activity是否没有标题
  if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
      requestFeature(FEATURE_NO_TITLE);
  }
  .....
    //判断Activity是否全屏
  if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
      setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
   }   
    // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;//没有修饰加载这个
            // System.out.println("Simple!");
            }
	......
	 //加载布局,并加入DecorView
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //通过findViewById,找到内容区域,是一个FrameLayout
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

可以看到 ID_ANDROID_CONTENT = com.android.internal.R.id.content;
我们查一下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>

总结步骤

FrameLayout的id是R.id.content

最后我们吧我们的布局放在R.id.content里面 我们可以总结出这样的步骤

1.Activity调创建一个PhoneWindow调用里面的setContentView

2.PhoneWindow创建一个DecorView 实质是一个FrameLayout

3. DecorView 通过 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);加载一个 R.layout.screen_simple的布局 ,有一个View的id是android.R.id.content。

4. 我们自己通过setContentView设置的布局id其实是解析到mParentContent里面的

大概是这个样子的

Android中setContentView很慢 android studio setcontentview_ide_03

2.AppCompatActivity的setContentView

这个代码理解起来就没什么难度了

@Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
    //我们看getDelegate()是怎么实现的
 /**
     * @return The {@link AppCompatDelegate} being used by this Activity.
     */
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
    //执行了 AppCompatDelegate.create(this, this)这个方法 我们需要去源码查看

搜索到这个东西

private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        if (Build.VERSION.SDK_INT >= 24) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

总结来说就是根据不同的版本加载不同的AppCompatDelegateImpl里面的setContentView的方法和Activity类似

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

找到android.R.id.content把我们的布局加进去
接下来是重点了Activity和AppCompatActivity同样的控件怎么就有不同的属性
我们看AppCompatDelegate的installViewFactory() 到底做了什么
源码可以看出 AppCompatDelegate 的实现类是AppCompatDelegateImpl

@Override
    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");
            }
        }
    }

setFactory2 里面的代码我就不粘贴了大概的意思就是进行安卓版本的适配
但是 我发现了Factory2 里只是执行了onCreateView方法 所以只有设置了Factory2就好执行onCreateView

public interface Factory2 extends Factory {
        /**
         * Version of {@link #onCreateView(String, Context, AttributeSet)}
         * that also supplies the parent that the view created view will be
         * placed in.
         *
         * @param parent The parent that the created view will be placed
         * in; <em>note that this may be null</em>.
         * @param name Tag name to be inflated.
         * @param context The context the view is being created in.
         * @param attrs Inflation attributes as specified in XML file.
         *
         * @return View Newly created view. Return null for the default
         *         behavior.
         */
        @Nullable
        View onCreateView(@Nullable View parent, @NonNull String name,
                @NonNull Context context, @NonNull AttributeSet attrs);
    }

我们回过头来看onCreateView 我看的是AppCompatDelegateImplV9里面的代码V7 的可能会有点差别 但是不影响理解

@Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        }

        boolean inheritContext = false;
        if (IS_PRE_LOLLIPOP) {
            inheritContext = (attrs instanceof XmlPullParser)
                    // If we have a XmlPullParser, we can detect where we are in the layout
                    ? ((XmlPullParser) attrs).getDepth() > 1
                    // Otherwise we have to use the old heuristic
                    : shouldInheritContext((ViewParent) parent);
        }
        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 */
        );
    }

反正是创建了一个AppCompatViewInflater

4. AppCompatViewInflater源码分析

public final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;

        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        View view = null;
		//在这里做了替换
        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break;
            case "EditText":
                view = new AppCompatEditText(context, attrs);
                break;
            case "Spinner":
                view = new AppCompatSpinner(context, attrs);
                break;
            case "ImageButton":
                view = new AppCompatImageButton(context, attrs);
                break;
            case "CheckBox":
                view = new AppCompatCheckBox(context, attrs);
                break;
            case "RadioButton":
                view = new AppCompatRadioButton(context, attrs);
                break;
            case "CheckedTextView":
                view = new AppCompatCheckedTextView(context, attrs);
                break;
            case "AutoCompleteTextView":
                view = new AppCompatAutoCompleteTextView(context, attrs);
                break;
            case "MultiAutoCompleteTextView":
                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                break;
            case "RatingBar":
                view = new AppCompatRatingBar(context, attrs);
                break;
            case "SeekBar":
                view = new AppCompatSeekBar(context, attrs);
                break;
        }

        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
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check its android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }

我们看到了view是在这里做了替换也就是为什么AppCompatActivity的控件和Activity加载的控件有区别的原因了

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

        try {
            mConstructorArgs[0] = context;
            mConstructorArgs[1] = attrs;

            if (-1 == name.indexOf('.')) {
                for (int i = 0; i < sClassPrefixList.length; i++) {
                    final View view = createView(context, name, sClassPrefixList[i]);
                    if (view != null) {
                        return view;
                    }
                }
                return null;
            } else {
                return createView(context, name, null);
            }
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        } finally {
            // Don't retain references on context.
            mConstructorArgs[0] = null;
            mConstructorArgs[1] = null;
        }
    }

createViewFromTag调用了createView方法

private View createView(Context context, String name, String prefix)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);

        try {
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                Class<? extends View> clazz = context.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                constructor = clazz.getConstructor(sConstructorSignature);
                sConstructorMap.put(name, constructor);
            }
            constructor.setAccessible(true);
            return constructor.newInstance(mConstructorArgs);
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        }
    }

createView通过反射拿到各种view并创建实例
到此我们也终于了解了setContentView的加载布局的源码,也看到了AppCompatActivity是怎么加载出具有新属性的控件的
需要8.0源码的留qq号码压缩包有点大没个百度网盘会员不好搞啊