天天看點

紅橙Darren視訊筆記 IOC注解架構 了解xUtils3與ButterKnife的原理

1.什麼是IOC

IOC是Inversion of Control的縮寫,直接翻譯過來就叫依賴反轉,看起來感覺不明覺厲,我覺得IOC就是一種解耦方式。比如原本我們在Activity中findviewbyId或者setOnClickListener時比較麻煩,需要寫很多代碼,比如findviewbyId需要讓Activity中的view和布局檔案的對應的view形成映射;setOnClickListener更複雜,除了前一步,還要建立Listener并将之綁定到view上, 有了IOC之後,關系被解耦。布局和view不再像之前緊密聯系,中間靠IOC維持關系,這樣是的代碼結構變得清晰。(實際關系還是緊密的,隻不過如果忽略中間的IOC層,結構變得清晰)

不管IOC是多麼高大上的名字 我們隻要清楚他可以減少我們代碼的備援程度,降低耦合性即可

關于IOC的解釋 這裡有一篇文章講的不錯

https://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html

2.xUtils3使用

1 gradle引入

2 代碼修改

//https://github.com/wyouflf/xUtils3
@ContentView(R.layout.activity_main)//注解布局
public class MainActivity extends AppCompatActivity {

    //聲明各種變量(id 來自布局xml)
    @ViewInject(R.id.btn)
    private ImageButton imageButton;
    @ViewInject(R.id.tv)
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);//将Activity注入xUtils
        textView.setText("Changed Text");
    }


    /**
     * 1. 方法必須私有限定,
     * 2. 方法參數形式必須和type對應的Listener接口一緻.
     * 3. 注解參數value支援數組: value={id1, id2, id3}
     * 4. 其它參數說明見{@link org.xutils.event.annotation.Event}類的說明.
     **/
    //聲明和使用點選事件
    @Event(value = R.id.btn,
            type = View.OnClickListener.class/*可選參數, 預設是View.OnClickListener.class*/)
    private void onButtonClick(View view) {
        Toast.makeText(this,"Image Click",Toast.LENGTH_SHORT).show();
    }

    @Event(value = R.id.tv,
            type = View.OnClickListener.class/*可選參數, 預設是View.OnClickListener.class*/)
    private void onTvClick(View view) {
        Toast.makeText(this,"Text Click",Toast.LENGTH_SHORT).show();
    }
}
           

3.xUtils3源碼分析

3.1 field屬性綁定

從x.view().inject入手

@Override
    public void inject(Activity activity) {
        //擷取Activity的ContentView的注解
        Class<?> handlerType = activity.getClass();
        try {
            ContentView contentView = findContentView(handlerType);
            if (contentView != null) {
                int viewId = contentView.value();
                if (viewId > 0) {
                    activity.setContentView(viewId);
                }
            }
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        }

        injectObject(activity, handlerType, new ViewFinder(activity));//跟下去
    }
           
// inject view
        Field[] fields = handlerType.getDeclaredFields();//擷取handlerType的所有屬性filed handlerType是Activity類型
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {

                Class<?> fieldType = field.getType();
                //跳過一些屬性
                if (
                    /* 不注入靜态字段 */     Modifier.isStatic(field.getModifiers()) ||
                        /* 不注入final字段 */    Modifier.isFinal(field.getModifiers()) ||
                        /* 不注入基本類型字段 */  fieldType.isPrimitive() ||
                        /* 不注入數組類型字段 */  fieldType.isArray()) {
                    continue;
                }

                ViewInject viewInject = field.getAnnotation(ViewInject.class);//擷取聲明為ViewInject注解的屬性
                if (viewInject != null) {
                    try {
                        View view = finder.findViewById(viewInject.value(), viewInject.parentId());//實際調用的就是view.findViewById或者activity.findViewById
                        if (view != null) {
                            field.setAccessible(true);
                            field.set(handler, view);//利用反射 将handler(activity)中的聲明了viewInject注解的地方 替換成剛剛find的view
                        } else {
                            throw new RuntimeException("Invalid @ViewInject for "
                                    + handlerType.getSimpleName() + "." + field.getName());
                        }
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } // end inject view
           

3.2 event事件綁定

事件綁定就在屬性綁定下面一點點

// inject event
        Method[] methods = handlerType.getDeclaredMethods();//擷取所有本類聲明的方法
        if (methods != null && methods.length > 0) {
            for (Method method : methods) {

                if (Modifier.isStatic(method.getModifiers())
                        || !Modifier.isPrivate(method.getModifiers())) {//跳過靜态方法和私有方法
                    continue;
                }

                //檢查目前方法是否是event注解的方法
                Event event = method.getAnnotation(Event.class);
                if (event != null) {
                    try {
                        // id參數
                        int[] values = event.value();//擷取event注解内部的id值value
                        int[] parentIds = event.parentId();
                        int parentIdsLen = parentIds == null ? 0 : parentIds.length;
                        //循環所有id,生成ViewInfo并添加代理反射
                        for (int i = 0; i < values.length; i++) {
                            int value = values[i];
                            if (value > 0) {
                                ViewInfo info = new ViewInfo();
                                info.value = value;
                                info.parentId = parentIdsLen > i ? parentIds[i] : 0;
                                method.setAccessible(true);
                                EventListenerManager.addEventMethod(finder, info, event, handler, method);//跟進去
                            }
                        }
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } // end inject event

    public static void addEventMethod(
            //根據頁面或view holder生成的ViewFinder
            ViewFinder finder,
            //根據目前注解ID生成的ViewInfo
            ViewInfo info,
            //注解對象
            Event event,
            //頁面或view holder對象
            Object handler,
            //目前注解方法
            Method method) {
        try {
            View view = finder.findViewByInfo(info);

            if (view != null) {
                // 注解中定義的接口,比如Event注解預設的接口為View.OnClickListener
                Class<?> listenerType = event.type();
                // 預設為空,注解接口對應的Set方法,比如setOnClickListener方法
                String listenerSetter = event.setter();
                if (TextUtils.isEmpty(listenerSetter)) {//取巧 加個set就變成了setOnClickListener setOnLongClickListener
                    listenerSetter = "set" + listenerType.getSimpleName();
                }


                String methodName = event.method();

                boolean addNewMethod = false;
                /*
                    根據View的ID和目前的接口類型擷取已經緩存的接口執行個體對象,
                    比如根據View.id和View.OnClickListener.class兩個鍵擷取這個View的OnClickListener對象
                 */
                Object listener = listenerCache.get(info, listenerType);
                DynamicHandler dynamicHandler = null;
                /*
                    如果接口執行個體對象不為空
                    擷取接口對象對應的動态代理對象
                    如果動态代理對象的handler和目前handler相同
                    則為動态代理對象添加代理方法
                 */
                if (listener != null) {
                    dynamicHandler = (DynamicHandler) Proxy.getInvocationHandler(listener);
                    addNewMethod = handler.equals(dynamicHandler.getHandler());
                    if (addNewMethod) {
                        dynamicHandler.addMethod(methodName, method);
                    }
                }

                // 如果還沒有注冊此代理
                if (!addNewMethod) {

                    dynamicHandler = new DynamicHandler(handler);

                    dynamicHandler.addMethod(methodName, method);

                    // 生成的代理對象執行個體,比如View.OnClickListener的執行個體對象
                    listener = Proxy.newProxyInstance(
                            listenerType.getClassLoader(),
                            new Class<?>[]{listenerType},
                            dynamicHandler);

                    listenerCache.put(info, listenerType, listener);
                }

                Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
                setEventListenerMethod.invoke(view, listener);//核心 利用反射調用方法
            }
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        }
    }

           

小結

屬性注入

周遊屬性->擷取特定注解的Annotation的屬性->擷取其值->findviewbyid(value)->反射注入屬性field.set(activity, view)

事件注入

周遊方法->擷取特定注解的Annotation的方法->擷取其值->findviewbyid(value)得到添加屬性的view->反射調用方法method.invoke(view, listener)

4.ButterKnife使用步驟

參考

https://www.jianshu.com/p/39fc66aa3297

第一步 在moudle的gradle配置butterknife

// 1 引入Butter knife到module
    implementation 'com.jakewharton:butterknife:10.2.3'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
           

第二步 初始化ButterKnife

//2 初始化ButterKnife
        ButterKnife.bind(this);
           

第三步 ButterKnife屬性初始化 注意View必須有id

//3 ButterKnife屬性初始化 注意View必須有id
    @BindView(R.id.tv)
    TextView mTextView;
    @BindView(R.id.btn)
    Button mButton;
           

第四步

//4 ButterKnife屬性使用
        mTextView.setText("ABC");
        mButton.setText("My Button");
           

第五步 ButterKnife Event使用

//5 ButterKnife Event使用
    @OnClick({R.id.tv,R.id.btn})
    void onItemClick(View view){
        switch (view.getId()){
            case R.id.tv:
                Toast.makeText(this,"text clicked",Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn:
                Toast.makeText(this,"button clicked",Toast.LENGTH_SHORT).show();
                break;
        }
    }
           

完整Activity的代碼

public class MainActivity extends AppCompatActivity {

    //3 ButterKnife屬性初始化 注意View必須有id
    @BindView(R.id.tv)
    TextView mTextView;
    @BindView(R.id.btn)
    Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //2 初始化ButterKnife
        ButterKnife.bind(this);
        //4 ButterKnife屬性使用
        mTextView.setText("ABC");
        mButton.setText("My Button");
    }

    //5 ButterKnife Event使用
    @OnClick({R.id.tv,R.id.btn})
    void onItemClick(View view){
        switch (view.getId()){
            case R.id.tv:
                Toast.makeText(this,"text clicked",Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn:
                Toast.makeText(this,"button clicked",Toast.LENGTH_SHORT).show();
                break;
        }
    }
}
           

5.ButterKnife的filed與事件綁定原理

因為我建立的項目支援AndroidX 而網上的資料的會ButterKnife基本都是8.8.1或以下的版本,我隻能用新的ButterKnife版本,否則會報一些support v4和Androidx的沖突錯誤。而ButterKnife在更新之後邏輯變化了很多,無奈太菜看不太懂ButterKnife的源碼,隻能大緻的結合網上的資訊猜測流程

我們肯定從初始化開始入手

@NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    //target是activity sourceView是activity的根view DecorView 是一個FrameLayout
    return bind(target, sourceView);
  }

  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //擷取activity的構造函數
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      //利用反射建立Activity
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }
           

關注findBindingConstructorForClass方法

//利用遞歸找到Activity的構造函數
  @Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    //遞歸到了Android内部的類 還是沒有找到構造方法 傳回null
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      //加載全類名+_ViewBinding
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      //找到全類名+_ViewBinding的構造方法
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    //緩存 備用
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }
           

這裡我們知道會有MainActivity_ViewBinding的一個類 我們可以在如下路徑找到它

項目路徑\moudle名稱\build\generated\ap_generated_sources\debug\out\包名\

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view7f080168;

  private View view7f080057;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;

    View view;
    //findRequiredView是重點
    view = Utils.findRequiredView(source, R.id.tv, "field 'mTextView' and method 'onItemClick'");
    target.mTextView = Utils.castView(view, R.id.tv, "field 'mTextView'", TextView.class);
    view7f080168 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onItemClick(p0);
      }
    });
    view = Utils.findRequiredView(source, R.id.btn, "field 'mButton' and method 'onItemClick'");
    target.mButton = Utils.castView(view, R.id.btn, "field 'mButton'", Button.class);
    view7f080057 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onItemClick(p0);
      }
    });
  }

  @Override
  @CallSuper
  public void unbind() {//可以在onDestroy調用 釋放資源
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.mTextView = null;
    target.mButton = null;

    view7f080168.setOnClickListener(null);
    view7f080168 = null;
    view7f080057.setOnClickListener(null);
    view7f080057 = null;
  }
}
           

可以猜到MainActivity_ViewBinding明顯是根據MainActivity生成的類 那這個類作用是什麼呢?

我們看一下findRequiredView方法

public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }
    String name = getResourceEntryName(source, id);
    throw new IllegalStateException("Required view '"
        + name
        + "' with ID "
        + id
        + " for "
        + who
        + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
        + " (methods) annotation.");
  }
           

source是根View DecorView 自然可以通過findViewById找到指定的view 這裡就完成了View與事件的綁定。當然如果view==null 說明我們綁定了一個不在目前加載xml中指定id的view,會抛出異常

那麼filed的綁定何時進行的呢 其實就是在castView 這個相對簡單多了,就是利用強制轉換,如果類型不對還是會報錯。不過這裡也利用了上一步的findRequiredView來確定綁定的View在xml中定義過。

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
      return cls.cast(view);
    } catch (ClassCastException e) {
      String name = getResourceEntryName(view, id);
      throw new IllegalStateException("View '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was of the wrong type. See cause for more info.", e);
    }
  }
           

這個檔案是如何産生的呢?

因為ButterKnife版本更新到10.2.3之後 我無法直接看到butterknife-compiler的相關代碼了 主要是ButterKnifeProcessor這個類,我想會不會是因為現在用的運作時生成檔案 是以我看不到該檔案了?

不管怎麼樣 我相信他的大緻原理還是和以前一樣 即像網上所說的用ButterKnifeProcessor解析注解,包(PackageElement)、類(TypeElement)、成員變量(VariableElement)、方法(ExecutableElement) 等資訊,當然中間需要利用Filer Trees 等工具類,最後使用JavaPoet來生成Java檔案。

小結

ButterKnife利用annotationProcessor 和 JavaPoet 技術根據MainActivity生成一個類似MainActivity_ViewBinding的類,該類内部通過findViewById找到View然後給他注冊各種事件,實作事件綁定

後記

對比xUtil和ButterKnife 他們的基本原理有一定的差別

xUtil使用反射+注解

ButterKnife 使用注解+annotationProcessor +JavaPoet

另外ButterKnife更新的變動感覺挺大,除了源碼的一些改動,支援了AndroidX,在ButterKnife7.0.1還是在編譯時@Retention(CLASS)生成MainActivity_ViewBinding,10.2.3的版本卻變成了運作時@Retention(RUNTIME)了。不過不明白為什麼這樣改,不會影響運作速度麼?還是因為現在機器已經很強大了,不需要在乎這點性能消耗呢。