Retrofit是一個當前很流行的網絡請求庫, 官網的介紹是: "Type-safe HTTP client for Android and Java". 本文介紹Retrofit的使用.
先介紹單獨使用Retrofit進行網絡請求, 後面主要介紹和RxJava結合的請求, 有實例代碼.html
首先在manifest中加上網絡權限:java
<uses-permission android:name="android.permission.INTERNET" />
而後在app/build.gradle
中加上依賴:react
compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.google.code.gson:gson:2.8.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0'
本例子中使用Github API作請求.android
以Github的Root Endpoint爲例:
https://api.github.com
.
首先, 咱們在命令行發送:git
curl https://api.github.com
或者在Postman發送這個請求, 兩種方法均可以獲得結果.github
這個請求返回的是一個json.json
利用這個網站: jsonschema2pojo, 能夠用json生成一個java類, 好比上面這個, 咱們給它起名字叫Endpoints.java
.api
以後例子中的API都是這種方式, 先發送請求獲得json, 而後轉成java的model類.bash
首先寫一個ServiceGenerator
類, 用於生成service:微信
public class ServiceGenerator { public static final String API_BASE_URL = "https://api.github.com"; private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); private static Retrofit.Builder builder = new Retrofit.Builder() .baseUrl(API_BASE_URL) .addConverterFactory(GsonConverterFactory.create()); public static <S> S createService(Class<S> serviceClass) { Retrofit retrofit = builder.client(httpClient.build()).build(); return retrofit.create(serviceClass); } }
這裏指定了咱們的base url.
createService()
方法返回的是一個泛型.
而後咱們建立GithubService
, 注意這是一個接口:
import com.ddmeng.helloretrofit.data.models.Endpoints; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Url; public interface GitHubService { @GET Call<Endpoints> getAllEndpoints(@Url String url); }
這裏@GET
指定了是一個GET請求, 由於咱們請求的就是base url, 因此是這樣寫的.
Endpoints
類是這個請求所返回的json轉化的java類.
好了, 準備工做作完了, 如今就能夠請求並獲得結果:
請求github api的root url, 獲得全部的endpoints:
GitHubService gitHubService = ServiceGenerator.createService(GitHubService.class); Call<Endpoints> endpointsCall = gitHubService.getAllEndpoints(""); endpointsCall.enqueue(new Callback<Endpoints>() { @Override public void onResponse(Call<Endpoints> call, Response<Endpoints> response) { Endpoints endpoints = response.body(); String repositoryUrl = endpoints.getRepositoryUrl(); LogUtils.i(repositoryUrl); Toast.makeText(MainActivity.this, "repository url: " + repositoryUrl, Toast.LENGTH_LONG).show(); } @Override public void onFailure(Call<Endpoints> call, Throwable t) { } });
說明:
首先利用前面的ServiceGenerator來建立Service, 而後調用接口中定義的getAllEndpoints()
方法, 此處傳入了空字符串, 由於我請求的就是base url.
這裏注意用Retrofit請求的返回值是Call<T>
(後面咱們還會介紹用RxJava的情形), 泛型T是model類型, 它有兩個方法:
execute()
是同步方法, 返回Response<T>
;enqueue()
是異步方法, 在上面的例子中用的就是這個, 在回調onResponse()
中返回了Response<T>
.Converter的做用: 若是不指定Converter, 默認狀況下Retrofit只能返回ResponseBody
類型, 加了Converter以後就能夠返回咱們定義的Model類型了.
因此Converter替咱們作了json -> model的工做.
本例子中ConverterFactory指定的是GsonConverterFactory
. 這裏咱們選的是Gson Converter, 因此依賴的是com.squareup.retrofit2:converter-gson
.
Retrofit支持多種converters:
從上面返回的endpoints能夠看到, user_url是: https://api.github.com/users/{user}
這是一個帶path參數的url, 咱們發請求的時候在{user}處寫一個github用戶名, 便可獲得該用戶的信息, 好比:
https://api.github.com/users/mengdd
.
那麼用Retrofit如何處理呢?
只須要在GithubService
中增長一個方法, 這樣寫:
public interface GitHubService { @GET Call<Endpoints> getAllEndpoints(@Url String url); @GET("users/{user}") Call<User> getUser(@Path("user") String user); }
使用時的方法徹底同樣, 再也不贅述, 同理, 若是要在後面加參數, 能夠用@Query
.
更多註解的例子見官方網站: Retrofit
RxJava近年來很流行, 主要優點是流式操做, 能夠處理並行發送請求, 使用靈活, 線程切換容易.
當你要處理的邏輯比較複雜時, 就會發現使用RxJava的優點.
以咱們的例子來講, 當前咱們利用一個請求能夠獲得一個用戶的信息並顯示出來.
若是咱們想獲得這個用戶的全部repo的全部者或者其餘信息, 全部他follow的人的信息, 以及他們的repo的信息呢?
這就須要發不少個請求, 而且其中有些請求是並行發送的, 若是按照前面的方法, 不斷地在callback裏面嵌套, 那就太難看了.
首先, 添加RxJava和RxAndroid的依賴:
compile 'io.reactivex:rxjava:1.2.2' compile 'io.reactivex:rxandroid:1.2.1'
注: 雖然在我寫這篇文章的時候(2016.11.4)RxJava2.0剛剛release, 可是咱們仍是先用RxJava1來寫這個demo.
而後添加retrofit的adapter-rxjava:
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
因此如今咱們的依賴總的看起來是這樣:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile "com.android.support:appcompat-v7:${supportLibVersion}" compile "com.android.support:design:${supportLibVersion}" compile "com.jakewharton:butterknife:${butterKnifeVersion}" apt "com.jakewharton:butterknife-compiler:${butterKnifeVersion}" compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.google.code.gson:gson:2.8.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' compile 'io.reactivex:rxjava:1.2.2' compile 'io.reactivex:rxandroid:1.2.1' testCompile 'junit:junit:4.12' }
Retrofit.Builder()中加入這一行:
.addCallAdapterFactory(RxJavaCallAdapterFactory.create());
ServiceGenerator變成這樣:
public class ServiceGenerator { public static final String API_BASE_URL = "https://api.github.com"; private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); private static Retrofit.Builder builder = new Retrofit.Builder() .baseUrl(API_BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()); public static <S> S createService(Class<S> serviceClass) { Retrofit retrofit = builder.client(httpClient.build()).build(); return retrofit.create(serviceClass); } }
這樣咱們在GithubService
中定義的接口方法, 既能夠像原來同樣返回Call
, 也能夠返回Observable
.
以單個請求爲例,
不用RxJava的時候:
@GET("users/{user}/following") Call<List<User>> getUserFollowing(@Path("user") String user);
請求的時候是這樣的:
請求指定用戶follow的全部人:
GitHubService service = ServiceGenerator.createService(GitHubService.class); Call<List<User>> userFollowing = service.getUserFollowing(inputUserNameView.getText().toString()); userFollowing.enqueue(new Callback<List<User>>() { @Override public void onResponse(Call<List<User>> call, Response<List<User>> response) { List<User> followingUsers = response.body(); peopleListAdapter.setUsers(followingUsers); peopleListAdapter.notifyDataSetChanged(); } @Override public void onFailure(Call<List<User>> call, Throwable t) { } });
如今改用RxJava了, 返回的不是Call而是Observable:
@GET("users/{user}/following") Observable<List<User>> getUserFollowingObservable(@Path("user") String user);
結合RxJava請求的時候變爲這樣:
仍是請求用戶follow的全部人:
GitHubService service = ServiceGenerator.createService(GitHubService.class); String username = inputUserNameView.getText().toString(); service.getUserFollowingObservable(username) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<User>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<User> users) { LogUtils.i("onNext: " + users.size()); peopleListAdapter.setUsers(users); peopleListAdapter.notifyDataSetChanged(); } });
用RxJava實現後, 請求返回的是一個Observable, 用subscribe()
添加一個訂閱者, 即它的觀察者.
當請求返回後, 回到主線程, 更新UI.
這是單個請求的例子, 因此RxJava的優點不是很明顯, 若是咱們有多個請求, 用RxJava進行變換組合顯然就是更好的選擇.
上個例子中.subscribeOn(Schedulers.io())
指定Observable的工做, 在咱們的例子中Observable的工做即發送請求, 在io線程作, 指定了被觀察者的處理線程;
.observeOn(AndroidSchedulers.mainThread())
指定最後onNext()回調在主線程, 即指定了通知後續觀察者的線程.
關於這兩個操做符的更多說明請看官方文檔: subscribeOn和observeOn.
設計這樣一個場景, 咱們如今取到了一個用戶follow的全部人, 可是取回的信息中並不包含每一個人擁有的repo個數, 只有一個url可用戶查看全部repo.
接下來咱們要取其中每個人的詳細信息, 就要查詢另外一個API, 從新查詢這我的的完整信息.
查詢用戶follow的全部人, 而後查詢每個人的詳細信息:
subscription = service.getUserFollowingObservable(username) .flatMap(new Func1<List<User>, Observable<User>>() { @Override public Observable<User> call(List<User> users) { return Observable.from(users); } }) .flatMap(new Func1<User, Observable<User>>() { @Override public Observable<User> call(User user) { return service.getUserObservable(user.getLogin()); } }) .toList() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<User>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<User> users) { peopleListAdapter.setUsers(users); peopleListAdapter.notifyDataSetChanged(); } });
能夠看到咱們加了兩個flatMap()
和一個toList()
來作這個事情.
首先, 第一步咱們用getUserFollowingObservable()
獲得的是一個Observable<List<User>>
;
咱們以後用.flatMap()
, 它的輸入是List<User>
, 返回的是Observable<User>
. 咱們在其中用了一個.from()
來生成一個發射一組User的Observable
;
以後第二個.flatMap()
裏, 輸入是前一個Observable
的輸出, 即User, 調用了getUserObservable()
, 返回的結果是Observable<User>
, 以後加一個.toList()
, 把輸出的結果從單個的User變爲List
只不過此時獲得的用戶信息是更詳細的用戶信息, 包含了他的repo數據和follow數據. 由於它們是經過單獨查詢每個人獲得的.
運行, 雖然能夠獲得咱們想要的結果, 可是這個例子仍然是有問題的.
上面多個請求的例子, 發現雖然實現了咱們的需求, 可是結果回來得很慢.
咱們加上一個.map
操做符來加上log:
(這裏省略了一些先後的代碼, 只是在.flatMap()
里加了一個.map()
)
... subscription = service.getUserFollowingObservable(username) .flatMap(new Func1<List<User>, Observable<User>>() { @Override public Observable<User> call(List<User> users) { return Observable.from(users); } }) .flatMap(new Func1<User, Observable<User>>() { @Override public Observable<User> call(User user) { return service.getUserObservable(user.getLogin()) .map(new Func1<User, User>() { @Override public User call(User user) { // this .map is used to output log information to check the threads LogUtils.i("getUserObservable: " + user.getLogin()); return user; } }); } }) .toList() ...
由Log能夠發現(log中的線程號是同樣的)單獨取每個用戶詳細信息的請求都發生在同一個線程, 是順次進行的.
查看代碼:
Demo地址: https://github.com/mengdd/HelloRetrofit.
git checkout multiple-requests-in-single-thread
回頭梳理一下咱們的需求, 請求一個全部follow的人, 返回一個follow的人的List, 而後對List中的每個人, 單獨請求詳細信息.
那麼按理來講, 第二個批量的請求是能夠同時發送, 並行進行的.
因此咱們想要的行爲實際上是平行發送多個請求, 而後最後統一結果到UI線程.
改動以下:
subscription = service.getUserFollowingObservable(username) .subscribeOn(Schedulers.io()) // 從io線程開始, 取用戶follow的全部人 .flatMap(new Func1<List<User>, Observable<User>>() { @Override public Observable<User> call(List<User> users) { LogUtils.i("from"); return Observable.from(users); } }) .flatMap(new Func1<User, Observable<User>>() { @Override public Observable<User> call(User user) { return service.getUserObservable(user.getLogin()) // 取每一個人的詳細信息 .subscribeOn(Schedulers.io()) // 指定取每一個人詳細信息的工做都在單獨的io線程 .map(new Func1<User, User>() { @Override public User call(User user) { // this map operation is just used for showing log LogUtils.i("getUserObservable: " + user.getLogin()); return user; } }); } }) .toList() .observeOn(AndroidSchedulers.mainThread()) // 最後返回到主線程 .subscribe(new Subscriber<List<User>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<User> users) { LogUtils.i("onNext: " + users.size()); peopleListAdapter.setUsers(users); peopleListAdapter.notifyDataSetChanged(); } })
給改動的部分加上了註釋, 這樣更清楚一些.
注意subscribeOn()
指定的是當前的這個Observable的工做在什麼線程進行.
因此在本例子中, subscribeOn(Schedulers.io())
的位置放在.flatMap()
裏面纔會產生多個請求並行的效果.
這樣一改, 咱們的顯示時間再也不是全部請求時間之和, 而是隻取決於最慢的那個請求時間.
查看代碼:
Demo地址: https://github.com/mengdd/HelloRetrofit
git checkout multiple-requests-in-multiple-threads
正常狀況下, 行爲結束以後, 到達onComplete()
或者onError()
, RxJava的訂閱會自動取消.
可是在處理網絡請求的時候, 極可能會出現請求尚未返回, 界面就已經結束了的狀況.
上面的代碼中已經出現了, 訂閱方法subscribe()
的返回值是一個Subscription
對象, 咱們保存了這個對象的引用, 而後在onPause()
的時候取消了請求, 防止內存泄露.
@Override public void onPause() { super.onPause(); if (subscription != null && subscription.isUnsubscribed()) { subscription.unsubscribe(); } }
固然也能夠選別的生命週期回調, 好比onDestroyView()
或者onDestroy()
.
若是有多個請求, 能夠用:
private CompositeSubscription compositeSubscription = new CompositeSubscription(); ... // 在發請求的地方, 返回subscription compositeSubscription.add(subscription); ... // 選一個生命週期註銷全部請求 @Override public void onPause() { super.onPause(); compositeSubscription.unsubscribe(); }
Demo地址: https://github.com/mengdd/HelloRetrofit
本Demo只用於展現Retrofit和RxJava結合的使用, 爲了清晰起見因此沒有采用MVP構架, 也沒有用Dagger進行依賴注入, 有的請求也沒有在生命週期結束時取消, 也沒有UI的loading效果和沒網狀況的處理等, 你們使用時請根據實際須要作一些處理.
這些沒有的東西會在我最近在作一個應用repo中出現: https://github.com/mengdd/GithubClient, 還在開發中, 能夠關注一下.
另, Demo使用有時候用着用着請求就返回了:
{"message":"API rate limit exceeded for xxx ip...
這是由於沒受權的用戶每小時最多隻能發60個請求:https://developer.github.com/v3/#rate-limiting
解決辦法就是..查following的人時, 不要查那種follow了不少人的帳號. orz.
歡迎關注個人微信公衆號, 按期更新Android開發資訊或文章: