學習Butterknife的一點心得(系列)三

一 運行期java


咱們在activity中的onCreate方法中會調用ButterKnife.bind(this);咱們進入這個方法:android


public static void bind(Activity target) {ide

  bind(target, target, Finder.ACTIVITY);ui

}this

target是這個activity,再進入bind方法,spa


static void bind(Object target, Object source, Finder finder) {debug

  Class<?> targetClass = target.getClass();code

  try {orm

    if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());內存

1    ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);

    if (viewBinder != null) {

2      viewBinder.bind(finder, target, source);

    }

  } catch (Exception e) {

    throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);

  }

}

核心是1,2行代碼,找到ViewBinder,而後調用它的bind方法。bind()方法的3個參數:sourrce和targetClass就是activity,finder是Finder.ACTIVITY,finder比較重要,調用他的內部方法來獲取資源,而後進入findViewBinderForClass方法查看


private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)

    throws IllegalAccessException, InstantiationException {

  ViewBinder<Object> viewBinder = BINDERS.get(cls);

  if (viewBinder != null) {

    if (debug) Log.d(TAG, "HIT: Cached in view binder map.");

    return viewBinder;

  }

  String clsName = cls.getName();

  if (clsName.startsWith("android.") || clsName.startsWith("java.")) {

    if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");

    return NOP_VIEW_BINDER;

  }

  try {

1    Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");

    //noinspection unchecked

2    viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();

    if (debug) Log.d(TAG, "HIT: Loaded view binder class.");

  } catch (ClassNotFoundException e) {

    if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());

    viewBinder = findViewBinderForClass(cls.getSuperclass());

  }

  BINDERS.put(cls, viewBinder);

  return viewBinder;

}

核心代碼是1,2根據簡單反射的原理,就是構造了一個類的實例,這個類是什麼呢,根據前面所知cls即爲activity,咱們在MainActivity用註解Bind,那cls就是MainActivity,


clsName + "$$ViewBinder"

拼接的字符串即爲Maintivity$$ViewBinder,正好是在編譯期建立的那個源文件的類名,執行2以後,咱們已得到了這個類的實例。切回到


static void bind(Object target, Object source, Finder finder)

這個方法中2行代碼,就是調用了源文件類所對應類實例中的bind方法。咱們再次看這個源文件bind方法中作了什麼。


public class MainActivity$$ViewBinder<T extends com.hsj.weather.ui.activity.MainActivity> implements ViewBinder<T> {

  @Override public void bind(final Finder finder, final T target, Object source) {

    View view;

1    view = finder.findRequiredView(source, 2131361899, "field 'iv_title_left'");

2    target.iv_title_left = finder.castView(view, 2131361899, "field 'iv_title_left'");

    view = finder.findRequiredView(source, 2131361901, "field 'iv_title_right'");

    target.iv_title_right = finder.castView(view, 2131361901, "field 'iv_title_right'");

    view = finder.findRequiredView(source, 2131361900, "field 'tv_title_center'");

    target.tv_title_center = finder.castView(view, 2131361900, "field 'tv_title_center'");

  }


  @Override public void unbind(T target) {

    target.iv_title_left = null;

    target.iv_title_right = null;

    target.tv_title_center = null;

  }

}

咱們看1,2對應的代碼便可


先看1,finder調用了findRequiredView(),這個Finder是ButterKnife的一個枚舉字段。這個finder的代碼貼一下比較重要,

public enum Finder {
  VIEW {
    @Override protected View findView(Object source, int id) {
      return ((View) source).findViewById(id);
    }

    @Override public Context getContext(Object source) {
      return ((View) source).getContext();
    }

    @Override protected String getResourceEntryName(Object source, int id) {
      final View view = (View) source;
      // In edit mode, getResourceEntryName() is unsupported due to use of BridgeResources
      if (view.isInEditMode()) {
        return "<unavailable while editing>";
      }
      return super.getResourceEntryName(source, id);
    }
  },
  ACTIVITY {
    @Override protected View findView(Object source, int id) {
      return ((Activity) source).findViewById(id);
    }

    @Override public Context getContext(Object source) {
      return (Activity) source;
    }
  },
  DIALOG {
    @Override protected View findView(Object source, int id) {
      return ((Dialog) source).findViewById(id);
    }

    @Override public Context getContext(Object source) {
      return ((Dialog) source).getContext();
    }
  };

  private static <T> T[] filterNull(T[] views) {
    int end = 0;
    for (int i = 0; i < views.length; i++) {
      T view = views[i];
      if (view != null) {
        views[end++] = view;
      }
    }
    return Arrays.copyOfRange(views, 0, end);
  }

  @SafeVarargs
  public static <T> T[] arrayOf(T... views) {
    return filterNull(views);
  }

  @SafeVarargs
  public static <T> List<T> listOf(T... views) {
    return new ImmutableList<>(filterNull(views));
  }

  public <T> T findRequiredView(Object source, int id, String who) {
    T view = findOptionalView(source, id, who);
    if (view == null) {
      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' annotation.");
    }
    return view;
  }

  public <T> T findOptionalView(Object source, int id, String who) {
    View view = findView(source, id);
    return castView(view, id, who);
  }

  @SuppressWarnings("unchecked") // That's the point.
  public <T> T castView(View view, int id, String who) {
    try {
      return (T) view;
    } catch (ClassCastException e) {
      if (who == null) {
        throw new AssertionError();
      }
      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);
    }
  }

  @SuppressWarnings("unchecked") // That's the point.
  public <T> T castParam(Object value, String from, int fromPosition, String to, int toPosition) {
    try {
      return (T) value;
    } catch (ClassCastException e) {
      throw new IllegalStateException("Parameter #"
          + (fromPosition + 1)
          + " of method '"
          + from
          + "' was of the wrong type for parameter #"
          + (toPosition + 1)
          + " of method '"
          + to
          + "'. See cause for more info.", e);
    }
  }

查看findRequiredView()方法(根據前面編譯期建立源碼時知道,根據前面分析所知這個Finder類型是Finder.ACTIVITY) ,故T爲Finder.ACTIVITY

public <T> T findRequiredView(Object source, int id, String who) {
  T view = findOptionalView(source, id, who);
  if (view == null) {
    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' annotation.");
  }
  return view;
}

進入findOptionalView方法,

public <T> T findOptionalView(Object source, int id, String who) {
  View view = findView(source, id);
  return castView(view, id, who);
}

進入findView方法:

protected abstract View findView(Object source, int id);

發現是其抽象方法,誰實現了它呢,在看Finder枚舉

VIEW {
  @Override protected View findView(Object source, int id) {
    return ((View) source).findViewById(id);
  }

  @Override public Context getContext(Object source) {
    return ((View) source).getContext();
  }

  @Override protected String getResourceEntryName(Object source, int id) {
    final View view = (View) source;
    // In edit mode, getResourceEntryName() is unsupported due to use of BridgeResources
    if (view.isInEditMode()) {
      return "<unavailable while editing>";
    }
    return super.getResourceEntryName(source, id);
  }
},
ACTIVITY {
  @Override protected View findView(Object source, int id) {
    return ((Activity) source).findViewById(id);
  }

  @Override public Context getContext(Object source) {
    return (Activity) source;
  }
},
DIALOG {
  @Override protected View findView(Object source, int id) {
    return ((Dialog) source).findViewById(id);
  }

  @Override public Context getContext(Object source) {
    return ((Dialog) source).getContext();
  }
};

由於那個T是Finder.ACTIVITY,因此會調用Finder.ACTIVITY的findView方法,方法裏看到一句寫爛的一句代碼:

((Activity) source).findViewById(id);

就是用了findViewById來獲取那個View。至此切換到那個MainActivity$$ViewBinder的Bind方法

 @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131361899, "field 'iv_title_left'");
1    target.iv_title_left = finder.castView(view, 2131361899, "field 'iv_title_left'");
    view = finder.findRequiredView(source, 2131361901, "field 'iv_title_right'");
    target.iv_title_right = finder.castView(view, 2131361901, "field 'iv_title_right'");
    view = finder.findRequiredView(source, 2131361900, "field 'tv_title_center'");
    target.tv_title_center = finder.castView(view, 2131361900, "field 'tv_title_center'");
  }

target就是MainActivity的實例,執行完在MainActivity中的變量iv_title_left就被賦值了,在這個activity中就能夠直接使用iv_title_left了,好比。

iv_title_left.setImageResource(R.drawable.btn_addcity_normal);


在activity中咱們重寫了onDestroy:

        @Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		ButterKnife.unbind(this);
	}

ButterKnife.unBind()的調用,咱們能夠猜到內部就是調用了,那個源文件類中的unBind()方法。

而在unBind()方法中,就是將這些變量賦值爲null。釋放掉內存。


進過系列分析,能夠得出它的原理運行圖:



ButterKnife在編譯期和運行期的運做機制原理差很少講到這裏了,對其餘註解元素,諸如BindBitmap,BindDimen等也同樣,最底層就是調用了咱們平時獲取資源的方法。

相關文章
相關標籤/搜索