PS:本文系转载文章,阅读原文可读性会更好,文章末尾有原文链接
ps:源码是基于 android api 27 来分析的
使用 Android API 21 的时候,google 推荐我们用的 Activity 是 ActionBarActivity;在 Android Level 21之后,Android 引入了 Material Design 的设计,为了支持 Material,Color、调色版、Toolbar等各种新特性,AppCompatActivity 就应用而生;AppCompatActivity 为了兼容以前的东西,就做了偷梁换柱,本篇文章我们就写偷梁换柱中的UI偷换。
为了方便分析过程,我们写一个 demo,demo 的 AppCompatActivity 的代码如下所示:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
//1、
super.onCreate(savedInstanceState);
//2、
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
}
}
我们来看注释1 中的代码,也就是 AppCompatActivity 的 onCreate 方法;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//3、
final AppCompatDelegate delegate = getDelegate();
//4、
delegate.installViewFactory();
......
}
注释3 中的 getDelegate 方法最终会调用到 AppCompatDelegate 的 create(Context context, Window window,AppCompatCallback callback) 方法;
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
if (BuildCompat.isAtLeastO()) {
return new AppCompatDelegateImplO(context, window, callback);
} else 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);
}
}
create(Context context, Window window,AppCompatCallback callback) 方法会根据 Android API 来初始化 AppCompatDelegate 相应的子类对象,我们就拿 AppCompatDelegateImplV9 来进行分析;我们回到上面的 AppCompatActivity 的 onCreate 方法中的注释4,这时候的 delegate 本质就是 AppCompatDelegateImplV9 类型的对象,那么注释4 的具体方法如下所示;
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
//5、
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
这里的 layoutInflater.getFactory() == null 条件为 true,所以我们看注释5 的代码,也就是 LayoutInflaterCompat 的 setFactory2(@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory)方法;
public static void setFactory2(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
IMPL.setFactory2(inflater, factory);
}
这里的 IMPL 是 LayoutInflaterCompatBaseImpl 类型的对象,但是 LayoutInflaterCompatBaseImpl 的实现类有2个,我们先看 IMPL的实例化;
static {
if (Build.VERSION.SDK_INT >= 21) {
IMPL = new LayoutInflaterCompatApi21Impl();
} else {
IMPL = new LayoutInflaterCompatBaseImpl();
}
}
它是根据 Android API 是否大于等于21 来决定用哪个子类来实例化的,我们就选 LayoutInflaterCompat 的静态内部类 LayoutInflaterCompatBaseImpl 来分析,我们看看 LayoutInflaterCompatBaseImpl 的 setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory)方法;
public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
inflater.setFactory2(factory);
......
}
我们再往下看 LayoutInflater 的 setFactory2(LayoutInflater.Factory2 factory) 方法;
public void setFactory2(LayoutInflater.Factory2 factory) {
......
//6、
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
在上面所讲的 AppCompatDelegateImplV9 的 installViewFactory方法中的 layoutInflater.getFactory() == null条件为 true,上面的 layoutInflater.getFactory() 拿到的其实是 LayoutInflater 的 mFactory,所以这里的 mFactory == null也为 true;在上面所说的,我们拿的是 AppCompatDelegate 的子类 AppCompatDelegateImplV9 来做分析的,所以 LayoutInflater 的 setFactory2(LayoutInflater.Factory2 factory) 方法中的 factory、mFactory2 和 mFactory 本质上是 AppCompatDelegateImplV9 对象,这里的 LayoutInflater 的 setFactory2(LayoutInflater.Factory2 factory) 方法只是保存了 AppCompatDelegateImplV9 对象。
我们回到 demo 中注释2 的代码,也就是 AppCompatActivity 的 setContentView(@LayoutRes int layoutResID) 方法;
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
上面说过 getDelegate 方法拿到的是 AppCompatDelegate 类型的对象,我们就拿 AppCompatDelegate 的子类 AppCompatDelegateImplV9 来分析,所以看 AppCompatDelegateImplV9 的 setContentView(int resId) 方法;
@Override
public void setContentView(int resId) {
......
LayoutInflater.from(mContext).inflate(resId, contentParent);
......
}
这里通过 LayoutInflater 的 inflate(@LayoutRes int resource, @Nullable ViewGroup root) 方法从 xml 文件解析元素生成对应的 View,我们看 inflate(@LayoutRes int resource, @Nullable ViewGroup root) 方法;
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
inflate(@LayoutRes int resource, @Nullable ViewGroup root) 方法调用了 LayoutInflater 的 inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 方法;
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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 方法又调用了 LayoutInflater 的 inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 方法;
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
......
try {
......
if (TAG_MERGE.equals(name)) {
......
} else {
// Temp is the root view that was found in the xml
//7、
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
......
}
} catch (XmlPullParserException e) {
......
} catch (Exception e) {
......
} finally {
......
}
return result;
}
}
我们看看注释7 中的方法,也就是 LayoutInflater 的 createViewFromTag(View parent, String name, Context context, AttributeSet attrs) 方法,它的作用是创建 View;
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
createViewFromTag(View parent, String name, Context context, AttributeSet attrs) 方法又调用了 LayoutInflater 的 createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) 方法;
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
......
try {
View view;
if (mFactory2 != null) {
//8、
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
......
return view;
} catch (InflateException e) {
......
} catch (ClassNotFoundException e) {
......
} catch (Exception e) {
......
}
}
这里看一下注释8 中的 mFactory2,上面我们对 mFactory2 进行了赋值,它的值就是我们上面所说的拿来做分析的 AppCompatDelegateImplV9,我们来看 AppCompatDelegateImplV9 的 onCreateView(View parent, String name, Context context, AttributeSet attrs)方法;
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
......
// If the Factory didn't handle it, let our createView() method try
return createView(parent, name, context, attrs);
}
onCreateView(View parent, String name, Context context, AttributeSet attrs)方法调用了 AppCompatDelegateImplV9 的 createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs)方法;
@Override
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 */
);
}
mAppCompatViewInflater 是 AppCompatViewInflater 类型的对象,我们看 AppCompatViewInflater 的 createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs,boolean inheritContext,boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) 方法;
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
......
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;
}
......
return view;
}
看到没,如果我们的类继续的是 AppCompatActivity,而不是 Activity,那么在 xml 文件中的 View 元素被创建之前会被拦截,在 xml 文件中的 View 元素是 XXX,那么真正创建出来的是 AppCompatXXX,AppCompatActivity 把我们 xml 文件中真实的 View 元素 XXX 换成了 AppCompatXXX。
我们验证一下,把 demo 里的 Button 输出一下;
Button btn = findViewById(R.id.btn);
Log.d("MainActivity","---Button = " + btn);
日志打印如下所示;
D/MainActivity: ---Button = android.support.v7.widget.AppCompatButton{adc47a3 VFED..C.. ......I. 0,0-0,0 #7f070021 app:id/btn}
看见没,我们 demo 中的 Button 被替换成了 AppCompatButton,Button 本质上是 AppCompatButton 了。