Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Retrofit和RxJava的结合使用

$
0
0

转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/53674161
本文出自:【顾林海的博客】

前言

Retrofit,一个时尚的代名词,好像不知道Retrofit就不算Android开发工程师了,因此我也来时尚一把,写这篇文章旨在使广大开发者能根据这篇浅薄的文章来了解Retrofit,并将它用到我们的项目中去,当然Retrofit和RxJava结合起来用是非常酸爽的。文章开头会先去介绍Retrofit,并单独使用Retrofit,后面会将Retrofit和RxJava结合起来使用,最后会封装一个Retrofit和RxJava结合的请求框架。

Retrofit介绍

Retrofit出自Square公司,是一个类型安全的REST安卓客户端请求库。这个库为网络认证、API请求以及用OkHttp发送网络请求提供了强大的框架 ,当然OkHttp也是出自这家公司。


在漫长的时间里,Retrofit经历了从1.x到2.1(最新版请参看这里“https://github.com/square/retrofit”),相比retrofit1.x来说,retrofit2.x更新了几个不错的功能点。



实例一(单独使用Retrofit )

该实例只会讲解单纯使用 Retrofit的用法,源码位于底下github地址,实例一位于demo1包下。

使用 Retrofit 前我们需要做以下两件事:

  • 在app/build.gradle 中引入 Retrofit。
  • 在AndroidManifest中添加网络请求权限。

在gradle中引入 Retrofit:

compile 'com.squareup.retrofit2:retrofit:2.1.0'

在AndroidManifest中添加网络请求权限:

<uses-permission android:name="android.permission.INTERNET" />

下面正式我们的Retrofit使用之旅。



步骤一:创建我们的请求API(Service)

这里的请求API其实就是我们的向服务端请求的接口地址。Retrofit2.x在定义Service时,已经不区分同步和异步之分了。可以直接看下面的代码,建议大家边看文章边动手撸下代码,代码中的接口地址替换成你们公司或是自己的服务器的地址。在Retrofit中使用注解的方式来区分请求类型.比如@GET(“”)表示一个GET请求,括号中的内容为请求的地址.

public interface APIService {

    @GET("getBrandBanner.php")
    Call<ResponseBody> getBanner(@Query("uid") String _uid, @Query("token") String _token);

    @GET("getHomePager.php")
    Call<ResponseBody> getHomePager();

    @FormUrlEncoded
    @POST("editUserInfo.php")
    Call<ResponseBody> postIP(@Field("name") String name, @Field("age") int age);
}



以上定义了两个请求方式,分别是Get和Post请求,其中Get请求分为无参和有参请求。至此接口地址已经创建完毕。

步骤二:创建Retrofit实例

在请求接口的API定义完毕后,就需要使用Retrofit Builder类,来指定Service的baseUrl(也就是域名)。

在Activity中编写代码:

private static final String API_URL = "http://n1.glh.la/apps/";


Retrofit retrofit = new Retrofit.Builder().baseUrl(API_URL).build();
APIService apiService = retrofit.create(APIService.class);
Call<ResponseBody> responseBodyCall = apiService.getBanner("10915", "585234059ab68");



创建完Retrofit实例后,通过该实例创建APIService,接着通过APIService中定义的方法来获取Call对象。前置工作已经准备完毕,剩下的就是进行请求。

步骤三:请求(异步与同步)

一、同步请求

同步请求使用execute方法,但不能在UI线程中执行,否则会阻塞UI线程,引起NetwordOnMainThreadException异常。因此,这里使用handler+thread的方式来进行请求,在子线程中请求数据,并通过handler来刷新界面。

handler:

private static class MyHandler extends Handler {

    WeakReference<DemoActivity1> weakReference;

    public MyHandler(DemoActivity1 activity) {
        weakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        DemoActivity1 activity = weakReference.get();
        if (activity != null) {
            String json = (String) msg.obj;
            activity.setData(json);
        }
        super.handleMessage(msg);
    }
}


Thead:

private static class MyThread extends Thread {
    private MyHandler myHandler;
    Call<ResponseBody> bodyResponse;

    public MyThread(Call<ResponseBody> responseBodyCall, MyHandler handler) {
        bodyResponse = responseBodyCall;
        myHandler = handler;
    }

    @Override
    public void run() {
        super.run();
        try {
            Response<ResponseBody> body = bodyResponse.execute();
            Message message = new Message();
            message.obj = body.body().string();
            myHandler.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


使用:

/**
 * 同步
 *
 * @param responseBodyCall
 */
private void synSendRequest(Call<ResponseBody> responseBodyCall) {
    MyHandler myHandler = new MyHandler(this);
    MyThread myThread = new MyThread(responseBodyCall, myHandler);
    myThread.start();
}

private void setData(String json) {
    tv_show.setText(json);
}


同步请求的整体流程大致就这样了。



二、异步请求

相比同步请求方式,异步比较简单,因此建议使用异步请求方式,异步请求方式使用enqueue方法,并通过回调Callback 泛型接口的两个方法:

/**
 * 异步
 *
 * @param responseBodyCall
 */
private void asySendRequest(Call<ResponseBody> responseBodyCall) {

    responseBodyCall.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            try {
                tv_show.setText(response.body().string());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            tv_show.setText("error");
        }
    });

}


其它一些注意事项

service 的模式变成Call的形式的原因是为了让正在进行的事务可以被取消。要做到这点,你只需调用call.cancel()。


实例二(Retrofit 与 Gson)

当然如果你想把json字符串解析成DAO(实体类对象),在Retrofit2.x中,Converter不再包含其中,因此需要我们把Gson Converter依赖添加进来,此实例源码在源文件中的demo2包下。

添加Gson Converter:

compile 'com.squareup.retrofit2:converter-gson:2.1.0'



demo1下的程序进行修改如下:


修改一:接口请求

public interface APIService {

    @GET("getBrandBanner.php")
    Call<HttpResult> getBanner(@Query("uid") String _uid, @Query("token") String _token);

}

在定义接口请求时,我们传入了一个HttpResult类,它是我们从服务器返回的json串解析后的实体类。

修改二:创建Retrofit实例

Retrofit retrofit = new Retrofit.Builder().baseUrl(API_URL).addConverterFactory(GsonConverterFactory.create()).build();
APIService apiService = retrofit.create(APIService.class);
Call<HttpResult> responseBodyCall = apiService.getBanner("10915", "58524bb42c9ca");



我们在通过Retrofit Builder类来构造Retrofit实例时插入了一个Converter(Gson Converter)。


修改三:请求


同步请求

/**
 * 同步
 *
 * @param responseBodyCall
 */
private void synSendRequest(Call<HttpResult> responseBodyCall) {
    MyHandler myHandler = new MyHandler(this);
    MyThread myThread = new MyThread(responseBodyCall, myHandler);
    myThread.start();
}

private void setData(String data) {
    tv_show.setText(data);
}

private static class MyHandler extends Handler {

    WeakReference<DemoActivity2> weakReference;

    public MyHandler(DemoActivity2 activity) {
        weakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        DemoActivity2 activity = weakReference.get();
        if (activity != null) {
            HttpResult hotBean = (HttpResult) msg.obj;
            if (hotBean != null) {
                activity.setData(hotBean.data.pc.number);
            }
        }
        super.handleMessage(msg);
    }
}

private static class MyThread extends Thread {
    private MyHandler myHandler;
    Call<HttpResult> bodyResponse;

    public MyThread(Call<HttpResult> responseBodyCall, MyHandler handler) {
        bodyResponse = responseBodyCall;
        myHandler = handler;
    }

    @Override
    public void run() {
        super.run();
        try {
            Response<HttpResult> body = bodyResponse.execute();
            Message message = new Message();
            message.obj = body.body();
            myHandler.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


异步请求

/**
 * 异步
 *
 * @param responseBodyCall
 */
private void asySendRequest(Call<HttpResult> responseBodyCall) {

    responseBodyCall.enqueue(new Callback<HttpResult>() {
        @Override
        public void onResponse(Call<HttpResult> call, Response<HttpResult> response) {
            HotBean hotBean = response.body().data;
            tv_show.setText(hotBean.pc.number);
        }

        @Override
        public void onFailure(Call<HttpResult> call, Throwable t) {
            tv_show.setText("error");
        }
    });

}


实例三(Retrofit与RxJava )

经历了上面的两个例子,大家对Retrofit的使用已经有了充分的认识了吧,如果不满足于此,可以继续看下去,因为高潮马上来了,接下来会讲解Retrofit与RxJava的结合使用。


RxJava介绍

想来想去对RxJava介绍的文章,网上多的是,当然我认为这篇文章《给Android开发者的RxJava讲解》(“http://gank.io/post/560e15be2dca930e00da1083“)还是很不错,所以啊,我就不介绍了,哈哈哈。。。。。,强烈建议大家将RxJava讲解这篇文章看看,当然,不看也没问题,除非你只是拿来就用,否则作为一个有”节操”的程序员,还是老老实实的研究下。


正题

Retrofit2.x中有个机制,叫做CallAdapter,而Retrofit团队已经提供了RxJava的CallAdapter,这时就需要在app/build.gradle中引入以下依赖:

compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:1.1.0'

在之前例子中,为了使用同步请求方式,需要自己创建线程,过程比较繁琐,所以只能使用异步方式,基于此,使用RxJava来解决异步的问题,代码实例在demo3包下。

步骤一:请求接口

在创建请求接口时,我们就可以将Service作为Observable返回:

public interface APIService {

    @GET("getBrandBanner.php")
    Observable<HttpResult> getBanner(@Query("uid") String _uid, @Query("token") String _token);

}


步骤二:创建Retrofit实例

//step1
Retrofit retrofit = new Retrofit.Builder().baseUrl(API_URL).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();
APIService apiService = retrofit.create(APIService.class);
Observable<HttpResult> httpResultObservable = apiService.getBanner("10915", "58524bb42c9ca");



使用CallAdapter这种机制,可以在 Retrofit Builder 链中调用addCallAdapterFactory方法。

步骤三:请求

httpResultObservable.subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<HttpResult>() {
    @Override
    public void onCompleted() {
        tv_show.append("请求结束");
    }

    @Override
    public void onError(Throwable e) {
        tv_show.append("请求错误");
    }

    @Override
    public void onNext(HttpResult httpResult) {
        tv_show.append(httpResult.data.pc.number);
    }

    @Override
    public void onStart() {
        tv_show.append("请求开始");
    }
});



上面指定subscribe发生在IO线程中,而指定的Subscriber的回调发生在Android的UI线程中。

呼~~~~,终于把上面的例子写完了,天已经黑了,看看还有什么要讲的,好吧,每次这样请求无意间代码量就增多了,而我们理想中的请求方式应该是这样的,在Activity中是这样的请求的:

private void getNews() {
    MainHttpRequest.getInstance().getBanner(new ListenerSubscriber<ArrayList<NewsBean>>(getNewsListener, DemoActivity4.this));
}



接着通过回调获取我们想要的数据:

private OnFunctionListener getNewsListener = new OnFunctionListener<ArrayList<NewsBean>>() {
    @Override
    public void success(ArrayList<NewsBean> o) {
        tv_show.setText(o.get(0).lname);
    }

    @Override
    public void fail(String message) {

    }

};



并且服务端返回的信息,我们应该是抽取其中有用的信息,而code 、message、success、client等等信息不是我们应该关心的,就拿下面的json串来说:

{
    "code":"200",
    "message":"数据返回成功",
    "success":true,
    "data":[
        {
            "id":"1",
            "lname":"香水合集 | 该换上适合秋天的味道啦~"
        },
        {
            "id":"3",
            "lname":"IOPE水滢多效气垫腮红"
        },
        {
            "id":"0",
            "lname":"单品小记∣毛孔收收收?痘痘消消消?"
        },
        {
            "id":"4",
            "lname":"雅诗兰黛肌透修护精萃蜜"
        }
    ]
}



我们应该是关心data节点下的json串,因此,不管是为了省代码量还是获取数据方便,我们都有必要对这些进行一定量的封装。


实例四:封装

还是拿上面的json串来说事,在这json串中我们业务层应该是只关心data节点下的数据,因此定义一个HttpResult类:

public class HttpResult<T> {

    private int code;
    private String message;
    private boolean success;

    private T data;


    public int getCode() {
        return code;
    }

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

    public String getMessage() {
        return message;
    }

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

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public T getData() {
        return data;
    }

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



接着自定义一个Func1的类,用于变换时获取data部分的数据:

/**
 * 用来统一处理Http的resultCode,并将HttpResult的Data部分剥离出来返回给subscriber
 *
 * @param <T> Subscriber真正需要的数据类型,也就是Data部分的数据类型
 */
public class HttpResultFunc<T> implements Func1<HttpResult<T>, T> {

    @Override
    public T call(HttpResult<T> httpResult) {
        if (httpResult.isSuccess()) {
            Log.e("TAG", "response error");
        }
        return httpResult.getData();
    }
}



自定义一个Converter类用于Gson解析:

public class ResponseConvertFactory extends Converter.Factory {

    private final Gson gson;

    public static ResponseConvertFactory create() {
        return create(new Gson());
    }

    public static ResponseConvertFactory create(Gson gson) {
        return new ResponseConvertFactory(gson);
    }


    private ResponseConvertFactory(Gson gson) {
        if (gson == null) {
            throw new NullPointerException("gson == null");
        }
        this.gson = gson;
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        return new GsonResponseBodyConverter<>(gson, type);
    }

}


class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final Type type;

    GsonResponseBodyConverter(Gson gson, Type type) {
        this.gson = gson;
        this.type = type;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        String response = value.string();
        HttpResult httpResult = gson.fromJson(response, HttpResult.class);
        if (!httpResult.isSuccess()) {
            Log.e("TAG", "request error");
        }
        return gson.fromJson(response, type);
    }
}



这样定义后,我们可以在底层根据返回数据的一些参数来判别一些错误类型,方便处理。

创建一个单例的Http类:

public class Http {

    public static final String BASE_URL = "http://n1.glh.la/apps_T1/";

    private static final int DEFAULT_TIMEOUT = 5;

    private Retrofit retrofit;


    //构造方法私有
    private Http() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
        retrofit = new Retrofit.Builder()
                .client(builder.build())
                .addConverterFactory(ResponseConvertFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(BASE_URL)
                .build();

    }

    private static class SingletonHolder {
        private static final Http INSTANCE = new Http();
    }

    public Retrofit getRetrofit() {
        return retrofit;
    }

    public static Http getInstance() {
        return SingletonHolder.INSTANCE;
    }


    public <T> void getData(Observable<T> o, Subscriber<T> s) {
        o.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(s);
    }

}



最后自定义一个Subscriber类,用于请求监听,比如在这里显示一个加载框或者是对一些错误码的处理:

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

    private OnFunctionListener mSubscriberOnNextListener;
    private Context context;


    public ListenerSubscriber(OnFunctionListener mSubscriberOnNextListener, Context context) {
        this.mSubscriberOnNextListener = mSubscriberOnNextListener;
        this.context = context;
    }


    @Override
    public void onStart() {
    }

    @Override
    public void onCompleted() {
    }

    @Override
    public void onError(Throwable e) {
        if (mSubscriberOnNextListener != null) {
            mSubscriberOnNextListener.fail("error");
        }
    }

    /**
     * 将onNext方法中的返回结果交给Activity或Fragment自己处理
     *
     * @param t 创建Subscriber时的泛型类型
     */
    @Override
    public void onNext(T t) {
        if (mSubscriberOnNextListener != null) {
            mSubscriberOnNextListener.success(t);
        }
    }

}


public interface OnFunctionListener<T> {
    void success(T t);
    void fail(String message);
}



整体封装完毕后,再在业务层将上面请求的方式再次进行封装一遍:

/**
 * 首页相关请求
 * Created by glh on 2016-12-15.
 */
public interface HomeCarouselService {
    @GET("getHomeCarousel.php")
    Observable<HttpResult<ArrayList<NewsBean>>> getNews();
}
/**
 * 首页相关的网络请求
 * Created by glh on 2016-12-14.
 */
public class MainHttpRequest {

    private HomeCarouselService mHomeCarouselService;
    private Http mHttpMethods;

    private MainHttpRequest() {
        mHttpMethods = Http.getInstance();
        mHomeCarouselService = mHttpMethods.getRetrofit().create(HomeCarouselService.class);
    }

    private static class SingletonHolder {
        private static final MainHttpRequest INSTANCE = new MainHttpRequest();
    }

    //获取单例
    public static MainHttpRequest getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * 获取广告
     *
     * @param subscriber 由调用者传过来的观察者对象
     */
    public void getBanner(Subscriber<ArrayList<NewsBean>> subscriber) {
        Observable observable = mHomeCarouselService.getNews()
                .map(new HttpResultFunc<ArrayList<NewsBean>>());
        mHttpMethods.getData(observable, subscriber);
    }

}


创建实体类:

public class NewsBean {
    public String id;
    public String lname;
    public String tid;
    public String imgurl;
    public String desc;
    public String ptype;
    public String url;
}

完整源码在源文件的demo4中。



项目下载地址


以下是完整的github项目地址,欢迎多多star和fork。
github项目源码地址:点击【项目源码】

作者:GULINHAI12 发表于2016/12/15 18:43:01 原文链接
阅读:132 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>