AppCompatActivity hook LayoutInf
基于androidx 1.1.0-alpha
AppCompatActivity的onCreate
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
if (delegate.applyDayNight() && mThemeId != 0) {
// If DayNight has been applied, we need to re-apply the theme for
// the changes to take effect. On API 23+, we should bypass
// setTheme(), which will no-op if the theme ID is identical to the
// current theme ID.
if (Build.VERSION.SDK_INT >= 23) {
onApplyThemeResource(getTheme(), mThemeId, false);
} else {
setTheme(mThemeId);
}
}
super.onCreate(savedInstanceState);
}
oncreate的时候做了不少事
1.获得代理 AppCompatDelegate是代理接口,实际代理到AppCompatDelegateImpl
2.代理安装了installViewFactory,把AppCompatDelegateImpl自己作为factory2的实现赋给了layoutinflater(就是实现了LayoutInflater.Factory2.onCreateView)
3.黑白夜模式的支持
AppCompatDelegateImpl是实际代理,setContentView依然是新建decor,找content的id然后去除老的添加新的view
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
重点关注2 了解layoutinflater
inflate做的事:
1.final Resources res = getContext().getResources() //获得资源
2.final XmlResourceParser parser = res.getLayout(resource) //读取XML
3.return inflate(parser, root, attachToRoot) //解析XML
4.解析XML的过程就是遍历节点,如果找到就final View temp = createViewFromTag(root, name, inflaterContext, attrs) //从XML节点生成根节点
5.rInflateChildren(parser, temp, attrs, true) //生成子节点
6.所有生成节点的过程都是在createViewFromTag
layoutinflater 定义了 factory(和factory2)接口,定义的是生产view的时候的接口
当layoutinflater 产生view的时候(createViewFromTag),先从factory2里获取,如果没有才自己生产
AppCompatDelegateImpl作为factory2实现在负责生产的时候(LayoutInflater.Factory2.onCreateView),调用了 AppCompatViewInflater具体生产,把button等翻译到appcompatbutton,这里直接new对象了
对于没有factory负责生产的对象,layoutinflater自己生产,使用反射获得构造函数并缓存。
layoutinflater的inflate流程
inflate-》rInflateChildren-》rInflate-》createViewFromTag
在理清楚这些后,就知道hook掉LayoutInflater.Factory2这个接口,就能让所有创建过程都经过我的控制。
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
发现delegate.installViewFactory() 是会判断已有的factory的,所以就简单,在这之前把hook的factory设置进去。
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
特别的一点,设置factory2后,mFactory也是factory2了。installViewFactory这样调用2次,第一次hook赋值,第二次mFactory已经有值,只会出现一个警告log。
所以具体的hook代码:
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), object : LayoutInflater.Factory2 {
override fun onCreateView(name: String?, context: Context?, attrs: AttributeSet?): View {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onCreateView(parent: View?, name: String?, context: Context?, attrs: AttributeSet?): View? {
val startTime = System.currentTimeMillis()
val view = delegate.createView(parent, name, context!!, attrs!!)
val cost = System.currentTimeMillis() - startTime
Log.d(tag, "加载控件:" + name + "耗时:" + cost)
return view
}
})
super.onCreate(savedInstanceState)
在super.onCreate(savedInstanceState)之前抢先设置Factory2,具体逻辑还是交还给delegate.createView,这里可以做一些hook。
比较坑爹的地方
java参考下面的代码就直接可以使用,kotlin用IDE的默认生成的接口实现的返回值是非空的,这里是需要能为空的,直接转换为KOTLIN代码使用报错,而且告诉你view必须不能为空。
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
long startTime = System.currentTimeMillis();
View view = getDelegate().createView(parent, name, context, attrs);
long cost = System.currentTimeMillis() - startTime;
Log.d(TAG, "加载控件:" + name + "耗时:" + cost);
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});