RxJava2+Retrofit2+okHttp的二次封裝
專案Demo已經上傳至github
本人android小白一枚,在學習android的過程中,我感受很深,在做了這麼多練手的專案之後,我覺得android就像一塊畫板,畫板是白紙,作為android的開發人員,要做的就是運用一切可能,去在畫板上畫出讓人看的懂並且美觀的畫。說起來,我們卻也屬於藝術家這一行了。。。
在我看來,android是為了展示資料的,這裡就要分兩個步驟了,第一就是怎樣去展示,這部分是相當於UI的工作,,第二就是資料的來源,資料從哪來,怎麼來,來的是什麼,有什麼規律,這些都需要我們去考慮,一般來說,就現在的開發模式來說,資料都是儲存在資料庫中,後臺的工作人員通過各種語言去實現對資料庫的增刪改查,然後將資料庫中的資料進行加工處理,部署在伺服器上,android端就需要通過訪問伺服器去獲取並解析資料,這裡就涉及到獲取並解析資料了。不涉及到網路的應用現如今基本上是不復存在的了,一款App,沒有強大的後臺作為其支撐,縱然你將介面做的再花哨華麗,終究只能算是“花瓶”,“雞肋”。
所以在開始開發一個專案之前,選取一款優秀的網路框架是非常有必要的。RxJava和Retrofit出現也很久了,之前是一直使用okHttp去封裝一個請求類,這樣雖然能夠正確的請求到資料,但是,程式碼不僅複雜繁多,遇到多請求問題就會讓邏輯不清晰,思維很容易進入混亂,說再多都無法表達多請求問題的恐怖,親身經歷才能夠感受它的複雜。
RxJava的概念其實很模糊,我對它的理解就是一個給你方便處理非同步問題的框架,到底有多方便,體會過才知道。。。
Retrofit就是對okhttp做了一層封裝。把網路請求都交給給了Okhttp,我們只需要通過簡單的配置就能使用retrofit來進行網路請求了,Retrofit 除了提供了傳統的 Callback 形式的 API,還有 RxJava 版本的 Observable 形式 API,在我看來,retrofit有點像javaee裡的springboot框架,通過xml檔案配置好一切後,直接就可以在方法頭前添加註釋來執行網路請求,實在是不能再方便了。
使用方法:build.gradle檔案中引入幾個jar
compile 'io.reactivex.rxjava2:rxjava:2.0.7' compile 'io.reactivex.rxjava2:rxandroid:2.0.1' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.squareup.okhttp3:okhttp:3.5.0'compile 'com.squareup.okhttp3:logging-interceptor:3.6.0' compile 'com.jakewharton:butterknife:7.0.1' compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0' compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
說再多,不如一個例子來的簡單,我們現在定義好一個場景,來練練手。
假設現在有這麼一組json資料
results是一個json資料。它的請求url是http://gank.io/api/data/福利/10/1。因為本篇重點就是在講運用網路框架去取資料,所以在這裡,只要取到資料就可以了,至於怎樣去展示,不是本篇重點,所以不做討論。
1.首先觀察json資料的規律,要自定義一個實體,負責將json資料解析成一個個實體類,方便去呼叫。
總體類:
public class BaseResult<T> { private int code; private String message; private T results; private boolean error; public T getResults() { return results; } public void setResults(T results) { this.results = results; } public boolean isError() { return error; } public void setError(boolean error) { this.error = error; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }results中的資料類:Meizi
public class MeiZi { /** * _id : 59cd9b53421aa9727fdb25eb * createdAt : 2017-09-29T09:01:07.894Z * desc : 9-29 * publishedAt : 2017-09-29T11:21:16.116Z * source : chrome * type : 福利 * url : https://ws1.sinaimg.cn/large/610dc034ly1fk05lf9f4cj20u011h423.jpg * used : true * who : daimajia */ private String _id; private String createdAt; private String desc; private String publishedAt; private String source; private String type; private String url; private boolean used; private String who; public String get_id() { return _id; } public void set_id(String _id) { this._id = _id; } public String getCreatedAt() { return createdAt; } public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public String getPublishedAt() { return publishedAt; } public void setPublishedAt(String publishedAt) { this.publishedAt = publishedAt; } public String getSource() { return source; } public void setSource(String source) { this.source = source; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public boolean isUsed() { return used; } public void setUsed(boolean used) { this.used = used; } public String getWho() { return who; } public void setWho(String who) { this.who = who; } }2.資料的容器準備好之後,就開始去獲取資料了,使用RxJava2與Retrofit2框架去進行網路請求,我覺得定義Retrofit訪問的介面
這個是最好想的,所以先編寫這個介面。
public interface ApiService { /** * 網路請求超時時間毫秒 */ int DEFAULT_TIMEOUT = 20000; String HOST = "http://gank.io/"; String API_SERVER_URL = HOST + "api/data/"; @GET("福利/10/1") Observable<BaseResult<List<MeiZi>>> getMezi(); /** * @param page * @param number * @return */ @Headers("Cache-Control: public, max-age=100")//設定快取 快取時間為100s @GET("everySay/selectAll.do") Observable<BaseResult<List<MeiZi>>> lookBack(@Query("page") int page, @Query("rows") int number); @POST("upload/uploadFile.do") Observable<BaseResult> uploadFiles(@Part("filename") String description, @Part("pic\"; filename=\"image1.png") RequestBody imgs1, @Part("pic\"; filename=\"image2.png") RequestBody imgs2); @POST("upload/uploadFile.do") Observable<BaseResult> uploadFiles(@Part("filename") String description, @PartMap() Map<String, RequestBody> maps); }3.就是對自己的Retrofit去進行封裝設定
(1)retrofit是基於okhttp的加強版,所以第一步去自定義一個okhttpclient,裡面設定兩個攔截器,一個是日誌攔截器,用於對日誌的篩選,還一個就是網路攔截器,用於對網路請求頭的總體設定。
//日誌攔截器 HttpLoggingInterceptor interceptor=new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(String message) { try { String text = URLDecoder.decode(message, "utf-8"); LogUtils.e(text); } catch (UnsupportedEncodingException e) { e.printStackTrace(); LogUtils.e(message); } } });
class HttpNetWorkInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (!NetUtil.checkNetWork(BaseApplication.getmContext())) { request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build(); LogUtils.d("no network"); } Response originalResponse = chain.proceed(request); if (NetUtil.checkNetWork(BaseApplication.getmContext())) { //有網的時候讀介面上的@Headers裡的配置,可以在這裡進行統一的設定 String cacheControl = request.cacheControl().toString(); return originalResponse.newBuilder() .header("Cache-Control", cacheControl) .removeHeader("Pragma") .build(); } else { return originalResponse.newBuilder() .header("Cache-Control", "public, only-if-cached, max-stale=2419200") .removeHeader("Pragma") .build(); } } }(2)建立一個okhttpclient
//初始化一個OKhttpClient OkHttpClient okHttpClient=new OkHttpClient.Builder() .addInterceptor(interceptor) .readTimeout(8, TimeUnit.SECONDS) .connectTimeout(8,TimeUnit.SECONDS) .addNetworkInterceptor(new HttpNetWorkInterceptor()) .cache(cache) .build();(3)建立一個Retrofit
Gson gson=new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create(); Retrofit retrofit=new Retrofit.Builder() .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(ApiService.API_SERVER_URL) .build(); service=retrofit.create(ApiService.class);(4)建立一個觀察者基類,在其中進行對請求錯誤的封裝和對進度條顯示與消失的封裝。
public abstract class DefaultObserver<T extends BaseResult> implements Observer<T> { private Activity activity; // Activity 是否在執行onStop()時取消訂閱 private boolean isAddInStop = false; private CommonDialogUtils dialogUtils; public DefaultObserver(Activity activity) { this.activity = activity; dialogUtils=new CommonDialogUtils(); dialogUtils.showProgress(activity); } public DefaultObserver(Activity activity, boolean isShowLoading) { this.activity = activity; dialogUtils=new CommonDialogUtils(); if (isShowLoading) { dialogUtils.showProgress(activity,"Loading..."); } } @Override public void onSubscribe(@NonNull Disposable d) { } @Override public void onNext(@NonNull T t) { dismissProgress(); if (!t.isError()) { onSuccess(t); } else { onFail(t); } } private void dismissProgress(){ if(dialogUtils!=null){ dialogUtils.dismissProgress(); } } @Override public void onError(@NonNull Throwable e) { LogUtils.e(e.getMessage()); dismissProgress(); if (e instanceof HttpException) { // HTTP錯誤 onException(ExceptionReason.BAD_NETWORK); } else if (e instanceof ConnectException || e instanceof UnknownHostException) { // 連線錯誤 onException(CONNECT_ERROR); } else if (e instanceof InterruptedIOException) { // 連線超時 onException(CONNECT_TIMEOUT); } else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) { // 解析錯誤 onException(PARSE_ERROR); } else { onException(UNKNOWN_ERROR); } } @Override public void onComplete() { } /** * 請求成功 * * @param response 伺服器返回的資料 */ abstract public void onSuccess(T response); /** * 伺服器返回資料,但響應碼不為200 * * @param response 伺服器返回的資料 */ public void onFail(T response) { String message = response.getMessage(); if (TextUtils.isEmpty(message)) { ToastUtils.show(R.string.response_return_error); } else { ToastUtils.show(message); } } /** * 請求異常 * * @param reason */ public void onException(ExceptionReason reason) { switch (reason) { case CONNECT_ERROR: ToastUtils.show(R.string.connect_error, Toast.LENGTH_SHORT); break; case CONNECT_TIMEOUT: ToastUtils.show(R.string.connect_timeout, Toast.LENGTH_SHORT); break; case BAD_NETWORK: ToastUtils.show(R.string.bad_network, Toast.LENGTH_SHORT); break; case PARSE_ERROR: ToastUtils.show(R.string.parse_error, Toast.LENGTH_SHORT); break; case UNKNOWN_ERROR: default: ToastUtils.show(R.string.unknown_error, Toast.LENGTH_SHORT); break; } } /** * 請求網路失敗原因 */ public enum ExceptionReason { /** * 解析資料失敗 */ PARSE_ERROR, /** * 網路問題 */ BAD_NETWORK, /** * 連線錯誤 */ CONNECT_ERROR, /** * 連線超時 */ CONNECT_TIMEOUT, /** * 未知錯誤 */ UNKNOWN_ERROR, } }(5)對BaseActivity的封裝,通過rxlifecycle對Rxjava的生命週期進行控制
public abstract class BaseActivity extends RxAppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId()); init(savedInstanceState); } protected void showToast(String msg) { ToastUtils.show(msg); } protected abstract @LayoutRes int getLayoutId(); protected abstract void init(Bundle savedInstanceState); }(6)對BaseFragment的封裝
public abstract class BaseFragment extends RxFragment { public View rootView; public LayoutInflater inflater; @Nullable @Override public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); this.inflater = inflater; if (rootView == null) { rootView = inflater.inflate(this.getLayoutId(), container, false); init(savedInstanceState); } ViewGroup parent = (ViewGroup) rootView.getParent(); if (parent != null) { parent.removeView(rootView); } return rootView; } protected abstract int getLayoutId(); protected abstract void init(Bundle savedInstanceState); protected void showToast(String msg) { ToastUtils.show(msg); } @Override public void onResume() { super.onResume(); } @Override public void onPause() { super.onPause(); } @Override public void onDestroyView() { super.onDestroyView(); } }(7)請求資料的方法
public void getData() { IdeaApi.getSingleHolder() .getMezi() .compose(this.<BaseResult<List<MeiZi>>>bindToLifecycle()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new DefaultObserver<BaseResult<List<MeiZi>>>(this) { @Override public void onSuccess(BaseResult<List<MeiZi>> response) { List<MeiZi> results = response.getResults(); showToast("請求成功,妹子個數為"+results.size()); } }); }