Android网络请求开源项目组合:Retrofit+Okhttp+Gson,并二次封装,简化开发时的调用。
1 Retrofit+Okhttp+Gson是什么
Retrofit和Okhttp都是square公司开源的网络请求开源项目,也是当前最流行的网络请求组合。
Gson可以将json反序列化为相应的数据model,相对于从json取数据,数据model的建立对于获取字段数据和了解接口定义更为清晰。
2 为什么选择Retrofit+Okhttp+Gson
对于网络框架的选择有volley,android-async-http等很多很多,这些其实都是基于android本身httpurlconnecttion二次封装便于使用。而okhttp的网络框架网络请求效率更高,retrofit其实是一种封装方式。
网上有很多关于几个网络框架的比较,毫无疑问的okhttp+retrofit取得压倒性优势。唯一缺点可能就是需要学习成本。
出于之后项目的开发的考虑,最终选择了较为流行和效率高的okhttp+retrofit组合。
3 如何使用Retrofit+Okhttp+Gson
一个最基本的例子:
定义一个接口,专门放置接口
public interface GitHubService {
@GET("test_retrofit.php")
Call
发起调用请求:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.2.135/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call
Api还可以定义Get、Post、文件上传下载等等。具体的使用文档可以上网搜索。(很多很多,本篇更偏重说明如何二次封装以便于项目开发使用)
4 二次封装
如果使用retrofit本来的调用方式,项目开发起来肯定Api会写的代码很冗余而且和retrofit耦合太高。so、必须进行一个简单的二次开发。
首先,需要依赖的库
dependencies {
...
//网络请求 retrofit+okhttp+gson
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.google.code.gson:gson:2.7'
compile 'com.squareup.retrofit2:converter-gson:2.0.2';
}
项目开发和服务端接口约定格式是json
如果成功则返回:
{
ret => 1,
data => 业务数据
}
如果失败返回:
{
ret => 0,
err_code => 错误码,
err_msg => 错误信息
}
于是定义一个基础数据model,所有Gson的model都继承该model,可以自定义业务数据
/**
* Gson返回Ret基本格式
* 成功:ret=1 + 业务数据
* 失败:ret=0 + err_code + err_msg
* Created by tsy on 16/7/21.
*/
public class BaseRetData {
public int ret; //成功-1 失败-0
public int err_code; //错误code
public String err_msg; //错误msg
}
下面,就是对retrofit的二次封装,先上代码后面解释怎么用,BaseApi:
/**
* BaseApi
* Created by tsy on 16/7/21.
*/
public class BaseApi {
private static final String mBaseUrl = "http://www.baidu.com/";
protected Retrofit mRetrofit;
private final String TAG = "BaseApi";
public BaseApi() {
mRetrofit = new Retrofit.Builder()
.baseUrl(mBaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
public BaseApi(String baseUrl) {
mRetrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
//处理retrofit回调 并调用ApiCallback相应返回
protected class RetrofitCallback implements Callback {
private ApiCallback mCallback;
public RetrofitCallback(ApiCallback callback) {
mCallback = callback;
}
@Override
public void onResponse(Call call, Response response) {
if(response.isSuccessful()) {
if(((BaseRetData)response.body()).ret == 1) {
mCallback.onSuccess(((T)response.body()));
} else {
mCallback.onError(((BaseRetData)response.body()).err_code, ((BaseRetData)response.body()).err_msg);
}
} else {
mCallback.onFailure();
}
}
@Override
public void onFailure(Call call, Throwable t) {
Log.e(TAG, "api failure,throw=" + t.getMessage());
t.printStackTrace();
mCallback.onFailure();
}
}
//api调用回调
public interface ApiCallback {
void onSuccess(T ret); //ret=1时返回
void onError(int err_code, String err_msg); //ret=0时返回
void onFailure(); //网络请求失败
}
//文件下载回调
public interface FileDownloadCallback {
void onSuccess(); //下载成功返回
void onProcess(long fileSizeDownloaded, long fileSize); //下载进度
void onFailure(); //网络请求失败
}
/**
* 下载文件
* @param fileUrl 下载url
* @param filePath 本地保存path
* @param callback FileDownloadCallback回调
*/
public void downloadFile(final String fileUrl, final String filePath, final FileDownloadCallback callback) {
final ApiStore apiStore = mRetrofit.create(ApiStore.class);
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
Call call = apiStore.downloadFile(fileUrl);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, final Response response) {
if (response.isSuccessful()) {
new AsyncTask() {
private boolean mWrittenToDisk;
@Override
protected Void doInBackground(Void... voids) {
mWrittenToDisk = writeResponseBodyToDisk(response.body(), filePath, callback);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
if(mWrittenToDisk) {
callback.onSuccess();
} else {
callback.onFailure();
}
}
}.execute();
} else {
callback.onFailure();
}
}
@Override
public void onFailure(Call call, Throwable t) {
callback.onFailure();
}
});
return null;
}
}.execute();
}
/**
* responsebody写入文件
* @param body
* @param filePath
* @param callback
* @return
*/
private boolean writeResponseBodyToDisk(ResponseBody body, String filePath, FileDownloadCallback callback) {
try {
File file = new File(filePath);
String dir = filePath.substring(0, filePath.lastIndexOf('/'));
File fileDir = new File(dir);
if(!fileDir.exists()) {
fileDir.mkdirs();
}
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(file);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
callback.onProcess(fileSizeDownloaded, fileSize);
}
outputStream.flush();
return true;
} catch (IOException e) {
file.delete();
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
public interface ApiStore {
@Streaming
@GET
Call downloadFile(@Url String fileUrl);
}
}
把上面2个Base先放到项目中,假如要开发一个登陆功能(这里举例子只列举一个接口)
先定义登陆Api:
public class LoginApi extends BaseApi {
private static final String mBaseUrl = "http://192.168.3.1/";
private ApiStore mApiStore;
public LoginApi() {
super(mBaseUrl);
mApiStore = mRetrofit.create(ApiStore.class);
}
public void login(String username, String password, ApiCallback callback) {
Call call = ((ApiStore)mApiStore).login();
call.enqueue(new RetrofitCallback(callback));
}
public interface ApiStore {
@GET("test_retrofit.php")
Call login();
}
}
定义该接口的返回数据model
public class LoginRetData extends BaseDataRet {
public int user_id;
}
如上所述。开发新的功能模块Api时,只要继承BaseApi,如果Domin是通用的直接可以吧Domin写在BaseApi中的BaseUrl,如果是以微服务模式开发,也可以写在独立功能模块Api的BaseUrl中。开发时只需要专注编写ApiStore中接口,然后增加一个对外调用的接口。
外部调用API层接口和回调:
new LoginApi().login("tsy", "as", new BaseApi.ApiCallback() {
@Override
public void onSuccess(BaseDataRet ret) {
Log.i("tsy", "onSuccess:");
}
@Override
public void onError(int err_code, String err_msg) {
Log.i("tsy", "onError:");
}
@Override
public void onFailure() {
Log.i("tsy", "onFailure:");
}
});
回调定义了3个方法,因为我和服务端约定的返回是固定的,所以onSuccess就是在成功返回并且ret=1时触发,onError是成功返回但是ret=0时触发,onFailure是网络请求失败或者结果解析Gson错误的,可以通用理解为服务器错误。
同时在BaseApi中提供有下载方法和回调。使用示例为:
mLoginApi.downloadFile(url, filePath, new BaseApi.FileDownloadCallback() {
@Override
public void onSuccess() {
Log.i("tsy", "download onSuccess");
}
@Override
public void onProcess(long fileSizeDownloaded, long fileSize) {
Log.i("tsy", "download onProcess:" + fileSizeDownloaded + "/" + fileSize);
}
@Override
public void onFailure() {
Log.i("tsy", "download onFailure");
}
});
5 总结
经过以上封装后,开发人员专注于编写各个功能模块的Api层,在Api层里面也只需要专注于定义接口即可。
外部调用接口时其实是不知道retrofit的存在,这样做到了解耦。万一以后需要改底层框架也不需要改动业务层。
以上代码Github地址:
https://github.com/tsy12321/BaseAndroidProject
注:该项目会做成一个基础的项目框架,包含各种封装好的工具,底层库和MVP架构,还在不断更新中,欢迎关注提Issue!
结尾
更多文章关注我的公众号