最近在研究ASM插桩实现大图的检测,发现xml定义中的ImageView依然没有被hook成我的目标对象。就想研究下xml转换成Java类的过程,意外发现了Android源码里一个有趣的现象。 首先是核心代码:
public class AppCompatViewInflater {
...
public final View createView(@Nullable View parent, @NonNull final String name,
@NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
...
```
//这里把原生的Widget变为了AppCompat Widget
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);
}
...
}
}
然后这这个方法里有这个标注:
Most developers should not call this method directly. Instead, use the layout inflater
provided by {@link AppCompatActivity#getLayoutInflater()} or call
{@link AppCompatDelegate#createView(View, String, Context, AttributeSet)}.
翻译过来大概的意思就是:
大部分开发者不被允许直接调用该方法,而是使用AppCompatActivity#getLayoutInflater()或者 AppCompatDelegate#createView(View, String, Context, AttributeSet)这两个方法提供的layout inflater调用
理解就是只能在Activity或者Fragment的layout inflater去调用; 具体的调用位置在这里 AppCompatDelegate
public abstract View createView(@Nullable View parent, String name, @NonNull Context context,
@NonNull AttributeSet attrs);
最顶层View的位置 FragmentActivity
@Override
@Nullable
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
final View v = dispatchFragmentsOnCreateView(parent, name, context, attrs);
if (v == null) {
return super.onCreateView(parent, name, context, attrs);
}
return v;
}
上面在Activity和Fragment下都做了xml初始化,在这个过程中完成了以上方法的调用
这里就可以回答一个问题: Android是如何把android.widget.* 转换为androidx.appcompat.widget.* 的