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

开发原因

目前工作需要在进行安卓开发的时候很容易写很多的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

发布了22 篇原创文章 · 获赞 4 · 访问量 4323

猜你喜欢

转载自blog.csdn.net/qq_38366111/article/details/96434826