IOC-控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转通常分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。这段百度对IOC框架的解释,对于Java开发者来说最著名的IOC框架莫过于Spring,而在咱们的Android开发中,IOC的使用更为常见,好比你们常常使用的XUtil、butterKnife、EventBus、dagger、dagger二、otto等等,这些第三方库几乎都使用了IOC思想,举个例子给你们:
一般咱们在Activity中获取一个图片组件采用以下方法:css
ImageView img; img = findViewById(R.id.img);
而使用IOC框架给我提供了一种基于注解的实现方式:java
@ViewInject(R.id.img) ImageView img;
能够看出这种方式彷佛更加简洁
其实这正是我本篇博文想给你们介绍的,IOC框架能够:android
1.让代码更加简洁 2.让模板式的代码更少,减小重复工做 3.把更多的精力放到业务逻辑上 4.解耦合
下面我给你们详细讲解下如何自定义IOC框架,在Android中咱们使用IOC框架更可能是为了方便注入全部的控件,好比说布局文件。编程
自定义注解工具库
总体库结构
定义注解工具类
布局注解
/** * * 功能:自定义ContentView注解 * 做者:猴子搬来的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016年10月14日 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)// 使用在类上面 public @interface CastielContentViewInject { int value();// 定义一个方法去拿注解里面的参数 }
组件注解
/** * * 功能:自定义View注解 * 做者:猴子搬来的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016年10月14日 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)// 使用在属性字段上 public @interface CastielViewInject { int value();
事件注解
/** * * 功能:自定义OnClick注解 * 做者:猴子搬来的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016年10月14日 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)// 使用在方法上面 @CastielEventBase(listenerSetter="setOnClickListener",listenerType=View.OnClickListener.class,callbackMethod="onClick") public @interface CastielOnClickInject { // 因为有不少个点击事件,因此要搞个数组 int[] value(); }
在定义事件注解类时,咱们须要在这个注解的基础上再定义一个注解,用于传递事件调用所需的三个重要元素setOnClickListener;传接口类型;回调方法名字数组
事件注解基类
public @interface CastielEventBase { // 1.设置事件监听的方法,配置方法的名字 String listenerSetter(); // 2.事件监听的类型 Class<?> listenerType(); // 3.回调方法的名字 String callbackMethod(); }
定义注入工具类
/** * 功能:InjectUtils注入工具类 * 做者:猴子搬来的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016/10/13 */ public class InjectUtils { public static void inject(Activity activity) { // 注入布局 injectLayout(activity); // 注入视图 injectViews(activity); // 注入事件 injectEvents(activity); } private static void injectEvents(Activity activity) { // 获取方法上面的注解 Class<? extends Activity> myClass = activity.getClass(); Method myMethod[] = myClass.getDeclaredMethods();// 先拿到所有方法 for (Method method : myMethod) { Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { Class<? extends Annotation> annotationType = annotation.annotationType(); CastielEventBase ceb = annotationType.getAnnotation(CastielEventBase.class);// 拿到注解里面的注解 // 获得事件的三要素 String listenerSetter = ceb.listenerSetter(); Class<?> listenerType = ceb.listenerType(); String callbackMethod = ceb.callbackMethod(); // 获取注解事件的控件对象Button try { Method valueMethod = annotationType.getDeclaredMethod("value"); try { int[] viewIds = (int[])valueMethod.invoke(annotation); for (int viewId : viewIds) { View view = activity.findViewById(viewId); // 反射setOnClickListener方法,这里要用到代理 Method setListenerMethod = view.getClass().getMethod(listenerSetter, listenerType); Map<String, Method> methodMap = new HashMap<String, Method>(); methodMap.put(callbackMethod, method); InvocationHandler invocationHandler = new ListenerInvocationHandler(activity, methodMap); Object newProxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[]{listenerType}, invocationHandler); setListenerMethod.invoke(view , newProxyInstance); } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } } } } private static void injectViews(Activity activity) { // 获取每个属性上的注解 Class<? extends Activity> myClass = activity.getClass(); Field[] myFields = myClass.getDeclaredFields();// 先拿到里面全部的成员变量 for (Field field : myFields) { CastielViewInject myView = field.getAnnotation(CastielViewInject.class); if (myView != null) { int value = myView.value();// 拿到属性id View view = activity.findViewById(value); // 将view赋值给类里面的属性 try { field.setAccessible(true);// 为了防止其实私有的的,须要设置容许访问 field.set(activity,view); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } } } } private static void injectLayout(Activity activity) { // 获取咱们自定义类CastielContentViewInject上面的注解 Class<? extends Activity> myClass = activity.getClass(); CastielContentViewInject myContentView = myClass.getAnnotation(CastielContentViewInject.class); int myLayoutResId = myContentView.value(); activity.setContentView(myLayoutResId); } }
使用代理模式
为了将咱们的方法点击方法代替系统的点击方法,咱们使用动态代理的方法去更替系统的点击事件markdown
public class ListenerInvocationHandler implements InvocationHandler { Activity activity; Map<String,Method> methodMap; public ListenerInvocationHandler(Activity activity,Map<String,Method> methodMap) { this.activity = activity; this.methodMap = methodMap; Log.i("castiel", "打印方法Map:" + methodMap.toString()); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // method就是咱们的CastielOnClickInject String name = method.getName(); Log.i("castiel", "打印方法name:" + name); Method mtd = methodMap.get(name); if (mtd != null) { return mtd.invoke(activity, args); } return method.invoke(activity, args); } }
最后封装Activity基类
/** * * 功能:封装的Activity基类,引入咱们自定义的注入工具类 * 做者:猴子搬来的救兵 * 博客地址:http://blog.csdn.net/mynameishuangshuai * 日期:2016年10月14日 */ public class BaseActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); InjectUtils.inject(this); } }
使用自定义的注解工具库
MainActivity.java框架
import com.castiel.ioc.activity.BaseActivity; import com.castiel.ioc.annotation.CastielContentViewInject; import com.castiel.ioc.annotation.CastielOnClickInject; import com.castiel.ioc.annotation.CastielViewInject; import android.os.Bundle; import android.widget.Button; import android.widget.TextView; @CastielContentViewInject(R.layout.activity_main) public class MainActivity extends BaseActivity { @CastielViewInject(R.id.tv) private TextView tv; @CastielViewInject(R.id.btn) private Button btn; @CastielOnClickInject({R.id.btn}) public void changText(){ tv.setText("猴子搬来的救兵 http://blog.csdn.net/mynameishuangshuai"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } }
布局文件ide
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_margin="20dp" android:text="Hello world!" android:textSize="18sp" /> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv" android:layout_margin="20dp" android:text="按钮" /> </RelativeLayout>
当我点击”按钮”,就能够实现TextView文字的切换。工具