Android开发中的网络框架经过多年的发展,目前比较主流的就是Retrofit了,Retrofit2版本出现也有几年了,为了方便使用,特封装了一些关于Retrofit2的代码,分享给大家。
框架主要包括:
使用效果预览:
Retrofit框架内部使用的还是OkHttp框架,在实例化的时候可以自定义OkHttpClient来实现一些个性化的设置,如超时时长、HTTPS协议支持等。
1、OkHttpClient实例
private static int TIME_OUT = 30; // 30秒超时断开连接
public static OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCert)
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
.build();
其中,自定义了sslSocketFactory和trustAllCert证书管理器:
private static X509TrustManager trustAllCert = new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
};
private static SSLSocketFactory sslSocketFactory = new SSLSocketFactoryCompat(trustAllCert);
SSLSocketFactoryCompat类是从网络上找的,这里就不多说了,详见源码部分。
2、Retrofit对象
/**
* 网络框架单例
* 因为示例中用到不同的接口地址,URL_BASE随便写了一个,如果是实际项目中,则设置为根路径即可
*/
private static Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constant.URL_BASE)
.client(client)
.build();
定义好OkHttpClient后,Retrofit对象的构造就很简单,需要注意的是,一定要为retrofit对象指定好baseUrl,如果是后台地址有多个,写其中一个就可以了。
网络上有不少涉及到动态注册baseUrl的文章,写demo的时候并没有遇到此类问题,如果有遇到的话再寻找对应方案即可。
封装的框架让使用者传递一个回调的对象,在网络请求开始、结束、异常等阶段根据需要将调用相应的回调接口。
该回调接口还能够自动处理一些网络异常,并给予相应的提示(按需修改)。
/**
* 网络请求结果处理类
* @param 请求结果封装对象
*/
public static abstract class ResultHandler<T> {
Context context;
public ResultHandler(Context context) {
this.context = context;
}
/**
* 判断网络是否未连接
*
* @return
*/
public boolean isNetDisconnected() {
return NetworkUtil.isNetDisconnected(context);
}
/**
* 请求成功之前
*/
public abstract void onBeforeResult();
/**
* 请求成功时
*
* @param t 结果数据
*/
public abstract void onResult(T t);
/**
* 服务器出错
*/
public void onServerError() {
// 服务器处理出错
Toast.makeText(context, R.string.net_server_error, Toast.LENGTH_SHORT).show();
}
/**
* 请求失败后的处理
*/
public abstract void onAfterFailure();
/**
* 请求失败时的处理
*
* @param t
*/
public void onFailure(Throwable t) {
if (t instanceof SocketTimeoutException || t instanceof ConnectException) {
// 连接异常
if (NetworkUtil.isNetworkConnected(context)) {
// 服务器连接出错
Toast.makeText(context, R.string.net_server_connected_error, Toast.LENGTH_SHORT).show();
} else {
// 手机网络不通
Toast.makeText(context, R.string.net_not_connected, Toast.LENGTH_SHORT).show();
}
} else if (t instanceof Exception) {
// 功能异常
Toast.makeText(context, R.string.net_unknown_error, Toast.LENGTH_SHORT).show();
}
}
}
其中的string定义:
<string name="net_not_connected">发送请求失败,请检查网络连接string>
<string name="net_server_connected_error">连接服务器失败,请稍后重试string>
<string name="net_server_error">服务器处理请求失败,请稍后重试string>
<string name="net_server_param_error">应用数据出错,请刷新重试string>
<string name="net_unknown_error">未知错误,请稍后重试string>
1、定义GetRequest
Retrofit通过定义service来发起网络请求服务,Get请求服务可以定义为:
package com.dommy.retrofitframe.network;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Url;
/**
* Get请求封装
*/
public interface GetRequest {
/**
* 发送Get请求请求
* @param url URL路径
* @return
*/
@GET
Call<ResponseBody> getUrl(@Url String url);
}
说明:
- 需要加@GET注解;
- url对应的参数需要加@Url注解。
2、封装get请求方法
定义static方法:
/**
* 发送GET网络请求
* @param url 请求地址
* @param clazz 返回的数据类型
* @param resultHandler 回调
* @param 泛型
*/
public static <T extends BaseResult> void sendGetRequest(String url, final Class<T> clazz, final ResultHandler<T> resultHandler) {
// 判断网络连接状况
if (resultHandler.isNetDisconnected()) {
resultHandler.onAfterFailure();
return;
}
GetRequest getRequest = retrofit.create(GetRequest.class);
// 构建请求
Call<ResponseBody> call = getRequest.getUrl(url);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
resultHandler.onBeforeResult();
try {
ResponseBody body = response.body();
if (body == null) {
resultHandler.onServerError();
return;
}
String string = body.string();
T t = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().fromJson(string, clazz);
resultHandler.onResult(t);
} catch (IOException e) {
e.printStackTrace();
resultHandler.onFailure(e);
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
resultHandler.onFailure(t);
resultHandler.onAfterFailure();
}
});
}
其中,返回值的Gson转换过程,可以根据需要选择去掉;如果返回值都是json格式,可以保留,这样在使用处就可以直接拿到Bean对象,比较方便。
3、发起Get请求
RetrofitRequest.sendGetRequest(url, WeatherResult.class, new RetrofitRequest.ResultHandler<WeatherResult>(this) {
@Override
public void onBeforeResult() {
// 这里可以放关闭loading
}
@Override
public void onResult(WeatherResult weatherResult) {
String weather = new Gson().toJson(weatherResult);
tvContent.setText(weather);
}
@Override
public void onAfterFailure() {
// 这里可以放关闭loading
}
});
其中,WeatherResult类:
package com.dommy.retrofitframe.network.result;
import com.google.gson.JsonObject;
public class WeatherResult extends BaseResult {
private int code;
private String msg;
private JsonObject data; // 数据部分也是一个bean,用JsonObject代替了
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public JsonObject getData() {
return data;
}
public void setData(JsonObject data) {
this.data = data;
}
}
1、定义PostRequest
Post请求服务可以定义为:
package com.dommy.retrofitframe.network;
import java.util.Map;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import retrofit2.http.Url;
/**
* Post请求封装
*/
public interface PostRequest{
/**
* 发送Post请求
* @param url URL路径
* @param requestMap 请求参数
* @return
*/
@FormUrlEncoded
@POST
Call<ResponseBody> postMap(@Url String url, @FieldMap Map<String, String> requestMap);
}
说明:
- 需要加@FormUrlEncoded、@POST注解;
- url对应的参数需要加@Url注解。
- post提交的参数需要加@FieldMap注解。
2、封装post请求方法
定义static方法:
/**
* 发送post网络请求
* @param url 请求地址
* @param paramMap 参数列表
* @param clazz 返回的数据类型
* @param resultHandler 回调
* @param 泛型
*/
public static <T extends BaseResult> void sendPostRequest(String url, Map<String, String> paramMap, final Class<T> clazz, final ResultHandler<T> resultHandler) {
// 判断网络连接状况
if (resultHandler.isNetDisconnected()) {
resultHandler.onAfterFailure();
return;
}
PostRequest postRequest = retrofit.create(PostRequest.class);
// 构建请求
Call<ResponseBody> call = postRequest.postMap(url, paramMap);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
resultHandler.onBeforeResult();
try {
ResponseBody body = response.body();
if (body == null) {
resultHandler.onServerError();
return;
}
String string = body.string();
T t = new Gson().fromJson(string, clazz);
resultHandler.onResult(t);
} catch (IOException e) {
e.printStackTrace();
resultHandler.onFailure(e);
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
resultHandler.onFailure(t);
resultHandler.onAfterFailure();
}
});
}
3、发起Post请求
RetrofitRequest.sendPostRequest(url, paramMap, WeatherResult.class, new RetrofitRequest.ResultHandler<WeatherResult>(this) {
@Override
public void onBeforeResult() {
// 这里可以放关闭loading
}
@Override
public void onResult(WeatherResult weatherResult) {
String weather = new Gson().toJson(weatherResult);
tvContent.setText(weather);
}
@Override
public void onAfterFailure() {
// 这里可以放关闭loading
}
});
1、定义FileRequest
package com.dommy.retrofitframe.network;
import java.util.Map;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.PartMap;
import retrofit2.http.Streaming;
import retrofit2.http.Url;
/**
* 文件上传请求封装
*/
public interface FileRequest {
/**
* 上传文件请求
* @param url URL路径
* @param paramMap 请求参数
* @return
*/
@Multipart
@POST
Call<ResponseBody> postFile(@Url String url, @PartMap Map<String, RequestBody> paramMap);
/**
* 下载文件get请求
* @param url 链接地址
* @return
*/
@Streaming
@GET
Call<ResponseBody> download(@Url String url);
}
说明:
- 文件上传需要额外添加@Multipart、@PartMap注解;
- 文件下载需要额外添加@Streaming注解。
2、封装文件上传方法
定义static方法:
/**
* 发送上传文件网络请求
* @param url 请求地址
* @param file 文件
* @param clazz 返回的数据类型
* @param resultHandler 回调
* @param 泛型
*/
public static <T extends BaseResult> void fileUpload(String url, File file, final Class<T> clazz, final ResultHandler<T> resultHandler) {
// 判断网络连接状况
if (resultHandler.isNetDisconnected()) {
resultHandler.onAfterFailure();
return;
}
FileRequest fileRequest = retrofit.create(FileRequest.class);
Map<String, RequestBody> paramMap = new HashMap<>();
addMultiPart(paramMap, "file", file);
// 构建请求
Call<ResponseBody> call = fileRequest.postFile(url, paramMap);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
resultHandler.onBeforeResult();
try {
ResponseBody body = response.body();
if (body == null) {
resultHandler.onServerError();
return;
}
String string = body.string();
T t = new Gson().fromJson(string, clazz);
resultHandler.onResult(t);
} catch (IOException e) {
e.printStackTrace();
resultHandler.onFailure(e);
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
resultHandler.onFailure(t);
resultHandler.onAfterFailure();
}
});
}
3、发起文件上传请求
因为没有找到现成的上传文件接口,所以这里就随便弄了个接口做了个例子。自己写的时候在后台做一个接口文件的接口就行了。
服务端写法如果有不明白的,可以参见:《HttpClient4.5.2由Client客户端上传File文件流至Server服务端》一文中的server端代码。
File file = null;
try {
// 通过新建文件替代文件寻址
file = File.createTempFile("abc", "txt");
} catch (IOException e) {
}
String url = Constant.URL_LOGIN;
RetrofitRequest.fileUpload(url, file, BaseResult.class, new RetrofitRequest.ResultHandler<BaseResult>(this) {
@Override
public void onBeforeResult() {
// 这里可以放关闭loading
}
@Override
public void onResult(BaseResult baseResult) {
tvContent.setText("上传成功");
}
@Override
public void onAfterFailure() {
// 这里可以放关闭loading
}
});
基于FileRequest的定义,封装fileDownload方法即可。
1、定义DownloadHandler
因为文件下载过程中涉及到进度的计算、显示,所以这里需要定义一个DownloadHandler回调:
/**
* 文件下载回调
*/
public interface DownloadHandler {
/**
* 接收到数据体
* @param body 响应体
*/
public void onBody(ResponseBody body);
/**
* 文件下载出错
*/
public void onError();
}
2、定义Retrofit对象
为了防止回调方法内部涉及到界面操作,出现NetworkOnMainThreadException问题,特别自定义一个回调方法执行器executorService。
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 网络框架
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constant.URL_BASE)
.callbackExecutor(executorService)
.build();
3、封装文件下载方法
/**
* 文件下载
* @param url 请求地址
* @param downloadHandler 回调接口
*/
public static void fileDownload(String url, final DownloadHandler downloadHandler) {
// 回调方法执行器,定义回调在子线程中执行,避免Callback返回到MainThread,导致文件下载出现NetworkOnMainThreadException
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 网络框架
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constant.URL_BASE)
.callbackExecutor(executorService)
.build();
FileRequest fileRequest = retrofit.create(FileRequest.class);
// 构建请求
Call<ResponseBody> call = fileRequest.download(url);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
// 写入文件
downloadHandler.onBody(response.body());
} else {
downloadHandler.onError();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
downloadHandler.onError();
}
});
}
4、文件下载示例
RetrofitRequest.fileDownload(Constant.URL_DOWNLOAD, new RetrofitRequest.DownloadHandler() {
@Override
public void onBody(ResponseBody body) {
if (!writeResponseBodyToDisk(body)) {
mHandler.sendEmptyMessage(DOWNLOAD_ERROR);
}
}
@Override
public void onError() {
mHandler.sendEmptyMessage(DOWNLOAD_ERROR);
}
});
其中涉及到的对象和方法:
private static final int DOWNLOAD_ING = 1;// 下载中
private static final int DOWNLOAD_FINISH = 2;// 下载结束
private static final int DOWNLOAD_ERROR = -1;// 下载出错
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DOWNLOAD_ING:
// 正在下载
showProgress();
break;
case DOWNLOAD_FINISH:
// 下载完成
onDownloadFinish();
break;
case DOWNLOAD_ERROR:
// 出错
onDownloadError();
break;
}
}
};
/**
* 写文件入磁盘
*
* @param body 请求结果
* @return boolean 是否下载写入成功
*/
private boolean writeResponseBodyToDisk(ResponseBody body) {
savePath = StorageUtil.getDownloadPath();
File apkFile = new File(savePath, fileName);
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
// 获取文件大小
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(apkFile);
// byte转Kbyte
BigDecimal bd1024 = new BigDecimal(1024);
totalByte = new BigDecimal(fileSize).divide(bd1024, BigDecimal.ROUND_HALF_UP).setScale(0).intValue();
// 只要没有取消就一直下载数据
while (!cancelUpdate) {
int read = inputStream.read(fileReader);
if (read == -1) {
// 下载完成
mHandler.sendEmptyMessage(DOWNLOAD_FINISH);
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
// 计算进度
progress = (int) (((float) (fileSizeDownloaded * 100.0 / fileSize)));
downByte = new BigDecimal(fileSizeDownloaded).divide(bd1024, BigDecimal.ROUND_HALF_UP).setScale(0).intValue();
// 子线程中,借助handler更新界面
mHandler.sendEmptyMessage(DOWNLOAD_ING);
}
outputStream.flush();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 显示下载进度
*/
private void showProgress() {
String text = progress + "% | " + downByte + "Kb / " + totalByte + "Kb";
tvContent.setText(text);
}
/**
* 下载出错处理
*/
private void onDownloadError() {
tvContent.setText("下载出错");
}
/**
* 下载完成
*/
private void onDownloadFinish() {
tvContent.setText("下载已完成");
}
Retrofit框架在APP中具有出现的性能,鉴于API的调用相对复杂(容易冗余),特封装了一套框架来支持日常开发使用。开发者可根据自身需要定义回调接口包含的方法,裁切部分功能。
特别感谢:
https://blog.csdn.net/c__chao/article/details/78573737 提供的API接口
https://blog.csdn.net/webber888/article/details/80993471 提供的https协议适配方法
https://github.com/ahuyangdong/RetrofitFrame
CSDN下载:
https://download.csdn.net/download/ahuyangdong/10722905