綜述
retrofit是由square公司開發的。square在github上釋出了很多優秀的Android開源專案。例如:otto(事件匯流排),leakcanary(排查記憶體洩露),android-times-square(日曆控制元件),dagger(依賴注入),picasso(非同步載入圖片),okhttp(網路請求),retrofit(網路請求)等等。更多square上的開源專案我們可以去square的GitHub進行檢視。這次就來介紹一下retrofit的一些基本用法。retrofit是REST安卓客戶端請求庫。使用retrofit可以進行GET,POST,PUT,DELETE等請求方式。下面就來看一下retrofit的基本用法。
Retrofit使用方法
由於retrofit2.0與先前版本的差別還是比較大,對於不同版本之間的差異在這裡就不在進行詳細區別。下面的例子也是針對於retrofit2.0進行介紹的。retrofit2.0它依賴於OkHttp,而且這部分也不再支援替換。在這裡我們也不需要顯示的匯入okHttp,在retrofit中已經匯入okhttp3。
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
在下面的例子當中採用與GitHub一些相關api進行演示。在這裡首先需要新增訪問網路的許可權。
<uses-permission android:name="android.permission.INTERNET"/>
簡單示例
新增Gradle依賴項
在這裡我們最好檢視一下retrofit的官網新增最新依賴。
compile 'com.squareup.retrofit2:retrofit:2.0.1'
建立API介面
在retrofit中通過一個Java介面作為http請求的api介面。
public interface GitHubApi {
@GET("repos/{owner}/{repo}/contributors")
Call<ResponseBody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo);
}
建立retrofit例項
在這裡baseUrl是在建立retrofit實力的時候定義的,我們也可以在API介面中定義完整的url。在這裡建議在建立baseUrl中以”/”結尾,在API中不以”/”開頭和結尾。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
呼叫API介面
在呼叫API介面請求後,獲得一個json字串,通過Gson進行解析,獲得login以及contributions。
GitHubApi repo = retrofit.create(GitHubApi.class);
Call<ResponseBody> call = repo.contributorsBySimpleGetCall(mUserName, mRepo);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Gson gson = new Gson();
ArrayList<Contributor> contributorsList = gson.fromJson(response.body().string(), new TypeToken<List<Contributor>>(){}.getType());
for (Contributor contributor : contributorsList){
Log.d("login",contributor.getLogin());
Log.d("contributions",contributor.getContributions()+"");
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
效果展示
這樣就完成了一個http請求,上面請求的完整地址為:https://api.github.com/repos/square/retrofit/contributors
然後我們看一下執行結果:
取消請求
我們可以終止一個請求。終止操作是對底層的httpclient執行cancel操作。即使是正在執行的請求,也能夠立即終止。
call.cancel();
轉換器
在上面的例子中通過獲取ResponseBody後,我們自己使用Gson來解析接收到的Json格式資料。在Retrofit中當建立一個Retrofit例項的時候可以為其新增一個Json轉換器,這樣就會自動將Json格式的響應體轉換為所需要的Java物件。那麼先來看一下如何根據已有的Json格式資料如何生成Java物件。當然我們可以根據已知的資料手動建立Java物件,也可以通過工具更具Json格式為我們自動生成Java物件。
自動生成Java物件
在這裡介紹兩種根據Json資料自動生成Java物件的工具。
jsonschema2pojo
可以通過訪問jsonschema2pojo網站。先來看一下它的使用方法。
上面配置中所選註解若是使用的Gson解析,可以選擇Gson,當然沒有也是可以的。對於@Generated註解若是需要保留的話新增如下依賴,也可以直接刪除@Generated註解,沒有任何影響。
compile 'org.glassfish:javax.annotation:10.0-b28'
GsonFormat是AndroidStudio中的一個外掛,在AndroidStudio的外掛選項中直接搜尋安裝這個外掛即可。在這裡看一下是如何使用這個外掛的。
新增轉換器
在這裡我們需要為retrofit新增gson轉換器的依賴。新增過converter-gson後不用再新增gson庫。在converter-gson中已經包含gson。
compile 'com.squareup.retrofit2:converter-gson:2.0.1'
在這裡先建立一個Java類Contributor,用來儲存接收到的資料。
public class Contributor {
private String login;
private Integer contributions;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public Integer getContributions() {
return contributions;
}
public void setContributions(Integer contributions) {
this.contributions = contributions;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
這時候修改我們的API介面。
@GET("repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributorsByAddConverterGetCall(@Path("owner") String owner, @Path("repo") String repo);
建立retrofit例項,我們通過addConverterFactory指定一個factory來對響應反序列化,在這裡converters被新增的順序將是它們被Retrofit嘗試的順序。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
呼叫上面所修改的API介面。
GitHubApi repo = retrofit.create(GitHubApi.class);
Call<List<Contributor>> call = repo.contributorsByAddConverterGetCall(mUserName, mRepo);
call.enqueue(new Callback<List<Contributor>>() {
@Override
public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
List<Contributor> contributorList = response.body();
for (Contributor contributor : contributorList){
Log.d("login", contributor.getLogin());
Log.d("contributions", contributor.getContributions() + "");
}
}
@Override
public void onFailure(Call<List<Contributor>> call, Throwable t) {
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
最後在來看一下執行結果。
retrofit不僅僅只支援gson,還支援其他許多json解析庫。以下版本號需要與retrofit版本號保持一致,並且以retrofit官網給出的版本號為準。
- Gson:
compile 'com.squareup.retrofit2:converter-gson:2.0.1'
- Jackson:
compile 'com.squareup.retrofit2:converter-jackson:2.0.1'
- Moshi:
compile 'com.squareup.retrofit2:converter-moshi:2.0.1'
- Protobuf:
compile 'com.squareup.retrofit2:converter-protobuf:2.0.1'
- Wire:
compile 'com.squareup.retrofit2:converter-wire:2.0.1'
- Simple XML:
compile 'com.squareup.retrofit2:converter-simplexml:2.0.1'
- Scalars (primitives, boxed, and String):
compile 'com.squareup.retrofit2:converter-scalars:2.0.1'
增加日誌資訊
在retrofit2.0中是沒有日誌功能的。但是retrofit2.0中依賴OkHttp,所以也就能夠通過OkHttp中的interceptor來實現實際的底層的請求和響應日誌。在這裡我們需要修改上一個retrofit例項,為其自定自定義的OkHttpClient。程式碼如下:
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build();
Retrofit retrofit = new Retrofit.Builder().addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(okHttpClient)
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
還需要新增如下依賴。
compile 'com.squareup.okhttp3:logging-interceptor:3.1.2'
其他程式碼沒有任何變化,我們來看一下執行結果。
新增請求頭
我們可以通過@Headers來新增請求頭。
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: RetrofitBean-Sample-App",
"name:ljd"
})
@GET("repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributorsAndAddHeader(@Path("owner") String owner,@Path("repo") String repo);
執行結果。
同步請求
在這裡我們可以直接通過call.execute()執行一個同步請求,由於不允許在主執行緒中進行網路請求操作,所以我們需要再子執行緒中進行執行。
new Thread(new Runnable() {
@Override
public void run() {
try {
Response<List<Contributor>> response = call.execute();
List<Contributor> contributorsList = response.body();
for (Contributor contributor : contributorsList){
Log.d("login",contributor.getLogin());
Log.d("contributions",contributor.getContributions()+"");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
在這裡看一下執行結果。
clone
在這裡無論是同步操作還是非同步操作每一個call物件例項只能被執行一次。多次執行丟擲如下異常。
在這裡如果我們的request和respone都是一一對應的。我們通過Clone方法建立一個一模一樣的例項,並且它的開銷也是很小的。
Call<List<Contributor>> cloneCall = call.clone();
cloneCall.execute();
get請求
在前面的一些例子當中我們都是採用get請求。當然我們也可以為URL指定查詢引數。使用@Query即可。
@GET("search/repositories")
Call<RetrofitBean> queryRetrofitByGetCall(@Query("q")String owner,
@Query("since")String time,
@Query("page")int page,
@Query("per_page")int per_Page);
當我們的引數過多的時候我們可以通過@QueryMap註解和map物件引數來指定每個表單項的Key,value的值。
@GET("search/repositories")
Call<RetrofitBean> queryRetrofitByGetCallMap(@QueryMap Map<String,String> map);
下面的call物件例項為上面api中所返回call物件。更具所返回的json資料所建立的實體類在這裡就不在貼出程式碼,下載原始碼詳細檢視。
call.enqueue(new Callback<RetrofitBean>() {
@Override
public void onResponse(Call<RetrofitBean> call, Response<RetrofitBean> response) {
RetrofitBean retrofit = response.body();
List<Item> list = retrofit.getItems();
if (list == null)
return;
Log.d(TAG, "total:" + retrofit.getTotalCount());
Log.d(TAG, "incompleteResults:" + retrofit.getIncompleteResults());
Log.d(TAG, "----------------------");
for (Item item : list) {
Log.d(TAG, "name:" + item.getName());
Log.d(TAG, "full_name:" + item.getFull_name());
Log.d(TAG, "description:" + item.getDescription());
Owner owner = item.getOwner();
Log.d(TAG, "login:" + owner.getLogin());
Log.d(TAG, "type:" + owner.getType());
}
}
@Override
public void onFailure(Call<RetrofitBean> call, Throwable t) {
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
上面請求中的完整連線為: https://api.github.com/search/repositories?q=retrofit&since=2016-03-29&page=1&per_page=3,執行結果如下。
在Retrofit 2.0新增了一個新的註解:@Url,它允許我們直接傳入一個請求的URL。這樣以來我們可以將上一個請求的獲得的url直接傳入進來。方便了我們的操作。
@GET
Call<List<Contributor>> repoContributorsPaginate(@Url String url);
我們可以使用@FormUrlEncoded註解來傳送表單資料。使用 @Field註解和引數來指定每個表單項的Key,value為引數的值。
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
當我們有很多個表單引數時可以通過@FieldMap註解和Map物件引數來指定每個表單項的Key,value的值。
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@FieldMap Map<String,String> fieldMap);
Multipart
我們還可以通過@Multipart註解來傳送Multipart資料。通過@Part註解來定義需要傳送的檔案。
@Multipart
@PUT("/user/photo")
User updateUser(@Part("photo") TypedFile photo, @Part("description") TypedString description);
Retrofit與RxJava結合
Retrofit能夠與RxJava進行完美結合。下面就來看一下Retrofit與RxJava是如何結合在一起的。對於RxJava在這就不在進行詳細介紹,對於RXJava的使用可以參考附錄裡面給出連結。
首先我們需要新增如下依賴。
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.1'
compile 'io.reactivex:rxandroid:1.1.0'
建立retrofit物件例項時,通過addCallAdapterFactory來新增對RxJava的支援。
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.github.com/")
.build();
使用Observable建立一個API介面。
@GET("repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> contributorsByRxJava(@Path("owner") String owner,@Path("repo") String repo);
下面來呼叫這個API介面。
private CompositeSubscription mSubscriptions = new CompositeSubscription();
mSubscriptions.add(
mGitHubService.contributorsByRxJava(mUserName, mRepo)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Contributor>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(List<Contributor> contributors) {
for (Contributor c : contributors) {
Log.d("TAG", "login:" + c.getLogin() + " contributions:" + c.getContributions());
}
}
}));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
下面來看一下執行結果。
如果我們想要檢視所有contributor的資訊,首先我們需要向gitHub請求獲取到所有contributor,然後再通過獲得contributor進行依次向github請求獲取contributor的資訊,在這時候我們使用RxJava也就非常方便了。下面看一下如何操作的。
首先再新增一個API介面。
@GET("repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> contributorsByRxJava(@Path("owner") String owner,
@Path("repo") String repo);
下面在看一下是如何進行根據獲得的contributor來檢視contributor的資訊。
mSubscriptions.add(mGitHubService.contributorsByRxJava(mUserName, mRepo)
.flatMap(new Func1<List<Contributor>, Observable<Contributor>>() {
@Override
public Observable<Contributor> call(List<Contributor> contributors) {
return Observable.from(contributors);
}
})
.flatMap(new Func1<Contributor, Observable<Pair<User, Contributor>>>() {
@Override
public Observable<Pair<User, Contributor>> call(Contributor contributor) {
Observable<User> userObservable = mGitHubService.userByRxJava(contributor.getLogin())
.filter(new Func1<User, Boolean>() {
@Override
public Boolean call(User user) {
return !isEmpty(user.getName()) && !isEmpty(user.getEmail());
}
});
return Observable.zip(userObservable,
Observable.just(contributor),
new Func2<User, Contributor, Pair<User, Contributor>>() {
@Override
public Pair<User, Contributor> call(User user, Contributor contributor) {
return new Pair<>(user, contributor);
}
});
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Pair<User, Contributor>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Pair<User, Contributor> pair) {
User user = pair.first;
Contributor contributor = pair.second;
Log.d(TAG, "name:" + user.getName());
Log.d(TAG, "contributions:" + contributor.getContributions());
Log.d(TAG, "email:" + user.getEmail());
}
}));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
最後在來看下執行結果。
Retrofit設定快取
對Retrofit設定快取,由於Retrofit是對OkHttp的封裝,所以我們可以直接通過OkHttpClient著手。也就是為OkHttp設定快取。設定快取程式碼如下所示。
private OkHttpClient getCacheOkHttpClient(Context context){
final File baseDir = context.getCacheDir();
final File cacheDir = new File(baseDir, "HttpResponseCache");
Timber.e(cacheDir.getAbsolutePath());
Cache cache = new Cache(cacheDir, 10 * 1024 * 1024);
Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = chain -> {
Request request = chain.request();
if(!NetWorkUtils.isNetWorkAvailable(context)){
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response originalResponse = chain.proceed(request);
if (NetWorkUtils.isNetWorkAvailable(context)) {
int maxAge = 60;
return originalResponse.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 24 * 4 * 7;
return originalResponse.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
};
return new OkHttpClient.Builder()
.addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
.addInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
.cache(cache)
.build();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
總結
在retrofit的使用中,對於檔案你上傳與下載,並沒有為我們提供進度更新的介面,在這裡就需要我們自己處理了。在下面的例子中給出一個檔案下載的例子,並且對下載進度更新通過logcat列印出來。可以下載進行檢視。到這裡retrofit的基本用法也就介紹完了,對於retrofit更多的好處在使用中我們可以慢慢體會。
附錄