Retrofit+Okhttp+Rxjava项目应用

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

如何使用Retrofit进行网络请求

  • 添加Retrofit依赖
  • 创建 用于描述网络请求 的接口
  • 创建 Retrofit 实例
  • 创建 网络请求接口实例 并 配置网络请求参数
  • 发送网络请求

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

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

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

请求流程原理
  • App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作;

  • 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析。

  • 附:也可通过添加依赖来指定OkHttp版本:

    compile 'com.squareup.retrofit2:retrofit:2.2.0'
    compile 'com.squareup.okhttp3:okhttp:3.3.1'

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

描述网络请求的接口
  • 这里定义的是interface;
  • 这里@GET注解是定义网络请求方式;
  • 后面的括号里的参数是接口路径名,我们知道无法只通过接口路径定位到接口,先带着疑问往下看;
  • 这里@Query注解是服务接口的方法参数;
  • 返回值这里先返回完整的响应体,稍后会解释怎么数据解析。

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

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

创建Retrofit实例
  • 解惑:在创建Retrofit实例的时候设置了baseUrl,Retrofit把 网络请求的URL 分成了两部分设置。网络请求的完整 Url =在创建Retrofit实例时通过.baseUrl()设置 +网络请求接口的注解设置;
  • 创建网络请求接口的实例,把Retrofit实例跟描述网络请求的接口关联起来;
  • 添加方法调用网络请求。

5.发送网络请求

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

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

Retrofit发起异步网络请求和响应
Retrofit发起同步网络请求
  • 通过获取Retrofit实例发起请求传递参数;
  • 在enqueue()里面设置请求响应的回调处理,我们可以看见,在回调里面能直接更新UI,说明这里回调是在主线程里,但具体是怎么操作的,后文去阅读源码再做介绍。

关于Retrofit那些注解

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

1.HTTP请求方法类注解

Http请求方法注解

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

用HTTP注解替代GET注解

2.标记类注解

标记类注解
接口定义
请求发起
  • @FormUrlEncoded 表示发送form-encoded的数据,每个键值对需要用@Filed来注解键名,随后的对象需要提供值;
  • @Multipart 表示发送form-encoded的数据(适用于 有文件 上传的场景),每个键值对需要用@Part来注解键名,随后的对象需要提供值。

3.网络请求参数类注解

网络请求参数类注解
  • @Header 添加请求头
  • @Headers 添加不固定的请求头
        @GET("user")
        Call<User> getUser(@Header("Authorization") String authorization)

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

        // 以上的效果是一致的。
        // 区别在于使用场景和使用方式
        // 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
        // 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法
  • @Body 以 Post方式 传递 自定义数据类型 给服务器;
    特别注意:如果提交的是一个Map,那么作用相当于 @Field。
    Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:
        FormBody.Builder builder = new FormBody.Builder();
        builder.add("key","value");
  • @Field & @FieldMap 发送 Post请求 时提交请求的表单字段
    与 @FormUrlEncoded 注解配合使用
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);
  • @Part & @PartMap 发送 Post请求 时提交请求的表单字段
    与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于有文件上传的场景
    与 @Multipart 注解配合使用
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);
}
  • @Query和@QueryMap 用于 @GET 方法的查询参数(URL请求里?后面的键值对)
    配置时只需要在接口方法中增加一个参数即可
        @GET("bookcontent/")
        Call<ResponseBody> getBookInfo(@Query("bookid") String bookId);
  • @Path URL地址的缺省值
    可用于配置动态的URL地址
        @GET("users/{user}/repos")
        Call<ResponseBody>  getBlog(@Path("user") String user );
        // 访问的API是:https://api.github.com/users/{user}/repos
        // 在发起请求时, {user} 会被替换为方法的第一个参数 user(被@Path注解作用)
  • @Url 直接传入一个请求的 URL变量 用于URL设置
        @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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342