共计 9122 个字符,预计需要花费 23 分钟才能阅读完成。
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 了。