博客主頁(yè) 這篇文章講解了編譯時(shí)注入,但運(yùn)行時(shí)注入框架也值得學(xué)習(xí)。 結(jié)下來(lái)的任務(wù)是分析xUtils3核心模塊IOC注入式的框架設(shè)計(jì),注解解決事件的三要素,靜態(tài)代理和動(dòng)態(tài)代理,運(yùn)行時(shí)注入布局,控件,事件 運(yùn)行時(shí)注入布局在Activity中加載布局文件一般都是通過在onCreate方法中調(diào)用setContentView方法設(shè)置布局 @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
如果在Activity的類上通過注解方式設(shè)置布局,如下代碼。運(yùn)行時(shí)注入布局方式實(shí)現(xiàn),@ContentView替代setContentView @ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity { }
然后只需要在onCreate方法中調(diào)用注入方法,就自動(dòng)幫助我們?cè)O(shè)置了布局 ViewInjector.inject(this);
實(shí)現(xiàn)方式就是被一些人所詬病反射技術(shù)實(shí)現(xiàn)。 public static void inject(Object target) {
Class<?> targetClass = target.getClass();
// 注入布局文件
// 獲取Activity上的ContentView注解
ContentView contentView = targetClass.getAnnotation(ContentView.class);
if (contentView != null) {
int layoutResid = contentView.value();
// 布局資源文件非法
if (layoutResid <= 0) {
throw new RuntimeException("注入的布局資源文件非法");
}
try {
Method setContentViewMethod = targetClass.getMethod("setContentView", int.class);
setContentViewMethod.invoke(target, layoutResid);
} catch (Exception e) {
throw new RuntimeException("注入的布局資源文件失敗::" + e.getMessage());
}
}
}
通過獲取Activity上的ContentView注解得到布局文件,使用反射調(diào)用setContentView方法。 運(yùn)行時(shí)注入控件通過在控件上添加@ViewInject,就可以代替findViewById public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.text)
private TextView text;
}
注入控件代碼實(shí)現(xiàn) public static void inject(Object target) {
// ...
injectObject(target, targetClass);
}
private static void injectObject(Object target, Class<?> targetClass) {
if (targetClass == null) return;
// 注入控件
Field[] fields = targetClass.getDeclaredFields();
if (fields.length > 0) {
for (Field field : fields) {
Class<?> fieldType = field.getType();
if (/*不注入基本類型字段*/ fieldType.isPrimitive() ||
/*不注入數(shù)組類型字段*/ fieldType.isArray() ||
/*不注入靜態(tài)字段*/ Modifier.isStatic(field.getModifiers()) ||
/*不注入final字段*/ Modifier.isFinal(field.getModifiers())) {
continue;
}
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
int viewResid = viewInject.value();
if (viewResid <= 0) continue;
try {
Method findViewByIdMethod = targetClass.getMethod("findViewById", int.class);
Object view = findViewByIdMethod.invoke(target, viewResid);
if (view != null) {
field.setAccessible(true);
field.set(target, view);
} else {
throw new RuntimeException("Invalid @ViewInject for "
+ targetClass.getSimpleName() + "." + field.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
通過反射拿到注入類中所有的字段,排除不需要注入的字段有:基本類型、數(shù)據(jù)類型、靜態(tài)修飾、final修飾。在獲取字段上的@ViewInject注解,使用反射調(diào)用findViewById找到view并設(shè)置給該字段。 運(yùn)行時(shí)注入事件在實(shí)現(xiàn)運(yùn)行時(shí)注入事件之前,先了解下動(dòng)態(tài)代理。 在動(dòng)態(tài)代理中,代理類并不是在java代碼中實(shí)現(xiàn),而是在運(yùn)行期生成,相比靜態(tài)代理,動(dòng)態(tài)代理可以很方便的對(duì)委托類的方法進(jìn)行統(tǒng)一處理。 事件的三要素:訂閱、事件源、事件 訂閱:事件的setter方法名,默認(rèn)為set+type,如setOnClickListener 事件源:事件的listener,默認(rèn)為點(diǎn)擊事件,如View.OnClickListener 事件:事件源中提供的方法,如onClick
/**
*
* 事件注解
* 被注解的方法必須:
* 1. private修飾
* 2. 返回值類型沒有要求
* 3. 參數(shù)簽名和type的接口要求的參數(shù)簽名一致
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
// 控件的id集合,當(dāng)id小于1時(shí)不執(zhí)行事件綁定
int[] value();
// 事件三要素:訂閱、事件源、事件
// 訂閱,事件的setter方法名,默認(rèn)為set+type
String setter() default "";
// 事件源,事件的listener,默認(rèn)為點(diǎn)擊事件
Class<?> type() default View.OnClickListener.class;
// 事件,如果type的接口類型提供多個(gè)方法,需要使用此參數(shù)指定方法名
String method() default "";
}
只需要在方法上添加@Event注解就可以代替View.setOnClickListener(OnClickListener l) @Event(R.id.text)
private void gotoOut(View view) {
Log.d("todo_xutils", "onCreate: 注入的方式點(diǎn)擊事件");
}
注入事件代碼實(shí)現(xiàn) private static void injectObject(Object target, Class<?> targetClass) {
// 注入事件
Method[] methods = targetClass.getDeclaredMethods();
if (methods.length > 0) {
for (Method method : methods) {
if (/*不注入靜態(tài)的方法*/ Modifier.isStatic(method.getModifiers()) ||
/*注入的方法必須是private的*/ !Modifier.isPrivate(method.getModifiers())) {
continue;
}
Event event = method.getAnnotation(Event.class);
if (event != null) {
int[] ids = event.value();
for (int i = 0; i < ids.length; i++) {
int id = ids[i];
if (id > 0) {
method.setAccessible(true);
EventListenerManager.addEventMethod(target, targetClass, id, event, method);
}
}
}
}
}
}
通過反射拿到注入類中所有的方法,注入的方法需要必須:private修飾、返回值類型沒有要求、參數(shù)簽名和type的接口要求的參數(shù)簽名一致。 class EventListenerManager {
static void addEventMethod(
Object target, /*注入的類*/
Class<?> targetClass, /*注入的類Class*/
int id, /*注入的控件的id*/
Event event, /*Event注解*/
Method method /*注入的方法*/
) {
try {
Method findViewByIdMethod = targetClass.getMethod("findViewById", int.class);
Object view = findViewByIdMethod.invoke(target, id);
if (view == null) {
throw new RuntimeException("No Found @Event for "
+ targetClass.getSimpleName() + "." + method.getName());
}
// view.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// }
// });
// 注解中定義的接口,如Event注解默認(rèn)的接口為View.OnClickListener
Class<?> listenerType = event.type();
// 默認(rèn)為空,事件的setter方法名,如:setOnClickListener
String listenerSetter = event.setter();
if (TextUtils.isEmpty(listenerSetter)) {
listenerSetter = "set" + listenerType.getSimpleName();
}
Object proxyListener = Proxy.newProxyInstance(
listenerType.getClassLoader(),
new Class<?>[]{listenerType},
new EventInvocationHandler(target, method)
);
// view.setOnClickListener(@Nullable OnClickListener l)
Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
setEventListenerMethod.invoke(view, proxyListener);
} catch (Exception e) {
e.printStackTrace();
}
}
}
獲取Method上的@Event注解后,獲取事件三要素。通過反射調(diào)用訂閱方法,方法的參數(shù)設(shè)置為代理類 private static class EventInvocationHandler implements InvocationHandler {
// 存放代碼對(duì)象,如MainActivity
private WeakReference<Object> targetRef;
private Method targetMethod;
EventInvocationHandler(Object target, Method method) {
targetRef = new WeakReference<>(target);
targetMethod = method;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object target = targetRef.get();
if (target != null) {
return targetMethod.invoke(target, args);
}
return null;
}
}
|