转载请标明出处:
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项目源码地址:点击【项目源码】