能夠說RxJava+Retrofit是整個2016年Android 開發圈內最受關注的的組合。各大Android論壇上有大量以RxJava+Retrofit+xxx 爲標題的文章,此類文章也備受你們的關注。這個組合彷彿已經成爲了Android開發的必備組件,項目裏沒使用這個組合好像本身都out了似的。javascript
平心而論,RxJava和Retrofit 相較於以往的各類框架(如 AsyncHttpClient,Volley等 )學習和使用起來會有一些難度;RxJava 強大而又龐大的操做符,Retrofit採用註解風格定義接口,都會讓初學者花費很多功夫,繞很多圈子,踩大量的坑。既然這樣,那麼就會有人懷疑,咱們真的須要學習RxJava和Retrofit嗎?java
任意一款須要聯網的APP,最典型的套路就是請求後端數據,解析數據進行UI更新;響應用戶操做,再次請求數據,更新UI。這裏咱們就從最基礎的網絡請求出發,帶着疑問,逐步瞭解一下Retrofit的前生今世,看一看RxJava和Retrofit的價值所在。react
初學Android開發時,還在上大學,那會兒還不知有AsyncHttpClient,Volley,OKHttp 這麼方便的框架存在於這個世界上;一個簡單的網絡請求一般要寫一大段代碼。android
class MyTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
InputStream mInputStream = null;
HttpURLConnection connection = getHttpUrlConnection(params[0]);
String result = "";
try {
connection.connect();
int statusCode = connection.getResponseCode();
String response = connection.getResponseMessage();
mInputStream = connection.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(mInputStream);
BufferedReader reader = new BufferedReader(inputStreamReader);
StringBuffer sb = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
result = "StatusCode: " + statusCode + "\n"
+ "Response" + response + "\n"
+ sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
tv.setText(s);
}
}
private HttpURLConnection getHttpUrlConnection(String url) {
HttpURLConnection connection = null;
try {
URL mUrl = new URL(url);
connection = (HttpURLConnection) mUrl.openConnection();
connection.setConnectTimeout(20000);
connection.setReadTimeout(40000);
connection.setRequestMethod("GET");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("Charset", "utf-8");
connection.setRequestProperty("Content-Length", "0");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return connection;
}複製代碼
new MyTask().execute(BASE_URL);複製代碼
這段代碼的邏輯很簡單,就是將網絡請求的結果顯示在一個TextView上。很大一部分的內容都是在執行HttpURLConnection 相關的配置及初始化工做。git
記得第一次經過網絡請求把數據顯示的Android模擬器(那時候仍是窮學生,買不起Android手機)的屏幕上時,雖然只是一大堆別人看不懂的json字符串,可是感受本身就要上天了,如今想一想真是。。。。。github
即使是這麼長的一段代碼,尚未包含網絡請求異常的內容,若是加上網絡請求失敗處理的邏輯,將使得整個代碼結構更加臃腫龐大。json
一款聯網的APP至少會有十幾回的網絡請求,更多的就沒法估計了。所以,每一次的網絡請求不可能像上面那樣寫。因此,咱們須要封裝,將一些固定的操做統一處理;固然已經有許多大神比我早想到了這個問題,便出現了許多對網絡請求進行封裝的庫。後端
這裏列出的幾個庫當中,我的使用AsyncHttpClient較多,AsyncHttpClient 的確很是好用,可是後來伴隨着Android sdk 23 中HttpClient的廢棄也逐漸被遺忘。
afinal和xUtils 都沒有在實際項目中沒用過,不作評價。api
Volley做爲Google官方在2013年I/O 大會上推出的庫,相較於AsyncHttpClient 更強大。安全
下面簡單列舉一個使用Volley進行get請求的demo。
添加依賴:
compile 'com.mcxiaoke.volley:library:1.0.19'複製代碼
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
queue = Volley.newRequestQueue(mContext);
setContentView(R.layout.activity_http_volley_demo);
tv = (TextView) findViewById(R.id.editText);
final StringRequest request = new StringRequest(Request.Method.GET, BASE_URL,
new ResponseSuccessListener(), new ResponseFailListener());
findViewById(R.id.volley).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
queue.add(request);
}
});
}
private class ResponseSuccessListener implements com.android.volley.Response.Listener<String> {
@Override
public void onResponse(String response) {
tv.setText(response);
}
}
private class ResponseFailListener implements Response.ErrorListener {
@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(mContext, error.toString(), Toast.LENGTH_SHORT).show();
}
}複製代碼
這段代碼和上面的功能同樣,都是將網絡請求的結果顯示在TextView。可是經過Volley對http請求進行一次封裝後,咱們再也不關注網絡請求的具體細節,而是將重點放在了對請求結果的處理上;網絡請求不管成功仍是失敗,咱們均可以不少好的應對。
並且在Volley中,異步網絡請求的回調方法已然處於UI線程中,這樣咱們就能夠直接在回調方法中進行UI更新了。
能夠說,使用Volley已經能夠很是方便的處理Android 網絡請求的相關內容了。既然如此,爲何還會有OKHttp和Retrofit的出現呢?他們的優點又在哪裏呢?
okHttp 是由square 推出的一個網絡請求庫,包括Retrofit也是由其開發,這裏爲square點個贊。
使用以前加入依賴
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.squareup.okio:okio:1.11.0'複製代碼
okHttp 網絡請求實現
findViewById(R.id.get).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv.setText("");
loading.setVisibility(View.VISIBLE);
client = new OkHttpClient();
Request.Builder builder = new Request.Builder()
.url(BASE_URL)
.method("GET", null);
request = builder.build();
Call mCall = client.newCall(request);
mCall.enqueue(new MyCallback());
}
});
private class MyCallback implements Callback {
@Override
public void onFailure(Call call, IOException e) {
Message msg = new Message();
msg.what = 100;
msg.obj = e;
handler.sendMessage(msg);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Message msg = new Message();
msg.what = 200;
msg.obj = response.body().string();
handler.sendMessage(msg);
}
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
loading.setVisibility(View.GONE);
switch (msg.what) {
case 100:
Object e = msg.obj;
Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
break;
case 200:
String response = (String) msg.obj;
tv.setText(response);
break;
case 300:
int percent = msg.arg1;
Log.e("llll", "the percent is " + percent);
if (percent < 100) {
progressDialog.setProgress(percent);
} else {
progressDialog.dismiss();
Glide.with(mContext).load(FILE_PATH).into(imageView);
}
break;
default:
break;
}
}
}複製代碼
這裏必須瞭解的是,okHttp的回調方法,並不處於UI 線程中,對網絡請求結果若是涉及UI 線程的操做,須要使用Handler。這麼看來,okHttp 貌似反而不如Volley了。其實否則,okhttp的封裝套路和Volley,AsyncHttp不是一個級別的,不能和後二者做比較,okhttp 和HttpClient、HttpUriConneciton 纔是一個級別的產物,相較於這二者,okhttp顯然強大了許多。
因此,OKHttp不只僅能夠用於Android開發,Java開發也是OK的。
A type-safe HTTP client for Android and Java
一個針對Android和Java類型安全的http客戶端
上面這句話,就是Squire對Retrofit的說明,言簡意賅。Retrofit實際上是對okhttp 作了進一步的封裝,有了okhttp 的基礎,使用Retrofit會很容易。
下面就來看看,使用Retrofit作網絡請求又是一種怎樣的體驗。
這裏爲了方便咱們使用
做爲網絡請求的接口基地址
使用以前加入依賴:
compile 'com.squareup.retrofit2:retrofit:2.1.0'複製代碼
定義接口
public interface GithubService {
@GET("users/{user}")
Call<ResponseBody> getUserString(@Path("user") String user);
}複製代碼
這裏咱們使用http中的get 方法獲取users這個接口下,當前user的具體信息,參數爲當前user名。返回內容爲Http請求的ResponseBody。
Retrofit 返回ResponseBody
private void SimpleRetrofit() {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL);
Retrofit retrofit = builder.client(httpClient.build()).build();
GithubService simpleService = retrofit.create(GithubService.class);
Call<ResponseBody> call = simpleService.getUserString(name);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
loading.dismiss();
try {
String result = response.body().string();
Gson gson = new Gson();
GithubUserBean bean = gson.fromJson(result, GithubUserBean.class);
setUserView(bean);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
loading.dismiss();
}
});
}
private void setUserView(GithubUserBean user) {
if (user != null) {
viewShell.removeAllViews();
View view = LayoutInflater.from(mContext).inflate(R.layout.user_item_layout, null);
TextView title = (TextView) view.findViewById(R.id.title);
TextView id = (TextView) view.findViewById(R.id.userId);
TextView creteaTime = (TextView) view.findViewById(R.id.createTime);
TextView updateTime = (TextView) view.findViewById(R.id.updateTime);
TextView bio = (TextView) view.findViewById(R.id.bio);
ImageView avatar = (ImageView) view.findViewById(R.id.avatar);
title.setText("Name: " + user.getLogin());
bio.setText("Bio: " + user.getBio());
id.setText("Id: " + String.valueOf(user.getId()));
creteaTime.setText("createTime: " + user.getCreated_at());
updateTime.setText("updateTime: " + user.getUpdated_at());
Glide.with(mContext).load(user.getAvatar_url()).into(avatar);
viewShell.addView(view);
} else {
Toast.makeText(mContext, "result is null", Toast.LENGTH_SHORT).show();
}
}複製代碼
GitHubUserBean 爲網絡請求結果json數據所對應的實體類。
經過這段代碼,咱們在最終的回調方法裏能夠友好的處理請求結果,失敗時onFailure方法執行。成功時,onResponse方法執行,咱們在這裏用Gson解析返回的數據,並進行UI更新操做(setUserView(bean)),
這裏咱們這樣作有些囉嗦,Gson轉換的方式都是相似,惟一不一樣的只是每次網絡請求結果對應的實體類;所以咱們能夠藉助強大的Retrofit幫助咱們完成Gson轉換的步驟。固然,若是在你所在的開發環境中,接口返回的並非json格式的數據,也沒有問題的。
上圖是Retrofit官網對可轉換類型給出的介紹。有這麼多種,固然了若是大家家服務器返回的數據格式比較神奇,你也能夠自定義轉換類。
好了,言歸正傳,這裏仍是以Json 格式數據爲例。
添加依賴:
compile 'com.squareup.retrofit2:converter-gson:2.1.0'複製代碼
注意這裏converter-gson 的版本號,要和以前Retrofit的版本號保持一致。
咱們從新定義接口:
public interface GithubService {
@GET("users/{user}")
Call<GithubUserBean> getUser(@Path("user") String user);
}複製代碼
這裏咱們用GithubUserBean取代ResponseBody,直接將其做爲返回類型。
Retrofit 返回對象
private void LazyRetrofit() {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.client(httpClient.build()).build();
GithubService service = retrofit.create(GithubService.class);
Call<GithubUserBean> call = service.getUser(name);
call.enqueue(new Callback<GithubUserBean>() {
@Override
public void onResponse(Call<GithubUserBean> call, Response<GithubUserBean> response) {
GithubUserBean bean = response.body();
setUserView(bean);
loading.dismiss();
}
@Override
public void onFailure(Call<GithubUserBean> call, Throwable t) {
loading.dismiss();
}
});
}複製代碼
這裏的實現方式和上面基本類似,只是多了一行
.addConverterFactory(GsonConverterFactory.create());複製代碼
這樣,咱們在onResponse中得到就是對象,再也不須要作額外的轉換工做,能夠直接使用。
Retrofit 簡單封裝
這裏咱們能夠看到,Retrofit使用有着必定的套路,因此咱們能夠將Retrofit初始化相關得內容作一次簡單的封裝。
public class GenServiceUtil {
private static final String BASE_URL = "https://api.github.com/";
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
private static Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = builder.client(httpClient.build()).build();
public static <S> S createService(Class<S> serviceClass) {
return retrofit.create(serviceClass);
}
}
private void EasyRetrofit() {
GithubService service = GenServiceUtil.createService(GithubService.class);
Call<GithubUserBean> call = service.getUser(name);
call.enqueue(new Callback<GithubUserBean>() {
@Override
public void onResponse(Call<GithubUserBean> call, Response<GithubUserBean> response) {
GithubUserBean bean = response.body();
loading.dismiss();
setUserView(bean);
}
@Override
public void onFailure(Call<GithubUserBean> call, Throwable t) {
loading.dismiss();
}
});
}複製代碼
咱們只需傳入定義好的藉口,會使代碼簡介許多。看到這裏能夠發現,Retrofit的確很厲害,那爲何又要將他和RxJava結合在一塊兒呢?下面咱們就來看看。
關於什麼是RxJava,這裏再也不贅述,不瞭解的看以看看這裏。這裏咱們就看看將RxJava 和咱們以前的內容結合在一塊兒會有怎樣的效果。
首先,加入依賴
compile 'io.reactivex:rxjava:1.1.7'
compile 'io.reactivex:rxandroid:1.2.1'複製代碼
private void RxRetrofit() {
GithubService service = GenServiceUtil.createService(GithubService.class);
final Call<GithubUserBean> call = service.getUser(name);
final Observable myObserable = Observable.create(new Observable.OnSubscribe<GithubUserBean>() {
@Override
public void call(Subscriber<? super GithubUserBean> subscriber) {
Response<GithubUserBean> bean = null;
try {
bean = call.execute();
subscriber.onNext(bean.body());
} catch (IOException e) {
e.printStackTrace();
subscriber.onError(e);
}
subscriber.onCompleted();
}
});
myObserable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<GithubUserBean, GithubUserBean>() {
@Override
public GithubUserBean call(GithubUserBean o) {
if (TextUtils.isEmpty(o.getBio())) {
o.setBio("nothing !");
}
return o;
}
})
.subscribe(new Subscriber<GithubUserBean>() {
@Override
public void onCompleted() {
loading.dismiss();
}
@Override
public void onError(Throwable e) {
loading.dismiss();
}
@Override
public void onNext(GithubUserBean o) {
setUserView(o);
}
});
}複製代碼
這裏有幾點須要注意:
咱們引入RxJava實現了一樣的功能,卻使得代碼量增長了不少。不由要問,RxJava的價值到底在哪裏呢?
好了,爲了說明爲題,咱們添加一個接口
public interface GithubService {
@GET("users/{user}")
Call<GithubUserBean> getUser(@Path("user") String user);
@GET("users/{user}/followers")Observable<List<UserFollowerBean>> followers(@Path("user") String usr);
}複製代碼
固然這裏依舊須要添加依賴:
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
`複製代碼
同時在Service的封裝方法中添加
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())複製代碼
這樣,RxJava就和Retrofit完美的關聯在了一塊兒。
咱們在接口中,定義followers()方法直接返回了Observable,由於Observable是RxJava的源頭,並且Retrofit能夠很好的支持RxJava,這樣就很是方便了。
private void RxRetrofitList() {
GithubService service = GenServiceUtil.createService(GithubService.class);
Observable<List<UserFollowerBean>> myObserve = service.followers(name);
myObserve
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<UserFollowerBean>>() {
@Override
public void onCompleted() {
loading.dismiss();
}
@Override
public void onError(Throwable e) {
loading.dismiss();
e.printStackTrace();
}
@Override
public void onNext(List<UserFollowerBean> userFollowerBeen) {
setFollowersView(userFollowerBeen);
}
});
}複製代碼
在接口中返回的內容就是Observable,所以不用再像以前同樣單獨定義Observable;在onNext 方法中,接收到返回的對象,更新UI。 這裏若是咱們不使用RxJava,單獨使用Retrofit實現這個過程是沒有任何問題的; RxJava看似沒有價值;可是假設如今出現以下之一的情景
.....
這種情景在實際開發中太常見了,試想若是沒有RxJava;那麼每一次需求的變動都意味着咱們須要去修改setFollowersView這個方法,需求一旦變動,就去修改這個方法,這樣會不可避免的產生各類bug。那有沒有辦法不去修改這個方法呢?這個時候,就須要強大的RxJava了。
這裏咱們就看看如何在不修改setFollowersView的前提下,實現對用戶名從小到大的排序:
private void RxRetrofitList() {
GithubService service = GenServiceUtil.createService(GithubService.class);
Observable<List<UserFollowerBean>> myObserve = service.followers(name);
myObserve
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<List<UserFollowerBean>, List<UserFollowerBean>>() {
@Override
public List<UserFollowerBean> call(List<UserFollowerBean> userFollowerBeen) {
for (UserFollowerBean bean : userFollowerBeen) {
String name = "";
name = bean.getLogin().substring(0, 1).toUpperCase() + bean.getLogin().substring(1, bean.getLogin().length());
bean.setLogin(name);
}
return userFollowerBeen;
}
})
.map(new Func1<List<UserFollowerBean>, List<UserFollowerBean>>() {
@Override
public List<UserFollowerBean> call(List<UserFollowerBean> userFollowerBean) {
Collections.sort(userFollowerBean, new Comparator<UserFollowerBean>() {
@Override
public int compare(UserFollowerBean o1, UserFollowerBean o2) {
return o1.getLogin().compareTo(o2.getLogin());
}
});
return userFollowerBean;
}
})
.subscribe(new Subscriber<List<UserFollowerBean>>() {
@Override
public void onCompleted() {
loading.dismiss();
}
@Override
public void onError(Throwable e) {
loading.dismiss();
e.printStackTrace();
}
@Override
public void onNext(List<UserFollowerBean> userFollowerBeen) {
setFollowersView(userFollowerBeen);
}
});
}複製代碼
RxJava 鏈式風格的代碼
在代碼中咱們使用RxJava的map 操做符,對返回數據作了兩次處理,首先將全部用戶名的首字母轉換爲大寫字母;而後對整個list按照用戶名從小到大排序。由於用戶名中同時包含以大小寫字母打頭的內容,因此爲了方便,咱們進行了一次轉換大寫的操做。
一樣是隨着需求變動,修改代碼;可是你會發現,使用RxJava的方式,會下降出現bug的機率,並且就算是不一樣的人去改,也會比較方便維護。
看到了吧,這就是RxJava的優勢,固然這個例子也只是冰山一角。這裏提到的map操做符只是RxJava龐大操做符集合中的一員,更特別的是,RxJava的操做符仍是能夠自定義的,這樣讓咱們的代碼有了無限的可能;RxJava的存在不只僅在於網絡請求,能夠用在別的方面;RxJava實際上是體現了一種思路,全部對數據的操做都在流上完成,將最終的結果返回給觀察者。同時,若是返回的followers 列表有任何異常,RxJava的onError 方法會執行,這就方便咱們去處理異常數據了。
通篇經過對Android 網絡請求各類實現的總結,能夠看到 相對於Volley,AsyncHttpClient 等庫,RxJava+Retrofit 的優點並不是特別顯著;在執行效率及功能上並沒有大的亮點;對Volley進行良好的封裝一樣能夠實現相似Retrofit自動轉Gson的功能;RxJava+Retrofit 結合會讓咱們寫代碼的方式更加有條理,雖然代碼量會增多,但邏輯的清晰纔是最重要的不是嗎?因此,RxJava+Retrofit 組合不失爲一種好的選擇。
因此,趕忙擁抱RxJava+Retrofit吧!
文中全部源碼地址github