那么什么是IOC,控制反转(Inversion of Control,英文缩写为IOC),其实就是反射加注解如果你学过Java后台这个在三大框架中会经常使用。过多的去解释其实也没什么意思,我们主要来看有什么用处。
今天主要讲的就是Android中IOC框架就是注入控件和布局或者说是设置点击监听,如果你用过xUtils,afinal,butterknife类的框架,你肯定不陌生~
接下来我们自己来实现一套IOC注解框架吧,采用的方式反射加注解和Xutils类似,但我们尽量不写那么麻烦,也不打算采用动态代理,我们扩展一个检测网络的注解,比如没网的时候我们不去执行方法而是给予没有网络的提示同时也不允许用户反复点击。
这个时候有人就开始喷了,明知道反射会影响性能为什么还要用?这里我就随便说说吧,我承认反射会影响性能但是问题不大我们可以自己去测试反射1万次大概会怎样,如果你非得去纠结那我也没办法,我们还是多花时间在UI渲染和Bitmap以及Service和Handler上面吧,我还从来没有遇到过反射调用gc或者内存溢出的情况,而且后面讲插件化开发的时候也会用到反射那砸门就不做了?不管了开工。
2.1 控件属性注入
package com.hc.essay.baselibrary;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*/
//代表Annovation的位置 FILED:属性上 METHOD:方法上
@Target(ElementType.FIELD)
//代表什么生效 CLASS:编译时生效 RUNTIME:运行时 SOURCE:源码资源
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
int value();
}
finder的类
package com.hc.essay.baselibrary;
import android.app.Activity;
import android.view.View;
/**
* View的findViewById的辅助类
*/
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);
}
}
package com.hc.essay.baselibrary;
import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.view.View;
import android.widget.Toast;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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);
}
//兼容上面三个方法,object->反射需要执行的类
public static void inject(ViewFinder finder, Object object) {
injectField(finder, object);
injectEvent(finder, object);
}
// 注入事件
private static void injectEvent(ViewFinder viewFinder, Object object) {
}
private static void injectField(ViewFinder finder, Object object) {
//1.获取类里面所有的属性
Class<?> clazz = object.getClass();
//获取所有属性,包括公有和私有的
Field[] fileds = clazz.getDeclaredFields();
//2.获取ViewById里面的value值
for (Field field : fileds) {
//获取注解里面的id值->R.id.test_tv
ViewById annotation = field.getAnnotation(ViewById.class);
if (annotation != null) {
int viewId = annotation.value();
//3.findviewbyId找到viewL
View view = finder.findViewById(viewId);
if (view != null) {
//4.动态的注入找到的view
//能够注入所有修饰符
field.setAccessible(true);
try {
field.set(object, view);//第一个参数是该field所属的对象,第二个参数view是要设置的值
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
private static class DeclareOnclickListener implements View.OnClickListener {
private Object mObject;
private Method mMethod;
private boolean mIsCheckNet;
public DeclareOnclickListener(Method method, Object object, boolean isCheckNet) {
this.mMethod = method;
this.mObject = object;
this.mIsCheckNet = isCheckNet;
}
@Override
public void onClick(View v) {
//需不需要检测网络
if(mIsCheckNet) {
//需要,如果没网打印,return
if (!networkAvailable(v.getContext())) {
Toast.makeText(v.getContext(),"亲,您的网络不太给力",Toast.LENGTH_LONG).show();
return;
}
}
//点击会调用该方法
try {
//私有方法可以点击
mMethod.setAccessible(true);
mMethod.invoke(mObject, v);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
private static boolean networkAvailable(Context context)
{
try {
ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
if(connectivityManager == null) {
return false;
}
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if(info != null && info.isConnected()) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
事件的注入我们只打算setOnclickListener其他不常见的我们先不管,也不打算采用动态代理的设计模式。
// 事件注入
private static void injectEvent(ViewFinder viewFinder, Object object) {
// 1.获取所有方法
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
// 2.获取方法上面的所有id
for (Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
int[] viewIds = onClick.value();
if (viewIds.length > 0) {
for (int viewId : viewIds) {
// 3.遍历所有的id 先findViewById然后 setOnClickListener
View view = viewFinder.findViewById(viewId);
if (view != null) {
view.setOnClickListener(new DeclaredOnClickListener(method, object));
}
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
private Method mMethod;
private Object mHandlerType;
public DeclaredOnClickListener(Method method, Object handlerType) {
mMethod = method;
mHandlerType = handlerType;
}
@Override
public void onClick(View v) {
// 4.反射执行方法
mMethod.setAccessible(true);
try {
mMethod.invoke(mHandlerType, v);
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mHandlerType, null);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.icon)
private ImageView mIconIv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mIconIv.setImageResource(R.drawable.icon);
}
@OnClick(R.id.icon)
private void onClick(View view) {
int i = 2 / 0;
Toast.makeText(this, "图片点击了"+i, Toast.LENGTH_LONG).show();
}
}
使用起来和xutils类似,方法和属性可以私有,但是有一点我们在Onclick点击事件的方法里面无论做什么操作都是不会报错的,所以如果发现bug需要留意警告日志,这不是坑嗲吗?其实在我们的开发过程给用户或者老板玩的时候我们最怕的是闪退,现在我们就算有Bug也不会出现闪退的情况只是调试的时候需要留意警告日志还是蛮不错的。
我们最后扩展一下加一个检测网络的注解,有的时候我们在点击的方法里面需要去检测网络,比如登陆注册,我们如果没网就没必要去调接口启动线程了,只需要提示用户当前无网络即可。当然这只是一个扩展而已。
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.icon)
private ImageView mIconIv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mIconIv.setImageResource(R.drawable.icon);
}
@OnClick(R.id.icon)
@CheckNet // 检测网络
private void onClick(View view) {
Toast.makeText(this, "图片点击了", Toast.LENGTH_LONG).show();
}
}
private static class DeclareOnclickListener implements View.OnClickListener {
private Object mObject;
private Method mMethod;
private boolean mIsCheckNet;
public DeclareOnclickListener(Method method, Object object, boolean isCheckNet) {
this.mMethod = method;
this.mObject = object;
this.mIsCheckNet = isCheckNet;
}
@Override
public void onClick(View v) {
//需不需要检测网络
if(mIsCheckNet) {
//需要,如果没网打印,return
if (!networkAvailable(v.getContext())) {
Toast.makeText(v.getContext(),"亲,您的网络不太给力",Toast.LENGTH_LONG).show();
return;
}
}
//点击会调用该方法
try {
//设置私有方法可以点击
mMethod.setAccessible(true);
mMethod.invoke(mObject, v);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
private static boolean networkAvailable(Context context)
{
try {
ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
if(connectivityManager == null) {
return false;
}
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if(info != null && info.isConnected()) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}