11

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

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ%3D%3D&%3Bmid=2247489543&%3Bidx=1&%3Bsn=de57dc7e308277f4ca8182f11c68bd05
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文由 黄光华 授权发表

原文链接:

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 结合 Lifecycle, 将 Http 生命周期管理到极致

retrofit-helper 简洁的封装retrofit,优雅的取消请求

Retrofit面试总结

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

B3Q7faa.jpg!web

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

欢迎投稿


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK