动态代理-事件注入(一)

1.目的:

通过代理的方式去注册控件的点击事件,长按事件等其他事件。

优点:隔离代码层,解耦 缺点:运行时反射的效率低

2.第一版本(先不用动态代理):

2.1 创建一个注解类 (用来区分方法是否需要注册点击事件)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
    //接收控件的ID
    int[] value() default -1;
}

2.2 创建一个注解工具类

public class InjectUtil {
    public static void init(Object content) {
        //1.获取类中所有的方法
        Method[] methods = content.getClass().getMethods();
        for (Method method : methods) {
            //2.找到有标注注解的方法
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (onClick != null) {
                int[] value = onClick.value();
                try {
                    //3.通过反射获取findViewById方法
                    Method findViewById = content.getClass().getMethod("findViewById", int.class);
                    for (int i : value) {
                        //4.获取控件
                        View view = (View) findViewById.invoke(content, i);
                        if (view != null) {
                            //5.设置监听
                            view.setOnClickListener(v -> {
                                try {
                                    //6.方法回调 method-> 就是我们外面标注注解的方法
                                    method.invoke(content, v);
                                } catch (IllegalAccessException | InvocationTargetException e) {
                                    e.printStackTrace();
                                }
                            });
                        }
                    }
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.3 注册工具类

BaseActivity

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtil.init(this);
    }
}

MainActivity

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        setContentView(R.layout.activity_main);
        super.onCreate(savedInstanceState);
    }


    @OnClick(R.id.btn)
    public void start(View view) {
        Toast.makeText(this,"我被点击了",Toast.LENGTH_SHORT).show();
    }

}

注意:MainActivity类中,setContentView必须在super.onCreate()方法前,因为只有在setContentView后控件才会被创建。

很简单的代码,但这样会产生一个问题;
1.那就是如果你还要注册长按事件,viewpager的滑动事件,那是不是还得继续往文件写代码,违背了类的单一原则,也不好扩展,
2.你如何区分哪个控件需要长按,哪个不需要?按照下面的写法,所有的控件都会被自动注册长按事件,
如下长按事件;

view.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        //6.方法回调 method-> 就是我们外面标注注解的方法
        try {
            //6.方法回调 method-> 就是我们外面标注注解的方法
            method.invoke(content, v);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return false;
    }
});

接下来我们用动态代理看下,能不能解决上面的问题

3.使用动态代理:

我们简单的修改下InjectUtil

public static void init(Object content) {
    //1.获取类中所有的方法
    Method[] methods = content.getClass().getMethods();
    for (Method method : methods) {
        //2.找到有标注注解的方法
        OnClick onClick = method.getAnnotation(OnClick.class);
        if (onClick != null) {
            int[] value = onClick.value();
            try {
                //3.通过反射获取findViewById方法
                Method findViewById = content.getClass().getMethod("findViewById", int.class);
                for (int i : value) {
                    //4.获取控件
                    View view = (View) findViewById.invoke(content, i);
                    //5.通过代理获取View.OnClickListener
                    View.OnClickListener listener = (View.OnClickListener) Proxy.newProxyInstance(content.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new OnClickInvocationHandler(content,method));
                    //6.设置监听
                    view.setOnClickListener(listener);
                }
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

OnClickInvocationHandler

public class OnClickInvocationHandler implements InvocationHandler {

    private final Object content;//可以是Activity,fragment,dialog
    private final Method method;

    public OnClickInvocationHandler(Object content, Method method) {
        this.content = content;
        this.method = method;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (content != null) {
            //这里的回调是 setOnClickListener的回调
            //method 也就是start方法
            this.method.invoke(content,args);
        }
        return null;
    }
}

动态代理用上了,但好像也没解决问题,只不过是换了个高级点的用法,屁用没有。
问题依旧存在,如下:

1.那就是如果你还要注册长按事件,viewpager的滑动事件,那是不是还得继续往文件写代码,违背了类的单一原则,也不好扩展,
2.你如何区分哪个控件需要长按,哪个不需要?按照下面的写法,所有的控件都会被自动注册长按事件。
留到下一章解决,haha

动态代理-事件注入(二)https://blog.csdn.net/wumeixinjiazu/article/details/122499788

代码地址:IOCDemo: 动态代理ioc注解https://gitee.com/small_insects/IOCDemo