什么是IOC?
看了很多文章說IOC是依賴注入,或者說是控制反轉,其實這里有多個聯(lián)系比較緊密的概念,純理論概念的內容,感興趣可以大概了解下:
DIP 依賴倒置原則(Dependency Inverse Rrinciple)
強調系統(tǒng)的“高層組件”不應當依賴于“底層組件”,并且不論是“高層組件”還是“底層組件”
都應當依賴于抽象。抽象不應當依賴于實現(xiàn),實現(xiàn)應當依賴于抽象(軟件設計原則)
IOC 控制反轉( Inverse of Control)
一種反轉流、依賴和接口的方式。就是將控制權“往高處/上層”轉移,
控制反轉是實現(xiàn)依賴倒置的一種方法(DIP的具體實現(xiàn)方 式)
DI 依賴注入(-Dependency Injection)
組件通過構造函數(shù)或者setter方法,將其依賴暴露給.上層,上層要設法取得組件的依賴, 并將其傳遞給組件
依賴注入是實現(xiàn)控制反轉的一種手段(IloC的具 體實現(xiàn)方式)
IOC容器
依賴注入的框架,用來映射依賴,管理對象創(chuàng)建和生存周期(DI框架)
注: 本文主要是借IOC實現(xiàn)事件注入,簡單串一下幾個只是點,屬于比較基礎的點,所以寫的不是很詳細,都寫在備注上了:
①實現(xiàn)布局的注入
布局的注入比較簡單:
主要實現(xiàn)的是用注解的方式,取代Activity的 setContentView()方法:
創(chuàng)建一個注解,作用在類上(Demo中是Activity),用于取注解的值,也就是對應(Activity)要設置的布局的值
MainActivity只有兩個控件,一個TextView,一個Button,代碼就不貼了,有需看文末完整代碼,
/**
* ContentView注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
//返回int類型的布局值
int value();
}
MainActivity和BaseActivity中代碼比較簡單:
MainActivity
//使用ContentView注解
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
}
BaseActivity
/**
* desc : BaseActivity
*/
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//幫助子類實現(xiàn)布局,控件,點擊事件注入
InjectManager.inject(this);
}
}
所以代碼邏輯主要在InjectManager中實現(xiàn),布局注入比較簡單,直接看代碼和備注吧,不做贅述,可以在Activity中的onResume()等方法中檢驗代碼是否有效~:
/**
* desc : InjectManager
* 注解管理類
*/
public class InjectManager {
/**
* @param activity
*/
public static void inject(Activity activity){
injectLayout(activity);
}
/**
* @param activity
* 布局注入
*/
public static void injectLayout(Activity activity){
//獲取類
Class<? extends Activity> clazz = activity.getClass();
//獲取作用在類上的注解,這里取的是ContentView類型的注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView!=null) {
//獲取ContentView注解的值,如Demo中Mainactivity取到的是 R.layout.activity_main
int layoutId = contentView.value();
try {
//獲取setContentView方法
Method method = clazz.getMethod("setContentView", int.class);
//執(zhí)行setContentView方法
method.invoke(activity,layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
②實現(xiàn)控件的注入
控件的注入就是為了通過注解取代findViewById方法,類似ButterKnife,只不過實現(xiàn)方式不同,ButterKnife采用的是APT,這里主要就是使用的反射,實現(xiàn)上首先定義個注解,并在MainActivity中使用,代碼如下:
InjectView注解
/**
* desc : InjectView
* InjectView注解,取代findViewById
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
int value();
}
MainActivity中使用
@InjectView(R.id.btn)
Button btn;
具體實現(xiàn)實在InjectManager中增加了一個方法 用于處理控件的注入,這里上面的布局注入相差不大,主要注意下需要將findViewById方法的返回值賦值給View,具體可以看下注釋:
/**
* @param activity
* 控件的注入
*/
private static void injectView(Activity activity) {
//獲取類
Class<? extends Activity> clazz = activity.getClass();
//獲取所有屬性,包括private等
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//獲取屬性上的InjectView類型的注解
InjectView injectView = field.getAnnotation(InjectView.class);
if (injectView != null) {
//獲取注解的值,就是View的id值
int viewId = injectView.value();
try {
//獲取findViewById方法
Method method = clazz.getMethod("findViewById", int.class);
//執(zhí)行findViewById方法
Object view = method.invoke(activity, viewId);
//打開私有訪問權限,即private屬性可以修改
field.setAccessible(true);
//將findViewById方法的返回值賦值給控件
field.set(activity,view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
③點擊事件的注入
首先我們都知道Android的點擊事件和長按事件是這樣的:
//點擊事件
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
//長按事件
btn.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
可以看到他們的共同點
- 監(jiān)聽方法名 setOnClickListener / setOnLongClickListener
- 監(jiān)聽對象 View.OnClickListener() / View.OnLongClickListener()
- 返回值 onClick(View v) / onLongClick(View v)
那么我們定義一個EventBase注解需要將這是哪個特點體現(xiàn)出來,另外還要定義一個注解OnClick,EventBase作用在OnClick注解之上,而OnClick作用在回調的方法之上,就是響應View的點擊事件或者長按事件的回調,也就是最終要在MainActivity中觸發(fā)的方法~
下面以注入點擊事件為例
首先我們定義兩個注解:
EventBase注解主要是拿到三個必要信息,
/**
* desc : EventBase
* 事件注入的注解
*/
@Target(ElementType.ANNOTATION_TYPE)//作用在其它注解之上
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
//1.監(jiān)聽方法名 setOnClickListener / setOnLongClickListener
String listenerSetter();
//2.監(jiān)聽對象 View.OnClickListener() / View.OnLongClickListener()
Class<?> listenerType();
//3. 返回值 onClick(View v) / onLongClick(View v)
String callBackListener();
}
/**
* desc : OnClick
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter="setOnClickListener",listenerType= View.OnClickListener.class,callBackListener="onClick")
public @interface OnClick {
int[] value();
}
然后在InjectManager中添加一個injectEvent的方法用于處理方法事件的注入,這里用到了動態(tài)代理,
目的是確定回調的方法,因為回調的方法有可能是onClick,也可能是onLongClick代碼如下:
/**
* @param activity
* 事件注入
*/
private static void injectEvent(Activity activity) {
//獲取類
Class<? extends Activity> clazz = activity.getClass();
//獲取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//獲取方法上的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//獲取注解的類型
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType != null) {
//獲取EventBase注解
EventBase eventBaseAnnotation = annotationType.getAnnotation(EventBase.class);
//獲取三個信息
if (eventBaseAnnotation!=null) {
//listenerSetter = setOnClickListener
String listenerSetter = eventBaseAnnotation.listenerSetter();
//listenerType = View.OnClickListener
Class<?> listenerType = eventBaseAnnotation.listenerType();
//
String callBackListener = eventBaseAnnotation.callBackListener();
try {
//獲取OnClick注解的value方法
Method valueMethod = annotationType.getDeclaredMethod("value");
//獲取OnClick注解的的值
int[] viewIds = (int[])valueMethod.invoke(annotation);
//代理的方式,根據(jù)注解類型,匹配對應的回調方法
//即View.OnClickListener 回調 onClick(View view)
//View.OnLongClickListener() 回調 onLongClick(View v)
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(activity);
//添加需要攔截的方法,和替換執(zhí)行的方法
//callBackListener onClick
//method clickEvent
Log.e("callBackListener:",callBackListener+"");
Log.e("method:",method.getName()+"");
listenerInvocationHandler.addMethod(callBackListener,method);
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);
for (int viewId : viewIds) {
//獲取findViewById方法
Method findViewMethod = clazz.getMethod("findViewById", int.class);
//執(zhí)行findViewById方法
View view = (View) findViewMethod.invoke(activity, viewId);
Method setter = view.getClass().getMethod(listenerSetter, listenerType);
setter.invoke(view,listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
貼一下代理的實現(xiàn):
/**
* desc : ListenerInvocationHandler
* 代理的實現(xiàn)
* AOP切面思想
*/
public class ListenerInvocationHandler implements InvocationHandler {
//攔截的對象,可能是Activity可能是Fragment
//這里是MainActivity的OnClick方法
private Object target;
//存儲方法名和方法
//這里key:OnClick value : 預期回調的目標方法 clickEvent
private HashMap<String,Method> methodHashMap=new HashMap<>();
public ListenerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (target!=null) {
String name = method.getName();
method=methodHashMap.get(name);//集合中有需要攔截的方法名直接攔截替換
if (method!=null) {
return method.invoke(target,args);
}
}
return null;
}
/**
* @param methodName 要攔截的方法名methodName:OnClick
* @param method 需要替換執(zhí)行的方法method:clickEvent
* 添加要攔截的方法
*/
public void addMethod(String methodName,Method method){
methodHashMap.put(methodName,method);
}
}
另外OnLongClick的注解如下,和上面OnClick不同的是如果替換onLongClick方法,需要有個boolean返回值
/**
* desc : OnLongClick
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter="setOnLongClickListener",listenerType= View.OnLongClickListener.class,callBackListener="onLongClick")
public @interface OnLongClick {
int[] value();
}
完整代碼可見:github