Retrofit是Square公司基于restful风格推出的网络框架封装,截止目前github已经有了37.2kstart,可见他的受欢迎程度非常高,Retrofit基于Okhttp封装,具有非常强大的解耦特点,高度的灵活解耦导致使用起来不够简洁,下面对Retrofit进行一次二次的封装,在使用上更加简洁。封装之后具有一下特点:
- 支持reftofit的单例模式配置,一次配置多处使用。
- 支持动态切换baseUrl不影响原有的baseUrl。
- 支持通用格式的网络请求,返回对象或者字符串。
- 支持快捷的请求方式返回字符串格式的数据。
- 支持大文件下载和进度回调以及动态取消。
- 支持单文件和多文件上传。
一、导入依赖库
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
二、reftofit的创建
Retrofit的创建方式如下:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
1、addConverterFactory
用于添加Retrofit返回数据的支持格式,通常有两个返回字符串格式或者返回一个对象,这块需要添加对应的依赖:
- 返回字符串:.addConverterFactory(GsonConverterFactory.create())
- 返回对象:.addConverterFactory(ScalarsConverterFactory.create())
二者不能同时添加,同时添加只有后添加的生效,需要依据需求或者项目配置。
2、baseUrl
用于设置请求的协议IP和端口,在配置时候需要确定,但是有些时候有些接口baseUrl会变化,这时候需要单独配置了,原始的retrofit不支持动态配置,目前市面上的解决方案是两种,一种是重新创建一个Retrofit设置baseUrl,另一种是直接在拦截器里面处理,通过Api接口动态配置header,获取对应的baseUrl,使其生效,如果没有配置就使用默认的。
3、Retrofit对象的封装
基于上面两点对retrofit进行封装,命名为RetrofitManager,该管理类中有一个统一的baseUrl配置,需要提前配置好,在请求中直接使用该默认的配置,如果有需要切换的baseUrl,额外创建一个Retrofit,为了避免每次调用创建,把他放在一个Map集合中,每次获取Retrofit根据baseUrl从集合中读取,如果没有则创建,这样可以避免频繁的创建不同baseUrl的Retrofit。另外换需要对于返回数据是String或者为对象的Retrofit的支持。
/**
* 返回全局对象
*
* 解析为对象
*/
public Retrofit getRetrofit() {
checkBaseUrl();
Retrofit retrofit = retrofitMap.get(baseUrl);
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
retrofitMap.put(baseUrl, retrofit);
}
return retrofit;
}
/**
* 返回局部对象
*
* 解析为对象
*/
public Retrofit getRetrofit(String baseUrl) {
checkBaseUrl(baseUrl);
Retrofit retrofit = retrofitMap.get(baseUrl);
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
retrofitMap.put(baseUrl, retrofit);
}
return retrofit;
}
/**
* 返回全局对象
*
* 解析为字符串
*/
public Retrofit getStringRetrofit() {
checkBaseUrl();
Retrofit retrofit = retrofitMap.get(baseUrl);
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
retrofitMap.put(baseUrl, retrofit);
}
return retrofit;
}
/**
* 返回局部对象
*
* 解析为字符串
*/
public Retrofit getStringRetrofit(String baseUrl) {
checkBaseUrl();
Retrofit retrofit = retrofitMap.get(baseUrl);
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
retrofitMap.put(baseUrl, retrofit);
}
return retrofit;
}
/**
* 设置全局的url
* @param baseUrl
* @return
*/
public RetrofitManager setBaseUrl(String baseUrl) {
RetrofitManager.baseUrl = baseUrl;
return this;
}
这样在使用中如果有全局统一的baseUrl,则设置:
- Retrofit retrofit = RetrofitManager.getInstance().setBaseUrl("http://127.0.0.1:3000/").getStringRetrofit();
- Retrofit retrofit = RetrofitManager.getInstance().setBaseUrl("http://127.0.0.1:3000/").getRetrofit();
如果是需要设置其他的baseUrl,则设置:
- Retrofit retrofit = RetrofitManager.getInstance().getRetrofit("http://www.weather.com.cn/");
- Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit("http://www.weather.com.cn/");
三、Retrofit的通用的请求
LoginApi loginApi = retrofit.create(LoginApi.class);
Call<ResponseBody> call = loginApi.getName();
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
Retrofit的请求如上,支持get/post/delete/patch/options等方式的请求,配置方式灵活多变,高度解耦,为了兼容各个请求和内容回调,和请求参数配置的解耦,既支持返回为String有支持返回为对象Call这个对象不能直接处理,需要用户配置,否则就是去了通用性,同时能够兼顾请求的简洁性,仅对回调做了处理,回调如下:
public interface ResponseCallback<T> {
void onSuccess(T t);
void onFailure(Throwable t);
}
封装为如下,T表示用户希望返回的数据类型和Call中的泛型保持一致:
public static <T> void executeAsync(Call<T> call, ResponseCallback<T> callback) {
call.enqueue(new Callback<T>() {
@Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.isSuccessful() && callback != null) {
callback.onSuccess(response.body());
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
使用封装后的内容如下:
LoginApi loginApi = retrofit.create(LoginApi.class);
Call<ResponseBody> call = loginApi.getName();
HttpUtils.executeAsync(call, new ResponseCallback<ResponseBody>() {
@Override
public void onSuccess(ResponseBody responseBody) {
}
@Override
public void onFailure(Throwable t) {
}
});
四、get和post请求
除了通用的网络请求外,很多时候是get或者post请求,需要返回的格式是字符串的数据,这时候需要对通用的请求进一步封装,返回String格式:
public interface StringCallback extends ResponseCallback<String> {
}
对于get和post请求,分为两大类,没有参数的请求和有参数的请求,进一步缩小了请求范围。统一一个StringApi:
public interface StringApi {
//带参数的通用get请求
@GET()
Call<String> executeGet(@Url String url, @QueryMap Map<String, String> maps);
//不带参数的通用get请求
@GET()
Call<String> executeGet(@Url String url);
//不带参数的通用post请求
@POST()
Call<String> executePost( @Url String url);
//带参数的通用post请求
@POST()
@FormUrlEncoded
Call<String> executePost( @Url String url, @FieldMap Map<String, String> maps);
}
对于call.enqueue的结果统一出来回调
public class RetrofitStringCallback implements Callback<String> {
private StringCallback callback;
public RetrofitStringCallback(StringCallback callback) {
this.callback = callback;
}
@Override
public void onResponse(Call<String> call, Response<String> response) {
if (response.isSuccessful()) {
callback.onSuccess(response.body());
} else {
try {
Throwable throwable = new Throwable(response.errorBody().string());
callback.onFailure(throwable);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call<String> call, Throwable t) {
callback.onFailure(t);
}
}
具体请求封装如下:
/**
* get:无参数
*/
public static void get(String url, StringCallback callback) {
Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
get(retrofit, url, callback);
}
public static void get(Retrofit retrofit, String url, StringCallback callback) {
StringApi baseApi = retrofit.create(StringApi.class);
Call<String> call = baseApi.executeGet(url);
call.enqueue(new RetrofitStringCallback(callback));
}
/**
* get:有参数
*/
public static void get(String url, Map<String, String> map, StringCallback callback) {
Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
get(retrofit, url, map, callback);
}
public static void get(Retrofit retrofit, String url, Map<String, String> map, StringCallback callback) {
StringApi baseApi = retrofit.create(StringApi.class);
Call<String> call = baseApi.executeGet(url, map);
call.enqueue(new RetrofitStringCallback(callback));
}
/**
* post:无参数
*/
public static void post(String url, StringCallback callback) {
Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
post(retrofit, url, callback);
}
public static void post(Retrofit retrofit, String url, StringCallback callback) {
StringApi baseApi = retrofit.create(StringApi.class);
Call<String> call = baseApi.executePost(url);
call.enqueue(new RetrofitStringCallback(callback));
}
/**
* post有参数
*/
public static void post(String url, Map<String, String> map, StringCallback callback) {
Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
post(retrofit, url, map, callback);
}
public static void post(Retrofit retrofit, String url, Map<String, String> map, StringCallback callback) {
StringApi baseApi = retrofit.create(StringApi.class);
Call<String> call = baseApi.executePost(url, map);
call.enqueue(new RetrofitStringCallback(callback));
}
这样,如果是get或者post请求就变的很简单:
get("aoi/weather", new StringCallback() {
@Override
public void onSuccess(String s) {
Log.d(TAG, "onSuccess: " + s);
}
@Override
public void onFailure(Throwable t) {
Log.d(TAG, "onSuccess: " + t);
}
});
四、文件下载
1、回调定义
public interface DownLoadListener {
void onStart();
void progress(int progress, float currentSize, float totalSize);
void onFinish(String path);
void onFailure(String msg);
void onCancel(String tag);
}
实现类:
public abstract class DownLoadCallback implements DownLoadListener {
private String folder;
private String fileName;
private String tag;
public DownLoadCallback(String folder, String fileName) {
this.folder = folder;
this.fileName = fileName;
}
public DownLoadCallback(String folder, String fileName, String tag) {
this.folder = folder;
this.fileName = fileName;
this.tag = tag;
}
public void onStart() {
}
public void progress(int progress, float currentSize, float totalSize) {
}
@Override
public void onCancel(String tag) {
}
public String getFolder() {
return folder;
}
public String getFileName() {
return fileName;
}
public String getTag() {
return tag;
}
}
tag用于标记那是那个请求,用于请求的取消和请求的回调标记,如果没有这个需求,则可以不用。
2、DownLoadApi
和get/post的请求一样,文件下载也定义两个接口,一个是有参数的下载,另一个是无参数的下载,返回格式必须是ResponseBody,用于处理下载结果,如果是大文件的话,需要加上@Streaming,因为Retrofit默认下载中回一次性把内容下载到内存中才回调结果,加上@Streaming之后会以流的方式读取写入,否则进度回调不到。
public interface DownLoadApi {
@Streaming
@GET()
Call<ResponseBody> downLoadApi(@Url String url);
@Streaming
@GET()
Call<ResponseBody> downLoadApi( @Url String url, @QueryMap Map<String, String> maps);
}
3、封装下载
文件下载是比较耗时间的任务,需要放在子线程中执行,最好放在个线程池executor中,下面直接看下载入口:
/**
* 文件下载
*/
public void executeDownLoadAsync(Retrofit retrofit, String downloadUrl, DownLoadCallback callback) {
DownLoadApi baseApi = retrofit.create(DownLoadApi.class);
Call<ResponseBody> call = baseApi.downLoadApi(downloadUrl);
setDownLoadTag(callback.getTag(), call);
executor.execute(new DownloadRunnable(call, callback, executor, callMap));
}
public void executeDownLoadAsync(Retrofit retrofit, String downloadUrl, Map<String, String> map, DownLoadCallback callback) {
DownLoadApi baseApi = retrofit.create(DownLoadApi.class);
Call<ResponseBody> call = baseApi.downLoadApi(downloadUrl, map);
setDownLoadTag(callback.getTag(), call);
executor.execute(new DownloadRunnable(call, callback, executor, callMap));
}
/**
* 设置取消下载回调
*/
private void setDownLoadTag(String tag, Call<ResponseBody> call) {
if (TextUtils.isEmpty(tag)) {
return;
}
Call put = callMap.get(tag);
if (put == null) {
callMap.put(tag, call);
}
}
4、下载任务
具体的下载任务放在了DownloadRunnable里面,实现了Runnable接口,run方法中直接发起请求:
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (callback == null) {
return;
}
if (response.isSuccessful()) {
callback.onStart();
executor.execute(() -> writeResponseBodyToDisk(response.body()));
} else {
callback.onFailure(response.message());
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
callback.onFailure(t.getMessage());
}
});
因为回调是在主线程的,所以需要开辟一块子线程进行磁盘的写入,调用executor.execute只写入:
private void writeResponseBodyToDisk(ResponseBody body) {
try {
File file = new File(callback.getFolder() + File.separator + callback.getFileName());
if (!file.getParentFile().exists()) {
file.getParentFile().mkdir();
}
if (file.exists()) {
file.delete();
}
if (!file.createNewFile()) {
handler.post(() -> callback.onFailure("file path is not exit"));
return;
}
InputStream inputStream = null;
OutputStream outputStream = null;
ProgerssRun progerssRun = new ProgerssRun(callback, file.getAbsolutePath());
try {
byte[] fileReader = new byte[1024 * 4];
long fileSize = body.contentLength();
long downLoadSize = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(file);
handler.postDelayed(progerssRun, 300);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
downLoadSize += read;
//回调结果保留几位小数
DecimalFormat df = new DecimalFormat("0.000");
df.setRoundingMode(RoundingMode.HALF_UP);
//将K转为M,保留3位小数返回
float currentSize = Float.parseFloat(df.format(downLoadSize / (1024 * 1024f)));
float totalSize = Float.parseFloat(df.format(fileSize / (1024 * 1024f)));
int progress = (int) (downLoadSize * 100 / fileSize);
progerssRun.setProgress(progress, currentSize, totalSize);
}
outputStream.flush();
} catch (IOException e) {
handler.post(() -> {
handler.removeCallbacks(progerssRun);
callback.onCancel(callback.getTag());
});
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
if (!TextUtils.isEmpty(callback.getTag()) && callMap.get(callback.getTag()) != null) {
callMap.remove(callback.getTag());
}
}
} catch (IOException e) {
handler.post(() -> {
callback.onFailure(e.getMessage());
});
}
}
上面的handler是主线程的handler,将结果回调给主线程,便于数据的设置,最后关于进度回调的类:
public static class ProgerssRun implements Runnable {
private int progress;
private float currentSize;
private float totalSize;
private DownLoadCallback callback;
private String filePath;
public ProgerssRun(DownLoadCallback callback, String filePath) {
this.callback = callback;
this.filePath = filePath;
}
public void setProgress(int progress, float currentSize, float totalSize) {
this.progress = progress;
this.currentSize = currentSize;
this.totalSize = totalSize;
}
@Override
public void run() {
if (currentSize == totalSize) {
callback.progress(progress, currentSize, totalSize);
handler.removeCallbacks(this);
handler.post(() -> callback.onFinish(filePath));
} else {
callback.progress(progress, currentSize, totalSize);
//延迟300毫秒回调
handler.postDelayed(this, 300);
}
}
}
这样就完成了文件的下载,用法如下:
downLoadFile(retrofit, url, new DownLoadCallback(folder, fileFile, tag) {
@Override
public void onFinish(String path) {
}
@Override
public void onFailure(String msg) {
}
@Override
public void progress(int progress, float currentSize, float totalSize) {
}
@Override
public void onCancel(String tag) {
}
});
5、取消下载
在请求的时候设置有一个tag,如果需要取消下载的需求,根据tag进行取消,如果不需要请求可以不用设置:
/**
* 取消指定的请求
*
* @param tag
*/
public void cancel(String tag) {
if (TextUtils.isEmpty(tag)) {
return;
}
Call call = callMap.get(tag);
if (call != null) {
callMap.remove(tag);
if (!call.isCanceled()) {
call.cancel();
}
}
}
/**
* 取消所有的请求
*/
public void cancelAll() {
Iterator it = callMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Call<ResponseBody> call = (Call<ResponseBody>) entry.getValue();
call.cancel();
}
}
六、文件上传
总结了一下常用的文件上传的Api格式,具体需要那个,直接调用:
public interface UpLoadApi {
/**
* 上传字符串
*/
@FormUrlEncoded
@POST()
Call<String> uploadStringApi(@Url String url, @FieldMap Map<String, String> map);
@POST()
Call<String> uploadStringApi(@Url String url, @Body RequestBody body);
/**
* 上传单个文件
*/
@Multipart
@POST()
Call<String> uploadFileApi(@Url String url, @Part MultipartBody.Part body);
/**
* 上传多个文件
*/
@Multipart
@POST()
Call<String> uploadFilesApi(@Url String url, @PartMap Map<String, RequestBody> map);
@Multipart
@POST()
Call<String> uploadFilesApi(@Url String url, @Part List<MultipartBody.Part> parts);
/**
* 文件字符串混合传递
*/
@Multipart
@POST()
Call<String> uploadFileStringApi(@Url String url, @Part("body") RequestBody body, @Part MultipartBody.Part file);
/**
* 通用的上传:这种方式上传的时候,不能再接口上加上@Multipart的注解,否者会报错
*/
@POST()
Call<String> uploadApi(@Url String url,@Body RequestBody body);
}
1、单个文件上传
/**
* 上传文件单个
*
* path:文件路径
* action:服务器接收的字段,和服务器对应
*/
public static void upLoadFile(String url, String path, String action, StringCallback callback) {
Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
upLoadFile(retrofit, url, path, action, callback);
}
public static void upLoadFile(Retrofit retrofit, String url, String path, String action, StringCallback callback) {
UpLoadApi upLoadApi = retrofit.create(UpLoadApi.class);
File file = new File(path);
RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part part = MultipartBody.Part.createFormData(action, file.getName(), body);
Call<String> call = upLoadApi.uploadFileApi(url, part);
call.enqueue(new RetrofitStringCallback(callback));
}
使用:
upLoadFile("upload", "/storage/emulated/0/DCIM/Screenshots/Screenshot_2020_1225_122945.png", "logo", new StringCallback() {
@Override
public void onSuccess(String s) {
Log.d(TAG, "onSuccess: " + s);
}
@Override
public void onFailure(Throwable t) {
Log.d(TAG, "onFailure: " + t.getMessage());
}
});
2、多个文件上传
/**
* 上传多个文件
* listPath:文件路径集合
* action:服务器接收的字段,和服务器对应
*/
public static void upLoadFiles(String url, List<String> listPath, String action, StringCallback callback) {
Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
upLoadFiles(retrofit, url, listPath, action, callback);
}
public static void upLoadFiles(Retrofit retrofit, String url, List<String> listPath, String action, StringCallback callback) {
Map<String, RequestBody> map = new HashMap<>();
for (int i = 0; i < listPath.size(); i++) {
File file = new File(listPath.get(i));
RequestBody body = RequestBody.create(MediaType.parse("image/*"), file);
map.put("" + action + "\"; filename=\"" + file.getName(), body);
}
UpLoadApi upLoadApi = retrofit.create(UpLoadApi.class);
Call<String> call = upLoadApi.uploadFilesApi(url, map);
call.enqueue(new RetrofitStringCallback(callback));
}
使用:
ArrayList<String> pathList = new ArrayList<>();
pathList.add("/storage/emulated/0/DCIM/Screenshots/Screenshot_2020_1225_122945.png");
pathList.add("/storage/emulated/0/DCIM/Screenshots/Screenshot_2020_1225_181054.png");
HttpUtils.upLoadFiles("form", pathList, "logo", new StringCallback() {
@Override
public void onSuccess(String s) {
Log.d(TAG, "onSuccess: " + s);
}
@Override
public void onFailure(Throwable t) {
Log.d(TAG, "onFailure: " + t.getMessage());
}
});
有什么改进方案,欢迎提出