前言:仍是由於公司特別閒,把基礎總結總結。若是對相關知識含糊不清,會致使你沒法隨意擴展你想要的框架和功能。可是以爲做爲程序員這行業,只要踏進來了,不是在學習的路上就是在被淘汰的路上,加油!!html
本文章將針對全部用戶,你們根據本身的所需自行跳過一些章節。首先仍是說說這個框架支持幹一些什麼事:java
get請求 | post請求 | 上傳文件 |
---|---|---|
![]() |
![]() |
![]() |
下載文件 | ||
![]() |
好了就這麼多,幾乎包括了你能用到的功能了。那麼接下來咱們分別來介紹RxJava是什麼?Retrofit是什麼?爲何使用MVP? 固然我這裏是引導你怎麼去學習RxJava和Retrofit,對這2方便本文章不會細將,只是把我封裝的這些會講的特別細,封裝RxJava結合Retrofit的MVP的實戰框架。正文開始了,初學者得一步一步來哦:git
以觀察者模式爲主的一個響應式編程。響應式編程是一種基於異步數據流概念的編程模式。數據流就像一條河:它能夠被觀測,被過濾,被操做,或者爲新的消費者與另一條流合併爲一條新的流。固然他不僅是異步,也有同步。 它最大的有點是能夠來回切換線程,不得不說很哇塞!!程序員
直接上乾貨,組好都看一遍,花不了多長時間:github
我的建議在學習的時候,最好是手敲代碼。雖然不少重複代碼,但這會讓你更加熟悉,甚至你都能拼出這些單詞。操做符只建議記住前2篇的就好了,由於實在太多了你也記不住,第3篇適合當api,沒事看看,有時候當api查查就行了編程
這裏我好想吐槽,好想吐槽!api
Retrofit就是基於OKHttp封裝的,只不過是原做者封裝的,以註解的方式配置好就能請求網絡了。說實話它或許真的不如一些優秀的OKHttp封裝,可是誰讓他支持RxJava呢。Retrofit結合RxJava使用簡直就是一大利器。剛剛說到了RxJava能夠切換線程,你能夠把全部耗時操做放在 子線程,到更改ui回調的時候切換到主線程就行了。緩存
下面是Retrofit的乾貨:markdown
看完了這些相信你對Retrofit有了大體瞭解。我的建議,最好把get請求,post請求,文件上傳都簡單跑通。這裏稍微說下@Url的用法,裏面是一個完整的url地址,會忽略Retrofit設置的baseUrl。同時,Retrofit是封裝好的。因此一些請求參數是不會打印的,固然能夠經過攔截器打印。
固然在項目中要封裝保持一個Retrofit,其中有個.client的方法,能夠放一個OkhttpClient。這個時候能夠自定義日誌攔截器:網絡
public class HttpLogInterceptor implements Interceptor { //這裏省略部分代碼,具體看項目 //這裏要注意去掉上傳文件的請求頭部打印,下載文件的返回參數不打印,由於文件太大處理很差會oom,在說是文件內容,不重要 private final Charset UTF8 = Charset.forName("UTF-8"); @Override public Response intercept(Chain chain) throws IOException { StringBuffer sbf = new StringBuffer(); Request request = chain.request(); RequestBody requestBody = request.body(); String body = null; if (requestBody != null) { Buffer buffer = new Buffer(); requestBody.writeTo(buffer); Charset charset = UTF8; MediaType contentType = requestBody.contentType(); if (contentType != null) { charset = contentType.charset(UTF8); } body = buffer.clone().readString(charset); if (!TextUtils.isEmpty(body)) { //若是是圖片上傳調用URLDecoder會報錯,即便tryCache都沒用,what!!! String netUrl = request.url().toString(); if (netUrl.contains(SystemConst.DIFFERT_URL)) { body = "本次請求圖片上傳或下載,沒法打印參數!"; } else { body = URLDecoder.decode(body, "utf-8"); } } } sbf.append(" \n請求方式:==> " + request.method()) .append("\nurl:" + request.url()) .append("\n請求頭:" + request.headers()) .append("\n請求參數: " + body); Response response = chain.proceed(request); String rBody = ""; String netUrl = request.url().toString(); if (!netUrl.contains(SystemConst.QQ_APK)) { ResponseBody responseBody = response.body(); BufferedSource source = responseBody.source(); source.request(Long.MAX_VALUE); Buffer buffer = source.buffer(); Charset charset = UTF8; MediaType contentType = responseBody.contentType(); if (contentType != null) { try { charset = contentType.charset(UTF8); } catch (UnsupportedCharsetException e) { e.printStackTrace(); } } rBody = buffer.clone().readString(charset); if (!TextUtils.isEmpty(rBody)) { rBody = decodeUnicode(rBody); } } sbf.append("\n收到響應: code ==> " + response.code()) .append("\nResponse: " + rBody); LogUtils.i("網絡請求", sbf.toString()); return response; } } 複製代碼
看看打印出來的效果,很清晰哦:
看這一點,要打印請求參數,返回參數,head等信息仍是要經過okhttp的攔截器去作。這是吐槽Retrofit封裝的一點
首先是我我的觀點。此框架適合大型一點項目,且此框架是代碼邏輯簡潔,清晰,分離出視圖層和Model層邏輯處理,適合作單元測試,且使activity裏的代碼減小,並且不會由於有些線程,異步的處理,有activity的引用致使沒法回收,引發一系列問題。仍是要強調一下只是代碼邏輯簡潔,不是代碼簡潔。由於他要增長不少類。那麼來講說怎麼代碼邏輯簡潔(適合其餘人接手項目,和團隊開發):
首先google官方demo,也是把功能頁面模塊化了。這樣你看個人分包,結合個人圖片展現一看就明白
是否是一看就很清楚,好比getfragment的全部東西都在裏面。固然你還能夠在裏面繼續分包
public interface GetContract { //view只有2個更新ui的方法 interface View extends BaseView { // 一、獲取get數據,更新ui void showGetData(String data); // 二、獲取get數據失敗,更新ui void showGetError(String msg); } //get的prensenter只有一個獲取get數據的數據請求 interface Prensenter { // 一、開啓get網絡請求 public void getGetData(String params); } } 複製代碼
好比你要看一個頁面有什麼更新UI的操做或者有什麼網絡請求,只要看契約類就對這個頁面很瞭解瞭如:
一、view就是至關於MVP的V層,通常被activity或者Fragment實現,能夠看到view裏有2個方法:
二、Prensenter就是MVP的P層須要使用到的方法,要搞清楚,其實還有一個實體類GetPresenter。實體類實現GetContract.Prensenter。能夠看到裏面就一個getGetData。的網絡請求。爲何這麼寫,第一契約類能夠減小類的數目,第二更重要的一點是你能夠很快的清楚你開發模塊的功能和操做
還有一點要注意,由於使用RxJava結合Retrofit,我把通用的方法所有封裝了BasePresenter裏。由於RxJava的鏈式編程,因此我直接省略掉了model層,固然你能夠寫一個接口,而後寫個model層把數據回調給Presenter。這裏要注意MVP,MVP只是開發框架,是咱們借鑑的開發思路。可能100我的有100個不一樣的MVP框架,這裏我是說說個人想法,你們能夠借鑑和學習。
public class RetrofitManager { //省略部分代碼,便於理解 private static RetrofitManager retrofitManager; private OkHttpClient okHttpClient; private Retrofit retrofit; //這是我retrofit的網絡請求配置 private RetrofitApiService retrofitApiService; private RetrofitManager() { //初始化Okhttp initOkHttpClient(); //初始化retrofit initRetrofit(); } public static RetrofitManager getRetrofitManager() { if (retrofitManager == null) { synchronized (RetrofitManager.class) { if (retrofitManager == null) { retrofitManager = new RetrofitManager(); } } } return retrofitManager; } private void initRetrofit() { retrofit = new Retrofit.Builder() .baseUrl(SystemConst.DEFAULT_SERVER) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) .build(); retrofitApiService = retrofit.create(RetrofitApiService.class); } public static RetrofitApiService getApiService() { return retrofitManager.retrofitApiService; } private void initOkHttpClient() { okHttpClient = new OkHttpClient.Builder() //設置緩存文件路徑,和文件大小 .cache(new Cache(new File(Environment.getExternalStorageDirectory() + "/okhttp_cache/"), 50 * 1024 * 1024)) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .addInterceptor(new HttpLogInterceptor()) //設置在線和離線緩存 .addInterceptor(OfflineCacheInterceptor.getInstance()) .addNetworkInterceptor(NetCacheInterceptor.getInstance()) .build(); } } 複製代碼
回想以前咱們有個BaseView。裏面有2個方法showLoading和hideLoading (只要記住你們通用的方法封裝成base,有些頁面不一樣就不調用就好了) 代碼以下:
public interface BaseView { //顯示正在加載loading void showLoading(String message); // 關閉正在加載loading void hideLoading(); //防止RxJava內存泄漏,能夠暫且忽略 LifecycleTransformer bindLifecycle(); } 複製代碼
那麼GetFragment契約類就是(還要說下這裏的presenter接口只是GetPresenter所要用的方法,放在契約類裏,一目瞭然):
public interface GetContract { //view只有2個更新ui的方法 interface View extends BaseView { // 一、獲取get數據,更新ui void showGetData(String data); // 二、獲取get數據失敗,更新ui void showGetError(String msg); } //get的prensenter只有一個獲取get數據的數據請求 interface Prensenter { // 一、開啓get網絡請求 public void getGetData(String params); } } 複製代碼
咱們想象有什麼通用方法是在BasePresenter裏的?固然你能夠把一些複雜的寫不少步驟的網絡請求封裝在這裏,好比咱們項目裏的下載文件等,這裏咱們講簡單的通用方法
GetPresenter須要view的引用 因此有:一、setView加入引用 二、view = null置空引用,防止oom
//這個是爲了退出頁面,取消請求的 public CompositeDisposable compositeDisposable; // 綁定的view private V mvpView; //綁定view,通常在初始化中調用該方法 public void attachView(V mvpView) { this.mvpView = mvpView; compositeDisposable = new CompositeDisposable(); } //置空view,通常在onDestroy中調用 public void detachView() { this.mvpView = null; //退出頁面的時候移除全部網絡請求 removeDisposable(); } //須要退出頁面移除網絡請求的加入進來 public void addDisposable(Disposable disposable) { compositeDisposable.add(disposable); } //退出頁面移除全部網絡請求 public void removeDisposable() { if (compositeDisposable != null) { compositeDisposable.dispose(); } } 複製代碼
使用一個方法快速獲取RetrofitManager裏的網絡配置
public RetrofitApiService apiService() { return RetrofitManager.getRetrofitManager().getApiService(); } 複製代碼
大部分的網絡請求都有showLoading和hideLoading,還有線程切換,封裝進來
//這裏我多加了個是否顯示loading的標識和loading上的文字,想偷懶能夠用方法重載把這2個參數默認 public <T> Observable<T> observe(Observable<T> observable, final boolean showDialog, final String message) { return observable.subscribeOn(Schedulers.io()) .doOnSubscribe(new Consumer<Disposable>() { @Override public void accept(Disposable disposable) throws Exception { if (showDialog) { mvpView.showLoading(message); } } }) .subscribeOn(AndroidSchedulers.mainThread()) .doFinally(new Action() { @Override public void run() throws Exception { if (showDialog) { mvpView.hideLoading(); } } }) .observeOn(AndroidSchedulers.mainThread()) //防止RxJava內存泄漏 .compose(mvpView.bindLifecycle()); } 複製代碼
那麼咱們的BasePresenter(移除部分代碼更清晰)
//這裏加上泛型,第一在使用Presenter一眼就看出對應哪一個View,其次肯定咱們V的類型 public abstract class BasePresenter<V extends BaseView> { public CompositeDisposable compositeDisposable; private V mvpView; public void attachView(V mvpView) { this.mvpView = mvpView; compositeDisposable = new CompositeDisposable(); } public void detachView() { this.mvpView = null; removeDisposable(); } //檢查是否有view的引用 public boolean isViewAttached() { return mvpView != null; } //獲取view的引用 public V getView() { return mvpView; } public RetrofitApiService apiService() { return RetrofitManager.getRetrofitManager().getApiService(); } //須要退出頁面移除網絡請求的加入進來 public void addDisposable(Disposable disposable) { } //退出頁面移除全部網絡請求 public void removeDisposable() {} //省的寫線程切換和showloading和hide的複雜操做 public <T> Observable<T> observe(Observable<T> observable, final boolean showDialog, final String message) {} } 複製代碼
那麼GetPresenter就是這樣
//省略部分代碼,便於理解。實體GetPresenter實現GetContract.Prensenter接口,裏面就一個getGetData方法 public class GetPresenter extends BasePresenter<GetContract.View> implements GetContract.Prensenter { @Override public void getGetData(String params) { if (!isViewAttached()) { //若是沒有View引用就不加載數據 return; } //BasePresenter裏有了封裝,切換線程和放置內存泄漏的 .compose(mvpView.bindLifecycle())都不用寫了 //代碼越能偷懶,說明框架越是封裝的完美 observe(apiService().getGank(params)) .subscribe(new Consumer<GankFatherBean>() { @Override public void accept(GankFatherBean gankFatherBean) throws Exception { getView().showGetData(gankFatherBean.getTitle()); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { getView().showGetError(throwable.toString()); } }); } } 複製代碼
由於使用RxJava,若是使用不當很容易形成RxJava內存泄漏,因此官方也出了方法繼承RxFragment
由於fragment是做爲view是要實現view接口的,同時每一個fragment都要有presenter去調用方法
初步以下(省了部分代碼,便於理解):
public abstract class BaseFragment<T extends BasePresenter> extends RxFragment implements BaseView { public T mPresenter; public abstract T cretaePresenter(); protected View mContentView; private Unbinder mUnbinder; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 避免屢次從xml中加載佈局文件 mPresenter = cretaePresenter(); if (mPresenter != null) { mPresenter.attachView(this); } return mContentView; } @Override public void onDestroy() { super.onDestroy(); if (mPresenter != null) { mPresenter.detachView(); mPresenter = null; } } @Override public void showLoading(String message) { LoadingDialog.getInstance().show(getActivity(), message); } @Override public void hideLoading() { LoadingDialog.getInstance().dismiss(); } //防止Rx內存泄漏 @Override public LifecycleTransformer bindLifecycle() { LifecycleTransformer objectLifecycleTransformer = bindToLifecycle(); return objectLifecycleTransformer; } } 複製代碼
public class GETFragment extends BaseFragment<GetPresenter> implements GetContract.View { @BindView(R.id.txt_content) TextView txt_content; @Override public GetPresenter cretaePresenter() { return new GetPresenter(); } @Override public int getContentViewId() { return R.layout.fragment_get; } //處理邏輯 @Override protected void processLogic(Bundle savedInstanceState) { } @OnClick(R.id.txt_get) public void getClick() { //請求網絡 mPresenter.getGetData("Android"); } @Override public void showGetData(String data) { txt_content.setText(data); } @Override public void showGetError(String msg) { ToastUtils.showToast(msg); } } 複製代碼
由於使用了強大的RxJava,這裏斷網重連用的就是操做符retryWhen,封裝在BasePresenter裏的observeWithRetry。這裏就很少講了,能夠先把操做符retryWhen瞭解清楚就看明白了
這裏的返回值就是一個disposable,若是主動取消就直接調 disposable.dispose();固然我這裏也封裝了離開頁面取消請求你能夠把它加到addDisposable(Disposable disposable)裏。就不用管了
Disposable disposable = observe(apiService().getGank(params)) .subscribe(new Consumer<GankFatherBean>() { @Override public void accept(GankFatherBean gankFatherBean) throws Exception { getView().showGetData(gankFatherBean.getTitle()); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { getView().showGetError(throwable.toString()); } }); 複製代碼
這裏是在RetrofitManage裏放了一個private ArrayList oneNetList;請求網絡的時候把,方法名當成tag加入進去,若是請求的時候判斷下,oneNetList裏若是已經有次tag,則直接return,在請求成功或者失敗的時候移除tag
這裏仍是利用了okhttp的攔截器,因此這裏再次吐槽下Retrofit。
這裏不清楚的能夠去看我以前的一篇文章EasyOk。講的很詳細,簡直就是如出一轍 本項目具體用法在,GetPresenter裏有。
由於用Restrofit上傳文件是這樣的。
@POST @Multipart Observable<ResponseBody> uploadPic(@Url String url, @Part MultipartBody.Part file); 複製代碼
參數是MultipartBody.Part。不監聽進度參數就是這樣寫的
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file); MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile); 複製代碼
若是要監聽進度,就要重寫RequestBody,把文件寫入進度返回出來,其實和EasyOk的思路是如出一轍的,本項目上傳文件相關類在: retrofitwithrxjava.uploadutils的包裏。用法在postfragment裏。其中包括多張圖片監聽,不一樣key,不一樣圖片。和同一key,不一樣圖片
首先咱們看下載文件請求
@GET @Streaming //10以上用@streaming。不會形成oom,反正你用就是了 Observable<ResponseBody> downloadFile(@Url String url); 複製代碼
返回的是ResponseBody,是否是靈感來了,咱們能夠重寫一個FileDownLoadObserver觀察者,而後利用map操做符,對ResponseBody進行轉換,轉換成File的FileDownLoadObserver,固然有了ResponseBody,文件寫入能夠在FileDownLoadObserver裏。有了文件寫入,那麼進度就來了。本項目下載文件相關類在:retrofitwithrxjava.downloadutils包裏。用法在downloadfragment裏。
斷點下載
咱們知道斷點下載,其實就是head里加個RANG的字段,其餘都是如出一轍的:
@GET @Streaming Observable<ResponseBody> downloadFile(@Url String url, @Header("RANGE")String range); 複製代碼
這個字段是這樣的: String range = "bytes=" + currentLength + "-";
currentLength是你下載文件裏,下載部分文件的長度,你能夠經過file.length獲取。而後讀寫的時候fos = new FileOutputStream(file, true),輸出流必須帶第二個參數true,意思就是繼續拼接的意思。所有功能都講完了。沒有講的太細是由於我以爲大部分額外功能都是重寫,並且都是利用okhttp,其實看過了我之前的EasyOk。其實就明白了。這也是我單獨吐槽Retrofit的緣由。我相信封裝的很完美的okhttp,確定比這個用起來好用,惋惜人家支持Rxjava。不過經過這個RANGE,其實要下載一個文件的話能夠分不少段下載,由於看他的字面意思就知道了"bytes=start-end",指的是一段範圍,我上面寫的是"bytes=start-"就是默認下載完,因此有些app開啓多線程下載,包括迅雷開啓vip就下的快,其實就是給你多開了幾條線程下載了。!!