Android性能优化系列-监听View inflate周期并动态替换

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前缀。
Android性能优化系列-监听View inflate周期并动态替换_第1张图片

测试代码:

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布局文件里定义的属性。
Android性能优化系列-监听View inflate周期并动态替换_第2张图片
按钮的标题变更为“我是HOOK替换的标题”了, 即在Activity的hook函数里可以篡改布局文件里的View属性或实例化其它View对象。
Android性能优化系列-监听View inflate周期并动态替换_第3张图片

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;

你可能感兴趣的:(Android)