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引入
implementation 'org.xutils:xutils:3.9.0'
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)了。不过不明白为什么这样改,不会影响运行速度么?还是因为现在机器已经很强大了,不需要在乎这点性能消耗呢。