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

272 阅读9分钟
原文链接: mp.weixin.qq.com
本文由 黄光华 授权发表原文链接: https://www.jianshu.com/p/145542aedd78

需求背景

相信大部分朋友都经历过,运营突然来要求,要给某部分接口带上某个参数(这个参数可能是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> T create(Class<T> service) {        // Retrofit 的代理        T retrofitProxy = retrofit.create(service);        //再添加一层自定义的代理。        T customProxy = addCustomProxy(retrofitProxy);        //返回这个嵌套的代理        return customProxy;    }
    /**     * 嵌套添加动态代理     * @param target 被代理的对象     * @return 返回嵌套动态代理之后的对象     */    public <T> 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<RootBean<Object>> savePost(@Body EditPostRequest editPostRequest);    /**     * 帖子点赞     */    @FormUrlEncoded    @POST("square/post/like")    Call<RootBean<Object>> likePost(@Field("likeType") int likeType, @Field("postId") long postId);    /**     * 帖子点赞     * 带"from"参数的版本     * 不要删除,动态代理会调用{@link  CustomProxy}     */    @FormUrlEncoded    @POST("square/post/like")    Call<RootBean<Object>> 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();                    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<String> 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 LiveData+Retrofit 网络请求实战 Retrofit 动态管理和修改 BaseUrl,从未如此简单 Retrofit 结合 Lifecycle, 将 Http 生命周期管理到极致 retrofit-helper 简洁的封装retrofit,优雅的取消请求 Retrofit面试总结

code小生  一个专注大前端领域的技术平台 公众号回复Android加入安卓技术群

如果你想要跟大家分享你的文章

欢迎投稿