基於MVP模式,設計自己的RxJava+Retrofit2+Okhttp3+Rxlifecycle開發框架
在開發階段,如果有一個好的開發框架,不僅能提高開發效率,更能減少後期維護的時間。結合自己的實際,封裝了一套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,P extends BasePresenter<V>> extends RxAppCompatActivity {
public Activity mActivity;
public P 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((V) this);
setContentView(getLayoutId());
initView();
initData();
initEvent();
}
protected abstract int getLayoutId();
protected abstract P 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); } /** * M層get請求的方法,帶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()); } }); } /** * M層post請求資料的方法 */ @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(); } }; //重寫了ResponseBody的HttpClient 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(); } }; //重寫了ResponseBody的HttpClient 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
相關文章
- 基於 MVP 的 Android 元件化開發框架實踐MVPAndroid元件化框架
- KCommon-使用Kotlin編寫,基於MVP的極速開發框架KotlinMVP框架
- 一個基於Android的MVP框架DemoAndroidMVP框架
- 基於AOP的MVP框架(一)GoMVP的使用MVP框架Go
- 打造屬於自己的underscore系列 ( 一 ) - 框架設計框架
- iOS開發-MVP架構模式iOSMVP架構模式
- 基於AOP的MVP框架(三)GoMVP進階註解MVP框架Go
- 基於AOP的MVP框架(二)GoMVP進階註解MVP框架Go
- 構建屬於自己的Flutter混合開發框架Flutter框架
- Android架構系列-基於MVP建立適合自己的架構Android架構MVP
- 【Android架構】基於MVP模式的Retrofit2+RXjava封裝(一)Android架構MVP模式RxJava封裝
- 用go設計開發一個自己的輕量級登入庫/框架吧Go框架
- MVP框架的演化MVP框架
- 基於Scrapy分散式爬蟲的開發與設計分散式爬蟲
- 基於SPI的增強式外掛框架設計框架
- 基於weex的有贊無線開發框架框架
- 前端工程師最好的全棧開發實踐-設計開發屬於自己的nodejs部落格前端工程師全棧NodeJS
- 基於Koa2打造屬於自己的MVC框架MVC框架
- 非同步程式設計:基於事件的非同步程式設計模式(EAP)非同步程式設計事件設計模式
- 開發一個自己的 CSS 框架(一)CSS框架
- mvp模式MVP模式
- 基於Netty自己動手實現Web框架NettyWeb框架
- 基於GO語言框架Gin開發的MVC輪子框架:GinLaravelGo框架MVCLaravel
- Bedrock——基於MVVM+Provider的Flutter快速開發框架MVVMIDEFlutter框架
- 【Android】一鍵生成MVP程式碼-DevMvp快速開發框架AndroidMVPdev框架
- 【Android架構】基於MVP模式的Retrofit2+RXjava封裝之多Url(七)Android架構MVP模式RxJava封裝
- 一款基於Laravel5.8開發的後臺開發框架Laravel框架
- 如何基於k8s 開發自己的擴充套件K8S套件
- 基於Vue,ElementUI開發的一款表單設計器VueUI
- 淺談基於 Laravel 開發的 MeEdu 的微服務架構設計Laravel微服務架構
- 基於javaEE的土地檔案管理系統的設計及開發Java
- 轉轉基於MQ的分散式重試框架設計方案MQ分散式框架
- 基於數字化工廠的車間佈局設計框架框架
- 基於Koa2/React的NodeJS全棧開發框架ReactNodeJS全棧框架
- 用go設計開發一個自己的輕量級登入庫/框架吧(業務篇)Go框架
- 玩轉 iOS 開發:《iOS 設計模式 — 代理模式》iOS設計模式
- 基於AOP的一種RecyclerView複雜樓層開發框架,支援元件化,全域性樓層打通,MVP等高擴充性功能View框架元件化MVP
- 基於ArkUI框架開發-ImageKnife渲染層重構UI框架