在上一篇我們已經(jīng)學(xué)習(xí)了OkHttp的使用,接下來我們再來看看與OkHttp配套使用的Retrofit窍株。
定義:基于OkHttp的網(wǎng)絡(luò)請求封裝框架
什么意思呢距芬?其實(shí)Retrofit只是對我們傳遞的參數(shù)通過注解的方式來進(jìn)行封裝悬槽,實(shí)際的網(wǎng)絡(luò)請求工作依舊是由OkHttp來完成的。
優(yōu)點(diǎn):
(1)功能強(qiáng)大影斑,不僅支持同步與異步给赞,還支持多種數(shù)據(jù)格式解析。
(2)支持RxJava鸥昏,實(shí)現(xiàn)線程調(diào)度塞俱。
(3)簡潔易用,通過注解的方式配置網(wǎng)絡(luò)請求參數(shù)吏垮。
(4)擴(kuò)展性能好障涯,功能模塊高度解耦罐旗。
在Retrofit的使用過程中,有一個(gè)很重要的知識(shí)點(diǎn)就是關(guān)于注解的使用唯蝶,因?yàn)镽etrofit框架的本質(zhì)就是通過注解的方式對OkHttp的請求參數(shù)進(jìn)行封裝九秀,對返回的結(jié)果進(jìn)行封裝的一個(gè)框架,所以我們很有必要先來了解一下注解粘我。
注解:
首先來看一張圖
注解的類型分為三類:
第一類:網(wǎng)絡(luò)請求方法
(1)@GET鼓蜒、@POST、@PUT征字、@DELETE都弹、@HEAD。以上方法分別對應(yīng) HTTP中的網(wǎng)絡(luò)請求方式匙姜。以GET請求方法為例:
fun retrofitGet(view: View) {
// 1.創(chuàng)建Retrofit的實(shí)例
val retrofit = Retrofit.Builder()
.baseUrl("http://www.baidu.com/") // 設(shè)置請求的完整域名
.build()
// 2.創(chuàng)建請求接口的實(shí)例
val apiService = retrofit.create(ApiService::class.java)
// 3.封裝請求
val call = apiService.getRequest("more")
// 4.執(zhí)行請求操作
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把網(wǎng)絡(luò)請求的URL 分成了兩部分設(shè)置畅厢,第一個(gè)部分是設(shè)置的baseUrl,即http://www.baidu.com/,而另外一部分則是在此基礎(chǔ)上進(jìn)行拼接組成的氮昧,通過注解的方式進(jìn)行替換框杜,所以上面的例子中最終的URL為:http://www.baidu.com/more。
(2)@HTTP
· 作用:替換@GET袖肥、@POST咪辱、@PUT、@DELETE椎组、@HEAD注解的作用 及 更多功能拓展
· 具體使用:通過屬性method油狂、path、hasBody進(jìn)行設(shè)置
@HTTP(method = "GET", path = "{path}/", hasBody = false)
Call<ResponseBody> getHttpRequest(@Path("path") String path);
第二類:標(biāo)記類
(1)@FormUrlEncoded表示請求體是一個(gè)Form表單庐杨,以鍵值對的形式來傳遞參數(shù)选调。
// Post請求
@FormUrlEncoded
@POST("{user}/")
Observable<ResponseBody> postData(@Path("user") String user,@FieldMap Map<String, String> maps, @Query("meta[]") String... linked);
// 執(zhí)行Post請求(包含數(shù)組)
@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表示請求體是一個(gè)支持文件的Form表單夹供。
// 上傳單個(gè)文件
@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) {
// 生成單個(gè)文件
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("avatar", file.getName(), requestFile);
// 將所有的字段進(jìn)行轉(zhuǎn)換
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表示返回的數(shù)據(jù)以流的形式進(jìn)行返回灵份,比如下載大的文件等等都可以采用這種方式來實(shí)現(xiàn),舉一個(gè)例子:
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)")
//通過線程池獲取一個(gè)線程哮洽,指定callback在子線程中運(yùn)行填渠。
.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寫入到從磁盤中,詳見下面分析
//注意鸟辅,這個(gè)方法是運(yùn)行在子線程中的
writeResponseToDisk(path, response, downloadListener);
}
@Override
public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable throwable) {
downloadListener.onFail("網(wǎng)絡(luò)錯(cuò)誤~");
}
});
}
第三類:網(wǎng)絡(luò)請求參數(shù)
@Header & @Headers
?作用:添加請求頭 &添加不固定的請求頭
?具體使用如下:
// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()
// 以上的效果是一致的氛什。
// 區(qū)別在于使用場景和使用方式
// 1. 使用場景:@Header用于添加不固定的請求頭,@Headers用于添加固定的請求頭
// 2. 使用方式:@Header作用于方法的參數(shù)匪凉;@Headers作用于方法
當(dāng)然枪眉,我們還可以通過攔截器的方式去添加我們的頭文件,比如:
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方式 傳遞 自定義數(shù)據(jù)類型 給服務(wù)器
特別注意:如果提交的是一個(gè)Map再层,那么作用相當(dāng)于 @Field
不過Map要經(jīng)過 FormBody.Builder 類處理成為符合 Okhttp 格式的表單贸铜,如:
FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");
@Field & @FieldMap
作用:發(fā)送 Post請求 時(shí)提交請求的表單字段
具體使用:與 @FormUrlEncoded 注解配合使用
@FormUrlEncoded
@POST("{user}/")
Observable<ResponseBody> postData(@Path("user") String user,@FieldMap Map<String, String> maps, @Query("meta[]") String... linked);
@Part & @PartMap
作用:發(fā)送 Post請求 時(shí)提交請求的表單字段
與@Field的區(qū)別:功能相同堡纬,但攜帶的參數(shù)類型更加豐富,包括數(shù)據(jù)流蒿秦,所以適用于 有文件上傳 的場景
具體使用:與 @Multipart 注解配合使用
@Multipart
@POST("{user}/")
Observable<String> upload(@Path("user") String user,@PartMap Map<String, RequestBody> maps, @Part MultipartBody.Part file);
@Query和@QueryMap
作用:用于 @GET 方法的查詢參數(shù)(Query = Url 中 ‘?’ 后面的 key-value)
@GET("{user}/")
Observable<String> getData(@Path("user") String user,@QueryMap TreeMap<String, Object> map);
@Path
作用:URL地址的缺省值
@Url
作用:直接傳入一個(gè)請求的 URL變量 用于URL設(shè)置
具體使用:
@GET
Observable<String> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
// 當(dāng)有URL注解時(shí)烤镐,@GET傳入的URL就可以省略
最后總結(jié)一下:
創(chuàng)建Retrofit的實(shí)例:
val retrofit = Retrofit.Builder()
.baseUrl("http://www.baidu.com/") // 設(shè)置請求的完整域名
.addConverterFactory(GsonConverterFactory.create()) // 設(shè)置數(shù)據(jù)解析器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava平臺(tái)
.build()
a. 關(guān)于數(shù)據(jù)解析器(Converter)
Retrofit支持多種數(shù)據(jù)解析方式
使用時(shí)需要在Gradle添加依賴
Scalars com.squareup.retrofit2:converter-scalars:2.0.2
Gson com.squareup.retrofit2:converter-gson:2.0.2
通常情況下我們選擇的是這兩種,其中Scalars代表的是基礎(chǔ)數(shù)據(jù)類型的解析棍鳖,而Gson代表的是Gson格式的解析炮叶。
b. 關(guān)于網(wǎng)絡(luò)請求適配器(CallAdapter)
Retrofit支持多種網(wǎng)絡(luò)請求適配器方式:guava、Java8和rxjava渡处,使用時(shí)如使用的是 Android 默認(rèn)的 CallAdapter镜悉,則不需要添加網(wǎng)絡(luò)請求適配器的依賴,否則則需要按照需求進(jìn)行添加Retrofit 提供的 CallAdapter医瘫,一般情況下我們選擇RxJava的適配器积瞒。
完整的請求例子:
(1)添加依賴
(2)添加網(wǎng)絡(luò)權(quán)限
(3)創(chuàng)建Retrofit實(shí)例
(4)創(chuàng)建用于描述網(wǎng)絡(luò)請求的接口
(5)對發(fā)送請求進(jìn)行封裝
(6)發(fā)送請求
(7)對返回?cái)?shù)據(jù)進(jìn)行解析
第一步:添加依賴
// okHttp網(wǎng)絡(luò)請求框架
// 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網(wǎng)絡(luò)請求框架
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//retrofit添加Json解析返回?cái)?shù)據(jù)
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//retrofit添加RxJava支持
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
第二步:添加網(wǎng)絡(luò)權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>
剩余步驟:
fun retrofitGet(view: View) {
// 3.創(chuàng)建Retrofit的實(shí)例
val retrofit = Retrofit.Builder()
.baseUrl("http://www.baidu.com/") // 設(shè)置請求的完整域名
.addConverterFactory(GsonConverterFactory.create()) // 設(shè)置數(shù)據(jù)解析器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava平臺(tái)
.build()
// 4.創(chuàng)建請求接口的實(shí)例
val apiService = retrofit.create(ApiService::class.java)
// 5.封裝請求
val call = apiService.getHttpRequest("more")
// 6.執(zhí)行請求操作
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
// 7.解析返回?cái)?shù)據(jù)
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()}")
}
})
}
注意:在設(shè)置數(shù)據(jù)轉(zhuǎn)換器的時(shí)候,通常情況下不能設(shè)置多個(gè)登下,因?yàn)闊o法保證后臺(tái)返回的數(shù)據(jù)類型同時(shí)滿足所有的轉(zhuǎn)換茫孔,一般情況下我們會(huì)使用Gson作為數(shù)據(jù)轉(zhuǎn)換的類型或者使用Scalars作為數(shù)據(jù)轉(zhuǎn)換的類型,如果我們需要對返回?cái)?shù)據(jù)進(jìn)行特殊處理被芳,那么可以考慮自定義數(shù)據(jù)轉(zhuǎn)換器缰贝。