Rx系列<第三十二篇>:RxJava+Retrofit+OkHttp+MVP

本章主要针对RxJavaRetrofitOkHttp结合使用,现在说明一下三者的职责所在。

OkHttp:负责网络请求;
Retrofit:主要负责网络请求接口的封装,结合OkHttp可以处理网络请求的结果;
RxJava:负责网络请求的异步;

(1)权限

网络请求别忘了添加网络权限

<uses-permission android:name="android.permission.INTERNET" />
(2)依赖
  • RxJava依赖
implementation 'io.reactivex.rxjava2:rxjava:2.1.3'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'

或者

implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0-alpha2'

如果使用rxbinding,就添加rxbinding相关依赖,rxbinding依赖本身支持rxjava和rxandroid,所以如果添加rxbinding依赖就可以去除rxjava和rxandroid依赖了。

  • okhttp3依赖
implementation 'com.squareup.okhttp3:okhttp:3.12.2'

//日志拦截器依赖
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
  • retrofit依赖
implementation 'com.squareup.retrofit2:retrofit:2.5.0'

//gson转换器依赖
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
  • RxJava和Retrofit混用的适配器
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
  • autodispose依赖
implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.0.0-RC2'
  • 其它辅助依赖
//gson依赖
implementation 'com.google.code.gson:gson:2.8.5'
(3)RxJava介绍

点击某按钮,请求网络接口,要求防抖动(防止不一小心点击两次,导致没必要的网络请求),RxJava可以实现按钮防抖动。

    RxView.clicks(findViewById(R.id.bt))
            .throttleFirst(2, TimeUnit.SECONDS)
            //AutoDispose的关键语句
            .as(AutoDispose.<Unit>autoDisposable(AndroidLifecycleScopeProvider.from(this)))
            .subscribe(new Consumer<Unit>() {
                @Override
                public void accept(Unit unit) throws Exception {
                    //获取新闻
                    mainPresenter.getWeather();

                }
            });

throttleFirst表示2秒内,只处理第一次点击的响应;
AutoDispose是为了防止内存泄漏;

RxJava还可以和Retrofit共同完成网络请求。

/**
 * 获取天气预报
 */
public void getWeather(ResultCallBack callBack){
    RetrofitUtils.getInstance().getDataFromAPI(retrofitAPI.getWeather(), callBack);
}


/**
 * 通用方法
 * @param observable
 * @param <T>
 */
public <T> void getDataFromAPI(Observable<T> observable, final ResultCallBack callBack){

    //绑定Activity的生命周期
    callBack.autoDispose(observable);

    observable
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<T>() {
                @Override
                public void onSubscribe(Disposable d) {
                    //开始请求

                    callBack.startRequest();

                }

                @Override
                public void onNext(T t) {
                    //成功
                    callBack.success(t);

                }

                @Override
                public void onError(Throwable e) {
                    e.printStackTrace();
                    //失败
                    Log.d("aaa", "onFailure:"+e.getCause() + "\n" + e.getMessage());

                    if (e instanceof SocketTimeoutException) {
                        callBack.failed("网络中断,请检查您的网络状态!");
                    } else if (e instanceof ConnectException) {
                        callBack.failed("网络中断,请检查您的网络状态!");
                    }else{
                        callBack.failed("未知异常!");
                    }
                }

                @Override
                public void onComplete() {
                    //结束请求

                    callBack.requestComplete();

                }
            });
}
(4)Retrofit介绍

单独使用Retrofit,网络请求的返回值类型是Call,与RxJava结合使用后返回值变成了observable,这样做的好处是:

  • 可以方便的控制线程
  • 可以知道网络请求什么时候开始以及什么时候结束

另外,OkHttp可以设置网络超时以及其他配置。

Retrofit在之前的文章中已经介绍过了,因此这里直接跳过,等下直接贴代码。

(5)MVP介绍

MVP模式请先看一下这篇博客

Andorid之MVP模式

这篇博客是最简单的MVP模式了,在实际项目中往往更加复杂,但是,复杂并不可怕,可怕的是你不知道怎么使用MVP。

MVP模式一般流向是:View-->Presenter-->Model,这里最好不要逆向,或者禁止逆向。

这个流向大致的意思是View直接访问Presenter,Presenter直接访问Model,那么问题来了:

  • Presenter怎么访问View?

Activity(Fragment)就是MVP的View,该Activity(Fragment)会实现一个IView接口,Presenter与IView具有依赖性,Presenter通过IView可以间接执行Activity(Fragment)的IView回调。

  • Model怎么访问Presenter?

通过CallBack访问,等下贴下代码就知道了。

  • Model怎么访问View?

先通过CallBack访问Presenter,再让Presenter通过IView访问Activity(Fragment)。

(6)完整封装

这里直接贴代码了。

WeatherBean.java(这是新闻类的Bean,网络请求之后数据装载类)

public class WeatherBean {

    /**
     * status : 201
     * message : APP被用户自己禁用,请在控制台解禁
     */

    private int status;
    private String message;

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

ResultCallBack.java(这是网络请求结果返回的callback)

public interface ResultCallBack<T> {

    //开始请求
    void startRequest();

    //请求成功
    void success(T t);

    //请求失败
    void failed(String error);

    //请求完成
    void requestComplete();

    //自动绑定生命周期
    void autoDispose(Observable observable);

}

RetrofitAPI.java(Retrofit接口类,这里会防止所有的网络请求接口)

public interface RetrofitAPI {

    @GET("weather?location=jiaxing&output=json&ak=5slgyqGDENN7Sy7pw29IUvrZ")
    Observable<WeatherBean> getWeather();

}

RetrofitUtils.java(Retrofit网络请求工具类,这是通用的)

public class RetrofitUtils {

    private RetrofitAPI retrofitAPI;

    private String BASE_URL = "http://api.map.baidu.com/telematics/v3/";

    private RetrofitUtils(){
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时长
                .readTimeout(20, TimeUnit.SECONDS)//设置读超时时长
                .writeTimeout(20, TimeUnit.SECONDS)//设置写超时时长
                .addInterceptor(getHttpLoggingInterceptor())//添加日志拦截器
                .build();
        Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create(new Gson()))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(client)
                .build();
        retrofitAPI = retrofit.create(RetrofitAPI.class);
    }

    static class SingleHolder{
        public static RetrofitUtils instance = new RetrofitUtils();
    }

    public static RetrofitUtils getInstance(){
        return RetrofitUtils.SingleHolder.instance;
    }

    /**
     * 创建日志拦截器
     * @return
     */
    private HttpLoggingInterceptor getHttpLoggingInterceptor() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(
                new HttpLoggingInterceptor.Logger() {

                    @Override
                    public void log(String message) {
                        Log.e("RetrofitUtils", "log = " + message);
                    }

                });
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        return loggingInterceptor;
    }

    public RetrofitAPI getRetrofitAPI(){
        return retrofitAPI;
    }


    /**
     * 通用方法
     * @param observable
     * @param <T>
     */
    public <T> void getDataFromAPI(Observable<T> observable, final ResultCallBack callBack){

        //绑定Activity的生命周期
        callBack.autoDispose(observable);

        observable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<T>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        //开始请求

                        callBack.startRequest();

                    }

                    @Override
                    public void onNext(T t) {
                        //成功
                        callBack.success(t);

                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                        //失败
                        Log.d("aaa", "onFailure:"+e.getCause() + "\n" + e.getMessage());

                        if (e instanceof SocketTimeoutException) {
                            callBack.failed("网络中断,请检查您的网络状态!");
                        } else if (e instanceof ConnectException) {
                            callBack.failed("网络中断,请检查您的网络状态!");
                        }else{
                            callBack.failed("未知异常!");
                        }
                    }

                    @Override
                    public void onComplete() {
                        //结束请求

                        callBack.requestComplete();

                    }
                });
    }

}

CarModel.java(MVP模式的Model,用于处理非UI操作,一般用于处理网络请求)

public class CarModel {

    private RetrofitAPI retrofitAPI;


    public CarModel(){
        retrofitAPI = RetrofitUtils.getInstance().getRetrofitAPI();
    }

    /**
     * 获取天气预报
     */
    public void getWeather(ResultCallBack callBack){
        RetrofitUtils.getInstance().getDataFromAPI(retrofitAPI.getWeather(), callBack);
    }
}

BasePresenter.java(MVP模式的Presenter的基类,主要负责处理一些通用的逻辑)

/**
 * Presenter基类
 * @param <V>
 */
public class BasePresenter<V extends IBaseView> {

    /**
     * 绑定的view
     */
    private V mvpView;

    /**
     * 绑定view
     */
    public void attachView(V mvpView) {
        this.mvpView = mvpView;
    }
    /**
     * 销毁view
     */
    public void detachView() {
        this.mvpView = null;
    }
    /**
     * 是否与View建立连接
     */
    public boolean isViewAttached(){
        return mvpView != null;
    }

    /**
     * 获取连接的view
     */
    public V getView(){
        return mvpView;
    }
}

MainPresenter.java(MVP模式的Presenter)

public class MainPresenter extends BasePresenter<IMainView> {

    private CarModel carModel;

    public MainPresenter(){
        carModel = new CarModel();
    }

    /**
     * 获取天气预报
     * @param
     */
    public void getWeather(){
        carModel.getWeather(new ResultCallBack<WeatherBean>() {
            @Override
            public void startRequest() {
                getView().loadingDialogShow("请稍等...");
            }

            @Override
            public void success(WeatherBean weatherBean) {
                if(weatherBean != null){
                    getView().setResult(weatherBean.getMessage());
                }
            }

            @Override
            public void failed(String error) {
                getView().setResult(error);
            }

            @Override
            public void requestComplete() {
                getView().loadingDialogDismiss();
            }

            @Override
            public void autoDispose(Observable observable) {
                //AutoDispose的关键语句
                getView().autoDispose(observable);
            }
        });
    }

}

IBaseView.java(MVP模式的View的基类,可以在Activity的基类中实现)

/**
 * 所有UI通用的View操作
 */
public interface IBaseView {

    /**
     * 显示等待对话框
     */
    void loadingDialogShow(String msg);

    /**
     * 关闭等待对话框
     */
    void loadingDialogDismiss();

    /**
     * 显示提示
     * @param msg
     */
    void showToast(String msg);

    /**
     * 获取上下文
     * @return 上下文
     */
    Context getContext();

    /**
     * Observable绑定生命周期
     * @param observable
     * @return
     */
    void autoDispose(Observable observable);
}

IMainView.java(MVP模式的View)

public interface IMainView extends IBaseView{

    void setResult(String result);

}

BaseActivity.java(所有Activity的基类,包含网络进度条、AutoDispose、Toast、网络请求等待对话框)

public abstract class BaseActivity extends AppCompatActivity implements IBaseView {

    private AlertDialog loadingDialog;

    protected Toolbar toolbar;
    private ViewFlipper mContentView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //初始化布局
        setContentView(R.layout.layout_base);
        mContentView = (ViewFlipper) findViewById(R.id.layout_container);
        toolbar = (Toolbar) findViewById(R.id.base_tool_bar);

        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1);
        mContentView.addView(getRootView(), lp);

        //初始化基类数据
        initBaseData();
        //初始化控件
        initView();
        //初始化数据
        initData();
        //初始化监听
        initListener();
    }

    /**初始化基类数据*/
    private void initBaseData(){}{
    }
    /**初始化布局*/
    protected abstract View getRootView();
    /**初始化控件*/
    protected abstract void initView();
    /**初始化数据*/
    protected abstract void initData();
    /**初始化点击事件*/
    protected abstract void initListener();

    /**
     * 启动Activity
     * @param clazz
     */
    protected void startActivity(Class<? extends Activity> clazz){
        Intent in = new Intent(this,clazz);
        startActivity(in);
    }

    @Override
    public void loadingDialogShow(String msg) {
        TextView tipTextView = null;
        LayoutInflater inflater = LayoutInflater.from(this);
        View view = inflater.inflate(R.layout.loading_dialog, null);// 得到加载view

        if (loadingDialog == null) {
            ImageView imageView = view.findViewById(R.id.img);
            tipTextView = view.findViewById(R.id.tipTextView);// 提示文字
            // 加载动画
            Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.loading_animation);
            // 使用ImageView显示动画
            imageView.startAnimation(hyperspaceJumpAnimation);
            tipTextView.setText(msg);// 设置加载信息

            AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.loading_dialog);
            loadingDialog =builder.create();
            builder.setCancelable(false);
        }

        if (!loadingDialog.isShowing()) {
            loadingDialog.show();
            loadingDialog.getWindow().setContentView(view);
        }
    }

    @Override
    public void loadingDialogDismiss() {
        if (loadingDialog != null && loadingDialog.isShowing()) {
            try {
                loadingDialog.dismiss();
                loadingDialog = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void showToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public Context getContext() {
        return this;
    }

    @Override
    public void autoDispose(Observable observable) {
        //AutoDispose的关键语句
        observable.as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)));
    }

    @Override
    protected void onDestroy() {
        //关闭进度条
        super.onDestroy();
        loadingDialogDismiss();
    }
}

MainActivity.java

public class MainActivity extends BaseActivity implements IMainView {

    private MainPresenter mainPresenter;
    private TextView result_text;

    @Override
    protected View getRootView() {
        return LayoutInflater.from(this).inflate(R.layout.activity_main, null);
    }

    @Override
    protected void initView() {
        result_text = findViewById(R.id.result_text);
    }

    @Override
    protected void initData() {
        mainPresenter = new MainPresenter();
        mainPresenter.attachView(this);
    }

    @Override
    protected void initListener() {

        RxView.clicks(findViewById(R.id.bt))
                .throttleFirst(2, TimeUnit.SECONDS)
                //AutoDispose的关键语句
                .as(AutoDispose.<Unit>autoDisposable(AndroidLifecycleScopeProvider.from(this)))
                .subscribe(new Consumer<Unit>() {
                    @Override
                    public void accept(Unit unit) throws Exception {
                        //获取新闻
                        mainPresenter.getWeather();

                    }
                });
    }

    @Override
    public void setResult(String result) {
        //显示结果
        result_text.setText(result);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mainPresenter.isViewAttached()){
            mainPresenter.detachView();
        }
    }
}

loading_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false" >

    <rotate
        android:duration="900"
        android:fromDegrees="0"
        android:interpolator="@android:anim/linear_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="-1"
        android:repeatMode="restart"
        android:startOffset="-1"
        android:toDegrees="+360" />

</set>

loading_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    android:gravity="center"
    android:orientation="horizontal"
    android:padding="10dp" >

    <ImageView
        android:id="@+id/img"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:scaleType="center"
        android:src="@mipmap/aliwx_img_loading" />

    <TextView
        android:id="@+id/tipTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:layout_marginLeft="5dp"
        android:textColor="#FF34A350"
        android:text="数据加载中……"
        android:textSize="16sp" />

</LinearLayout>

layout_base.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/base_tool_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </android.support.v7.widget.Toolbar>

    <ViewFlipper
        android:id="@+id/layout_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/rootview"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_centerInParent="true">
        <Button
            android:id="@+id/bt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="点击开始网络请求" />

        <TextView
            android:id="@+id/result_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"/>

    </LinearLayout>


</RelativeLayout>

图片:

aliwx_img_loading.png

style:

<!-- 自定义loading dialog -->
<style name="loading_dialog" parent="android:style/Theme.Dialog">
    <item name="android:windowFrame">@null</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowIsFloating">true</item>
    <item name="android:backgroundDimEnabled">false</item>
    <item name="android:windowBackground">@color/bg_dialog_white</item>
    <item name="android:windowContentOverlay">@null</item>
</style>

[本章完...]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容