OkHttp 拦截器实现转发、鉴权、加密

  1. URL重定向
  2. 请求体数据加密
  3. HEAD动态添加
  4. 请求日志抓取

URL重定向

如何重定向,说白了就是更换个新的url,但是一般服务端做比较好,客户端就显得有些鸡肋。但是这个东西日常也会有用到,比如一些场景,测试生成环境的切换。业务多了,几个人混合开发的后台,每个人的代码不同意导致了baseurl还不同,这时候可以通过一个入口来修改就行,不然每个地方都去修改,接口量大的话会很麻烦。

  • 自定义一个Interceptor(TestInterceptor后面都是基于这个来讲解),直接创建个类实现Interceptor接口即可,然后在Okhttp初始化的是添加这个拦截器即可
public class TestInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {

        return null;
    }
}

然后在你需要初始化的okhttp的地方添加这个拦截器

public class TestActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        OkHttpClient build = new OkHttpClient.Builder()
                .addInterceptor(new TestInterceptor())
                .build();
    }
}

那么接下来我们需要干什么呢,拦截器已经写好了,既然定义是拦截器,肯定请求体信息都在那里。我们可以在接口Interceptor里面的方法intercept给了我们一个参数Chain,一切的源头都在这里面。来看看接下来如何进行骚操作来改变初始的URL达到重定向的功能


public class TestInterceptor implements Interceptor {
    private String newHost = "127.0.0.1";
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        HttpUrl url = request.url();
        //http://127.0.0.1/test/upload/img?userName=xiaoming&userPassword=12345
        String scheme = url.scheme();//  http https
        String host = url.host();//   127.0.0.1
        String path = url.encodedPath();//  /test/upload/img
        String query = url.encodedQuery();//  userName=xiaoming&userPassword=12345

        StringBuffer sb = new StringBuffer();
        String newUrl = sb.append(scheme).append(newHost).append(path).append("?").append(query).toString();

        Request.Builder builder = request.newBuilder()
                .url(newUrl);

        return chain.proceed(builder.build());
    }
}

上面只是简单的换了一下host retrofit可以轻易做到,在构造retrofit的时候可以修改baseUrl,但是如果要换path呢,这时候可能retrofit不是那么好做,而且你需要每个地方都要换,这时候你可以在这里当成一个统一的入口new一个新的path即可。简单吧,对于后面的每个字段说明意思我在后面也有备注,可以看到拆分后的样子

请求体数据加密

既然要对请求体加密,那肯定要知道请求体在哪里,然后才能加密,其实都一样不论是加密url里面的query内容还是加密body体里面的都一样,只要拿到了对应的数据我们想怎么做怎么,有的接口比较奇葩,他需要根据请求体的内容进行签名认证。不论如何我们拿到了请求体当然想怎么样就怎么样,看下嘛的操作

  1. 加密query内容
public class TestInterceptor implements Interceptor {
    private String newHost = "127.0.0.1";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        HttpUrl url = request.url();

        //http://127.0.0.1/test/upload/img?userName=xiaoming&userPassword=12345
        String scheme = url.scheme();//  http https
        String host = url.host();//   127.0.0.1
        String path = url.encodedPath();//  /test/upload/img
        String query = url.encodedQuery();//  userName=xiaoming&userPassword=12345

        StringBuffer sb = new StringBuffer();
        sb.append(scheme).append(newHost).append(path).append("?");
        Set<String> queryList = url.queryParameterNames();
        Iterator<String> iterator = queryList.iterator();
        
        for (int i = 0; i < queryList.size(); i++) {
            
            String queryName = iterator.next();
            sb.append(queryName).append("=");
            String queryKey = url.queryParameter(queryName);
            //对query的key进行加密
            sb.append(CommonUtils.getMD5(queryKey));
            if (iterator.hasNext()) {
                sb.append("&"); 
            }
        }


        String newUrl = sb.toString();

        Request.Builder builder = request.newBuilder()
                .url(newUrl);

        return chain.proceed(builder.build());
    }
}

这样拼接的query内容就可以直接统一加密了。我这里只是简单的md5加密 如果说服务端和客户端定义了一套加密解密协议,就可以在这里进行特定的加密方式进行加密

  1. 加密body体内容

public class TestInterceptor implements Interceptor {
    private String newHost = "127.0.0.1";


    public static String requestBodyToString(RequestBody requestBody) throws IOException {
        Buffer buffer = new Buffer();
        requestBody.writeTo(buffer);
        return buffer.readUtf8();
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        HttpUrl url = request.url();

        //http://127.0.0.1/test/upload/img?userName=xiaoming&userPassword=12345
        String scheme = url.scheme();//  http https
        String host = url.host();//   127.0.0.1
        String path = url.encodedPath();//  /test/upload/img
        String query = url.encodedQuery();//  userName=xiaoming&userPassword=12345

        StringBuffer sb = new StringBuffer();
        sb.append(scheme).append(newHost).append(path).append("?");
        Set<String> queryList = url.queryParameterNames();
        Iterator<String> iterator = queryList.iterator();

        for (int i = 0; i < queryList.size(); i++) {

            String queryName = iterator.next();
            sb.append(queryName).append("=");
            String queryKey = url.queryParameter(queryName);
            //对query的key进行加密
            sb.append(CommonUtils.getMD5(queryKey));
            if (iterator.hasNext()) {
                sb.append("&");
            }
        }


        String newUrl = sb.toString();


        RequestBody body = request.body();
        String bodyToString = requestBodyToString(body);
        TestBean testBean = GsonTools.changeGsonToBean(bodyToString, TestBean.class);
        String userPassword = testBean.getUserPassword();
        //加密body体中的用户密码
        testBean.setUserPassword(CommonUtils.getMD5(userPassword));
        
        String testGsonString = GsonTools.createGsonString(testBean);
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), testGsonString);
        


        Request.Builder builder = request.newBuilder()
                .post(requestBody)
                .url(newUrl);

        return chain.proceed(builder.build());
    }
}

从上面可以看出我们先拿到body体的内容然后解析后,拿到对应的实体类,然后进行加密,再次创建一个新的body体里面然后post过去,即可达到body体加密,我这只是一种body加密方法,也有可能是拿到body然后进行加密然后对加密后的东西加入head 当成签名使用。

最终这种拦截器方式的加密也是一种统一代码入口的方式。

HEAD动态添加

在日常的开发中,可能每个接口对应的header不同有的多有的少。不可能说每个接口都写一个拦截器进行添加头部。这时候我们可以换个思维来考虑这个问题。怎么做呢,其实也是为了统一代码,同一种操作不要再多个地方进行,这样修改起来很麻烦。统一入口,统一出口。


public class TestInterceptor implements Interceptor {
    private String newHost = "127.0.0.1";
    private String path1 = "/test/upload/img";
    private String path2 = "/test/upload/voice";

    public static String requestBodyToString(RequestBody requestBody) throws IOException {
        Buffer buffer = new Buffer();
        requestBody.writeTo(buffer);
        return buffer.readUtf8();
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        HttpUrl url = request.url();

        //http://127.0.0.1/test/upload/img?userName=xiaoming&userPassword=12345
        String scheme = url.scheme();//  http https
        String host = url.host();//   127.0.0.1
        String path = url.encodedPath();//  /test/upload/img
        String query = url.encodedQuery();//  userName=xiaoming&userPassword=12345

        StringBuffer sb = new StringBuffer();
        sb.append(scheme).append(newHost).append(path).append("?");
        Set<String> queryList = url.queryParameterNames();
        Iterator<String> iterator = queryList.iterator();

        for (int i = 0; i < queryList.size(); i++) {

            String queryName = iterator.next();
            sb.append(queryName).append("=");
            String queryKey = url.queryParameter(queryName);
            //对query的key进行加密
            sb.append(CommonUtils.getMD5(queryKey));
            if (iterator.hasNext()) {
                sb.append("&");
            }
        }


        String newUrl = sb.toString();


        RequestBody body = request.body();
        String bodyToString = requestBodyToString(body);
        TestBean testBean = GsonTools.changeGsonToBean(bodyToString, TestBean.class);
        String userPassword = testBean.getUserPassword();
        //加密body体中的用户密码
        testBean.setUserPassword(CommonUtils.getMD5(userPassword));

        String testGsonString = GsonTools.createGsonString(testBean);
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), testGsonString);
        
        Request.Builder builder = request.newBuilder()
                .post(requestBody)
                .url(newUrl);

        switch (path) {
            case path1:
                builder.addHeader("token","token");
                break;
            case path2:
                builder.addHeader("token","token");
                builder.addHeader("uid","uid");
                break;
        }


    


        return chain.proceed(builder.build());
    }
}


骚不骚,根据url中path的不同来动态的添加header,其实写代码吗?每个人实现的方式不同,只要路子对,怎么撸都行,看个人。

请求日志抓取

这个其实已经有现成的log拦截器了,大家日常开发中已经有用过,但是如果说我们只想看到我想要的,过滤那些不要的东西。怎么办呢。只能自己来自定义了。把自己需要的东西打印出来。以免太多每次看很乱。这时候怎么通过拦截器完成这样的一个骚操作呢。也很简单,基于上面的基础我们应该该拿到的都拿到了。


public class TestInterceptor implements Interceptor {
    private String newHost = "127.0.0.1";
    private String path1 = "/test/upload/img";
    private String path2 = "/test/upload/voice";
    private String TAG = "TestInterceptor";
    public static String requestBodyToString(RequestBody requestBody) throws IOException {
        Buffer buffer = new Buffer();
        requestBody.writeTo(buffer);
        return buffer.readUtf8();
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        
        HttpUrl url = request.url();
        //http://127.0.0.1/test/upload/img?userName=xiaoming&userPassword=12345
        String scheme = url.scheme();//  http https
        String host = url.host();//   127.0.0.1
        String path = url.encodedPath();//  /test/upload/img
        String query = url.encodedQuery();//  userName=xiaoming&userPassword=12345
        
        RequestBody body = request.body();
        String bodyToString = requestBodyToString(body);

        
        
        Log.e(TAG,scheme);
        Log.e(TAG,host);
        Log.e(TAG,path);
        Log.e(TAG,query);

        if (response != null) {
            ResponseBody responseBody = response.body();
            long contentLength = responseBody.contentLength();
            String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
            
            Log.e(TAG,response.code() + ' ' 
                    + response.message() + ' '
                    + response.request().url()+' '
                    + bodySize
                 );

            Headers headers = response.headers();
            for (int i = 0, count = headers.size(); i < count; i++) {
                Log.e(TAG,headers.name(i) + ": " + headers.value(i));
            }
            
            
        }
        
        


        return chain.proceed(request);
    }
}

根据自己的需求来吧,我只是简单的打印一些东西。也是基于上面的代码来的。



作者:cuieney
链接:https://www.jianshu.com/p/eaee7cd227cd
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

<think>好的,我现在需要帮助用户了解开发Android端社交项目所需的知识点,包括UI设计、网络通信、数据存储、用户认证等方面的最佳实践和常用技术栈。首先,我需要整理这些方面的关键技术和工具,确保涵盖用户提到的所有领域。 首先,UI设计部分,现代Android开发推荐使用Jetpack Compose,因为它更高效和灵活。同时需要提到布局优化、动画和Material Design,这些都是提升用户体验的重要部分。引用中提到了Compose和Navigation组件,所以需要引用[^2]。 接下来是网络通信,社交应用离不开与服务器的交互。常用的库有Retrofit和OkHttp,可能需要提到WebSocket用于实时通信,还有序列化工具如Gson或Moshi。这部分可能需要参考常见的实践,但引用中没有直接提到,所以暂时不需要引用。 数据存储方面,本地存储可以使用Room数据库,SharedPreferences或DataStore。对于云端存储,Firebase是常用的后端服务,包括实时数据库和存储。引用[^3]提到Spring Boot的生态系统,但可能不直接相关,不过Firebase是Android常用技术,可能需要单独说明。 用户认证部分,需要集成第三方登录如Google、微信,以及实现手机号验证。Firebase Authentication和OAuth2.0协议是常用的,这里可以引用[^2]提到的开发效率,或者[^1]提到的开放性,但可能更直接的是Firebase相关的引用,不过用户提供的引用中没有明确提到,所以需要看是否有间接关联。 实时通信功能可能需要使用WebSocket、Firebase的实时数据库或第三方服务如Socket.IO。消息推送可以用Firebase Cloud Messaging,这部分同样可能需要引用关于高效和灵活性的部分。 安全方面,数据加密和HTTPS是必须的,可能提到ProGuard或R8混淆代码,这些属于最佳实践,引用可能需要结合提到的开源资源和灵活性。 性能优化方面,内存管理和网络优化是关键,LeakCanary工具检测内存泄漏,Glide或Coil处理图片加载。这里可以引用提到的提高代码质量部分。 测试与部署,单元测试和UI测试工具,持续集成工具如Jenkins,以及发布流程,这些属于开发流程的最佳实践,引用可能关联提到的Spring Boot生态系统,但不太直接,所以可能不引用。 技术栈组合示例需要综合上述技术,比如Compose+Retrofit+Room+Firebase,这里可以引用[^2]和关于开发效率和开放性的部分。 最后生成相关问题,确保问题覆盖各个知识点,并引导深入学习。需要检查引用是否恰当,每个引用标记要对应正确的引用内容。例如,提到Compose时引用[^2],提到生态系统时引用[^3],提到市场占有率和开放性时引用。 现在需要组织这些点,确保结构清晰,每个部分都有对应的技术和最佳实践,同时自然添加引用标记。可能遗漏的部分需要补充,比如推送通知、安全措施等。确保每个引用都准确,不强制引用所有,只在相关部分添加。</think>### Android社交应用开发知识点详解 #### 一、UI设计与交互 1. **现代UI框架** - 使用$Jetpack Compose$构建声明式界面,简化复杂布局开发。例如实现动态列表: ```kotlin @Composable fun SocialFeed(posts: List<Post>) { LazyColumn { items(posts) { post -> PostCard(post) } } } ``` - 采用$Material Design 3$规范,确保界面符合现代设计标准 - 通过$MotionLayout$实现精细动画,提升用户交互体验 2. **导航管理** - 使用$Navigation Component$管理Fragment/Compose页面跳转 - 深层链接处理:`navDeepLink { uriPattern = "app://profile/{userId}" }` #### 二、网络通信 1. **RESTful API交互** - 使用$Retrofit2 + OkHttp3$处理网络请求 - 添加拦截器实现统一: ```kotlin val client = OkHttpClient.Builder() .addInterceptor(AuthInterceptor()) .build() ``` 2. **实时通信** - 通过$WebSocket$或$Socket.IO$实现即时消息 - 使用$Firebase Realtime Database$同步在线状态 3. **数据序列化** - 采用$kotlinx.serialization$或$Moshi$进行JSON转换 - 使用$Protobuf$优化数据传输效率 #### 三、数据存储 1. **本地存储** - 关系型数据:$Room$数据库+$Paging3$分页加载 - 键值对存储:$DataStore$替代SharedPreferences 2. **云端同步** - 使用$Firebase Firestore$实现多设备同步 - 图片/视频存储:$Firebase Storage$分块上传 #### 四、用户认证 1. **登录方式整合** - 手机验证:集成$Firebase Authentication$短信验证[^2] - 第三方登录:$OAuth2.0$对接微信/QQ/Google - 生物识别:$Biometric API$实现指纹/面部识别 2. **安全策略** - 使用$JWT$令牌管理会话 - 敏感数据加密:$Android Keystore$存储密钥 #### 五、社交核心功能 1. **好友关系系统** - 实现关注/粉丝逻辑: $$ \text{关系状态} = \begin{cases} 0 & \text{无关系} \\ 1 & \text{已关注} \\ 2 & \text{互相关注} \end{cases} $$ - 使用$GraphQL$优化复杂关系查询 2. **内容互动** - 点赞/评论实时更新:$WebSocket$推送 - 消息推送:$Firebase Cloud Messaging(FCM)$集成 #### 六、性能优化 1. **内存管理** - 使用$LeakCanary$检测内存泄漏 - 图片加载:$Coil$或$Glide$实现懒加载 2. **网络优化** - 响应缓存:$Retrofit$添加`Cache-Control`头 - 请求合并:GraphQL替代多REST请求 #### 七、技术栈组合示例 | 领域 | 推荐技术组合 | |-----------|-----------------------------| | UI层 | Compose + Material3 + Navigation | | 网络层 | Retrofit2 + OkHttp3 + Gson | | 本地存储 | Room + DataStore | | 云端服务 | Firebase生态体系 | | 实时通信 | Socket.IO + WebSocket | | 安全认证 | JWT + Keystore | #### 八、最佳实践建议 1. 采用$MVVM$架构隔离业务逻辑与UI 2. 使用$Hilt$依赖注入提升可测试性 3. 通过$WorkManager$处理后台任务 4. 实施$CI/CD$流程:Jenkins/GitLab CI自动化构建
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值