一、概述
之前寫了個okhttputils的工具類,然後有很多同學詢問這個工具類和retrofit
什麼區別,於是上了下官網,發現其底層對網絡的訪問默認也是基於okhttp
,不過retrofit
非常適合於restful url
格式的請求,更多使用註解的方式提供功能。
既然這樣,我們本篇博文首先研究其所提供的常用的用法:
-
一般的get請求(如何通過註解攜帶參數,拼接url)
-
一般的post請求(包含各種註解的使用)
-
上傳文件
-
下載文件等
此外,由於其內部提供了ConverterFactory
用於對返回的requestBody進行轉化,所以本文也包含:
-
如何自定義
Converter.Factory
最後呢,因爲其源碼並不複雜,本文將對源碼進行整體的介紹,即
-
retrofit 源碼分析
ok,說這麼多,既然需要restful url
,我只能撿起我那個半桶水的spring mvc 搭建一個服務端的小例子~~
最後本文使用版本:
compile 'com.squareup.retrofit2:retrofit:2.0.2'
二、retrofit 用法示例
(1)一般的get請求
retrofit
在使用的過程中,需要定義一個接口對象,我們首先演示一個最簡單的get請求,接口如下所示:
public interface IUserBiz
{
@GET("users")
Call<List<User>> getUsers();
}
可以看到有一個getUsers()方法,通過@GET
註解標識爲get請求,@GET
中所填寫的value和baseUrl
組成完整的路徑,baseUrl
在構造retrofit對象時給出。
下面看如何通過retrofit
完成上述的請求:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.31.242:8080/springmvc_users/user/")
.addConverterFactory(GsonConverterFactory.create())
.build();
IUserBiz userBiz = retrofit.create(IUserBiz.class);
Call<List<User>> call = userBiz.getUsers();
call.enqueue(new Callback<List<User>>()
{
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response)
{
Log.e(TAG, "normalGet:" + response.body() + "");
}
@Override
public void onFailure(Call<List<User>> call, Throwable t)
{
}
});
依然是構造者模式,指定了baseUrl
和Converter.Factory
,該對象通過名稱可以看出是用於對象轉化的,本例因爲服務器返回的是json格式的數組,所以這裏設置了GsonConverterFactory
完成對象的轉化。
ok,這裏可以看到很神奇,我們通過Retrofit.create
就可以拿到我們定義的IUserBiz
的實例,調用其方法即可拿到一個Call
對象,通過call.enqueue
即可完成異步的請求。
具體retrofit怎麼得到我們接口的實例的,以及對象的返回結果是如何轉化的,我們後面具體分析。
這裏需要指出的是:
-
接口中的方法必須有返回值,且比如是
Call<T>
類型 -
.addConverterFactory(GsonConverterFactory.create())
這裏如果使用gson,需要額外導入:compile 'com.squareup.retrofit2:converter-gson:2.0.2'
當然除了gson以外,還提供了以下的選擇:
Gson: com.squareup.retrofit2:converter-gson Jackson: com.squareup.retrofit2:converter-jackson Moshi: com.squareup.retrofit2:converter-moshi Protobuf: com.squareup.retrofit2:converter-protobuf Wire: com.squareup.retrofit2:converter-wire Simple XML: com.squareup.retrofit2:converter-simplexml Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
當然也支持自定義,你可以選擇自己寫轉化器完成數據的轉化,這個後面將具體介紹。
-
既然
call.enqueue
是異步的訪問數據,那麼同步的訪問方式爲call.execute
,這一點非常類似okhttp的API,實際上默認情況下內部也是通過okhttp3.Call
實現。
那麼,通過這麼一個簡單的例子,應該對retrofit
已經有了一個直觀的認識,下面看更多其支持的特性。
(2)動態的url訪問@PATH
文章開頭提過,retrofit
非常適用於restful url
的格式,那麼例如下面這樣的url:
//用於訪問zhy的信息
http://192.168.1.102:8080/springmvc_users/user/zhy
//用於訪問lmj的信息
http://192.168.1.102:8080/springmvc_users/user/lmj
即通過不同的username訪問不同用戶的信息,返回數據爲json字符串。
那麼可以通過retrofit提供的@PATH
註解非常方便的完成上述需求。
我們再定義一個方法:
public interface IUserBiz
{
@GET("{username}")
Call<User> getUser(@Path("username") String username);
}
可以看到我們定義了一個getUser方法,方法接收一個username參數,並且我們的@GET
註解中使用{username}
聲明瞭訪問路徑,這裏你可以把{username}
當做佔位符,而實際運行中會通過@PATH("username")
所標註的參數進行替換。
那麼訪問的代碼很類似:
//省略了retrofit的構建代碼
Call<User> call = userBiz.getUser("zhy");
//Call<User> call = userBiz.getUser("lmj");
call.enqueue(new Callback<User>()
{
@Override
public void onResponse(Call<User> call, Response<User> response)
{
Log.e(TAG, "getUsePath:" + response.body());
}
@Override
public void onFailure(Call<User> call, Throwable t)
{
}
});
(3)查詢參數的設置@Query
看下面的url
http://baseurl/users?sortby=username
http://baseurl/users?sortby=id
即一般的傳參,我們可以通過@Query
註解方便的完成,我們再次在接口中添加一個方法:
public interface IUserBiz
{
@GET("users")
Call<List<User>> getUsersBySort(@Query("sortby") String sort);
}
訪問的代碼,其實沒什麼寫的:
//省略retrofit的構建代碼
Call<List<User>> call = userBiz.getUsersBySort("username");
//Call<List<User>> call = userBiz.getUsersBySort("id");
//省略call執行相關代碼
ok,這樣我們就完成了參數的指定,當然相同的方式也適用於POST,只需要把註解修改爲@POST
即可。
對了,我能剛纔學了@PATH
,那麼會不會有這樣嘗試的衝動,對於剛纔的需求,我們這麼寫:
@GET("users?sortby={sortby}")
Call<List<User>> getUsersBySort(@Path("sortby") String sort);
乍一看別說好像有點感覺,哈,實際上運行是不支持的~估計是@ Path
的定位就是用於url的路徑而不是參數,對於參數還是選擇通過@Query
來設置。
(4)POST請求體的方式向服務器傳入json字符串@Body
大家都清楚,我們app很多時候跟服務器通信,會選擇直接使用POST方式將json字符串作爲請求體發送到服務器,那麼我們看看這個需求使用retrofit
該如何實現。
再次添加一個方法:
public interface IUserBiz
{
@POST("add")
Call<List<User>> addUser(@Body User user);
}
提交的代碼其實基本都是一致的:
//省略retrofit的構建代碼
Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "[email protected]"));
//省略call執行相關代碼
ok,可以看到其實就是使用@Body
這個註解標識我們的參數對象即可,那麼這裏需要考慮一個問題,retrofit是如何將user對象轉化爲字符串呢?下文將詳細解釋~
下面對應okhttp,還有兩種requestBody,一個是FormBody
,一個是MultipartBody
,前者以表單的方式傳遞簡單的鍵值對,後者以POST表單的方式上傳文件可以攜帶參數,retrofit
也二者也有對應的註解,下面繼續~
(5)表單的方式傳遞鍵值對@FormUrlEncoded
這裏我們模擬一個登錄的方法,添加一個方法:
public interface IUserBiz
{
@POST("login")
@FormUrlEncoded
Call<User> login(@Field("username") String username, @Field("password") String password);
}
訪問的代碼:
//省略retrofit的構建代碼
Call<User> call = userBiz.login("zhy", "123");
//省略call執行相關代碼
ok,看起來也很簡單,通過@POST
指明url,添加FormUrlEncoded
,然後通過@Field
添加參數即可。
(6)單文件上傳@Multipart
下面看一下單文件上傳,依然是再次添加個方法:
public interface IUserBiz
{
@Multipart
@POST("register")
Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password);
}
這裏@MultiPart
的意思就是允許多個@Part
了,我們這裏使用了3個@Part
,第一個我們準備上傳個文件,使用了MultipartBody.Part
類型,其餘兩個均爲簡單的鍵值對。
使用:
File file = new File(Environment.getExternalStorageDirectory(), "icon.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "icon.png", photoRequestBody);
Call<User> call = userBiz.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));
ok,這裏感覺略爲麻煩~~不過還是蠻好理解~~多個@Part
,每個Part指定一個RequestBody。
這裏插個實驗過程,其實我最初對於文件,也是嘗試的@Part RequestBody
,因爲@Part("key")
,然後傳入一個代表文件的RequestBody
,我覺得更加容易理解,後來發現試驗無法成功,而且查了下issue,給出了一個很奇怪的解決方案,這裏可以參考:https://github.com/square/retrofit/issues/1063。
給出了一個類似如下的方案:
public interface ApiInterface {
@Multipart
@POST ("/api/Accounts/editaccount")
Call<User> editUser (@Header("Authorization") String authorization, @Part("file\"; filename=\"pp.png") RequestBody file , @Part("FirstName") RequestBody fname, @Part("Id") RequestBody id);
}
可以看到對於文件的那個@Part
value竟然寫了這麼多奇怪的東西,而且filename竟然硬編碼了~~這個不好吧,我上傳的文件名竟然不能動態指定。
爲了文件名不會被寫死,所以給出了最上面的上傳單文件的方法,ps:上面這個方案經測試也是可以上傳成功的。
最後看下多文件上傳~
(7)多文件上傳@PartMap
再添加一個方法~~~
public interface IUserBiz
{
@Multipart
@POST("register")
Call<User> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);
}
這裏使用了一個新的註解@PartMap
,這個註解用於標識一個Map,Map的key爲String類型,代表上傳的鍵值對的key(與服務器接受的key對應),value即爲RequestBody,有點類似@Part
的封裝版本。
執行的代碼:
File file = new File(Environment.getExternalStorageDirectory(), "messenger_01.png");
RequestBody photo = RequestBody.create(MediaType.parse("image/png", file);
Map<String,RequestBody> photos = new HashMap<>();
photos.put("photos\"; filename=\"icon.png", photo);
photos.put("username", RequestBody.create(null, "abc"));
Call<User> call = userBiz.registerUser(photos, RequestBody.create(null, "123"));
可以看到,可以在Map中put進一個或多個文件,鍵值對等,當然你也可以分開,單獨的鍵值對也可以使用@Part
,這裏又看到設置文件的時候,相對應的key很奇怪,例如上例"photos\"; filename=\"icon.png"
,前面的photos就是與服務器對應的key,後面filename是服務器得到的文件名,ok,參數雖然奇怪,但是也可以動態的設置文件名,不太影響使用~~
(8)下載文件
這個其實我覺得直接使用okhttp就好了,使用retrofit去做這個事情真的有點瞎用的感覺~~
增加一個方法:
@GET("download")
Call<ResponseBody> downloadTest();
調用:
Call<ResponseBody> call = userBiz.downloadTest();
call.enqueue(new Callback<ResponseBody>()
{
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response)
{
InputStream is = response.body().byteStream();
//save file
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t)
{
}
});
可以看到可以返回ResponseBody
,那麼很多事都能幹了~~
but,也看出這種方式下載感覺非常雞肋,並且onReponse回調雖然在UI線程,但是你還是要處理io操作,也就是說你在這裏還要另外開線程操作,或者你可以考慮同步的方式下載。
最後還是建議使用okhttp去下載,例如使用okhttputils.
有人可能會問,使用okhttp,和使用retrofit會不會造成新建多個OkHttpClient
對象呢,其實是可設置的,參考下文。
ok,上面就是一些常用的方法,當然還涉及到一些沒有介紹的註解,但是通過上面這麼多方法的介紹,再多一二個註解的使用方式,相信大家能夠解決。
三、配置OkHttpClient
這個需要簡單提一下,很多時候,比如你使用retrofit需要統一的log管理,給每個請求添加統一的header等,這些都應該通過okhttpclient去操作,比如addInterceptor
例如:
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log,統一的header等
{
@Override
public okhttp3.Response intercept(Chain chain) throws IOException
{
return null;
}
}).build();
或許你需要更多的配置,你可以單獨寫一個OkhttpClient的單例生成類,在這個裏面完成你所需的所有的配置,然後將OkhttpClient
實例通過方法公佈出來,設置給retrofit。
設置方式:
Retrofit retrofit = new Retrofit.Builder()
.callFactory(OkHttpUtils.getClient())
.build();
callFactory
方法接受一個okhttp3.Call.Factory
對象,OkHttpClient
即爲一個實現類。
明天繼續關注:Retrofit2 完全解析 探索與okhttp之間的關係(二)