自己来实现一套IOC注解框架
我们自己来实现一套IOC注解框架吧,采用的方式反射加注解和Xutils类似,但我们尽量不写那么麻烦,也不打算采用动态代理,我们扩展一个检测网络的注解,比如没网的时候我们不去执行方法而是给予没有网络的提示同时也不允许用户反复点击。
这个时候有人就开始喷了,明知道反射会影响性能为什么还要用?这里我就随便说说吧,我承认反射会影响性能但是问题不大我们可以自己去测试反射1万次大概会怎样,如果你非得去纠结那我也没办法,我们还是多花时间在UI渲染和Bitmap以及Service和Handler上面吧,我还从来没有遇到过反射调用gc或者内存溢出的情况,而且后面讲插件化开发的时候也会用到反射那砸门就不做了?不管了开工。
这个时候有人就开始喷了,明知道反射会影响性能为什么还要用?这里我就随便说说吧,我承认反射会影响性能但是问题不大我们可以自己去测试反射1万次大概会怎样,如果你非得去纠结那我也没办法,我们还是多花时间在UI渲染和Bitmap以及Service和Handler上面吧,我还从来没有遇到过反射调用gc或者内存溢出的情况,而且后面讲插件化开发的时候也会用到反射那砸门就不做了?不管了开工。
1 控件属性注入
先来处理控件属性的注入,但是需要考虑各种情况:
① IOC的View属性注解类:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ViewById { int value(); }
②IOC 注入 ViewUtils
1 public class ViewUtils { 2 3 public static void inject(Activity activity) { 4 inject(new ViewFinder(activity), activity); 5 } 6 7 // 兼容View 8 public static void inject(View view) { 9 inject(new ViewFinder(view), view); 10 } 11 12 // 兼容Fragment 13 public static void inject(View view, Object object) { 14 inject(new ViewFinder(view), object); 15 } 16 17 private static void inject(ViewFinder viewFinder, Object object) { 18 injectFiled(viewFinder, object); 19 injectEvent(viewFinder, object); 20 } 21 22 // 注入事件 23 private static void injectEvent(ViewFinder viewFinder, Object object) { 24 25 } 26 27 /** 28 * 注入属性 29 */ 30 private static void injectFiled(ViewFinder viewFinder, Object object) { 31 // object --> activity or fragment or view 是反射的类 32 // viewFinder --> 只是一个view的findViewById的辅助类 33 34 // 1. 获取所有的属性 35 Class<?> clazz = object.getClass(); 36 // 获取所有属性包括私有和公有 37 Field[] fields = clazz.getDeclaredFields(); 38 39 for (Field field : fields) { 40 // 2. 获取属性上面ViewById的值 41 ViewById viewById = field.getAnnotation(ViewById.class); 42 43 if (viewById != null) { 44 // 获取ViewById属性上的viewId值 45 int viewId = viewById.value(); 46 // 3. 通过findViewById获取View 47 View view = viewFinder.findViewById(viewId); 48 49 if (view != null) { 50 // 4. 反射注入View属性 51 // 设置所有属性都能注入包括私有和公有 52 field.setAccessible(true); 53 try { 54 field.set(object, view); 55 } catch (IllegalAccessException e) { 56 e.printStackTrace(); 57 } 58 } else { 59 throw new RuntimeException("Invalid @ViewInject for " 60 + clazz.getSimpleName() + "." + field.getName()); 61 } 62 } 63 } 64 } 65 }
2 点击事件注入
事件的注入我们只打算setOnclickListener其他不常见的我们先不管,也不打算采用动态代理的设计模式。
①OnClick
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnClick { int []value(); }
②ViewUtils.java
// 事件注入 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也不会出现闪退的情况只是调试的时候需要留意警告日志还是蛮不错的。
3.3 扩展动态检测网络注解
我们最后扩展一下加一个检测网络的注解,有的时候我们在点击的方法里面需要去检测网络,比如登陆注册,我们如果没网就没必要去调接口启动线程了,只需要提示用户当前无网络即可。当然这只是一个扩展而已。
①点击之后是否要执行网络检测
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CheckNet { String value() default "亲,您的网络链接有问题哦!"; }
②View帮助类
1 public class ViewHelper { 2 3 private Activity mActivity; 4 private View mView; 5 6 public ViewHelper(Activity activity) { 7 this.mActivity = activity; 8 } 9 10 public ViewHelper(View v) { 11 this.mView = v; 12 } 13 14 public View findViewById(@IdRes int id) { 15 16 return mActivity == null ? mView.findViewById(id) : mActivity.findViewById(id); 17 } 18 }
③ioc注入工具类
1 public class ViewUtils { 2 3 public static void inject(Activity activity) { 4 inject(new ViewHelper(activity), activity); 5 } 6 7 //为了兼容View 8 public static void inject(View v) { 9 inject(new ViewHelper(v), v); 10 } 11 12 //为了兼容Fragment 13 public static void inject(View v, Object o) { 14 inject(new ViewHelper(v), o); 15 } 16 17 18 /** 19 * 最终都调用这个方法 20 * 21 * @param helper View的帮助类 通过这个类根据id找到相应View 22 * @param o 相关对象 Activity view 等 从那个类传进来的 23 */ 24 private static void inject(ViewHelper helper, Object o) { 25 injectField(helper, o); 26 injectMethod(helper, o); 27 } 28 29 /** 30 * 通过@ViewById得到id注入相应的View 31 * 32 * @param helper View帮助类 33 * @param o 相关对象 Activity view 等 从那个类传进来的 34 */ 35 private static void injectField(ViewHelper helper, Object o) { 36 //1.获取到Object中所有的带有@ViewById的字段 37 Class<?> clazz = o.getClass(); 38 //获取所有的字段 39 Field[] fields = clazz.getDeclaredFields(); 40 for (Field field : fields) { 41 //2.获取到相应的value值,也就是id值得到相应的View 42 ViewById viewById = field.getAnnotation(ViewById.class); 43 if (viewById != null) { 44 int viewId = viewById.value(); 45 View view = helper.findViewById(viewId); 46 if (view != null) { 47 try { 48 //3.设置字段值,也就是给字段赋值 49 field.setAccessible(true);//为了使不被修饰符梭影响 50 field.set(o, view); 51 } catch (Exception e) { 52 e.printStackTrace(); 53 } 54 } 55 } 56 } 57 } 58 59 /** 60 * 设置点击事件 61 * 62 * @param helper View帮助类 63 * @param o 相关对象 Activity view 等 从那个类传进来的 64 */ 65 private static void injectMethod(ViewHelper helper, Object o) { 66 //1.获取到所有带有@OnClick的方法 67 Class<?> clazz = o.getClass(); 68 Method[] methods = clazz.getDeclaredMethods();//获取所有方法 69 for (Method method : methods) { 70 OnClick onClick = method.getAnnotation(OnClick.class); 71 if (onClick != null) { 72 //网络检测 73 CheckNet checkNet = method.getAnnotation(CheckNet.class); 74 String hint = null; 75 if (checkNet != null) { 76 hint = checkNet.value(); 77 } 78 79 //2.获取到相应的value值,也就是要设置点击时间的id数组 80 int[] values = onClick.value(); 81 for (int viewId : values) { 82 //3.通过id获取到相应的Vie,然后设置点击事件 83 View view = helper.findViewById(viewId); 84 if (view != null) { 85 view.setOnClickListener(new DeclaredOnClickListener(method, o, hint)); 86 } 87 } 88 } 89 } 90 } 91 92 private static class DeclaredOnClickListener implements View.OnClickListener { 93 94 //设置点击事件的方法 95 private Method mMethod; 96 //在那个类中 97 private Object mObject; 98 //是否检查网络 99 private String mNoNetHint; 100 101 public DeclaredOnClickListener(Method method, Object o, String hint) { 102 this.mMethod = method; 103 this.mObject = o; 104 this.mNoNetHint = hint; 105 } 106 107 @Override 108 public void onClick(View v) { 109 try { 110 mMethod.setAccessible(true);//所有修饰符都可以搞事 111 if (mNoNetHint != null && !isNetConnected(v.getContext())) { 112 Toast.makeText(v.getContext(), mNoNetHint, Toast.LENGTH_SHORT).show(); 113 return; 114 } 115 mMethod.invoke(mObject, v);//可以避免点击闪退 116 } catch (Exception e) { 117 e.printStackTrace(); 118 try { 119 mMethod.invoke(mObject, (Object[]) null);//当方法体里面没有参数时候调用改方法,执行没有方法体的修饰的函数 120 } catch (Exception e1) { 121 e1.printStackTrace(); 122 } 123 } 124 } 125 } 126 127 128 /** 129 * 检测网络是否连接 130 * 131 * @return 132 */ 133 private static boolean isNetConnected(Context context) { 134 ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 135 if (cm != null) { 136 NetworkInfo[] infos = cm.getAllNetworkInfo(); 137 if (infos != null) { 138 for (NetworkInfo ni : infos) { 139 if (ni.isConnected()) { 140 return true; 141 } 142 } 143 } 144 } 145 return false; 146 } 147 148 }
④使用
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(); } }