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文字的切換。工具