AppCompatActivity的作用分析


对于v7包下的AppCompatActivity,我是比较晚入的android(并不知道是不是坑),所以一开始就用的as创建项目就很奇怪为什么我的activity自动继承了AppCompatActivity,不是应该继承Activity吗?Ecm?我仿佛感觉受到了欺骗,于是就对其进行了研究(就是看源码拉)。(因为当时是边看边解析边写的,所以思路可能有点乱,自己再看一遍源码更好,也是学习的一部分)

一开始我就发现了v4 v7 v13等等的support库,然后发现这些库是用来解决兼容问题的,数字分别对应了android的API版本,即分别适配android版本4,7,13以上的app。看完之后发现google程序员为了适配也是良苦用心啊。

从AppCompatActivity源码的onCreate看到这个类初始化的第一步就调用了getDelegate获得了一个代理,并且你会发现下面所有的生命周期的方法都交由这个代理类来实现了,那么这个东西是什么呢?其实就是AppCompatDelegate这个抽象类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
if (delegate.applyDayNight() && mThemeId != 0) {
// If DayNight has been applied, we need to re-apply the theme for
// the changes to take effect. On API 23+, we should bypass
// setTheme(), which will no-op if the theme ID is identical to the
// current theme ID.
if (Build.VERSION.SDK_INT >= 23) {
onApplyThemeResource(getTheme(), mThemeId, false);
} else {
setTheme(mThemeId);
}
}
super.onCreate(savedInstanceState);
}

/**
* @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方法

1
2
3
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return create(activity, activity.getWindow(), callback);
}

然后调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (BuildCompat.isAtLeastN()) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}

是不是感觉这些名字有点熟悉呢。

这个方法返回了一些根据版本号的实现类。

下面我将给出这个抽象类的类之间的继承关系:

在as 中使用ctrl + H的快捷键可以很快的看到类之间的继承图。

发现他们之间相互继承,于是开始看V9的源码,找到了一个可疑的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
这个方法会(invoke) {当我们使用自己的layoutInflater的factory的时候}
*/
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
final boolean isPre21 = Build.VERSION.SDK_INT < 21;

if (mAppCompatViewInflater == null) {
mAppCompatViewInflater = new AppCompatViewInflater();
}

// We only want the View to inherit its context if we're running pre-v21
final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent);

return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
isPre21, /* 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 */
);
}
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory(layoutInflater, this);
} else {
if (!(LayoutInflaterCompat.getFactory(layoutInflater)
instanceof AppCompatDelegateImplV9)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}

突然想起来一句话,兼容问题其实就是着色问题,AppCompatDelegate 的工作就是涂色。发现这个方法在AppCompatDelegate里面定义的,别问我怎么找到的,你在V9这个类看到1000多行代码的时候找到了,然后我跑回去看AppCompatDelegate这个类里面的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Installs AppCompat's {@link android.view.LayoutInflater} Factory so that it can replace
* the framework widgets with compatible tinted versions. This should be called before
* {@code super.onCreate()} as so:
* <pre class="prettyprint">
* protected void onCreate(Bundle savedInstanceState) {
* getDelegate().installViewFactory();
* getDelegate().onCreate(savedInstanceState);
* super.onCreate(savedInstanceState);
*
* // ...
* }
* </pre>
* If you are using your own {@link android.view.LayoutInflater.Factory Factory} or
* {@link android.view.LayoutInflater.Factory2 Factory2} then you can omit this call, and instead call
* {@link #createView(android.view.View, String, android.content.Context, android.util.AttributeSet)}
* from your factory to return any compatible widgets.
*/
public abstract void installViewFactory();

/**
* This should be called from a
* {@link android.view.LayoutInflater.Factory2 LayoutInflater.Factory2} in order
* to return tint-aware widgets.
* <p>
* This is only needed if you are using your own
* {@link android.view.LayoutInflater LayoutInflater} factory, and have therefore not
* installed the default factory via {@link #installViewFactory()}.
*/
public abstract View createView(@Nullable View parent, String name, @NonNull Context context,
@NonNull AttributeSet attrs);

看到上面的注释我突然顿悟了,找了半天的在哪里设置的layoutInflater的factory,原来是这样子,于是又去AppCompatActivity瞅了一眼:

1
2
3
4
5
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
}

发现onCreate方法里果然有这句话,installViewFactory();这个方法就是给The Activity’s LayoutInflater设置一个自己的factory,于是就会调用createView这个方法

注意createView里面的这句话:

1
2
3
4
5
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
isPre21, /* 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的createView方法看,终于找到了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 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;
}

原来在activity的oncreate一开始创建的时候,系统就自动帮我们把这些色调给我们换掉了所以你写在XML的控件,都会被换成AppCompat开头的兼容性的控件,当然前提是你要继承AppCompatActivity,这就是系统为我们所做的适配了,请注意,AppCompatViewInflater也给我们提供了一个热换肤的思路,下次有时间再说吧。

总结起来,这个兼容其实就是偷梁换柱,而且看源码一定不能被细枝末节影响,像我一样,如果我能做点笔记,而不是草率的开始,应该过程会更加的轻松。