什麼是IOC?
官方定義:IOC是原來由程序中主動獲取的資源,轉變爲由第三方提供,並有原來的程序被動接受的方式,已達到解耦的效果.
當然,這種以專業名詞解釋專業名詞的方式,看了還是一頭霧水.
直白點說:Inversion of control ,直接翻譯過來就是控制翻轉,這個理論主要用於實現對象之間的解耦.
也叫依賴注入,就是IOC容器在運行過程中,動態的將某種依賴關係注入到對象之中.
比如說對象A依賴對象B,通常我們使用new關鍵字來實現兩個對象之間組合,這樣耦合性就很高,引入IOC之後,當A對象需要B對象時,IOC容器就會創建一個對象B送給對象A,而A對象不需要去關心B對象是怎麼創建的,該怎麼銷燬,這些都由IOC去完成.
IOC最核心的技術就是反射.
Android中有不少應用了IOC技術的框架,如xUtils,EventBus.
這裏不是介紹那些框架怎麼使用, 而是以一種簡單的方式,來理解這些的框架的思想.理解了它的思想,不管是去使用,還是去開發新的框架,都會更容易打開思路.
下面結合代碼,來看運行時注入,在android中是怎麼使用的.
首先,看View的注入,也包括layout,contentView的注入,代碼中加了註釋,應該很容易看明白,
//定義一個類似IOC容器的類,這個類通常是一個基類中使用,比如在BaseActivity中使用,
// 那麼所以繼承自BaseActivity的類都具有了這個功能
//這個工具類中實現了,怎樣去注入一個view,及注入佈局資源。
public class IOCInjectUtils {
public static void inject(Object context) {
injectLayout(context);
injectView(context);
}
private static void injectLayout(Object context) {
//通過反射去調用MainActivity的setContentView。
int layoutId = 0;
Class<?> mainActivityClass = context.getClass();
//通過MainActivity上的註解,拿到佈局id
InjectContentView injectContentView = mainActivityClass.getAnnotation(InjectContentView.class);
if (null != injectContentView) {
layoutId = injectContentView.value();
//反射調用setContentView
try {
Method method = mainActivityClass.getMethod("setContentView", int.class);
method.invoke(context, layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void injectView(Object context) {
Class<?> mainActivityClass = context.getClass();
//拿到MainActivity中所有的屬性變量
Field[] fields = mainActivityClass.getDeclaredFields();
for (Field field : fields) {
//找到被InjectViewComponent註解過的組件
InjecViewComponent injecViewComponent = field.getAnnotation(InjecViewComponent.class);
if (null != injecViewComponent) {
int componentId = injecViewComponent.value();
try {
//通過反射調用findViewById爲組件賦值
Method method = mainActivityClass.getMethod("findViewById", int.class);
View view = (View) method.invoke(context, componentId);
field.setAccessible(true);
//修改MainActivity中控件的值爲view對象。
field.set(context, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
這個工具類,在基類中使用。
public class IOCBaseActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IOCInjectUtils.inject(this);
}
}
MainActivity繼承使用了注入工具的基礎類,
//資源文件的注入
@InjectContentView(R.layout.activity_main)
public class MainActivity extends IOCBaseActivity {
// 控件的注入
@InjecViewComponent(R.id.button)
Button btn;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//通過注入實現佈局資源設置
//setContentView(R.layout.activity_main);
// 控件的注入
btn.setText("IOC注入");
}
}
對佈局文件的註解,要寫在類上的,對Button view的註解寫在具體控件上,
這裏定義了兩個註解,InjectContentView用來注入佈局文件,InjectViewComponent用來注入view
@Target(ElementType.TYPE)//註解應用在什麼地方,
@Retention(RetentionPolicy.RUNTIME)//註解的生命週期
public @interface InjectContentView {
int value();
}
@Target(ElementType.FIELD)//註解應用在什麼地方,
@Retention(RetentionPolicy.RUNTIME)//註解的生命週期
public @interface InjecViewComponent {
int value();
}
接着,看下對事件的注入是怎麼實現的.
先看常規的事件處理代碼:
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//
}
});
這段代碼中,有幾個關鍵因素必不可少,一,btn對象,事件作用對象,二,onClickListener這是一個什麼類型的事件,三,onClick,具體處理事件的函數,四,setOnClickListener,綁定事件訂閱關係的方法.
在事件注入時,這三個關鍵因素都是會變化的,所以,針對事件的註解的定義,要考慮到這幾個因素.
這裏用到了註解的多態,我們先定義一個基礎的註解,讓他作用在別的註解上面,也即是寫在具體的事件註解類上面.
@Target(ElementType.ANNOTATION_TYPE)//註解應用在什麼地方,
@Retention(RetentionPolicy.RUNTIME)//註解的生命週期
public @interface InjectBaseEvent {
String listenerSetter();//表示訂閱關係
Class<?> listenerType();//表示事件本身
String callbackMethod();//表示事件處理的方法名
}
定義一個針對事件類型的註解,基礎的註解,可以作用在這個註解上,並且給事件相關的因素賦值。
//可能會有多個控件,同時應用了這樣的事件注入,所以value屬性定義成了數組。
@Target(ElementType.METHOD)//註解應用在什麼地方,
@Retention(RetentionPolicy.RUNTIME)//註解的生命週期
@InjectBaseEvent( listenerSetter = "setOnClickListener",
listenerType = View.OnClickListener.class,
callbackMethod = "onClick")
public @interface InjectOnClick {
int[] value() default -1;
}
在MainActivity中的使用方式,這裏可以填入多個控件的id,
@InjectOnClick(R.id.button)
public void injectClickEvent(View view) {
}
如果要添加對長按的注入,可以仿照InjectOnClick註解,同時修改相應的事件因素,這樣就很容易增加多種類型的事件注入。這個做法就是註解的多態。
最後,看下在注入工具類中,是怎麼實現事件注入的。我把註釋加在了代碼中,跟隨代碼更容易說清楚。
public class IOCInjectUtils {
public static void inject(Object context) {
//injectLayout(context);
//injectView(context);
injectEvent(context);
}
private static void injectEvent(Object context) {
Class<?> mainActivityClass = context.getClass();
//拿到所有的方法
Method[] methods = mainActivityClass.getDeclaredMethods();
for (Method method : methods) {
//方法上所有的註解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//拿到註解對應的class,來判斷它是不是一個需要處理事件的方法
Class<?> annotationClass = annotation.annotationType();
InjectBaseEvent annotationBaseEvent = annotationClass.getAnnotation(InjectBaseEvent.class);
//如果沒有InjectBaseEvent註解,說明它不是一個需要處理事件的方法。
if (null == annotationBaseEvent) {
continue;
}
//否則獲取事件處理的因素,確定是那種事件
String listenerSetter = annotationBaseEvent.listenerSetter();
Class<?> listenerType = annotationBaseEvent.listenerType();
String callbackMethod = annotationBaseEvent.callbackMethod();
Method value = null;
try {
//拿到註解(如InjectOnClick註解)中的value方法,
// 是爲了拿到value中都有哪些控件ID,需要應用這個註解。
value = annotationClass.getDeclaredMethod("value");
//獲取控件id
int[] viewIds = (int[])value.invoke(annotation);
for (int viewId : viewIds) {
//拿到id對應的控件對象, 如Button btn
Method findViewById = mainActivityClass.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(context,viewId);
//下面就可以執行控件對象身上的事件處理方法了,如onClick方法,
// 但是因爲onClick中,具體的業務代碼是未知的,不確定的,
// 所以這裏用到了動態代理,去執行程序員定義的業務代碼,
// 讓這個動態代理去告訴MainActivity,有事件(如onClick)被執行了
if (null == view) {
continue;
}
//現在讓動態代理跟事件處理的方法(如onClick)綁定起來,
// 然後讓代理類去執行MainActivity中真實的業務處理方法,如果injectClickEvent
ListenerInvocationHandler listenerInvocationHandler =
new ListenerInvocationHandler(context, method);
//生成代理對象,代理對象,代理的是View.OnClickListener,
// 執行的它的onClick方法
Object proxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType}, listenerInvocationHandler);
//讓proxy去執行View上的onClick方法。
//viewMethod拿到的是button控件上的setOnClickListener方法,
Method viewMethod = view.getClass().getMethod(listenerSetter, listenerType);
//執行控件button的setOnClickListener方法,
// 然後由proxy執行MainActivity中使用事件註解的方法,
// 也就是MainActivity中自定義的injectClickEvent方法
viewMethod.invoke(view, proxyInstance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
下面是動態代理類的實現代碼:
//讓這個代理類去實現 事件本身定義的接口,如View.OnClicklistener,
// 執行接口中方法,也即是事件處理中的方法,如onClick
//在事件處理方法中(onClick),實際要執行的是MainActivity中的injectClickEvent這個自定義的方法,
// 也即是添加了註解@InjectOnClick(R.id.button)
public class ListenerInvocationHandler implements InvocationHandler {
//要執行的是那個類中的那個方法,如果MainActivity中的injectClickEvent方法,
private Object activity;
private Method methodInActivity;
public ListenerInvocationHandler(Object activity, Method methodInActivity) {
this.activity = activity;
this.methodInActivity = methodInActivity;
}
//這裏要執行的實際是事件處理方法,如onClick,也即是說,當點擊button時,
// framework層執行的onClick方法將會跳轉到這裏來,
// 所以我們的注入框架中,會去處理invoke和onClick的關聯
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//這裏去調用真實要去執行的方法,如MainActivity中的injectClickEvent。
return methodInActivity.invoke(activity, args);
}
}
到這裏就完成了了事件注入的代碼實現。
IOC概念的理解,參考博文:
https://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html