IOC的定义
英文全称为:Inversion of Control 控制和反转。翻译到代码就是反射加注解。
第三方IOC框架源码解析
IOC框架主要是注入控件和布局、设置点击事件。接下来分析一下,第三方IOC框架:xUtils和BufferKnife的源码。
xUtils
- 基本使用
@ContentView(R.layout.activity_main)
public class MainActivityTest extends AppCompatActivity {
@ViewInject(R.id.text)
private TextView mTextView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
x.view().inject(this);
mTextView.setText("你好");
}
/**
* 1. 方法必须私有限定,
* 2. 方法参数形式必须和type对应的Listener接口一致.
* 3. 注解参数value支持数组: value={id1, id2, id3}
* 4. 其它参数说明见{@link org.xutils.view.annotation.Event}类的说明.
**/
@Event(value = R.id.text,
type = View.OnClickListener.class /*可选参数, 默认是View.OnClickListener.class*/)
private void onClick(View view){
Toast.makeText(this,"Toast",Toast.LENGTH_SHORT).show();
}
}
x.view().inject(this)
首先来分析这行注册代码。点击inject之后,发现跳转到了接口方法。
public interface ViewInjector {
/**
* 注入view
*/
void inject(View view);
/**
* 注入activity
*/
void inject(Activity activity);
/**
* 注入view holder
*
* @param handler view holder
*/
void inject(Object handler, View view);
/**
* 注入fragment
*/
View inject(Object fragment, LayoutInflater inflater, ViewGroup container);
}
然后发现这个接口的实现类是ViewInjectorImpl,那在这个实现类中查看inject(activity)方法做了什么
- 首先查看是否使用了ContentView注解来设置setContentView()
- 设置控件和点击事件
@Override
public void inject(Activity activity) {
//获取Activity的ContentView的注解
Class<?> handlerType = activity.getClass();
try {
//利用注解设置setContentView
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));
}
/**
* 从父类获取注解View
*/
private static ContentView findContentView(Class<?> thisCls) {
if (thisCls == null || IGNORED.contains(thisCls) || thisCls.getName().startsWith("androidx.")) {
return null;
}
ContentView contentView = thisCls.getAnnotation(ContentView.class);
if (contentView == null) {
return findContentView(thisCls.getSuperclass());
}
return contentView;
}
属性设置:利用反射去获取 Annotaion --> value --> findViewById --> 反射注入
事件设置: 利用反射去获取Annotaion-->value-->findViewById --> setOnClickListener -->动态代理执行方法
rivate static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
// .......
// 从父类到子类递归
injectObject(handler, handlerType.getSuperclass(), finder);
// inject view 注入控件View
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
// ......
// 获取viewInject 注解
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
// 其实最终还是调用findViewById的方法
View view = finder.findViewById(viewInject.value(),
viewInject.parentId());
if (view != null) {
// 利用反射 把View注入到该属性中
field.setAccessible(true);
field.set(handler, view);
} else {
// ......
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject view
// inject event 注入事件
Method[] methods = handlerType.getDeclaredMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
// ......
//检查当前方法是否是event注解的方法
Event event = method.getAnnotation(Event.class);
if (event != null) {
try {
// id参数
int[] values = event.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 动态代理执行相应的方法
EventListenerManager.addEventMethod(
finder, info, event, handler, method);
}
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject event
}
ButterKnife
butterknife是一个安卓大神写的,https://github.com/JakeWharton/butterknife
其效率比xUtils更高,但在组件化开发中也挺多坑的,一般不在组件化开发中使用。
对于其源码的分析,不能像xUtils一样通过注册代码就可以找到整个线索。
- 基本使用
public class MainActivity extends AppCompatActivity {
@Bind(R.id.icon)
ImageView icon;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.icon)
public void onClick() {
Toast.makeText(this, "toast", Toast.LENGTH_LONG).show();
}
}
ButterKnife.bind(this)
@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());
//重点在这个方法
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source);
}
...
}
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//查看map中是否有这个类,有则直接返回
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();
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
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
}
...
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
可以看到ButterKnife并不像xUtils那样在注册时就通过反射找到注解然后设置相关代码。
我们来看一下注解:
@Retention(RUNTIME) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
Retention为RUNTIME 的 Annotation,由apt(Annotation Processing Tool) 解析自动解析。就是在Java代码运行的时候就处理@BindView、@OnClick(ButterKnife还支持很多其他的注解)这些注解了。
Annotation processing是在运行阶段执行的,它的原理就是读入Java源代码,解析注解,然后成新的Java代码生。新生成的Java代码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法。其他同理。
-
开始它会扫描Java代码中所有的ButterKnife注解@BindView、@OnClick、@OnItemClicked等。
-
当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似_ViewBinder,这个新生成的类实现了ViewBinder接口。
-
这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等。
-
最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法。
看看生成的类:
public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131427372, "field 'icon' and method 'onClick'");
//为原来的类属性findViewById
target.icon = finder.castView(view, 2131427372, "field 'icon'");
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override public void doClick(View p0) {
//调用原来的类的方法
target.onClick();
}
});
}
@Override public void unbind(T target) {
target.icon = null;
}
}
用反射自定义IOC注解
属性
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewById {
int value();
}
点击
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
int[] value();
}
网络
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckNet {
}
/**
* 根据id查询属性
*/
public class ViewFinder {
private Activity mActivity;
private View mView;
public ViewFinder(Activity activity) {
this.mActivity = activity;
}
public ViewFinder(View view) {
this.mView = view;
}
public View findViewById(int viewId) {
return mActivity != null ? mActivity.findViewById(viewId) : mView.findViewById(viewId);
}
}
public class ViewUtils {
public static void inject(Activity activity) {
inject(new ViewFinder(activity), activity);
}
public static void inject(View view) {
inject(new ViewFinder(view), view);
}
public static void inject(View view, Object object) {
inject(new ViewFinder(view), object);
}
//兼容给上面三个方法
public static void inject(ViewFinder finder, Object object) {
//注入属性
injectFiled(finder, object);
//注入事件
injectEvent(finder, object);
}
//属性
private static void injectFiled(ViewFinder finder, Object object) {
//1.获取类里面所有的属性
Class<?> clazz = object.getClass();
//获取所有的属性包括私有和公有
Field[] fields = clazz.getDeclaredFields();
//2.获取viewById的里面的value值
for (Field field : fields) {
ViewById viewById = field.getAnnotation(ViewById.class);
if (viewById != null) {
// 获取注解里面的id值 --》 R.id.test_tv
int viewId = viewById.value();
//3.findViewById 找到View
View view = finder.findViewById(viewId);
if (view != null) {
//能够注入所有修饰符
field.setAccessible(true);
//4.动态注入找到view
try {
field.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
//点击和检查网络
private static void injectEvent(ViewFinder finder, Object object) {
//1.获取类里面的所有方法
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
//2.获取onclick里面的value值
for (Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
int[] viewIds = onClick.value();
for (int viewId : viewIds) {
//3.findViewById 找到View
View view = finder.findViewById(viewId);
//扩展功能 检测网络
boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
if (view != null) {
//4.setOnClickListener()
view.setOnClickListener(new DeclareOnClickListener(method, object, isCheckNet));
}
}
}
}
}
private static class DeclareOnClickListener implements View.OnClickListener {
private Object object;
private Method method;
private boolean isCheckNet;
public DeclareOnClickListener(Method method, Object object, boolean isCheckNet) {
this.method = method;
this.object = object;
this.isCheckNet = isCheckNet;
}
@Override
public void onClick(View v) {
//是否需要检测网络
if (isCheckNet) {
//没网则不往下执行
if (isNetworkConnected(v.getContext())) {
Toast.makeText(v.getContext(), "网络没有连接", Toast.LENGTH_SHORT).show();
return;
}
}
//点击会调用该方法
try {
//所有方法都可以,包括私有共有
method.setAccessible(true);
//5.反射执行方法
method.invoke(object, v);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
try {
method.invoke(object, v);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
public static boolean isNetworkConnected(Context context) {
if (context != null) {
ConnectivityManager mConnectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
if (mNetworkInfo != null) {
return mNetworkInfo.isAvailable();
}
}
return false;
}
}
使用
public class MainActivityTest extends AppCompatActivity {
@ViewById(R.id.text)
public TextView mTtext;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mTtext.setText("IOCTV");
}
@Override
protected void onResume() {
super.onResume();
}
@OnClick({R.id.text})
@CheckNet //没网不执行
public void login(View view) {
Toast.makeText(this, "有网,被点击了", Toast.LENGTH_SHORT).show();
}
@OnClick(R.id.two)
public void click(View view) {
Toast.makeText(this, "被点击了", Toast.LENGTH_SHORT).show();
}
}
网友评论