Android网络篇(二)—— Retrofit的基本使用

在上一篇我们已经学习了OkHttp的使用,接下来我们再来看看与OkHttp配套使用的Retrofit。

定义:基于OkHttp的网络请求封装框架

什么意思呢?其实Retrofit只是对我们传递的参数通过注解的方式来进行封装,实际的网络请求工作依旧是由OkHttp来完成的。

优点:

(1)功能强大,不仅支持同步与异步,还支持多种数据格式解析。
(2)支持RxJava,实现线程调度。
(3)简洁易用,通过注解的方式配置网络请求参数。
(4)扩展性能好,功能模块高度解耦。

在Retrofit的使用过程中,有一个很重要的知识点就是关于注解的使用,因为Retrofit框架的本质就是通过注解的方式对OkHttp的请求参数进行封装,对返回的结果进行封装的一个框架,所以我们很有必要先来了解一下注解。

注解:

首先来看一张图


1.png

注解的类型分为三类:

第一类:网络请求方法

2.png

(1)@GET、@POST、@PUT、@DELETE、@HEAD。以上方法分别对应 HTTP中的网络请求方式。以GET请求方法为例:

fun retrofitGet(view: View) {
    // 1.创建Retrofit的实例
    val retrofit = Retrofit.Builder()
        .baseUrl("http://www.baidu.com/") // 设置请求的完整域名
        .build()
    // 2.创建请求接口的实例
    val apiService = retrofit.create(ApiService::class.java)
    // 3.封装请求
    val call = apiService.getRequest("more")
    // 4.执行请求操作
    call.enqueue(object : Callback<ResponseBody> {
        override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
            Log.i("retrofit", response.body().toString())
            Toast.makeText(this@NetWorkRetrofitActivity,response.body().toString(),Toast.LENGTH_SHORT).show()
        }

        override fun onFailure(call: Call<ResponseBody>, e: Throwable) {
            Log.i("retrofit", "请求失败${e.printStackTrace()}")
        }
    })
}

public interface ApiService {

    @GET("{path}/")
    Call<ResponseBody> getRequest(@Path("path") String path);


}

Retrofit把网络请求的URL 分成了两部分设置,第一个部分是设置的baseUrl,即http://www.baidu.com/,而另外一部分则是在此基础上进行拼接组成的,通过注解的方式进行替换,所以上面的例子中最终的URL为:http://www.baidu.com/more

(2)@HTTP

· 作用:替换@GET、@POST、@PUT、@DELETE、@HEAD注解的作用 及 更多功能拓展

· 具体使用:通过属性method、path、hasBody进行设置

@HTTP(method = "GET", path = "{path}/", hasBody = false)
Call<ResponseBody> getHttpRequest(@Path("path") String path);

第二类:标记类

3.png

(1)@FormUrlEncoded表示请求体是一个Form表单,以键值对的形式来传递参数。

// Post请求
@FormUrlEncoded
@POST("{user}/")
Observable<ResponseBody> postData(@Path("user") String user,@FieldMap Map<String, String> maps, @Query("meta[]") String... linked);
// 执行Post请求(包含数组)
@Override
public void mHttpPost(Context context,String api, TreeMap map, String[] data, int type, HttpRequestCallback mCallBack) {
    map = HttpTool.getTreeCrc(map);
    Observable<String> observable = apiManager.postData(api,map, data);
    observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new BaseObserver(context, mCallBack, type));
}

(2)@Multipart表示请求体是一个支持文件的Form表单。

// 上传单个文件
@Multipart
@POST("{user}/")
Observable<String> upload(@Path("user") String user,@PartMap Map<String, RequestBody> maps, @Part MultipartBody.Part file);
@Override
public void mHttpFile(Context context,String api, File file, TreeMap map, int type, HttpRequestCallback mCallBack) {
    // 生成单个文件
    RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
    MultipartBody.Part body = MultipartBody.Part.createFormData("avatar", file.getName(), requestFile);

    // 将所有的字段进行转换
    map = HttpTool.getTreeCrc(map);
    Map<String, RequestBody> mapValue = new HashMap<>();
    for (Object key : map.keySet()) {
        mapValue.put(key.toString(), HttpTool.convertToRequestBody(map.get(key).toString()));
    }
    Observable<String> observable = apiManager.upload(api,mapValue, body);
    observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new BaseObserver(context, mCallBack, type));
}

(3)@Streaming表示返回的数据以流的形式进行返回,比如下载大的文件等等都可以采用这种方式来实现,举一个例子:

public interface DownloadService {

  @Streaming
  @GET
  Call<ResponseBody> download(@Url String url);

}

public static void download(String url, final String path, final DownloadListener downloadListener) {

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("[<u>http://www.xxx.com</u>](http://www.xxx.com)")
        //通过线程池获取一个线程,指定callback在子线程中运行。
        .callbackExecutor(Executors.newSingleThreadExecutor())
        .build();

    DownloadService service = retrofit.create(DownloadService.class);
    Call<ResponseBody> call = service.download(url);
    call.enqueue(new Callback<ResponseBody>() {

      @Override
      public void onResponse(@NonNull Call<ResponseBody> call, @NonNull final Response<ResponseBody> response) {

        //将Response写入到从磁盘中,详见下面分析
        //注意,这个方法是运行在子线程中的
        writeResponseToDisk(path, response, downloadListener);

      }

      @Override

      public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable throwable) {
        downloadListener.onFail("网络错误~");
      }

    });

}

第三类:网络请求参数

4.png

@Header & @Headers
作用:添加请求头 &添加不固定的请求头
具体使用如下:

// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

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

// 以上的效果是一致的。
// 区别在于使用场景和使用方式
// 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
// 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法

当然,我们还可以通过拦截器的方式去添加我们的头文件,比如:

public class HttpHeaderInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        int userId = IOFactoryUtil.getIOFactoryUtil().getDefaultHandler().getInt("user_id", 0);
        Log.i("zhoufan",userId+"");
        if (userId > 0) {
            Request request = original.newBuilder()
                    .header("userID", String.valueOf(userId))
                    .build();
            return chain.proceed(request);
        }
        return chain.proceed(original);
    }
}

@Body
作用:以 Post方式 传递 自定义数据类型 给服务器
特别注意:如果提交的是一个Map,那么作用相当于 @Field
不过Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:

FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");

@Field & @FieldMap
作用:发送 Post请求 时提交请求的表单字段
具体使用:与 @FormUrlEncoded 注解配合使用

@FormUrlEncoded
@POST("{user}/")
Observable<ResponseBody> postData(@Path("user") String user,@FieldMap Map<String, String> maps, @Query("meta[]") String... linked);

@Part & @PartMap
作用:发送 Post请求 时提交请求的表单字段
与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于 有文件上传 的场景
具体使用:与 @Multipart 注解配合使用

@Multipart
@POST("{user}/")
Observable<String> upload(@Path("user") String user,@PartMap Map<String, RequestBody> maps, @Part MultipartBody.Part file);

@Query和@QueryMap
作用:用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value)

@GET("{user}/")
Observable<String> getData(@Path("user") String user,@QueryMap TreeMap<String, Object> map);

@Path
作用:URL地址的缺省值
@Url
作用:直接传入一个请求的 URL变量 用于URL设置
具体使用:

@GET
Observable<String> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
// 当有URL注解时,@GET传入的URL就可以省略
最后总结一下:
5.png

创建Retrofit的实例:

val retrofit = Retrofit.Builder()
    .baseUrl("http://www.baidu.com/") // 设置请求的完整域名
    .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava平台
    .build()
a. 关于数据解析器(Converter)

Retrofit支持多种数据解析方式
使用时需要在Gradle添加依赖
Scalars com.squareup.retrofit2:converter-scalars:2.0.2
Gson com.squareup.retrofit2:converter-gson:2.0.2
通常情况下我们选择的是这两种,其中Scalars代表的是基础数据类型的解析,而Gson代表的是Gson格式的解析。

b. 关于网络请求适配器(CallAdapter)

Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava,使用时如使用的是 Android 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加Retrofit 提供的 CallAdapter,一般情况下我们选择RxJava的适配器。

完整的请求例子:
(1)添加依赖
(2)添加网络权限
(3)创建Retrofit实例
(4)创建用于描述网络请求的接口
(5)对发送请求进行封装
(6)发送请求
(7)对返回数据进行解析

第一步:添加依赖
// okHttp网络请求框架
// define a BOM and its version
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))

// define any required OkHttp artifacts without version
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")

//retrofit网络请求框架
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//retrofit添加Json解析返回数据
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//retrofit添加RxJava支持
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
第二步:添加网络权限
<uses-permission android:name="android.permission.INTERNET"/>
剩余步骤:
fun retrofitGet(view: View) {
    // 3.创建Retrofit的实例
    val retrofit = Retrofit.Builder()
        .baseUrl("http://www.baidu.com/") // 设置请求的完整域名
        .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava平台
        .build()
    // 4.创建请求接口的实例
    val apiService = retrofit.create(ApiService::class.java)
    // 5.封装请求
    val call = apiService.getHttpRequest("more")
    // 6.执行请求操作
    call.enqueue(object : Callback<ResponseBody> {
        override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
            // 7.解析返回数据
            Log.i("retrofit", response.body().toString())
            Toast.makeText(this@NetWorkRetrofitActivity,response.body().toString(),Toast.LENGTH_SHORT).show()
        }

        override fun onFailure(call: Call<ResponseBody>, e: Throwable) {
            Log.i("retrofit", "请求失败${e.printStackTrace()}")
        }
    })
}

注意:在设置数据转换器的时候,通常情况下不能设置多个,因为无法保证后台返回的数据类型同时满足所有的转换,一般情况下我们会使用Gson作为数据转换的类型或者使用Scalars作为数据转换的类型,如果我们需要对返回数据进行特殊处理,那么可以考虑自定义数据转换器。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容