给Retrofit嵌套动态代理,高效处理运营打点

本篇文章已授权微信公众号 玉刚说 (任玉刚)独家发布

需求背景:

相信大部分朋友都经历过,运营突然来要求,要给某部分接口带上某个参数(这个参数可能是from,表示当前在哪个页面;或者 duration,表示当前界面停留了多久)。这个时候,最直接的做法就是,直接加呗~ 有些接口还被多个界面调用,要改代码的界面可能是十多个,也可能是大几十个。

//举例子,帖子点赞,原本的请求调用:
ApiService.getInstance().likePost(likeType, postId);
//直接在调用方法时加 from 参数:
ApiService.getInstance().likePost(likeType, postId, from);

而我收到的需求则是要带上当前页面和上一级页面。。。这个需求,按常规做法,在各个Activity间的intent都要传入上一级Activity的信息。这个代码量就更大了,而且代码会很累赘。
这时候,我的上级给了我提示,可以试下多重动态代理。之前我也考虑过这个需求适合用动态代理做,但是我知道Retrofit本身已经用了,我没想到还可以多重动态代理。接着就试了一下,还真的OK,果然还是大佬牛啊~!

给Retrofit嵌套一层动态代理后,我们项目中调用请求接口的地方不需要修改代码了,不用每处请求都手动添加上 from 参数,因为在这个自定义的动态代理工作时,已经帮我们统一加上了这个 from 参数。

相关知识

动态代理:方便的对被代理类的方法进行统一处理。
反射:一种能够在程序运行时动态访问、修改某个类中任意属性(状态)和方法(行为)的机制(包括private实例和方法)。
阅读本文需要你对动态代理和反射有一定的理解,不然建议先熟悉一下相关知识点。

Retrofit嵌套动态代理步骤:

1.给 Retrofit.create (final Class service)方法的返回值,再加上自定义的动态代理

    /**
     * 获取对应的 Service
     */
     T create(Class service) {
        // Retrofit 的代理
        T retrofitProxy = retrofit.create(service);
        //再添加一层自定义的代理。
        T customProxy = addCustomProxy(retrofitProxy);
        //返回这个嵌套的代理
        return customProxy;
    }
    /**
     * 嵌套添加动态代理
     * @param target 被代理的对象
     * @return 返回嵌套动态代理之后的对象
     */
    public  T addCustomProxy(T target) {
        CustomProxy customProxy = new CustomProxy();
        return (T) customProxy.newProxy(target);
    }

2.在原来的请求接口的基础上,加上带运营打点所需要的参数(在本例就是 from 参数)
如果请求参数是每个值分开传的才需要这一步( 例如这里的 likePost 接口);
对于请求参数是一个bean类或者Map,不需要这一步( 例如这里的 savePost 接口)。

    /**
     * 广场发帖
     */
    @FormUrlEncoded
    @POST("square/post/save")
    Call> savePost(@Body EditPostRequest editPostRequest);

    /**
     * 帖子点赞
     */
    @FormUrlEncoded
    @POST("square/post/like")
    Call> likePost(@Field("likeType") int likeType, @Field("postId") long postId);

    /**
     * 帖子点赞
     * 带"from"参数的版本
     * 不要删除,动态代理会调用{@link  CustomProxy}
     */
    @FormUrlEncoded
    @POST("square/post/like")
    Call> likePost(@Field("likeType") int likeType, @Field("postId") long postId, @Field("from") String from);

PS:注释说明“不要删除,动态代理会调用”建议一定不能省~~因为动态代理的方法在IDE中是索引不到的,同事甚至自己很容易删掉,编译是不会报错的。

3.在自定义的代理类里,真正执行统一加参数的操作
(这里还是以加 from 做例子)

    /**
     * 嵌套添加动态代理
     * 简例:https://blog.csdn.net/zhenghuangyu/article/details/102808338
     */
    public static class CustomProxy implements InvocationHandler {
        //被代理对象,在这里就是 Retrofit.create(service) 的返回值
        private Object mTarget;

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String from = "testFrom";
            final String methodName = method.getName();

            switch (methodName) {

                case "savePost": {
                    //形参是一个bean类,用这种方式

                    //获取第一个请求参数args[0],这是我们定义该接口形参时的bean类
                    EditPostRequest editPostRequest = (EditPostRequest) args[0];
                    //以变量形式设置
                    editPostRequest.setFrom(from);
                    break;
                }

                case "likePost": {
                    //形参是一个个值的形式,用这种方式

                    //将参数长度+1,作为新的参数数组
                    args = Arrays.copyOf(args, (args.length + 1));
                    //在新的参数数组末端加上 from 
                    args[args.length - 1] = from;

                    //为了调用带 from 版本的方法,构造新的形参
                    Class[] newParams = Arrays.copyOf(method.getParameterTypes(), (method.getParameterTypes().length + 1));
                    //新的形参里,最后一个参数 from 是String类型的,这个必须声明,才能准确调用反射
                    newParams[newParams.length - 1] = String.class;

                    //找出新method对象,就是带 from 版本的那个方法
                    method = mTarget.getClass().getDeclaredMethod(method.getName(), newParams);
                    break;
                }
            }
            //正式执行方法
            return method.invoke(mTarget, args);
        }

        //在这里嵌套外层的动态代理
        public Object newProxy(Object target) {
            this.mTarget = target;
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    }

嗯,这样就完成了为多个接口添加参数的需求。本来少说也要修改几十个地方,现在简单优雅的解决了。更重要的是,不需要机械地添加累赘的代码,用工程化的方案解决问题。

文章重点是多重动态代理。至于我的需求里,怎么优雅地处理当前和上一级Activity的路径,我想到的方法有两种:1.用AMS获取Activity栈 2.用ActivityLifecycle 。
我用的是第二种,并通过一个Stack对象,自行记录Activity的入栈出栈。不过这个不是文章重点,不详细展开了。放上简单代码:

    /**
     * 要记录最新的两个页面,用栈操作
     */
    private Stack tagsRecords = new Stack<>();

    /**
     * 标签入栈
     */
    public void pushTagRecord(String tag) {
        tagsRecords.push(tag);
    }

    /**
     * 标签出栈
     */
    public void popTagRecord() {
        tagsRecords.pop();
    }

    //注册LifeCycle监听,在这里完成界面对应tag的出栈入栈
    Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            //新建界面,入栈
            pushTagRecord(activity.getLocalClassName());
        }

        @Override
        public void onActivityDestroyed(Activity activity) {
            //界面销毁,出栈
            popTagRecord();
        }
    }
//然后在项目Application类的初始化方法中注册lifecycle
registerActivityLifecycleCallbacks(lifecycleCallbacks);

嗯,通过这种用lifecycle配合栈结构的方式,记录页面访问路径,就避免了在每处 startActivity()的intent里传递参数。而且这种方法比AMS获取Activity栈的方式更灵活。例如我的实际需求就是,特定的几个Activity才算有效路径。在Activity入栈出栈时,我可以做一层判断过滤,而AMS我是控制不了的。

你可能感兴趣的:(给Retrofit嵌套动态代理,高效处理运营打点)