美文网首页
AppCompatActivity hook LayoutInf

AppCompatActivity hook LayoutInf

作者: 水清波 | 来源:发表于2019-04-18 12:23 被阅读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;
                }
            });
    

    相关文章

      网友评论

          本文标题:AppCompatActivity hook LayoutInf

          本文链接:https://www.haomeiwen.com/subject/iimzwqtx.html