LayoutInflater hook点
在Activity里执行setContentView或者inflate布局文件最终都会走到如下代码:
LayoutInflater.java
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
try {
View view;
if (mFactory2 != null) {
//1 hook点 mFactory2优先
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
//2 hook点
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
//如果fatory2、factory都返回null则进入函数体
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//framework.jar里的View,即安卓原生View控件。 例如name是TextView、Button。 会执行createView(name, "android.view.", attrs); 即添加包名后执行createView函数
view = onCreateView(parent, name, attrs);
} else {
//自定义View( 含support库里的View), name是包名+类名。通过反射实例化
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
}
}
通过代码得到结论, 在inflate时优先使用mFactory2和mFactory实例化, 如果都实例化失败时执行createView函数实例化View, 而实例化是用类反射的方式实现的,需要完整的包名和类名; 如果是安卓原生控件需要添加android.view前缀。
测试代码:
public class TestFragmentActivity extends Activity {
private final String TAG = "brycegao/Main";
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
long startTime = System.currentTimeMillis();
View view = null;
try {
//这里可以“偷梁换柱”, 实例化其它View对象或修改view属性
view = getLayoutInflater().createView(name, null, attrs);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
long cost = System.currentTimeMillis() - startTime;
Log.d(TAG, "加载布局:" + name + "耗时:" + cost);
int n = attrs.getAttributeCount();
for (int i = 0; i < n; i++) {
Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
}
//hook控件或属性
if (view != null && view instanceof Button) {
((Button) view).setText("我是hook替换的标题");
}
return view;
}
@Override public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
setContentView(R.layout.activity_main);
}
回调函数里有所有xml布局文件里定义的属性。
按钮的标题变更为“我是HOOK替换的标题”了, 即在Activity的hook函数里可以篡改布局文件里的View属性或实例化其它View对象。
HOOK可以统一管理inflate过程, 具体的应用场景包括:
1、 统计View的inflate时间;
2、归口统一修改View的属性或者实例化其它View对象, 例如全局替换字体。 可以在基类Activity的onCreate函数里替换所有TextView的字体。
3、换肤需求。
4、在hook函数使用new方式实例化自定义View, 但JDK8优化了反射性能, 删除了synchronized同步机制。 参考 https://www.jianshu.com/p/b80ebc5d507c
安卓修改了invoke方法, 直接调用native方法。
@CallerSensitive
// Android-changed: invoke(Object, Object...) implemented natively.
@FastNative
public native Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;