RxJava + Retrofit + MVP(看完還不明白,吐槽我。適合初學者,VIP版MVP框架!!)

前言:仍是由於公司特別閒,把基礎總結總結。若是對相關知識含糊不清,會致使你沒法隨意擴展你想要的框架和功能。可是以爲做爲程序員這行業,只要踏進來了,不是在學習的路上就是在被淘汰的路上,加油!!html

本文章將針對全部用戶,你們根據本身的所需自行跳過一些章節。首先仍是說說這個框架支持幹一些什麼事:java

  • 支持全部網絡請求類型,get,post,put...(廢話了!!Retrofit已經幹了全部事情)
  • 支持上傳文件並監聽上傳進度
  • 支持下載文件和斷點下載並監聽下載進度
  • 有網絡時,支持在線緩存(鏈接網絡時的有效期)
  • 斷開網絡,支持離線緩存(離線緩存有效期)
  • 屢次請求同一url,在網絡還在請求時,是否只請求一次
  • 支持網絡請求失敗,自動重連
  • 支持取消網絡請求

一、先看看一些簡單效果(建議打開權限)

get請求 post請求 上傳文件
下載文件

好了就這麼多,幾乎包括了你能用到的功能了。那麼接下來咱們分別來介紹RxJava是什麼?Retrofit是什麼?爲何使用MVP? 固然我這裏是引導你怎麼去學習RxJava和Retrofit,對這2方便本文章不會細將,只是把我封裝的這些會講的特別細,封裝RxJava結合Retrofit的MVP的實戰框架。正文開始了,初學者得一步一步來哦:git

二、RxJava 是什麼?

以觀察者模式爲主的一個響應式編程。響應式編程是一種基於異步數據流概念的編程模式。數據流就像一條河:它能夠被觀測,被過濾,被操做,或者爲新的消費者與另一條流合併爲一條新的流。固然他不僅是異步,也有同步。 它最大的有點是能夠來回切換線程,不得不說很哇塞!!程序員

直接上乾貨,組好都看一遍,花不了多長時間:github

2.一、拋物線的RxJava講解,適合初學者

2.二、nanchen簡書熱門文章,RxJava教程系列

2.三、RxJava全部操做符

2.四、RxJava系列文章

我的建議在學習的時候,最好是手敲代碼。雖然不少重複代碼,但這會讓你更加熟悉,甚至你都能拼出這些單詞。操做符只建議記住前2篇的就好了,由於實在太多了你也記不住,第3篇適合當api,沒事看看,有時候當api查查就行了編程

三、Retrofit 是什麼?

這裏我好想吐槽,好想吐槽!api

Retrofit就是基於OKHttp封裝的,只不過是原做者封裝的,以註解的方式配置好就能請求網絡了。說實話它或許真的不如一些優秀的OKHttp封裝,可是誰讓他支持RxJava呢。Retrofit結合RxJava使用簡直就是一大利器。剛剛說到了RxJava能夠切換線程,你能夠把全部耗時操做放在 子線程,到更改ui回調的時候切換到主線程就行了。緩存

下面是Retrofit的乾貨:網絡

3.一、很詳細的 Retrofit 2.0 使用教程,來自Carson_Ho

3.二、Retrofit結合RxJava簡單使用

看完了這些相信你對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封裝的一點

四、MVP開發框架

首先是我我的觀點。此框架適合大型一點項目,且此框架是代碼邏輯簡潔,清晰,分離出視圖層和Model層邏輯處理,適合作單元測試,且使activity裏的代碼減小,並且不會由於有些線程,異步的處理,有activity的引用致使沒法回收,引發一系列問題。仍是要強調一下只是代碼邏輯簡潔,不是代碼簡潔。由於他要增長不少類。那麼來講說怎麼代碼邏輯簡潔(適合其餘人接手項目,和團隊開發):

4.一、先看看項目的分包

首先google官方demo,也是把功能頁面模塊化了。這樣你看個人分包,結合個人圖片展現一看就明白

  • downfragemnt---->就是下載文件的頁面
  • getfragment----->get請求的頁面
  • main------------>mianActivity頁面
  • postfragment----->post請求頁面
  • uploadfragment---->上傳文件頁面

是否是一看就很清楚,好比getfragment的全部東西都在裏面。固然你還能夠在裏面繼續分包

4.二、在來看看契約類,我的以爲更加清晰,咱們來看看get的器樂類GetContract

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個方法:

  • showGetData 獲取get請求數據,更新UI
  • showGetError get請求失敗了,更新失敗UI
  • BaseView 放的都是通用的方法,這裏放了2個:showLoading,hideLoading(固然有些網絡請求不須要轉loading,那麼不調用就行了)

二、Prensenter就是MVP的P層須要使用到的方法,要搞清楚,其實還有一個實體類GetPresenter。實體類實現GetContract.Prensenter。能夠看到裏面就一個getGetData。的網絡請求。爲何這麼寫,第一契約類能夠減小類的數目,第二更重要的一點是你能夠很快的清楚你開發模塊的功能和操做

還有一點要注意,由於使用RxJava結合Retrofit,我把通用的方法所有封裝了BasePresenter裏。由於RxJava的鏈式編程,因此我直接省略掉了model層,固然你能夠寫一個接口,而後寫個model層把數據回調給Presenter。這裏要注意MVP,MVP只是開發框架,是咱們借鑑的開發思路。可能100我的有100個不一樣的MVP框架,這裏我是說說個人想法,你們能夠借鑑和學習。

五、RxJava結合Retrofit的MVP

5.一、封裝Retrofit,其實和OkHttp沒有區別

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();
    }
}

複製代碼

5.二、好比咱們來寫GetFragment這塊。首先寫契約類

回想以前咱們有個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);
    }
}
複製代碼

5.三、接下來是GetPresenter的實體類,固然這裏有個BasePresenter(如下講解移除部分代碼更清晰)

咱們想象有什麼通用方法是在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());
                    }
                });
        
    }
}
複製代碼

5.四、其次是BaseFragment

由於使用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;
    }
}
複製代碼

5.五、在GetFragment裏使用就是這樣(省略部分代碼,便於理解)

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。

  • 離線緩存攔截器 OfflineCacheInterceptor
  • 在線緩存攔截器 NetCacheInterceptor

這裏不清楚的能夠去看我以前的一篇文章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就下的快,其實就是給你多開了幾條線程下載了。!!

但願個人文章對你有幫助,看到這就給我一個贊把

最近項目引進了Dagger2的使用,MVP使用了Dagger2簡直不要太舒服了!詳細教程

本MVP框架引入Dagger2教程地址

本demo github地址

相關文章
相關標籤/搜索