Android 中的原生TextView是如何转换为AppCompatTextView

267 阅读2分钟

最近在研究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.* 的