Retrofit是Square公司开发的一款针对Android网络请求的框架,Retrofit2底层基于OkHttp实现的,OkHttp现在已经得到Google官方认可,大量的app都采用OkHttp做网络请求,其源码详见OkHttp Github。
二、如何使用
首先需要在build.gradle文件中引入需要的第三包,配置如下:
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-scalars:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'io.reactivex:rxjava:1.3.0'
compile 'io.reactivex:rxandroid:1.2.1'
导入第三方包,接下来是使用retrofit进行网络请求,接下来我会使用我项目内的代码进行说明
Post请求:
Post请求需要把请求参数放置在请求体中,而非拼接在url后面
/**
* 普通写法
*/
//附近的商品
@POST("open/market/getNearbyGoods.do")
Observable<String> getNearbyGoods(@QueryMap Map<String, String> map);
注释写的比较清晰,所有我这里说下,它的缺点,每个接口都需要写一个类似的方法,比较繁琐,重新造轮子。下面是优化版:
/**
* post方式 map传参
* @param url
* @param maps
* @return
*/
@POST("{url}")
Observable<String> executePost(
@Path(value = "url", encoded = true) String url,
@QueryMap Map<String, String> maps);
/**
* post方式 json传参
* @param url
* @param jsonStr
* @return
*/
@POST("{url}")
Observable<String> json(
@Path(value = "url", encoded = true) String url,
@Body RequestBody jsonStr);
上传文件
/**
* post方式 上传单个文件
* @param url
* @param description 文件描述
* @param file
* @return
*/
@Multipart
@POST("{url}")
Observable<String> upLoadFile(
@Path(value = "url", encoded = true) String url,
@Part("description") RequestBody description,
@Part MultipartBody.Part file);
/**
* post方式 上传多个文件
* @param url
* @param maps
* @return
*/
@POST("{url}")
Observable<String> upLoadFiles(
@Path(value = "url", encoded = true) String url,
@PartMap() Map<String, MultipartBody.Part> maps);
图文上传
/**
* post方式 图文同时上传
* @param url
* @param partMap
* @param file
* @return
*/
@Multipart
@POST
Observable<String> uploadFileWithPartMap(
@Url() String url,
@QueryMap() Map<String, String> partMap,
@Part("file") MultipartBody.Part file);
Get请求:
get请求跟post请求差不多,这里只写一些
/**
* get方式 map传参
* @param url
* @param maps
* @return
*/
@GET("{url}")
Observable<String> executeGet(
@Path(value = "url", encoded = true) String url,
@QueryMap Map<String, String> maps
);
/**
* get方式 下载文件
* @param fileUrl
* @return
*/
@Streaming
@GET
Observable<ResponseBody> downloadFile(@Url String fileUrl);
1.创建业务请求接口,具体代码如下:
这里需要稍作说明,@GET注解就表示get请求,@Query表示请求参数,将会以key=value的方式拼接在url后面
public interface BlueService {
@GET("book/search")
Call<BookSearchResponse> getSearchBooks(@Query("q") String name,
@Query("tag") String tag, @Query("start") int start,
@Query("count") int count);
}
2.创建一个Retrofit的示例,并完成相应的配置
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.douban.com/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build();
BlueService service = retrofit.create(BlueService.class);
这里的baseUrl就是网络请求URL相对固定的地址,一般包括请求协议(如Http)、域名或IP地址、端口号等,当然还会有很多其他的配置,下文会详细介绍。还有addConverterFactory方法表示需要用什么转换器来解析返回值,GsonConverterFactory.create()表示调用Gson库来解析json返回值,具体的下文还会做详细介绍。
3. 调用请求方法,并得到Call实例
Call<BookSearchResponse> call = mBlueService.getSearchBooks("小王子", "", 0, 3);
Call其实在Retrofit中就是行使网络请求并处理返回值的类,调用的时候会把需要拼接的参数传递进去
4. 使用Call实例完成同步或异步请求
4.1同步请求
BookSearchResponse response = call.execute().body();
这里需要注意的是网络请求一定要在子线程中完成,不能直接在UI线程执行,不然会crash
4.2异步请求
call.enqueue(new Callback<BookSearchResponse>() {
@Override
public void onResponse(Call<BookSearchResponse> call, Response<BookSearchResponse> response) {
asyncText.setText("异步请求结果: " + response.body().books.get(0).altTitle);
}
@Override
public void onFailure(Call<BookSearchResponse> call, Throwable t) {
}
});
上面是简单的使用
我项目中使用的是rxjava+retrofit框架,目前是比较大众的写法,网上有许多框架的封装。这里我把我自己的代码直接上
/**
* RetrofitClient
*/
public class RetrofitClient {
private static final int DEFAULT_TIMEOUT = 20;
private Retrofit retrofit;
private Cache cache = null;
private File httpCacheDirectory;
private static OkHttpClient okHttpClient;
public static RetrofitClient getInstance(Context context) {
return new RetrofitClient(context);
}
public RetrofitClient(Context context) {
if (httpCacheDirectory == null) {
httpCacheDirectory = new File(context.getCacheDir(), "tamic_cache");
}
try {
if (cache == null) {
cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
}
} catch (Exception e) {
Log.e("OKHttp", "Could not create http cache", e);
}
okHttpClient = new OkHttpClient.Builder()
//设置可以从传入的HTTP响应接受cookie并向传出HTTP请求提供cookie的处理程序。
// .cookieJar(new NovateCookieManger(context))
//设置响应缓存用于读取和写入缓存响应。
.cache(cache)
//链接超时
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
//新链接默认超时时间
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
//设置用于回收HTTP和HTTPS连接的连接池。
.connectionPool(new ConnectionPool(8, 15, TimeUnit.SECONDS))
// 这里你可以根据自己的机型设置同时连接的个数和时间,我这里8个,和每个保持时间为10s
.build();
retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(BaseRetrofit.API)
//增加返回值为String的支持
.addConverterFactory(ScalarsConverterFactory.create())
//增加返回值为Gson的支持(以实体类返回)
.addConverterFactory(GsonConverterFactory.create())
//增加返回值为Oservable<T>的支持
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
/**
* post方式的访问api
*
* @param url
* @param map
* @param subscribe
* @return
*/
public Subscription initMap(String url, Map<String, String> map, Subscriber<String> subscribe) {
BaseRetrofit service = retrofit.create(BaseRetrofit.class);
Subscription subscription = service.executePost(url, map)
.compose(schedulersTransformer())
.subscribe(subscribe);
return subscription;
}
/**
* post方式 单个文件上传
*
* @param url
* @param file
* @param subscribe
* @return
*/
public Subscription upLoadFile(String url, File file, Subscriber<String> subscribe) {
// 创建 RequestBody,用于封装构建RequestBody
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
// MultipartBody.Part 和后端约定好Key,这里的partName是用image
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
// 添加描述
String descriptionString = "hello, 这是文件描述";
RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);
BaseRetrofit service = retrofit.create(BaseRetrofit.class);
Subscription subscription = service.upLoadFile(url, description, body)
.compose(schedulersTransformer())
.subscribe(subscribe);
return subscription;
}
/**
* post方式 多个文件上传
*
* @param url
* @param files
* @param subscribe
* @return
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public Subscription upLoadFiles(String url, Map<String, File> files, Subscriber<String> subscribe) {
Map<String, MultipartBody.Part> map = new ArrayMap<>();
for (int i = 0; i < files.size(); i++) {
// 创建 RequestBody,用于封装构建RequestBody
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), files.get(i));
// MultipartBody.Part 和后端约定好Key,这里的partName是用image
MultipartBody.Part body = MultipartBody.Part.createFormData("image", files.get(i).getName(), requestFile);
map.put(i + "", body);
}
// 添加描述
BaseRetrofit service = retrofit.create(BaseRetrofit.class);
Subscription subscription = service.upLoadFiles(url, map)
.compose(schedulersTransformer())
.subscribe(subscribe);
return subscription;
}
/**
* get方式 下载文件
* @param url 文件url
* @param subscribe
* @return
*/
public Subscription downLoadFile(String url, Subscriber<ResponseBody> subscribe) {
// 添加描述
BaseRetrofit service = retrofit.create(BaseRetrofit.class);
Subscription subscription = service.downloadFile(url)
.subscribeOn(Schedulers.io())
.subscribe(subscribe);
return subscription;
}
Observable.Transformer schedulersTransformer() {
return new Observable.Transformer() {
@Override
public Object call(Object observable) {
return ((Observable) observable).subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
/* @Override
public Observable call(Observable observable) {
return observable.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}*/
};
}
}
网络请求错误进行自定义判断
public class ExceptionHandle {
private static final int UNAUTHORIZED = 401;
private static final int FORBIDDEN = 403;
private static final int NOT_FOUND = 404;
private static final int REQUEST_TIMEOUT = 408;
private static final int INTERNAL_SERVER_ERROR = 500;
private static final int BAD_GATEWAY = 502;
private static final int SERVICE_UNAVAILABLE = 503;
private static final int GATEWAY_TIMEOUT = 504;
public static ResponeThrowable handleException(Throwable e) {
ResponeThrowable ex;
if (e instanceof HttpException) {
HttpException httpException = (HttpException) e;
ex = new ResponeThrowable(e, ERROR.HTTP_ERROR);
switch (httpException.code()) {
case UNAUTHORIZED:
case FORBIDDEN:
case NOT_FOUND:
case REQUEST_TIMEOUT:
case GATEWAY_TIMEOUT:
case INTERNAL_SERVER_ERROR:
case BAD_GATEWAY:
case SERVICE_UNAVAILABLE:
default:
ex.message = "网络错误";
break;
}
return ex;
} else if (e instanceof ServerException) {
ServerException resultException = (ServerException) e;
ex = new ResponeThrowable(resultException, resultException.code);
ex.message = resultException.message;
return ex;
} else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) {
ex = new ResponeThrowable(e, ERROR.PARSE_ERROR);
ex.message = "解析错误";
return ex;
} else if (e instanceof ConnectException) {
ex = new ResponeThrowable(e, ERROR.NETWORD_ERROR);
ex.message = "连接失败";
return ex;
} else if (e instanceof javax.net.ssl.SSLHandshakeException) {
ex = new ResponeThrowable(e, ERROR.SSL_ERROR);
ex.message = "证书验证失败";
return ex;
} else if (e instanceof ConnectTimeoutException) {
ex = new ResponeThrowable(e, ERROR.TIMEOUT_ERROR);
ex.message = "连接超时";
return ex;
} else if (e instanceof java.net.SocketTimeoutException) {
ex = new ResponeThrowable(e, ERROR.TIMEOUT_ERROR);
ex.message = "连接超时";
return ex;
} else {
if (!NetworkUtil.isNetworkAvailable(App.getInstance())) {
ex = new ResponeThrowable(e, ERROR.UNKNOWN);
ex.message = "无网络,请连接网络";
}else{
ex = new ResponeThrowable(e, ERROR.UNKNOWN);
ex.message = "未知错误";
}
return ex;
}
}
/**
* 约定异常
*/
class ERROR {
/**
* 未知错误
*/
public static final int UNKNOWN = 1000;
/**
* 解析错误
*/
public static final int PARSE_ERROR = 1001;
/**
* 网络错误
*/
public static final int NETWORD_ERROR = 1002;
/**
* 协议出错
*/
public static final int HTTP_ERROR = 1003;
/**
* 证书出错
*/
public static final int SSL_ERROR = 1005;
/**
* 连接超时
*/
public static final int TIMEOUT_ERROR = 1006;
}
public static class ResponeThrowable extends Exception {
public int code;
public String message;
public ResponeThrowable(Throwable throwable, int code) {
super(throwable);
this.code = code;
}
}
public class ServerException extends RuntimeException {
public int code;
public String message;
}
}
public abstract class BaseSubscriber<T> extends Subscriber<T> {
@Override
public void onStart() {
onReStart();
super.onStart();
}
@Override
public void onError(Throwable e) {
if (e instanceof ExceptionHandle.ResponeThrowable) {
onError((ExceptionHandle.ResponeThrowable) e);
} else {
onError(ExceptionHandle.handleException(e));
}
}
@Override
public void onNext(T t) {
BaseResponse baseResponse = ResponseParserUtils.parseResponse((String) t, String.class, false);
onNext(baseResponse);
}
public abstract void onNext(BaseResponse baseResponse);
public abstract void onReStart();
public abstract void onError(ExceptionHandle.ResponeThrowable e);
}
准备好了之后,进行网络请求方法:
Subscription subscription = RetrofitClient.getInstance(this)
.downLoadFile(downUrl, new Subscriber<ResponseBody>() {
@Override
public void onCompleted() {
stopService(intent);
CatalogActivity.downOk=true;
}
@Override
public void onError(Throwable e) {
LogUtils.i("DownNovelService", e.toString());
ToastUtils.showMessageLong(e.toString());
stopService(intent);
CatalogActivity.downOk=true;
}
@Override
public void onNext(ResponseBody responseBody) {
if (WriteFileManager.writeResponseBodyToDisk(responseBody, novelName + ".text")) {
ToastUtils.showMessageLong("下载成功");
} else {
ToastUtils.showMessageLong("下载失败");
}
}
});
三、其他需要知道事项
网络请求看起来是那么完美,但是仔细看并进行试验时,你就会发现,怎么没有取消网络请求的方法。
retrofit取消请求:call.cancel();
rxjava取消订阅:Subscription.unsubscribe();
看具体情况使用。
这里我封装了一个rxjava+retrofit取消请求的类,直接上代码:
public interface RxActionManager<T> {
void add(T tag, Subscription subscription);
void remove(T tag);
void cancel(T tag);
void cancelAll();
}
/**
* describe Retrofit+RxJava取消接口管理
* authors liuyaolin
* createTime 2017/6/6 16:31
*/
public class RxApiManager implements RxActionManager<Object> {
private static RxApiManager sInstance = null;
private ArrayMap<Object, Subscription> maps;
public static RxApiManager get() {
if (sInstance == null) {
synchronized (RxApiManager.class) {
if (sInstance == null) {
sInstance = new RxApiManager();
}
}
}
return sInstance;
}
private RxApiManager() {
maps = new ArrayMap<>();
}
@Override
public void add(Object tag, Subscription subscription) {
maps.put(tag, subscription);
}
@Override
public void remove(Object tag) {
if (!maps.isEmpty()) {
maps.remove(tag);
}
}
public void removeAll() {
if (!maps.isEmpty()) {
maps.clear();
}
}
@Override
public void cancel(Object tag) {
if (maps.isEmpty()) {
return;
}
if (maps.get(tag) == null) {
return;
}
if (!maps.get(tag).isUnsubscribed()) {
maps.get(tag).unsubscribe();
maps.remove(tag);
}
}
@Override
public void cancelAll() {
if (maps.isEmpty()) {
return;
}
Set<Object> keys = maps.keySet();
for (Object apiKey : keys) {
cancel(apiKey);
}
}
}
调用方法是:
先添加请求到Map集合内
RxApiManager.get().add(this, subscription);
RxApiManager.get().cancel(this);
四、结束语
关于Retrofit常用的方法基本上已经介绍完了,有些请求由于工作保密性的原因,所以就没有放出来,但是基本的方法和操作都是有的,仿照文中提到的代码就可以实现你想要的功能。参考了网络一些retrofit相关资料。由于本人能力有限,有错误或者表述不准确的地方还望多多留言指正。