学习Butterknife的一点心得(系列)三

一 运行期


我们在activity中的onCreate方法中会调用ButterKnife.bind(this);我们进入这个方法:


public static void bind(Activity target) {

  bind(target, target, Finder.ACTIVITY);

}

target是这个activity,再进入bind方法,


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

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

  try {

    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。释放掉内存。


进过系列分析,可以得出它的原理运行图:


142738_3mME_1463920.png


ButterKnife在编译期和运行期的运作机制原理差不多讲到这里了,对其他注解元素,诸如BindBitmap,BindDimen等也一样,最底层就是调用了我们平时获取资源的方法。




转载于:https://my.oschina.net/u/1463920/blog/512784

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值