博客主頁
這篇文章講解了編譯時注入,但運行時注入框架也值得學習。
結下來的任務是分析xUtils3核心模塊IOC注入式的框架設計,注解解決事件的三要素,靜態(tài)代理和動態(tài)代理,運行時注入布局,控件,事件
在Activity中加載布局文件一般都是通過在onCreate方法中調用setContentView方法設置布局
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);}
如果在Activity的類上通過注解方式設置布局,如下代碼。運行時注入布局方式實現(xiàn),@ContentView替代setContentView
@ContentView(R.layout.activity_main)public class MainActivity extends AppCompatActivity { }
然后只需要在onCreate方法中調用注入方法,就自動幫助我們設置了布局
ViewInjector.inject(this);
實現(xiàn)方式就是被一些人所詬病反射技術實現(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注解得到布局文件,使用反射調用setContentView方法。
通過在控件上添加@ViewInject,就可以代替findViewById
public class MainActivity extends AppCompatActivity { @ViewInject(R.id.text) private TextView text;}
注入控件代碼實現(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注解,使用反射調用findViewById找到view并設置給該字段。
在實現(xiàn)運行時注入事件之前,先了解下動態(tài)代理。
在動態(tài)代理中,代理類并不是在java代碼中實現(xiàn),而是在運行期生成,相比靜態(tài)代理,動態(tài)代理可以很方便的對委托類的方法進行統(tǒng)一處理。
事件的三要素:訂閱、事件源、事件
訂閱:事件的setter方法名,默認為set+type,如setOnClickListener
事件源:事件的listener,默認為點擊事件,如View.OnClickListener
事件:事件源中提供的方法,如onClick
/** * * 事件注解 * 被注解的方法必須: * 1. private修飾 * 2. 返回值類型沒有要求 * 3. 參數(shù)簽名和type的接口要求的參數(shù)簽名一致 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Event { // 控件的id集合,當id小于1時不執(zhí)行事件綁定 int[] value(); // 事件三要素:訂閱、事件源、事件 // 訂閱,事件的setter方法名,默認為set+type String setter() default ""; // 事件源,事件的listener,默認為點擊事件 Class<?> type() default View.OnClickListener.class; // 事件,如果type的接口類型提供多個方法,需要使用此參數(shù)指定方法名 String method() default "";}
只需要在方法上添加@Event注解就可以代替View.setOnClickListener(OnClickListener l)
@Event(R.id.text)private void gotoOut(View view) { Log.d("todo_xutils", "onCreate: 注入的方式點擊事件");}
注入事件代碼實現(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注解默認的接口為View.OnClickListener Class<?> listenerType = event.type(); // 默認為空,事件的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注解后,獲取事件三要素。通過反射調用訂閱方法,方法的參數(shù)設置為代理類
private static class EventInvocationHandler implements InvocationHandler { // 存放代碼對象,如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; }}