在开发阶段,如果有一个好的开发框架,不仅能提高开发效率,更能减少后期维护的时间。结合自己的实际,封装了一套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模式大家应该都有了解过,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 BehaviorSubjectlifecycleSubject = BehaviorSubject.create(); @Override public final Observable 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, 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 BasePresenterimplements 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( mActivity) { @Override public void onSuccess(HttpResulthttpResult) { 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() { @Override public void onSuccess(HttpResulthttpResult) { 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() { @Override public void onSuccess(HttpResulthttpResult) { 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() { @Override public void onSuccess(HttpResulthttpResult) { callBack.fileUploadsCompleted(httpResult.getData()); } }); } /** * 文件下载 */ @Override public void downLoadFile(final LoadingCallBack callBack) { String fileName = "app.apk"; File externalFilesDir = ContextUtil.getContext().getExternalFilesDir(null);//外部存储的私有目录,应用删除后此文件也会被删除 final FileCallBackdownLoadCallback = new FileCallBack(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() { @Override public void call(ResponseBody body) { downLoadCallback.saveFile(body); } }) .observeOn(AndroidSchedulers.mainThread()) //在主线程中更新ui .compose(mActivity.bindUntilEvent(ActivityEvent. DESTROY)) .subscribe(new FileSubscriber(downLoadCallback)) ; } /** * 无网络取缓存 */ @Override public void getSimpleCacheData(final LoadingCallBack callBack) { addActSubscribe(HttpClient.getService(AppService.class).simpleGetCache(), new RxRequestCallBack() { @Override public void onSuccess(HttpResulthttpResult) { callBack.simpleCacheDataCompleted(httpResult.getData()); } }); } }
这里是MainModelImp类,上面的这些网络请求方法可以先不管,我们只要知道这些复杂的逻辑都在这里处理,然后通过接口将结果回调给P层就可以了。
网络层的设计,代码结构如下:
虽然看起来有那么多类别,但是里面的结构还是很清晰的。我们先来看最关键的RxRequestCallBack类,如下所示:
public abstract class RxRequestCallBack<T> extends SubscriberT>> 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
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); } }
通过getService()得到请求的服务类,服务类如下:
public interface AppService { //Get请求 @GET("https://www.mrallen.cn/test.php") Observable> simpleGet(); //Post请求 @FormUrlEncoded @POST("https://www.mrallen.cn/api/allen_restful/rcloud/group/queryGroupMemberInfo.php") Observable> simplePost(@Field("group_id") String groupId); //单图上传 @Multipart @POST("allen_restful/upload/upload.php") Observable> uploadPic(@PartMap Map, RequestBody> map); //多图上传 @Multipart @POST("https://www.mrallen.cn/api/allen_restful/upload/testMuchUploadImg.php") Observable > uploadPics(@PartMap Map, RequestBody> map); //文件下载 @Streaming @GET Observable download(@Url String url); //无网络取缓存 @Headers(AcheInterceptor.CACHE) @GET("https://www.mrallen.cn/test.php") Observable > simpleGetCache(); }
有了Observable和RxRequestCallBack,最基本的请求格式应该是这样的:
接口类:
//Get请求 @GET("https://www.mrallen.cn/test.php") Observable> simpleGet();
MainModelImp调用代码:
HttpClient.getService(AppService.class).simpleGet().subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .compose(mActivity.>bindUntilEvent(ActivityEvent. DESTROY))//绑定生命周期,防止内存泄露 .subscribe(new RxRequestCallBack() { @Override public void onSuccess(HttpResulthttpResult) { } }) ;
感觉要调用那么多代码,可以将公共的部分抽出来封装成一个方法,放到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( mActivity) { @Override public void onSuccess(HttpResulthttpResult) { callBack.simpleDataCompleted(httpResult.getData()); } });
看起来简洁多了,如果要处理失败逻辑,重写OnFailed()或者OnError() 就可以了。
说完了基本的使用,get/post请求应该没有问题了,现在对图片上传功能做封装。
文件的上传,主要在UploadUtil工具类,如下:
public class UploadUtil { public static class Builder { Map, RequestBody> params; public Builder() { params = new LinkedHashMap<>(); } public Map , 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> uploadPic(@PartMap Map, 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() { @Override public void onSuccess(HttpResulthttpResult) { callBack.fileUploadCompleted(httpResult.getData()); } }); }
将图片转成数组,UploadUtil工具类确定图片的名称和格式,通过builder.build()方法得到Map
关于文件的下载,这里参考了简书里的一篇文章,有兴趣的看官可以看一下,链接如下:
https://www.jianshu.com/p/060d55fc1c82
接口类的定义如下:
//文件下载 @Streaming @GET Observabledownload(@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() { @Override public void call(FileLoadEvent fileLoadEvent) { progress(fileLoadEvent.getBytesLoaded()); } }, new Action1() { @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 FileCallBackdownLoadCallback = new FileCallBack(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() { @Override public void call(ResponseBody body) { downLoadCallback.saveFile(body); } }) .observeOn(AndroidSchedulers.mainThread()) //在主线程中更新ui .compose(mActivity.bindUntilEvent(ActivityEvent. DESTROY)) .subscribe(new FileSubscriber(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> simpleGetCache();
使用:
/** * 无网络取缓存 */ @Override public void getSimpleCacheData(final LoadingCallBack callBack) { addActSubscribe(HttpClient.getService(AppService.class).simpleGetCache(), new RxRequestCallBack() { @Override public void onSuccess(HttpResult 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
点击打开链接