Android使用AOP依赖倒置----------------注解开发

it2022-05-05  175

开发原因

目前工作需要在进行安卓开发的时候很容易写很多的findById或者事件监听方法,显得特别的臃肿。另外注解开发一贯是spring的使用风格,能不能直接用spring的思想来依赖注入安卓控件的值,答案是可以的。注解开发框架有很多,比如xUtils,但xUtils比较臃肿,它包含了数据库,网络请求,注解开发,图片加载。因为这是个比较成熟的框架,所以引入到项目中如果只使用其中的一个注解开发,会显得很笨重。所以引入了注解开发。

第一步搞清楚注解开发能减少那些工作

setContentView布局注入findById控件注入     onClick事件注入

废话少说上代码吧。

创建类选择Annotation类型

布局注入注解

package com.tydfd.annotationlibrary.iocannotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Classname ContentView * @Description Activity使用的布局文件注解 * @Target(ElementType.TYPE) // 接口、类、枚举、注解 * * @Target(ElementType.FIELD) // 属性、枚举的常量 * * @Target(ElementType.METHOD) // 方法 * * @Target(ElementType.PARAMETER) // 方法参数 * * @Target(ElementType.CONSTRUCTOR) // 构造函数 * * @Target(ElementType.LOCAL_VARIABLE)// 局部变量 * * @Target(ElementType.ANNOTATION_TYPE)// 该注解使用在另一个注解上 * * @Target(ElementType.PACKAGE) // 包 * @Date 2019/7/17 15:41 * @Created by liudo * @Author by liudo * 生命周期:SOURCE < CLASS < RUNTIME * * 1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解 * * 2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃 * * 3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解 */ // 该注解作用于类,接口或者枚举类型上 @Target(ElementType.TYPE) // 注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容 @Retention(RetentionPolicy.RUNTIME) public @interface ContentView { /** * @return int 类型布局 */ int value(); }

控件注入

package com.tydfd.annotationlibrary.iocannotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Classname ViewInject * @Description findById注入 * @Date 2019/7/17 16:02 * @Created by liudo * @Author by liudo */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface InjectView { int value(); }

事件注入(有点复杂)运用AOP切面,使用java8的动态代理来拦截事件方法,然后将事件方法拦截替换自定义方法,随后将自定义方法代替事件方法来执行。废话少说,上代码。

定义注解上的注解基类,获取事件方法的名称进行拦截操作。

package com.tydfd.annotationlibrary.iocannotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author liudo */ @Target(ElementType.ANNOTATION_TYPE) // 放在注解的上面 @Retention(RetentionPolicy.RUNTIME) public @interface EventBase { // 事件的三个成员 // 1、set方法名 String listenerSetter(); // 2、监听的对象 Class<?> listenerType(); // 3、回调方法 String callBackListener(); }

然后定义点击注解(这只是个例子,然后依此例子开发别的事件)

package com.tydfd.annotationlibrary.iocannotation; import android.view.View; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author liudo */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callBackListener = "onClick") public @interface OnClick { int[] value(); }

布局注入方法,通过java的反射。

//布局的注入 private static void injectLayout(Activity activity) { //获取类 Class<? extends Activity> clazz = activity.getClass(); //获取类的注解 ContentView contentView = clazz.getAnnotation(ContentView.class); if(contentView!=null){ //获取注解的值(R.layout.xxxxx) int layoutId = contentView.value(); try { //获取指定的方法(setContentView) Method method = clazz.getMethod("setContentView", int.class); Log.i(TAG, "injectLayout: "+method.getName()); //执行方法 method.invoke(activity,layoutId); } catch (Exception e) { e.printStackTrace(); } } }

控件注入方法

private static void injectViews(Activity activity) { //获取类 Class<? extends Activity> clazz = activity.getClass(); //获取类的所有属性 Field[] fields = clazz.getDeclaredFields(); //循环,拿到每个属性 for(Field field:fields){ //获得属性上的注解 InjectView injectView = field.getAnnotation(InjectView.class); //获得注解的值 并不是所有的属性都有注解 if(injectView != null){ int viewId = injectView.value(); try { //获取findViewById方法,并执行 Method method = clazz.getMethod("findViewById", int.class); Object view = method.invoke(activity, viewId); //设置访问修饰符,访问权限private field.setAccessible(true); //属性的值赋给控件,在当前Activity field.set(activity,view); } catch (Exception e) { e.printStackTrace(); } } } }

事件注入方法(有点复杂,不过运用动态代理拦截官方方法,用自定义方法替换官方方法)

private static void injectEvents(Activity activity) { // 获取类 Class<? extends Activity> clazz = activity.getClass(); // 获取类的所有方法 Method[] methods = clazz.getDeclaredMethods(); // 遍历方法 for (Method method : methods) { // 获取每个方法的注解(多个控件id) Annotation[] annotations = method.getAnnotations(); // 遍历注解 for (Annotation annotation : annotations) { // 获取注解上的注解 // 获取OnClick注解上的注解类型 Class<? extends Annotation> annotationType = annotation.annotationType(); if (annotationType != null) { // 通过EventBase指定获取 EventBase eventBase = annotationType.getAnnotation(EventBase.class); if (eventBase != null) { // 有些方法没有EventBase注解 // 事件3大成员 String listenerSetter = eventBase.listenerSetter(); Class<?> listenerType = eventBase.listenerType(); String callBackListener = eventBase.callBackListener(); // 获取注解的值,执行方法再去获得注解的值 try { // 通过annotationType获取onClick注解的value值 Method valueMethod = annotationType.getDeclaredMethod("value"); Log.i(TAG, "injectEvents: "+valueMethod.getName()); // 执行value方法获得注解的值 int[] viewIds = (int[]) valueMethod.invoke(annotation); Log.i(TAG, "injectEvents: 333333333333333333"+ Arrays.toString(viewIds)); // 代理方式(3个成员组合) // 拦截方法 // 得到监听的代理对象(新建代理单例、类的加载器,指定要代理的对象类的类型、class实例) ListenerInvocationHandler handler = new ListenerInvocationHandler(activity); // 添加到拦截列表里面 Log.i(TAG, callBackListener+"============="+method); handler.addMethod(callBackListener, method); // 监听对象的代理对象 // ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的 // Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型 // InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法 Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, handler); // 遍历注解的值 for (int viewId : viewIds) { // 获得当前activity的view(赋值) View view = activity.findViewById(viewId); // 获取指定的方法 Method setter = view.getClass().getMethod(listenerSetter, listenerType); // 执行方法 setter.invoke(view, listener); } } catch (Exception e) { e.printStackTrace(); } } } } } } 拦截器,将拦截的OnClick方法替换成我们自定义的方法 package com.tydfd.annotationlibrary.listener; import android.util.Log; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; /** * @author liudo * // 将回调的onClick方法拦截,执行我们自己自定义的方法(aop概念) */ public class ListenerInvocationHandler implements InvocationHandler { private static final String TAG = "LIUDONG"; // 需要拦截的对象 private Object target; // 需要拦截的对象键值对 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) { Log.i(TAG, "invoke: "+ Arrays.toString(args)+"---------------------"+method); // 获取需要拦截的方法名 // 假如是onClick String methodName = method.getName(); Log.i(TAG, "invoke: "+methodName+"================="+methodHashMap.toString()); // 重新赋值,将拦截的方法换为show // 执行拦截的方法,show method = methodHashMap.get(methodName); if (method != null) { return method.invoke(target, args); } } return null; } /** * 将需要拦截的方法添加 * @param methodName 需要拦截的方法,如:onClick() * @param method 执行拦截后的方法,如:show() */ public void addMethod(String methodName, Method method) { methodHashMap.put(methodName, method); } }

将注解封装成一个module,引入其中,直接可以使用。

BaseActivity

package com.tydfd.iocdemo; import android.os.Bundle; import android.os.PersistableBundle; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.tydfd.annotationlibrary.InjectManager; /** * @Classname BaseActivity * @Description TODO * @Date 2019/7/17 15:57 * @Created by liudo * @Author by liudo */ public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 帮助子类进行,布局、控件、事件的注入 InjectManager.inject(this); } }

MainActivity

package com.tydfd.iocdemo; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.tydfd.annotationlibrary.iocannotation.ContentView; import com.tydfd.annotationlibrary.iocannotation.InjectView; import com.tydfd.annotationlibrary.iocannotation.OnClick; /** * @author liudo */ @ContentView(R.layout.activity_main) public class MainActivity extends BaseActivity { @InjectView(R.id.btn) private Button btn; @InjectView(R.id.tv) private TextView tv; @Override protected void onResume() { super.onResume(); Toast.makeText(this, tv+"", Toast.LENGTH_SHORT).show(); // Toast.makeText(this, btn.getText().toString(), Toast.LENGTH_SHORT).show(); } @OnClick({R.id.tv,R.id.btn}) public void show(View view){ switch (view.getId()){ case R.id.btn: Toast.makeText(this, btn.getText(), Toast.LENGTH_SHORT).show(); break; case R.id.tv: Toast.makeText(this, tv.getText(), Toast.LENGTH_SHORT).show(); break; } } }

总结

注解开发很方便但是资源消耗很大,能够后期维护迅速,开发简单。虽然现在本人做的这个demo比不上优秀的注解开发模块,但是其思路还是很棒的,减少很多代码开发。自我感觉良好。有时候思路比实现更重要。加油实习生,目标猪场。欢迎来交流,欢迎沟通。

本人微信《liudongGeek245210》欢迎骚扰。

本例子Demo链接。https://download.csdn.net/download/qq_38366111/11383221


最新回复(0)