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前綴。
在這裏插入圖片描述

測試代碼:

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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章