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

本文由 黄光华 授权投稿
原文链接: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 Classservice)方法的返回值,再加上自定义的动态代理:

    /**
     * 获取对应的 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我是控制不了的。

推荐阅读
二话不说,开干!
漫画:什么是996?
推荐一个必备的开源项目

编程·思维·职场
欢迎扫码关注

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值