Retrofit+Okhttp+Rxjava项目应用

2018-04-20  本文已影响417人  Rave_Tian

Retrofit出来之后,就曾学习过它的使用方法,也做过简单Demo。但是这次在项目中应用Retrofit2.0的时候,还是发现一些新问题。趁着项目封包上线,特来全面梳理总结,从最简单的请求网络一步步的丰富功能打造一套完善可行的网络请求框架,以及自己在项目应用中遇到的问题和解决办法:
先附上官方介绍:RetrofitGithub源码地址

如何使用Retrofit进行网络请求

1.添加Retrofit及相关库的依赖

compile 'com.squareup.retrofit2:retrofit:2.2.0'

这里我们看见只添加Retrofit的依赖,但是大家不要误会,Retrofit只是RESTful 的 HTTP 网络请求框架的封装。可以在Retrofit 2.0源码的maven配置里发现添加了对OkHttp的依赖。网络请求本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装。

请求流程原理
    compile 'com.squareup.retrofit2:retrofit:2.2.0'
    compile 'com.squareup.okhttp3:okhttp:3.3.1'

2.创建用于描述网络请求的接口

描述网络请求的接口

这里注解还有很多,这里暂不一一解释,后文会给出。

3.创建Retrofit实例 & 4.创建网络请求接口实例并配置网络请求参数

创建Retrofit实例

5.发送网络请求

通过上面的工作,我们的Retrofit就已经做好了网络请求的准备了。下面正式发起请求,来验证流程是否正确。

注意申请网络权限
<uses-permission android:name="android.permission.INTERNET"/>

Retrofit发起异步网络请求和响应 Retrofit发起同步网络请求

关于Retrofit那些注解

Retrofit的注解我们上面已经接触过了@GET、@Query,现在我来更全面的了解这些注解。其实Retrofit提供三类一共22个注解,帮助我们在使用过程中配置网络。

1.HTTP请求方法类注解

Http请求方法注解

表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:method、path、hasBody。下面我们试着用@HTTP替代我们上面接口里面的定义:

用HTTP注解替代GET注解

2.标记类注解

标记类注解 接口定义 请求发起

3.网络请求参数类注解

网络请求参数类注解
        @GET("user")
        Call<User> getUser(@Header("Authorization") String authorization)

        // @Headers
        @Headers("Authorization: authorization")
        @GET("user")
        Call<User> getUser()

        // 以上的效果是一致的。
        // 区别在于使用场景和使用方式
        // 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
        // 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法
        FormBody.Builder builder = new FormBody.Builder();
        builder.add("key","value");
public interface GetRequest_Interface {
        /**
         *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
         * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);

        /**
         * Map的key作为表单的键
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);

}

        // 具体使用
        // @Field
        Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);

        // @FieldMap
        // 实现的效果与上面相同,但要传入Map
        Map<String, Object> map = new HashMap<>();
        map.put("username", "Carson");
        map.put("age", 24);
        Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);
public interface GetRequest_Interface {

          /**
         * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
         * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
         */
        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);

        /**
         * PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
         * 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
         * 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
         */
        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);

        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
}

        // 具体使用
        MediaType textType = MediaType.parse("text/plain");
        RequestBody name = RequestBody.create(textType, "Carson");
        RequestBody age = RequestBody.create(textType, "24");
        RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");

        // @Part
        MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
        Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
        ResponseBodyPrinter.printResponseBody(call3);

        // @PartMap
        // 实现和上面同样的效果
        Map<String, RequestBody> fileUpload2Args = new HashMap<>();
        fileUpload2Args.put("name", name);
        fileUpload2Args.put("age", age);
        //这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
        //fileUpload2Args.put("file", file);
        Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
        ResponseBodyPrinter.printResponseBody(call4);
}
        @GET("bookcontent/")
        Call<ResponseBody> getBookInfo(@Query("bookid") String bookId);
        @GET("users/{user}/repos")
        Call<ResponseBody>  getBlog(@Path("user") String user );
        // 访问的API是:https://api.github.com/users/{user}/repos
        // 在发起请求时, {user} 会被替换为方法的第一个参数 user(被@Path注解作用)
        @GET
        Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
       // 当有URL注解时,@GET传入的URL就可以省略
       // 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供

一般在Android开发中,设计网络请求框架的时候我们都要考虑三个功能:网络请求、请求结果数据解析、异步响应(因为考虑到要在主线程刷新UI)。在上文中我们关于Retrofit网络请求模块的使用已经有了完整的认识,下面我们继续介绍数据解析、和异步响应的知识点。

Retrofit数据解析器(Converter)

在前面的例子中我们请求中都是直接返回的Call<ResponseBody> ,这里并没有做响应结果的解析,我们还需要自己去响应体里提取和解析数据。其实,这些需求Retrofit的设计者们都已经给我们考虑到了,Retrofit支持包括常见的Json在内的,多种数据解析方式。以Json为例具体如下使用:

1.使用时需要在Gradle添加依赖

数据解析器 Gradle依赖
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2
// 在build.gradle中添加依赖
compile 'com.squareup.retrofit2:converter-gson:2.2.0'

2.创建Retrofit实例时,添加ConverterFactory

添加GsonConverterFactory

3.创建bean类并设置接口返回类型

更改接口返回值类型

Retrofit网络请求适配器(CallAdapter)

Retrofit支持多种网络请求适配器方式:guava、Java8和Rxjava ,使用时如使用的是 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加,以Rxjava为例具体如下使用:

1.使用时需要在Gradle添加依赖

网络请求适配器 Gradle依赖
guava com.squareup.retrofit2:adapter-guava:2.0.2
Java8 com.squareup.retrofit2:adapter-java8:2.0.2
rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

这里有一个很坑的地方是, com.squareup.retrofit2:adapter-rxjava:2.0.2,用的是RxJava1.X,现在所以很多Rx2的新特性都没有,所以在这里为了支持Rx2.0,并不建议使用square的适配器。我用的是jakewharton大神出的一款适配器,应用如下:

    // 引入请求适配器
    compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0-RC3'
    // 引入RxAndroid适应Android开发需求
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

2.创建Retrofit实例时,添加CallAdapterFactory

添加CallAdapterFactory

3.修改接口返回类型

修改接口返回类型

调用接口请求数据:

调用接口请求

至此关于在项目中引入Retrofit+OkHttp+RxJava的流程已经简单的使用就已经介绍完毕,但是自己在项目的使用过程中还是遇见了一些问题,下面会做出总结。

在项目使用过程中遇见的问题

1.OkHttp在4.4及以下不支持TLS协议的解决方法

这个bug最新是测试发现安装包在某个机型上无法请求网络,然后找来测试机调试。发现在网络请求的时候,会报异常,具体信息如下:

javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x79f145b0: Failure in SSL library, usually a protocol error
error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version

解决方案参考:# OkHttp在4.4及以下不支持TLS协议的解决方法

2.混淆问题

这个问题就有点坑了,测试用release包发现所有页面都请求不到数据。当时听描述就大概锁定了是混淆问题。但是自己多次对照官方文档的混淆配置,发现自己的配置跟官方配置一样。然后调试的时候看了一下异常信息,大概是说解析的时候的问题。然后我去掉了Retrofit的Gson数据解析器进行测试,发现请求是能正常发送和响应的。锁定了问题之后问题之后开始找解决办法,由于converter-gson的资料很少。我想着其原理应该是Gson类似,于是这是找Gson有关的混淆问题,最后解决办法如下:

converter-gson混淆
# Retrofit
-dontnote retrofit2.Platform
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
-dontwarn retrofit2.Platform$Java8
-keepattributes Signature
-keepattributes Exceptions

# okhttp
-dontwarn okio.**

# converter-gson
-keep class cn.tianya.light.reader.model.bean.**{*;} # 自定义数据模型的bean目录

参考:

https://blog.csdn.net/m0_37698652/article/details/77658915

上一篇下一篇

猜你喜欢

热点阅读