AppCompatActivity hook LayoutInf

2019-04-18  本文已影响0人  水清波

基于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;
            }
        });
上一篇下一篇

猜你喜欢

热点阅读