Android網路框架Retrofit2使用封裝:Get/Post/檔案上傳/下載
背景
Android開發中的網路框架經過多年的發展,目前比較主流的就是Retrofit了,Retrofit2版本出現也有幾年了,為了方便使用,特封裝了一些關於Retrofit2的程式碼,分享給大家。
框架主要包括:
- Get請求
- Post請求
- 檔案上傳
- 檔案下載
使用效果預覽:
Retrofit物件
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物件
/**
* 網路框架單例
* <p>因為示例中用到不同的介面地址,URL_BASE隨便寫了一個,如果是實際專案中,則設定為根路徑即可</p>
*/
private static Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constant.URL_BASE)
.client(client)
.build();
定義好OkHttpClient後,Retrofit物件的構造就很簡單,需要注意的是,一定要為retrofit物件指定好baseUrl,如果是後臺地址有多個,寫其中一個就可以了。
網路上有不少涉及到動態註冊baseUrl的文章,寫demo的時候並沒有遇到此類問題,如果有遇到的話再尋找對應方案即可。
請求框架
定義回撥
封裝的框架讓使用者傳遞一個回撥的物件,在網路請求開始、結束、異常等階段根據需要將呼叫相應的回撥介面。
該回調介面還能夠自動處理一些網路異常,並給予相應的提示(按需修改)。
/**
* 網路請求結果處理類
* @param <T> 請求結果封裝物件
*/
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>
Get請求
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 <T> 泛型
*/
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;
}
}
Post請求
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 <T> 泛型
*/
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 <T> 泛型
*/
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、發起檔案上傳請求
因為沒有找到現成的上傳檔案介面,所以這裡就隨便弄了個介面做了個例子。自己寫的時候在後臺做一個介面檔案的介面就行了。
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的呼叫相對複雜(容易冗餘),特封裝了一套框架來支援日常開發使用。開發者可根據自身需要定義回撥介面包含的方法,裁切部分功能。
特別感謝: