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

前言:仍是由于公司特别闲,把基础总结总结。若是对相关知识含糊不清,会致使你没法随意扩展你想要的框架和功能。可是以为做为程序员这行业,只要踏进来了,不是在学习的路上就是在被淘汰的路上,加油!!html

本文章将针对全部用户,你们根据本身的所需自行跳过一些章节。首先仍是说说这个框架支持干一些什么事:java

  • 支持全部网络请求类型,get,post,put...(废话了!!Retrofit已经干了全部事情)
  • 支持上传文件并监听上传进度
  • 支持下载文件和断点下载并监听下载进度
  • 有网络时,支持在线缓存(链接网络时的有效期)
  • 断开网络,支持离线缓存(离线缓存有效期)
  • 屡次请求同一url,在网络还在请求时,是否只请求一次
  • 支持网络请求失败,自动重连
  • 支持取消网络请求

一、先看看一些简单效果(建议打开权限)

get请求 post请求 上传文件
下载文件

好了就这么多,几乎包括了你能用到的功能了。那么接下来咱们分别来介绍RxJava是什么?Retrofit是什么?为何使用MVP? 固然我这里是引导你怎么去学习RxJava和Retrofit,对这2方便本文章不会细将,只是把我封装的这些会讲的特别细,封装RxJava结合Retrofit的MVP的实战框架。正文开始了,初学者得一步一步来哦:git

二、RxJava 是什么?

以观察者模式为主的一个响应式编程。响应式编程是一种基于异步数据流概念的编程模式。数据流就像一条河:它能够被观测,被过滤,被操做,或者为新的消费者与另一条流合并为一条新的流。固然他不仅是异步,也有同步。 它最大的有点是能够来回切换线程,不得不说很哇塞!!程序员

直接上干货,组好都看一遍,花不了多长时间:github

2.一、抛物线的RxJava讲解,适合初学者

2.二、nanchen简书热门文章,RxJava教程系列

2.三、RxJava全部操做符

2.四、RxJava系列文章

我的建议在学习的时候,最好是手敲代码。虽然不少重复代码,但这会让你更加熟悉,甚至你都能拼出这些单词。操做符只建议记住前2篇的就好了,由于实在太多了你也记不住,第3篇适合当api,没事看看,有时候当api查查就行了编程

三、Retrofit 是什么?

这里我好想吐槽,好想吐槽!api

Retrofit就是基于OKHttp封装的,只不过是原做者封装的,以注解的方式配置好就能请求网络了。说实话它或许真的不如一些优秀的OKHttp封装,可是谁让他支持RxJava呢。Retrofit结合RxJava使用简直就是一大利器。刚刚说到了RxJava能够切换线程,你能够把全部耗时操做放在 子线程,到更改ui回调的时候切换到主线程就行了。缓存

下面是Retrofit的干货:markdown

3.一、很详细的 Retrofit 2.0 使用教程,来自Carson_Ho

3.二、Retrofit结合RxJava简单使用

看完了这些相信你对Retrofit有了大体了解。我的建议,最好把get请求,post请求,文件上传都简单跑通。这里稍微说下@Url的用法,里面是一个完整的url地址,会忽略Retrofit设置的baseUrl。同时,Retrofit是封装好的。因此一些请求参数是不会打印的,固然能够经过拦截器打印。
固然在项目中要封装保持一个Retrofit,其中有个.client的方法,能够放一个OkhttpClient。这个时候能够自定义日志拦截器:网络

public class HttpLogInterceptor implements Interceptor {
    //这里省略部分代码,具体看项目
    //这里要注意去掉上传文件的请求头部打印,下载文件的返回参数不打印,由于文件太大处理很差会oom,在说是文件内容,不重要
    private final Charset UTF8 = Charset.forName("UTF-8");

    @Override
    public Response intercept(Chain chain) throws IOException {
        StringBuffer sbf = new StringBuffer();
        Request request = chain.request();

        RequestBody requestBody = request.body();
        String body = null;
        if (requestBody != null) {
            Buffer buffer = new Buffer();
            requestBody.writeTo(buffer);
            Charset charset = UTF8;
            MediaType contentType = requestBody.contentType();
            if (contentType != null) {
                charset = contentType.charset(UTF8);
            }
            body = buffer.clone().readString(charset);
            if (!TextUtils.isEmpty(body)) {
                //若是是图片上传调用URLDecoder会报错,即便tryCache都没用,what!!!
                String netUrl = request.url().toString();
                if (netUrl.contains(SystemConst.DIFFERT_URL)) {
                    body = "本次请求图片上传或下载,没法打印参数!";
                } else {
                    body = URLDecoder.decode(body, "utf-8");
                }

            }
        }
        sbf.append(" \n请求方式:==> " + request.method())
                .append("\nurl:" + request.url())
                .append("\n请求头:" + request.headers())
                .append("\n请求参数: " + body);

        Response response = chain.proceed(request);
        String rBody = "";

        String netUrl = request.url().toString();
        if (!netUrl.contains(SystemConst.QQ_APK)) {
            ResponseBody responseBody = response.body();
            BufferedSource source = responseBody.source();
            source.request(Long.MAX_VALUE);
            Buffer buffer = source.buffer();

            Charset charset = UTF8;
            MediaType contentType = responseBody.contentType();
            if (contentType != null) {
                try {
                    charset = contentType.charset(UTF8);
                } catch (UnsupportedCharsetException e) {
                    e.printStackTrace();
                }
            }

            rBody = buffer.clone().readString(charset);
            if (!TextUtils.isEmpty(rBody)) {
                rBody = decodeUnicode(rBody);
            }
        }

        sbf.append("\n收到响应: code ==> " + response.code())
                .append("\nResponse: " + rBody);
        LogUtils.i("网络请求", sbf.toString());

        return response;
    }
}

复制代码

看看打印出来的效果,很清晰哦:

看这一点,要打印请求参数,返回参数,head等信息仍是要经过okhttp的拦截器去作。这是吐槽Retrofit封装的一点

四、MVP开发框架

首先是我我的观点。此框架适合大型一点项目,且此框架是代码逻辑简洁,清晰,分离出视图层和Model层逻辑处理,适合作单元测试,且使activity里的代码减小,并且不会由于有些线程,异步的处理,有activity的引用致使没法回收,引发一系列问题。仍是要强调一下只是代码逻辑简洁,不是代码简洁。由于他要增长不少类。那么来讲说怎么代码逻辑简洁(适合其余人接手项目,和团队开发):

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

首先google官方demo,也是把功能页面模块化了。这样你看个人分包,结合个人图片展现一看就明白

  • downfragemnt---->就是下载文件的页面
  • getfragment----->get请求的页面
  • main------------>mianActivity页面
  • postfragment----->post请求页面
  • uploadfragment---->上传文件页面

是否是一看就很清楚,好比getfragment的全部东西都在里面。固然你还能够在里面继续分包

4.二、在来看看契约类,我的以为更加清晰,咱们来看看get的器乐类GetContract

public interface GetContract {

    //view只有2个更新ui的方法
    interface View extends BaseView {
        // 一、获取get数据,更新ui
        void showGetData(String data);

        // 二、获取get数据失败,更新ui
        void showGetError(String msg);
    }

    //get的prensenter只有一个获取get数据的数据请求
    interface Prensenter {
        // 一、开启get网络请求
        public void getGetData(String params);
    }
}
复制代码

好比你要看一个页面有什么更新UI的操做或者有什么网络请求,只要看契约类就对这个页面很了解了如:

一、view就是至关于MVP的V层,通常被activity或者Fragment实现,能够看到view里有2个方法:

  • showGetData 获取get请求数据,更新UI
  • showGetError get请求失败了,更新失败UI
  • BaseView 放的都是通用的方法,这里放了2个:showLoading,hideLoading(固然有些网络请求不须要转loading,那么不调用就行了)

二、Prensenter就是MVP的P层须要使用到的方法,要搞清楚,其实还有一个实体类GetPresenter。实体类实现GetContract.Prensenter。能够看到里面就一个getGetData。的网络请求。为何这么写,第一契约类能够减小类的数目,第二更重要的一点是你能够很快的清楚你开发模块的功能和操做

还有一点要注意,由于使用RxJava结合Retrofit,我把通用的方法所有封装了BasePresenter里。由于RxJava的链式编程,因此我直接省略掉了model层,固然你能够写一个接口,而后写个model层把数据回调给Presenter。这里要注意MVP,MVP只是开发框架,是咱们借鉴的开发思路。可能100我的有100个不一样的MVP框架,这里我是说说个人想法,你们能够借鉴和学习。

五、RxJava结合Retrofit的MVP

5.一、封装Retrofit,其实和OkHttp没有区别

public class RetrofitManager {
    //省略部分代码,便于理解
    private static RetrofitManager retrofitManager;
    private OkHttpClient okHttpClient;
    private Retrofit retrofit;
    //这是我retrofit的网络请求配置
    private RetrofitApiService retrofitApiService;

    private RetrofitManager() {
        //初始化Okhttp
        initOkHttpClient();
        //初始化retrofit
        initRetrofit();
    }

    public static RetrofitManager getRetrofitManager() {
        if (retrofitManager == null) {
            synchronized (RetrofitManager.class) {
                if (retrofitManager == null) {
                    retrofitManager = new RetrofitManager();
                }
            }
        }
        return retrofitManager;
    }
    
    private void initRetrofit() {
        retrofit = new Retrofit.Builder()
                .baseUrl(SystemConst.DEFAULT_SERVER)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .build();
        retrofitApiService = retrofit.create(RetrofitApiService.class);
    }
    
    public static RetrofitApiService getApiService() {
        return retrofitManager.retrofitApiService;
    }
    
    private void initOkHttpClient() {
        okHttpClient = new OkHttpClient.Builder()
                //设置缓存文件路径,和文件大小
                .cache(new Cache(new File(Environment.getExternalStorageDirectory() + "/okhttp_cache/"), 50 * 1024 * 1024))
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .addInterceptor(new HttpLogInterceptor())
                //设置在线和离线缓存
                .addInterceptor(OfflineCacheInterceptor.getInstance())
                .addNetworkInterceptor(NetCacheInterceptor.getInstance())
                .build();
    }
}

复制代码

5.二、好比咱们来写GetFragment这块。首先写契约类

回想以前咱们有个BaseView。里面有2个方法showLoading和hideLoading (只要记住你们通用的方法封装成base,有些页面不一样就不调用就好了) 代码以下:

public interface BaseView {
    
     //显示正在加载loading
    void showLoading(String message);
    
   // 关闭正在加载loading
    void hideLoading();

    //防止RxJava内存泄漏,能够暂且忽略
    LifecycleTransformer bindLifecycle();

}
复制代码

那么GetFragment契约类就是(还要说下这里的presenter接口只是GetPresenter所要用的方法,放在契约类里,一目了然):

public interface GetContract {

    //view只有2个更新ui的方法
    interface View extends BaseView {
        // 一、获取get数据,更新ui
        void showGetData(String data);

        // 二、获取get数据失败,更新ui
        void showGetError(String msg);
    }

    //get的prensenter只有一个获取get数据的数据请求
    interface Prensenter {
        // 一、开启get网络请求
        public void getGetData(String params);
    }
}
复制代码

5.三、接下来是GetPresenter的实体类,固然这里有个BasePresenter(如下讲解移除部分代码更清晰)

咱们想象有什么通用方法是在BasePresenter里的?固然你能够把一些复杂的写不少步骤的网络请求封装在这里,好比咱们项目里的下载文件等,这里咱们讲简单的通用方法

GetPresenter须要view的引用 因此有:一、setView加入引用 二、view = null置空引用,防止oom

//这个是为了退出页面,取消请求的
    public CompositeDisposable compositeDisposable;
    // 绑定的view
    private V mvpView;
    //绑定view,通常在初始化中调用该方法
    public void attachView(V mvpView) {
        this.mvpView = mvpView;
        compositeDisposable = new CompositeDisposable();
    }
    //置空view,通常在onDestroy中调用
    public void detachView() {
        this.mvpView = null;
        //退出页面的时候移除全部网络请求
        removeDisposable();
    }
    //须要退出页面移除网络请求的加入进来
    public void addDisposable(Disposable disposable) {
        compositeDisposable.add(disposable);
    }
    //退出页面移除全部网络请求
    public void removeDisposable() {
        if (compositeDisposable != null) {
            compositeDisposable.dispose();
        }
    }
复制代码

使用一个方法快速获取RetrofitManager里的网络配置

public RetrofitApiService apiService() {
        return RetrofitManager.getRetrofitManager().getApiService();
    }
复制代码

大部分的网络请求都有showLoading和hideLoading,还有线程切换,封装进来

//这里我多加了个是否显示loading的标识和loading上的文字,想偷懒能够用方法重载把这2个参数默认
public <T> Observable<T> observe(Observable<T> observable, final boolean showDialog, final String message) {
        return observable.subscribeOn(Schedulers.io())
                .doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(Disposable disposable) throws Exception {
                        if (showDialog) {
                            mvpView.showLoading(message);
                        }
                    }
                })
                .subscribeOn(AndroidSchedulers.mainThread())
                .doFinally(new Action() {
                    @Override
                    public void run() throws Exception {
                        if (showDialog) {
                            mvpView.hideLoading();
                        }
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                //防止RxJava内存泄漏
                .compose(mvpView.bindLifecycle());
    }
复制代码

那么咱们的BasePresenter(移除部分代码更清晰)

//这里加上泛型,第一在使用Presenter一眼就看出对应哪一个View,其次肯定咱们V的类型
public abstract class BasePresenter<V extends BaseView> {
    public CompositeDisposable compositeDisposable;

    private V mvpView;

    public void attachView(V mvpView) {
        this.mvpView = mvpView;
        compositeDisposable = new CompositeDisposable();
    }
    public void detachView() {
        this.mvpView = null;
        removeDisposable();
    }
    //检查是否有view的引用
    public boolean isViewAttached() {
        return mvpView != null;
    }

    //获取view的引用
    public V getView() {
        return mvpView;
    }

    public RetrofitApiService apiService() {
        return RetrofitManager.getRetrofitManager().getApiService();
    }
    //须要退出页面移除网络请求的加入进来
    public void addDisposable(Disposable disposable) { }
    
    //退出页面移除全部网络请求
    public void removeDisposable() {}
    
    //省的写线程切换和showloading和hide的复杂操做
    public <T> Observable<T> observe(Observable<T> observable, final boolean showDialog, final String message) {}

}
复制代码

那么GetPresenter就是这样

//省略部分代码,便于理解。实体GetPresenter实现GetContract.Prensenter接口,里面就一个getGetData方法
public class GetPresenter extends BasePresenter<GetContract.View> implements GetContract.Prensenter {
    
    @Override
    public void getGetData(String params) {
        if (!isViewAttached()) {
            //若是没有View引用就不加载数据
            return;
        }
        //BasePresenter里有了封装,切换线程和放置内存泄漏的 .compose(mvpView.bindLifecycle())都不用写了
        //代码越能偷懒,说明框架越是封装的完美
        observe(apiService().getGank(params))
                .subscribe(new Consumer<GankFatherBean>() {
                    @Override
                    public void accept(GankFatherBean gankFatherBean) throws Exception {
                        getView().showGetData(gankFatherBean.getTitle());
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        getView().showGetError(throwable.toString());
                    }
                });
        
    }
}
复制代码

5.四、其次是BaseFragment

由于使用RxJava,若是使用不当很容易形成RxJava内存泄漏,因此官方也出了方法继承RxFragment

由于fragment是做为view是要实现view接口的,同时每一个fragment都要有presenter去调用方法
初步以下(省了部分代码,便于理解):

public abstract class BaseFragment<T extends BasePresenter> extends RxFragment implements BaseView {
    public T mPresenter;
    public abstract T cretaePresenter();
    protected View mContentView;
    private Unbinder mUnbinder;

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        // 避免屡次从xml中加载布局文件
        mPresenter = cretaePresenter();
        if (mPresenter != null) {
            mPresenter.attachView(this);
        }
        return mContentView;
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.detachView();
            mPresenter = null;
        }
    }

    @Override
    public void showLoading(String message) {
        LoadingDialog.getInstance().show(getActivity(), message);
    }

    @Override
    public void hideLoading() {
        LoadingDialog.getInstance().dismiss();
    }

    //防止Rx内存泄漏
    @Override
    public LifecycleTransformer bindLifecycle() {
        LifecycleTransformer objectLifecycleTransformer = bindToLifecycle();
        return objectLifecycleTransformer;
    }
}
复制代码

5.五、在GetFragment里使用就是这样(省略部分代码,便于理解)

public class GETFragment extends BaseFragment<GetPresenter> implements GetContract.View {
    @BindView(R.id.txt_content) TextView txt_content;

    @Override
    public GetPresenter cretaePresenter() {
        return new GetPresenter();
    }

    @Override
    public int getContentViewId() {
        return R.layout.fragment_get;
    }

    //处理逻辑
    @Override
    protected void processLogic(Bundle savedInstanceState) {

    }

    @OnClick(R.id.txt_get)
    public void getClick() {
        //请求网络
        mPresenter.getGetData("Android");
    }

    @Override
    public void showGetData(String data) {
        txt_content.setText(data);
    }

    @Override
    public void showGetError(String msg) {
        ToastUtils.showToast(msg);
    }
}

复制代码

六、断网重连

由于使用了强大的RxJava,这里断网重连用的就是操做符retryWhen,封装在BasePresenter里的observeWithRetry。这里就很少讲了,能够先把操做符retryWhen了解清楚就看明白了

七、取消网络请求

这里的返回值就是一个disposable,若是主动取消就直接调 disposable.dispose();固然我这里也封装了离开页面取消请求你能够把它加到addDisposable(Disposable disposable)里。就不用管了

Disposable disposable = observe(apiService().getGank(params))
                .subscribe(new Consumer<GankFatherBean>() {
                    @Override
                    public void accept(GankFatherBean gankFatherBean) throws Exception {
                        getView().showGetData(gankFatherBean.getTitle());
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        getView().showGetError(throwable.toString());
                    }
                });
复制代码

八、屡次请求同一网络,只请求一次

这里是在RetrofitManage里放了一个private ArrayList oneNetList;请求网络的时候把,方法名当成tag加入进去,若是请求的时候判断下,oneNetList里若是已经有次tag,则直接return,在请求成功或者失败的时候移除tag

九、在线缓存和离线缓存

这里仍是利用了okhttp的拦截器,因此这里再次吐槽下Retrofit。

  • 离线缓存拦截器 OfflineCacheInterceptor
  • 在线缓存拦截器 NetCacheInterceptor

这里不清楚的能够去看我以前的一篇文章EasyOk。讲的很详细,简直就是如出一辙 本项目具体用法在,GetPresenter里有。

十、上传文件进度监听

由于用Restrofit上传文件是这样的。

@POST
    @Multipart
    Observable<ResponseBody> uploadPic(@Url String url, @Part MultipartBody.Part file);
复制代码

参数是MultipartBody.Part。不监听进度参数就是这样写的

RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
复制代码

若是要监听进度,就要重写RequestBody,把文件写入进度返回出来,其实和EasyOk的思路是如出一辙的,本项目上传文件相关类在: retrofitwithrxjava.uploadutils的包里。用法在postfragment里。其中包括多张图片监听,不一样key,不一样图片。和同一key,不一样图片

十一、下载文件进度监听及断点下载

首先咱们看下载文件请求

@GET
    @Streaming //10以上用@streaming。不会形成oom,反正你用就是了
    Observable<ResponseBody> downloadFile(@Url String url);
复制代码

返回的是ResponseBody,是否是灵感来了,咱们能够重写一个FileDownLoadObserver观察者,而后利用map操做符,对ResponseBody进行转换,转换成File的FileDownLoadObserver,固然有了ResponseBody,文件写入能够在FileDownLoadObserver里。有了文件写入,那么进度就来了。本项目下载文件相关类在:retrofitwithrxjava.downloadutils包里。用法在downloadfragment里。

断点下载

咱们知道断点下载,其实就是head里加个RANG的字段,其余都是如出一辙的:

@GET
    @Streaming
    Observable<ResponseBody> downloadFile(@Url String url, @Header("RANGE")String range);
复制代码

这个字段是这样的: String range = "bytes=" + currentLength + "-";
currentLength是你下载文件里,下载部分文件的长度,你能够经过file.length获取。而后读写的时候fos = new FileOutputStream(file, true),输出流必须带第二个参数true,意思就是继续拼接的意思。所有功能都讲完了。没有讲的太细是由于我以为大部分额外功能都是重写,并且都是利用okhttp,其实看过了我之前的EasyOk。其实就明白了。这也是我单独吐槽Retrofit的缘由。我相信封装的很完美的okhttp,确定比这个用起来好用,惋惜人家支持Rxjava。不过经过这个RANGE,其实要下载一个文件的话能够分不少段下载,由于看他的字面意思就知道了"bytes=start-end",指的是一段范围,我上面写的是"bytes=start-"就是默认下载完,因此有些app开启多线程下载,包括迅雷开启vip就下的快,其实就是给你多开了几条线程下载了。!!

但愿个人文章对你有帮助,看到这就给我一个赞把

最近项目引进了Dagger2的使用,MVP使用了Dagger2简直不要太舒服了!详细教程

本MVP框架引入Dagger2教程地址

本demo github地址

相关文章
相关标签/搜索