基於MVP模式,設計自己的RxJava+Retrofit2+Okhttp3+Rxlifecycle開發框架

小小力力發表於2018-06-22

在開發階段,如果有一個好的開發框架,不僅能提高開發效率,更能減少後期維護的時間。結合自己的實際,封裝了一套MVP+RxJava+Retrofit2+Okhttp3+Rxlifecycle+Butterknife的開發框架。架構層:V層只負責檢視的操作,P層只負責資料的互動,M層負責邏輯的處理,應該屬於完整意義上的MVP程式碼結構模式。網路層包括:普通的get/post請求,檔案上傳、檔案帶進度下載,無網路快取策略,請求帶載入的dialog等。

先直接上程式碼連結:

GitHub: https://github.com/634930172/JohnDevFrame

點選開啟連結

CSDN: https://download.csdn.net/download/a634930172a/10489294

點選開啟連結

 

本項新增的依賴如下:

//Network
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
//RxJava
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex:rxjava:1.3.0'
//RxLifecycle
implementation 'com.trello:rxlifecycle:0.3.0'
implementation 'com.trello:rxlifecycle-components:0.3.0'
//ButterKnife
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor'com.jakewharton:butterknife-compiler:8.8.1'


MVP模式設計

MVP模式大家應該都有了解過,V層想要得到某個資料然後做檢視操作,需通過P層向M層傳送請求,M層處理後的結果回撥給P層,P層再回撥給V層,期間的傳遞過程都是通過介面訪問的。


本專案的結構如下所示:


 

下面看看封裝MVP的思路吧。

首先是對Base各個基類的封裝。BaseAct如下所示:

public abstract class BaseAct<V,extends BasePresenter<V>> extends RxAppCompatActivity  {
    
public Activity mActivity;
    public 
mPresenter;


    
@Override
    
public void setContentView(@LayoutRes int layoutResID) {
        
super.setContentView(layoutResID);
        
ButterKnife.bind(this);
        
mActivity this;
    
}

    
@Override
    
public void setContentView(View view) {
        
super.setContentView(view);
        
ButterKnife.bind(this);
        
mActivity this;
    
}

    
@Override
    
protected void onCreate(Bundle savedInstanceState) {
        
super.onCreate(savedInstanceState);
        
mPresenter = createPresenter();
        
mPresenter.attachView((Vthis);
        
setContentView(getLayoutId());
        
initView();
        
initData();
        
initEvent();

    
}


    
protected abstract int getLayoutId();

    protected abstract 
createPresenter();


    
/**
     * 
初始化View
     */
    
protected void initView() {
    }

    
/**
     * 
初始化資料
     
*/
    
protected void initData() {
    }

    
/**
     * 
初始化事件
     
*/
    
protected void initEvent() {
    }

    
@Override
    
protected void onDestroy() {
        
if (mPresenter != null) {
           
mPresenter.detachView();
        
}
        
super.onDestroy();

    
}


}

其中V為View的泛型,P為Presenter的泛型,RxAppCompatActivity是Rxlifecycle包下的,如下所示。該類繼承了AppCompatActivity,增加了bindUntilEvent()和bindtoLifecycle()等方法,配合網路請求繫結生命週期的方法,可以避免記憶體洩漏。mPresenter通過呼叫attachView()和detachView()實現手動繫結和解綁,也起到了防止記憶體洩漏的作用。

public class RxAppCompatActivity extends AppCompatActivity implements ActivityLifecycleProvider {

    private final BehaviorSubject<ActivityEvent> lifecycleSubject = BehaviorSubject.create();

    @Override
    public final Observable<ActivityEvent> lifecycle() {
        return lifecycleSubject.asObservable();
    }

    @Override
    public final <T> Observable.Transformer<T, T> bindUntilEvent(ActivityEvent event) {
        return RxLifecycle.bindUntilActivityEvent(lifecycleSubject, event);
    }

    @Override
    public final <T> Observable.Transformer<T, T> bindToLifecycle() {
        return RxLifecycle.bindActivity(lifecycleSubject);
    } 

BasePresenter如下所示:

public abstract class BasePresenter<V> implements Presenter<V> {

    protected WeakReference<V> mMvpView;

    @Override
    public void attachView(V mvpView) {
        this.mMvpView = new WeakReference<>(mvpView);
    }


    protected V getView() {
        return mMvpView.get();
    }


    @Override
    public void detachView() {
        if (mMvpView != null) {
            mMvpView.clear();
            mMvpView = null;
        }
    }

BasePresenter將BaseAct繫結的View檢視包裝成弱引用,防止了記憶體洩漏(三重保障,穩穩的不洩漏)。Presenter是一個提供方法的介面,如下:

public interface Presenter<V> {

    void attachView(V view);

    void detachView();

}

BaseModule如下所示:

public class BaseModule {

    protected BaseAct mActivity;
    protected BaseFrag mFragment;

    public BaseModule(BaseAct act){
        this.mActivity=act;
    }

    public BaseModule(BaseFrag frag){
        this.mFragment=frag;
    }

    protected  <T> void addActSubscribe(Observable<T> observable,Subscriber<T> subscriber ) {
        observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .compose(mActivity.<T>bindUntilEvent(ActivityEvent.DESTROY))//繫結生命週期,防止記憶體洩露
                .subscribe(subscriber);
    }

    protected  <T> void addFragSubscribe(Observable<T> observable,Subscriber<T> subscriber ) {
        observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .compose(mFragment.<T>bindUntilEvent(FragmentEvent.DESTROY))//繫結生命週期,防止記憶體洩露
                .subscribe(subscriber);
    }


}

 裡面將原始的網路請求方法抽了出來,並持有Act或者Frag的物件。只要子類繼承了BaseModule,就可以在Module層為所欲為處理自己想要的邏輯了。

下面是具體的MainAct,MainPresenter,MainModelImp,如下所示:

public class MainAct extends BaseAct<MainView, MainPresenter> implements MainView {

    @BindView(R.id.bt)
    Button bt;
    @BindView(R.id.bt2)
    Button bt2;
    @BindView(R.id.bt3)
    Button bt3;
    @BindView(R.id.bt4)
    Button bt4;
    @BindView(R.id.bt5)
    Button bt5;
    @BindView(R.id.bt6)
    Button bt6;
    @BindView(R.id.tv)
    TextView tv;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected MainPresenter createPresenter() {
        return new MainPresenter(this);
    }


    /**
     * 點選事件 業務請求
     */
    @OnClick({R.id.bt, R.id.bt2, R.id.bt3, R.id.bt4, R.id.bt5, R.id.bt6})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.bt://get請求
                mPresenter.SimpleGet();
                break;
            case R.id.bt2://post請求
                mPresenter.SimplePost();
                break;
            case R.id.bt3://單圖上傳
                mPresenter.fileUpload();
                break;
            case R.id.bt4://多圖上傳
                mPresenter.fileUploads();
                break;
            case R.id.bt5://檔案帶進度下載
                mPresenter.fileDownLoad();
                break;
            case R.id.bt6://無網路取快取,測試時將網路關閉
                mPresenter.simpleGetCache();
                break;
        }
    }

    //------------------------------業務回撥---------------------------

    /**
     * get請求回撥
     */
    @Override
    public void simpleGetCallback(String str) {
        tv.setText(String.valueOf("get成功請求返回資料: " + str));
    }


    /**
     * Post請求回撥
     */
    @Override
    public void simplePostCallback(JsonObject jsonObject) {
        tv.setText(String.valueOf("post成功請求返回資料: " + jsonObject));
    }


    /**
     * 單圖上傳回撥
     */
    @Override
    public void fileUploadCallback(JsonObject jsonObject) {
        tv.setText(String.valueOf("單圖上傳成功返回資料: " + jsonObject));
    }


    /**
     * 多圖上傳回撥
     */
    @Override
    public void fileUploadsCallback(JsonObject jsonObject) {
        tv.setText(String.valueOf("多圖上傳成功返回資料: " + jsonObject));
    }


    /**
     * 檔案下載回撥
     */
    @Override
    public void fileDownLoadCallback() {
        tv.setText(String.valueOf("檔案下載成功"));
    }

    /**
     * 無網路取快取回撥
     */
    @Override
    public void noNetworkCacheCallback(String str) {
        tv.setText(String.valueOf("無網路快取資料: " + str));
    }


}

 裡面只有mPresenter請求和回撥成功後檢視的操作,感覺還是很清爽的。

 MainPresenter:

public class MainPresenter extends BasePresenter<MainView> implements MainModel.LoadingCallBack {

    private MainModel mMainModelImp;

    public MainPresenter(BaseAct act) {
        mMainModelImp = new MainModelImp(act);
    }

    //------------------------V層請求-------------------------------

    /**
     * 普通get請求
     */
    public void SimpleGet() {
        mMainModelImp.getSimpleData(this);
    }

    /**
     * 普通post請求
     */
    public void SimplePost() {
        mMainModelImp.getPostData(this);
    }


    /**
     * 單圖上傳上傳
     */
    public void fileUpload() {
        mMainModelImp.fileUpload(this);
    }

    /**
     * 多圖片上傳
     */
    public void fileUploads() {
        mMainModelImp.fileUploads(this);
    }

    /**
     * 檔案帶進度下載
     */
    public void fileDownLoad() {
        mMainModelImp.downLoadFile(this);
    }

    /**
     * 無網路取快取
     */
    public void simpleGetCache() {
        mMainModelImp.getSimpleCacheData(this);
    }

    //---------------------M層回撥-----------------------------------

    /**
     *
     */
    @Override
    public void simpleDataCompleted(String data) {
        getView().simpleGetCallback(data);
    }

    @Override
    public void simplePostCompleted(JsonObject jsonObject) {
        getView().simplePostCallback(jsonObject);
    }

    @Override
    public void fileUploadCompleted(JsonObject jsonObject) {
        getView().fileUploadCallback(jsonObject);
    }

    @Override
    public void fileUploadsCompleted(JsonObject jsonObject) {
        getView().fileUploadsCallback(jsonObject);
    }

    @Override
    public void downLoadFileCompleted() {
        getView().fileDownLoadCallback();
    }

    @Override
    public void simpleCacheDataCompleted(String data) {
        getView().noNetworkCacheCallback(data);
    }


}

MainPresenter只負責將V層傳過來的命令傳送給M層,然後將M層處理的結果回撥給V層,其中沒有任何的邏輯操作,P層的職責是很明確的。

MainModelImp:

public class MainModelImp extends BaseModule implements MainModel {


    public MainModelImp(BaseAct act) {
        super(act);
    }

    /**
     * Mget請求的方法,帶dialog形式請求
     */
    @Override
    public void getSimpleData(final LoadingCallBack callBack) {
        addActSubscribe(HttpClient.getService(AppService.class).simpleGet(), new RxRequestCallBack<String>(mActivity) {
            @Override
            public void onSuccess(HttpResult<String> httpResult) {
                callBack.simpleDataCompleted(httpResult.getData());
            }
        });
    }

    /**
     * Mpost請求資料的方法
     */
    @Override
    public void getPostData(final LoadingCallBack callBack) {

        String GROUP_ID = "298cea3dabeb1545004451982d6c04f6";
        addActSubscribe(HttpClient.getService(AppService.class).simplePost(GROUP_ID), new RxRequestCallBack<JsonObject>() {
            @Override
            public void onSuccess(HttpResult<JsonObject> httpResult) {
                callBack.simplePostCompleted(httpResult.getData());
            }
        });

    }

    /**
     * post單圖上傳
     */
    @Override
    public void fileUpload( final LoadingCallBack callBack) {
        Bitmap bitmap = BitmapFactory.decodeResource(mActivity.getResources(), R.mipmap.ic_launcher);
        byte[] bytes = BitmapUtil.bitmapToBytes(bitmap);//拿到陣列
        UploadUtil.Builder builder = new UploadUtil.Builder().
                addByte("upload", bytes);//檔案上傳工具類
        addActSubscribe(HttpClient.getService(AppService.class).uploadPic(builder.build()), new RxRequestCallBack<JsonObject>() {
            @Override
            public void onSuccess(HttpResult<JsonObject> httpResult) {
                callBack.fileUploadCompleted(httpResult.getData());
            }
        });
    }

    /**
     * post多圖上傳
     */
    @Override
    public void fileUploads( final LoadingCallBack callBack) {
        Bitmap bitmap = BitmapFactory.decodeResource(mActivity.getResources(), R.mipmap.ic_launcher);
        byte[] bytes = BitmapUtil.bitmapToBytes(bitmap);//拿到陣列
        UploadUtil.Builder builder = new UploadUtil.Builder();
        //多張圖片
        for (int i = 0; i < 3; i++) {
            builder.addByte("image[]", bytes, i);
        }
        addActSubscribe(HttpClient.getService(AppService.class).uploadPics(builder.build()), new RxRequestCallBack<JsonObject>() {
            @Override
            public void onSuccess(HttpResult<JsonObject> httpResult) {
                callBack.fileUploadsCompleted(httpResult.getData());
            }
        });
    }

    /**
     * 檔案下載
     */
    @Override
    public void downLoadFile(final LoadingCallBack callBack) {
        String fileName = "app.apk";
        File externalFilesDir = ContextUtil.getContext().getExternalFilesDir(null);//外部儲存的私有目錄,應用刪除後此檔案也會被刪除
        final FileCallBack<ResponseBody> downLoadCallback = new FileCallBack<ResponseBody>(externalFilesDir.toString(), fileName) {

            @Override
            public void onSuccess(ResponseBody responseBody) {
                callBack.downLoadFileCompleted();
            }

            @Override
            public void progress(long progress) {
                LogUtil.e("progress: " + progress / 1024 + "kb  total: " + FileLoadEvent.getInstance().getTotal() / 1024 + "kb");
            }

            @Override
            public void onStart() {
                LogUtil.e("onStart");
            }

            @Override
            public void onCompleted() {
                LogUtil.e("onCompleted");
            }

            @Override
            public void onError(Throwable e) {
                if (e instanceof SocketTimeoutException) {
                    LogUtil.e("SocketTimeoutException: 網路中斷,請檢查您的網路狀態");
                } else if (e instanceof ConnectException) {
                    LogUtil.e("ConnectException: 網路中斷,請檢查您的網路狀態");
                } else if (e instanceof UnknownHostException) {
                    LogUtil.e("UnknownHostException: 網路中斷,請檢查您的網路狀態");
                } else {
                    LogUtil.e("onError:其他錯誤:" + e.getMessage() + "  cause: " + e.getCause());
                }
                e.printStackTrace();
            }
        };
        //重寫了ResponseBodyHttpClient
        String URL = "http://download.fir.im/v2/app/install/5818acbcca87a836f50014af?download_token=a01301d7f6f8f4957643c3fcfe5ba6ff";
        DownLoadHttpClient.getService(AppService.class).download(URL)
                .subscribeOn(Schedulers.io())//請求網路 在排程者的io執行緒
                .observeOn(Schedulers.io()) //指定執行緒儲存檔案
                .doOnNext(new Action1<ResponseBody>() {
                    @Override
                    public void call(ResponseBody body) {
                        downLoadCallback.saveFile(body);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread()) //在主執行緒中更新ui
                .compose(mActivity.<ResponseBody>bindUntilEvent(ActivityEvent.DESTROY))
                .subscribe(new FileSubscriber<ResponseBody>(downLoadCallback));
    }


    /**
     * 無網路取快取
     */
    @Override
    public void getSimpleCacheData(final LoadingCallBack callBack) {
        addActSubscribe(HttpClient.getService(AppService.class).simpleGetCache(), new RxRequestCallBack<String>() {
            @Override
            public void onSuccess(HttpResult<String> httpResult) {
                callBack.simpleCacheDataCompleted(httpResult.getData());
            }
        });
    }


}

這裡是MainModelImp類,上面的這些網路請求方法可以先不管,我們只要知道這些複雜的邏輯都在這裡處理,然後通過介面將結果回撥給P層就可以了。


網路層

網路層的設計,程式碼結構如下:


 雖然看起來有那麼多類別,但是裡面的結構還是很清晰的。我們先來看最關鍵的RxRequestCallBack類,如下所示:

public abstract class RxRequestCallBack<T> extends Subscriber<HttpResult<T>> implements DialogInterface.OnCancelListener {

    private LoadingDialog dialog;
    private static final String TAG="RxRequestCallBack";
    /**
     * 網路請求成功的回撥方法,必須重寫
     */
    public abstract void onSuccess(HttpResult<T> httpResult);

    /**
     * 預設訪問請求
     */
    protected RxRequestCallBack() {

    }

    @Override
    public void onStart() {
        super.onStart();
        if (dialog != null) {
            dialog.show();
            LogUtil.e("dialogShow");
        }
    }

    /**
     * 請求有進度框
     */
    public RxRequestCallBack(Context context) {
        super();
        dialog = new LoadingDialog(context);
        dialog.setOnCancelListener(this);
    }

    /**
     * 請求有進度框
     */
    public RxRequestCallBack(Context context, String loadingMsg) {
        super();
        dialog = new LoadingDialog(context, loadingMsg);
        dialog.setOnCancelListener(this);
    }

    @Override
    public void onCompleted() {
        dismissDialog();
        Log.e(TAG, "onCompleted: " );
    }

    @Override
    public void onError(Throwable e) {
        dismissDialog();
        if (e instanceof SocketTimeoutException) {
            Log.e(TAG,"SocketTimeoutException: 網路中斷,請檢查您的網路狀態");
        } else if (e instanceof ConnectException) {
            Log.e(TAG,"ConnectException: 網路中斷,請檢查您的網路狀態");
        } else if (e instanceof UnknownHostException) {
            Log.e(TAG,"UnknownHostException: 網路中斷,請檢查您的網路狀態");
        } else {
            Log.e(TAG,"onError:其他錯誤:" + e.getMessage() + "  cause: " + e.getCause());
        }
        e.printStackTrace();
    }

    @Override
    public void onNext(HttpResult<T> tHttpResult) {
        Log.e(TAG,"onNext:   code--" + tHttpResult.getCode() + "--msg--" + tHttpResult.getMsg());
        if (tHttpResult.getCode() == 401) {
            Log.e(TAG,"onNext:  Json_error");
        } else if (tHttpResult.getCode() != 200) {
            onFailed(tHttpResult);
            Log.e(TAG,"onNext:  onFailed");
        } else {
            onSuccess(tHttpResult);
            Log.e(TAG,"onNext:  onSuccess");
        }
    }

    protectedvoid onFailed(HttpResult<T> tHttpResult) {
    }


    private void dismissDialog() {
        //延時消除dialog,為了演示效果  實際專案中可去掉
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (dialog != null && dialog.isShowing()) {
                    dialog.dismiss();
                    Log.e(TAG,"dialogDismiss");
                }
            }
        },3000);


    }

    /**
     * dialog消失的時候取消訂閱
     */
    @Override
    public void onCancel(DialogInterface dialogInterface) {
        if (!this.isUnsubscribed()) {
            this.unsubscribe();
        }
    }


}

該類繼承了Subscriber,Subscriber的結構,點開檢視:

public abstract class Subscriber<T> implements Observer<T>, Subscription {

是一個抽象類,它實現了Observer和Subscription,所以它既可以充當Observer的實現類,又可以通過Subscription實現訂閱以及取消訂閱的功能。HttpResult<T>類將請求結果的最外層做了統一的處理,HttpResult類如下: 

public class HttpResult<T> {
    /**
     * 錯誤碼
     */
    private int code;
    /**
     * 錯誤資訊
     */
    private String msg;
    /**
     * 訊息響應的主體
     */
    private T data;
    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }


}

因服務端定義最外層的欄位不一樣,我們可以自己修改裡面的欄位,這裡例子裡面只定義了code、msg還有裡面的資料data,由於data不是固定的,所以定義成泛型。這樣做的好處就是可以先對最外層統一判斷code和msg的值,再處理返回資料後的邏輯。

再回來看RxRequestCallback,繼承Subsriber後,會實現Observer的三個方法,onNext()、onError()和onCompleted(),Subsriber還有一個onStart()方法,可以處理開始請求時的一些邏輯操作,我將dialog的顯示邏輯放在這了,然後在請求結束後關閉。正常情況下,資料請求成功會走onStart()、onNext()、onCompleted()三個方法,異常會走onStart()、onError()、onCompleted()三個方法。先看定義成功的邏輯:在這裡我們定義返回成功是服務端返回的code為200的時候,並將後面的處理邏輯用抽象方法抽出來,讓後面具體的請求後具體實現它的邏輯。失敗的邏輯:走OnNext()方法但code不是200的情況,需要處理的話重寫就好了。

第二個是HttpClient類,用來請求資料的client,應該沒有什麼可以說的:

public class HttpClient {

    private static final long cacheSize = 1024 * 1024 * 10;// 快取檔案最大限制大小10M
    private static String cacheDirectory = ContextUtil.getContext().//快取路徑
            getExternalCacheDir().getAbsolutePath() + "/OkHttp_Cache";
    private static Cache cache = new Cache(new File(cacheDirectory), cacheSize)//快取物件

    private Retrofit retrofit;

    private static final int DEFAULT_TIMEOUT = 5;

    //構造方法私有
    private HttpClient() {
        // 建立一個OkHttpClient
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        // 設定網路請求超時時間
        builder.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
        builder.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
        builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
        // 失敗後嘗試重新請求
        builder.retryOnConnectionFailure(true);
        //----------------------------基本設定------------------------------------------------------
        builder.addInterceptor(new AcheInterceptor());//快取攔截器
        builder.cache(cache);//快取設定
        retrofit = new Retrofit.Builder()
                .client(builder.build())
                .addConverterFactory(HttpCovertFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(BaseConfig.BASE_URL)
                .build();
    }

    /**
     * 呼叫單例物件
     */
    private static HttpClient getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * 建立單例物件
     */
    private static class SingletonHolder {
        static HttpClient INSTANCE = new HttpClient();
    }

    /**
     * @return 指定service例項
     */
    public static <T> T getService(Class<T> clazz) {
        return HttpClient.getInstance().retrofit.create(clazz);
    }

}

 AppService

通過getService()得到請求的服務類,服務類如下:

public interface AppService {

    //Get請求
    @GET("https://www.mrallen.cn/test.php")
    Observable<HttpResult<String>> simpleGet();

    //Post請求
    @FormUrlEncoded
    @POST("https://www.mrallen.cn/api/allen_restful/rcloud/group/queryGroupMemberInfo.php")
    Observable<HttpResult<JsonObject>> simplePost(@Field("group_id") String groupId);

    //單圖上傳
    @Multipart
    @POST("allen_restful/upload/upload.php")
    Observable<HttpResult<JsonObject>> uploadPic(@PartMap Map<String, RequestBody> map);

    //多圖上傳
    @Multipart
    @POST("https://www.mrallen.cn/api/allen_restful/upload/testMuchUploadImg.php")
    Observable<HttpResult<JsonObject>> uploadPics(@PartMap Map<String, RequestBody> map);

    //檔案下載
    @Streaming
    @GET
    Observable<ResponseBody> download(@Url String url);

    //無網路取快取
    @Headers(AcheInterceptor.CACHE)
    @GET("https://www.mrallen.cn/test.php")
    Observable<HttpResult<String>> simpleGetCache();


}


get/post請求

有了Observable和RxRequestCallBack,最基本的請求格式應該是這樣的:

介面類:

//Get請求
@GET("https://www.mrallen.cn/test.php")
Observable<HttpResult<String>> simpleGet();

MainModelImp呼叫程式碼:

HttpClient.getService(AppService.class).simpleGet().subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .compose(mActivity.<HttpResult<String>>bindUntilEvent(ActivityEvent.DESTROY))//繫結生命週期,防止記憶體洩露
        .subscribe(new RxRequestCallBack<String>() {
            @Override
            public void onSuccess(HttpResult<String> httpResult) {
                
            }
        });

感覺要呼叫那麼多程式碼,可以將公共的部分抽出來封裝成一個方法,放到BaseModule去,,如下:

protected  <T> void addActSubscribe(Observable<T> observable,Subscriber<T> subscriber ) {
    observable.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .compose(mActivity.<T>bindUntilEvent(ActivityEvent.DESTROY))//繫結生命週期,防止記憶體洩露
            .subscribe(subscriber);
}

所以在MainModelImp的請求就可以變成這樣:

addActSubscribe(HttpClient.getService(AppService.class).simpleGet(), new RxRequestCallBack<String>(mActivity) {
    @Override
    public void onSuccess(HttpResult<String> httpResult) {
        callBack.simpleDataCompleted(httpResult.getData());
    }
});

看起來簡潔多了,如果要處理失敗邏輯,重寫OnFailed()或者OnError() 就可以了。


檔案上傳

說完了基本的使用,get/post請求應該沒有問題了,現在對圖片上傳功能做封裝。

檔案的上傳,主要在UploadUtil工具類,如下:

public class UploadUtil {

    public static class Builder {

        Map<String, RequestBody> params;

        public Builder() {
            params = new LinkedHashMap<>();
        }

        public Map<String, RequestBody> build() {
            return params;
        }

        /**
         * 檔案形式上傳表單
         */
        public Builder addFile(String key, File file) {
            RequestBody requestBody = RequestBody
                    .create(MultipartBody.FORM, file);
            params.put(key + "\"; filename=\"" + file.getName(), requestBody);
            return this;
        }

        /**
         * 引數上傳
         */
        public Builder addString(String key, String value) {
            RequestBody requestBody = RequestBody
                    .create(MultipartBody.FORM, value);
            params.put(key, requestBody);
            return this;
        }

        /**
         * 陣列形式上傳表單
         */
        public Builder addByte(String key, byte[] bytes) {
            RequestBody requestBody = RequestBody
                    .create(MultipartBody.FORM, bytes);
            long currentTime = System.currentTimeMillis();
            params.put(key + "\"; filename=\"" + currentTime + ".jpg", requestBody);
            return this;
        }


        /**
         * 多圖上傳
         */
        public Builder addByte(String key, byte[] bytes, int size) {
            RequestBody requestBody = RequestBody
                    .create(MultipartBody.FORM, bytes);
            long currentTime = System.currentTimeMillis();
            params.put(key + "\"; filename=\"" + currentTime + size + ".jpg", requestBody);
            return this;
        }


    }

}

支援以檔案、陣列的形式上傳,本例都是以陣列的形式上傳的,圖片上傳的使用示例如下:

介面類:

//單圖上傳
@Multipart
@POST("allen_restful/upload/upload.php")
Observable<HttpResult<JsonObject>> uploadPic(@PartMap Map<String, RequestBody> map);

呼叫層,以傳一張預設的luncher圖片為例:

/**
 * post單圖上傳
 */
@Override
public void fileUpload( final LoadingCallBack callBack) {
    Bitmap bitmap = BitmapFactory.decodeResource(mActivity.getResources(), R.mipmap.ic_launcher);
    byte[] bytes = BitmapUtil.bitmapToBytes(bitmap);//拿到陣列
    UploadUtil.Builder builder = new UploadUtil.Builder().
            addByte("upload", bytes);//檔案上傳工具類
    addActSubscribe(HttpClient.getService(AppService.class).uploadPic(builder.build()), new RxRequestCallBack<JsonObject>() {
        @Override
        public void onSuccess(HttpResult<JsonObject> httpResult) {
            callBack.fileUploadCompleted(httpResult.getData());
        }
    });
}

將圖片轉成陣列,UploadUtil工具類確定圖片的名稱和格式,通過builder.build()方法得到Map<String,RequestBody>物件,這樣圖片的上傳就完成了,多檔案上傳的原理是一樣的。

 

檔案帶進度下載

關於檔案的下載,這裡參考了簡書裡的一篇文章,有興趣的看官可以看一下,連結如下:

https://www.jianshu.com/p/060d55fc1c82

介面類的定義如下:

//檔案下載
@Streaming
@GET
Observable<ResponseBody> download(@Url String url);

使用@streaming配合@url註解,將檔案以流的形式處理, okHttp3預設的ResponseBody因為不知道進度的相關資訊,所以需要對其進行改造。可以使用介面監聽進度資訊。改造的ProgressResponseBody如下:

public class ProgressResponseBody extends ResponseBody {
    private ResponseBody responseBody;
    private BufferedSource bufferedSource;

    public ProgressResponseBody(ResponseBody responseBody) {
        this.responseBody = responseBody;

    }

    @Nullable
    @Override
    public MediaType contentType() {
        return  responseBody.contentType();
    }

    @Override
    public long contentLength() {
        FileLoadEvent.getInstance().setTotalProgress(responseBody.contentLength());
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;

    }

    private Source source(Source source) {
        return new ForwardingSource(source) {
            long bytesReaded = 0;
            @Override
            public long read(@NonNull Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                bytesReaded += bytesRead == -1 ? 0 : bytesRead;
                //實時傳送當前已讀取的位元組和總位元組
                RxBus.getInstance().post(FileLoadEvent.getInstance().setProgress(bytesReaded));
                return bytesRead;
            }
        };
    }


}

關鍵是read()方法,每有讀取的位元組,都會有回撥,然後通過RxBus實時更新給FileLoadEvent。總檔案的大小可通過contentLenght()方法獲得。

FileLoadEvent如下:

public class FileLoadEvent {

    private static FileLoadEvent INSTANCE = new FileLoadEvent();

    private FileLoadEvent() {
    }

    public static FileLoadEvent getInstance() {
        return INSTANCE;
    }


    private long total;
    private long bytesLoaded;

    long getBytesLoaded() {
        return bytesLoaded;
    }

   public long getTotal() {
        return total;
    }

    void setTotalProgress(long total) {
        this.total = total;
    }

    FileLoadEvent setProgress(long bytesLoaded) {
        this.bytesLoaded = bytesLoaded;
        return this;
    }

}

作用就是記錄當前進度和總進度。

定義好重寫的ResponseBody後,將它寫入Intercepter攔截器中:

public class ProgressInterceptor implements Interceptor {


    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Response originalResponse = chain.proceed(chain.request());
        //新專案下載下來的
        return originalResponse.newBuilder()
                .body(new ProgressResponseBody(originalResponse.body()))
                .build();
    }
}

 為了獲得當前進度以及儲存下載的檔案的操作,我們定義了一個FileCallBack類,如下所示:

public abstract class FileCallBack<T> {


    private String destFileDir;
    private String destFileName;


    public FileCallBack(String destFileDir, String destFileName) {
        this.destFileDir = destFileDir;
        this.destFileName = destFileName;
        subscribeLoadProgress();
    }

    public abstract void onSuccess(T t);

    public abstract void progress(long progress);

    public abstract void onStart();

    public abstract void onCompleted();

    public abstract void onError(Throwable e);

    public void saveFile(ResponseBody body) {
        InputStream is = null;
        byte[] buf = new byte[2048];
        int len;
        FileOutputStream fos = null;
        try {
            is = body.byteStream();
            File dir = new File(destFileDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            File file = new File(dir, destFileName);
            fos = new FileOutputStream(file);
            while ((len = is.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
            fos.flush();
            unsubscribe();
            //onCompleted();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null)
                    is.close();
                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                Log.e("saveFile", e.getMessage());
            }
        }
    }

    /**
     * 訂閱載入的進度條
     */
    public void subscribeLoadProgress() {
        Subscription subscription = RxBus.getInstance().doSubscribe(FileLoadEvent.class, new Action1<FileLoadEvent>() {
            @Override
            public void call(FileLoadEvent fileLoadEvent) {
                progress(fileLoadEvent.getBytesLoaded());
            }
        }, new Action1<Throwable>() {
            @Override
            public void call(Throwable throwable) {
                //TODO 對異常的處理
            }
        });
        RxBus.getInstance().addSubscription(this, subscription);
    }

    /**
     * 取消訂閱,防止記憶體洩漏
     */
    public void unsubscribe() {
        RxBus.getInstance().unSubscribe(this);
    }

}

然後重寫一個FileSubscriber繼承Subscriber,如下:

public class FileSubscriber<T> extends Subscriber<T> {

    private FileCallBack fileCallBack;

    public FileSubscriber(FileCallBack fileCallBack) {
        this.fileCallBack = fileCallBack;
    }

    @Override
    public void onStart() {
        super.onStart();
        if (fileCallBack != null)
            fileCallBack.onStart();
    }

    @Override
    public void onCompleted() {
        if (fileCallBack != null)
            fileCallBack.onCompleted();
    }

    @Override
    public void onError(Throwable e) {
        if (fileCallBack != null)
            fileCallBack.onError(e);
    }

    @Override
    public void onNext(T t) {
        if (fileCallBack != null)
            fileCallBack.onSuccess(t);
    }




}

最後,在MainModuelImp的呼叫如下:

/**
 * 檔案下載
 */
@Override
public void downLoadFile(final LoadingCallBack callBack) {
    String fileName = "app.apk";
    File externalFilesDir = ContextUtil.getContext().getExternalFilesDir(null);//外部儲存的私有目錄,應用刪除後此檔案也會被刪除
    final FileCallBack<ResponseBody> downLoadCallback = new FileCallBack<ResponseBody>(externalFilesDir.toString(), fileName) {

        @Override
        public void onSuccess(ResponseBody responseBody) {
            callBack.downLoadFileCompleted();
        }

        @Override
        public void progress(long progress) {
            LogUtil.e("progress: " + progress / 1024 + "kb  total: " + FileLoadEvent.getInstance().getTotal() / 1024 + "kb");
        }

        @Override
        public void onStart() {
            LogUtil.e("onStart");
        }

        @Override
        public void onCompleted() {
            LogUtil.e("onCompleted");
        }

        @Override
        public void onError(Throwable e) {
            if (e instanceof SocketTimeoutException) {
                LogUtil.e("SocketTimeoutException: 網路中斷,請檢查您的網路狀態");
            } else if (e instanceof ConnectException) {
                LogUtil.e("ConnectException: 網路中斷,請檢查您的網路狀態");
            } else if (e instanceof UnknownHostException) {
                LogUtil.e("UnknownHostException: 網路中斷,請檢查您的網路狀態");
            } else {
                LogUtil.e("onError:其他錯誤:" + e.getMessage() + "  cause: " + e.getCause());
            }
            e.printStackTrace();
        }
    };
    //重寫了ResponseBodyHttpClient
    String URL = "http://download.fir.im/v2/app/install/5818acbcca87a836f50014af?download_token=a01301d7f6f8f4957643c3fcfe5ba6ff";
    DownLoadHttpClient.getService(AppService.class).download(URL)
            .subscribeOn(Schedulers.io())//請求網路 在排程者的io執行緒
            .observeOn(Schedulers.io()) //指定執行緒儲存檔案
            .doOnNext(new Action1<ResponseBody>() {
                @Override
                public void call(ResponseBody body) {
                    downLoadCallback.saveFile(body);
                }
            })
            .observeOn(AndroidSchedulers.mainThread()) //在主執行緒中更新ui
            .compose(mActivity.<ResponseBody>bindUntilEvent(ActivityEvent.DESTROY))
            .subscribe(new FileSubscriber<ResponseBody>(downLoadCallback));
}

對網路結果的的儲存,且不改變資料流的格式,一般在doOnext()方法中進行,並且設定在子執行緒中完成。需要進度的回撥會在onprogress()方法中獲得,成功後會回撥onSuccess()方法,這樣就完成了對檔案的下載以及過程的進度監聽。

 

無網路取快取

無網路從快取的檔案中獲取,是從Intercepter攔截器中實現的。自定義AcheIntercepter類如下所示:

public class AcheInterceptor implements Interceptor {
    public static final String CACHE = "Cache-Control:max-stale=" + (60 * 60 * 24 * 7);//離線快取7天

    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request request = chain.request();
        if (request.method().equals("GET")) {
            if (!NetworkUtil.isNetworkConnected(ContextUtil.getContext())) {
                request = request.newBuilder()
                        .cacheControl(new CacheControl.Builder().onlyIfCached()
                                .maxStale(request.cacheControl().maxStaleSeconds(), TimeUnit.SECONDS).build())
                        .build();
            }
            Response response = chain.proceed(request);
            if (NetworkUtil.isNetworkConnected(ContextUtil.getContext())) {
                response.newBuilder()
                        .header("Cache-Control", "Cache-Control:,max-age=0")
                        .removeHeader("Pragma")
                        .build();
            } else {
                response.newBuilder()
                        .header("Cache-Control", "Cache-Control:public, only-if-cached")
                        .removeHeader("Pragma")
                        .build();
            }
            return response;
        }
        return chain.proceed(request);
    }
}

這裡我們只對GET方法做快取處理,判斷是否有網路,有網路取網路獲取的資料,過期時間設定為0,沒有網路取之前快取下來的資料,並設定過期時間為7天。

介面類:

//無網路取快取
@Headers(AcheInterceptor.CACHE)
@GET("https://www.mrallen.cn/test.php")
Observable<HttpResult<String>> simpleGetCache();

使用:

/**
 * 無網路取快取
 */
@Override
public void getSimpleCacheData(final LoadingCallBack callBack) {
    addActSubscribe(HttpClient.getService(AppService.class).simpleGetCache(), new RxRequestCallBack<String>() {
        @Override
        public void onSuccess(HttpResult<String> httpResult) {
            callBack.simpleCacheDataCompleted(httpResult.getData());
        }
    });
}

只需在GET方法上新增註解@headers(AcheIntercepter.CACHE),告訴這個介面採用無網路取快取的策略就可以了,感覺還是挺方便的。

以上就是基於MVP模式封裝的一套RxJava+Retrofit2+Okhttp3+Rxlifecycle框架了,程式碼的連結如下,有更好建議或者疑問的看官歡迎提出來互相交流學習。喜歡的看官可以給我點一個小星星吧,小小的鼓勵,哈哈哈哈........

GitHub: https://github.com/634930172/JohnDevFrame

點選開啟連結

CSDN: https://download.csdn.net/download/a634930172a/10489294

點選開啟連結

相關文章