背景:
最近翻看了一下OkHttp的源码,看完之后想自己重新封装一个OkHttp框架,同时检验一下自己的水平。
功能:
1.get请求:
// url:请求地址
//MyCallBack:结果回调,目前只支持返回参数为Json和String,
// 此时传入的是WeatherEntity,那么返回的是通过Gson把数据解析成WeatherEntity的数据
OkHttpUtils.getInstance().get(url, new MyCallBack() {
@Override
public void onSuccess(WeatherEntity weatherEntity) {
WeatherEntity mWeatherEntity = weatherEntity;
tv_result.setText(mWeatherEntity.getStatus());
}
@Override
public void onFail(String err) {
super.onFail(err);
}
});
目前get请求分为onSuccess的回调接口和onFail的回调接口
2.文件下载:
//url:请求地址
//path:文件下载的目录位置
//MyCallBack:结果回调,目前只支持返回参数为Json和String,
// 此时传入的是WeatherEntity,那么返回的是通过Gson把数据解析成WeatherEntity的数据
OkHttpUtils.getInstance().download(url, path, new MyCallBack() {
@Override
public void onSuccess(String s) {
super.onSuccess(s);
Log.e("TAG","成功");
tvResult.setText("成功");
}
@Override
public void onFail(String err) {
super.onFail(err);
}
@Override
public void onDownloading(int progress) {
super.onDownloading(progress);
Log.e("onDownloading","下载进度"+progress);
tvResult.setText(progress+"");
}
});
目前文件下载只有成功、失败和下载进度监听。
尴尬之处:因为没有后台人员配合,而网上的提供的开源的接口只有GET请求的接口,所以目前只封装了这两种请求。如果哪位大佬能提供好POST请求的接口或者知道哪里有开源的,还请告知,在此多谢。
好的,下面开始介绍:
首先大家知道OkHttp的一般请求格式为:
// 第一步:创建 OkHttpClient 对象
OkHttpClient okHttpClient = new OkHttpClient();
// 第二步:创建 Request 对象
Request request = new Request.Builder()
.url(url)
.build();
// 第三步:发起 HTTP 请求
//没有回调接口
okHttpClient.newCall(request).execute();
//有回调接口
okHttpClient.newCall(request).enqueue(callBack);
构建一个网络请求分为三步:
第一步:创建 OkHttpClient 对象
第二步:创建 Request 对象
第三步:发起 HTTP 请求
注意: okHttpClient.newCall(request).enqueue(callBack);中的callBack的回调不是在主线程,这点需要注意。
开始封装:
首先:我们需要知道一点就是OkHttp官方文档并不建议我们创建多个OkHttpClient,因此全局使用一个。 如果有需要,可以使用clone方法,再进行自定义。所以就必须要使用单例了:
/**
* Created by Administrator on 2017/12/23.
*/
public class OkHttpUtils {
private static OkHttpUtils okHttpUtils = null;
private OkHttpClient httpClient;
private Gson mGson;
private Handler mDelivery;
private OkHttpUtils() {
//创建okHttpClient
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//添加拦截器
builder.addInterceptor(new HttpParamInterceptor());
httpClient = builder.build();
//因为回调默认是在子线程,所以想在UI线程处理就要用到Handler
mDelivery = new Handler(Looper.getMainLooper());
}
public static OkHttpUtils getInstance() {
if (okHttpUtils == null) {
synchronized (OkHttpUtils.class) {
if (okHttpUtils == null) {
okHttpUtils = new OkHttpUtils();
}
}
}
return okHttpUtils;
}
//异步get请求
public void get(String url, final MyCallBack myCallBack) {
//创建请求
Request request = new Request.Builder()
.url(url)
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
String err = "请求失败";
sendFailResult(myCallBack, err);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
sendSuccessResult(myCallBack, response.body().string());
}
});
}
//异步文件下载
public void download(final String url, final String destFileDir, final MyCallBack myDownloadCallBack) {
//创建请求
Request request = new Request.Builder()
.url(url)
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
String err = "请求失败";
sendFailResult(myDownloadCallBack, err);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//判断文件夹是否存在,如果不存在就创建文件夹
File dirFolder = new File(destFileDir);
if (!dirFolder.exists()) { //如果该文件夹不存在,则进行创建
dirFolder.mkdirs();//创建文件夹
}
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
is = response.body().byteStream();
long total = response.body().contentLength();
File file = new File(destFileDir, "QQ.apk");
fos = new FileOutputStream(file);
long sum = 0;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
final int progress = (int) (sum * 1.0f / total * 100);
// 下载中
// myDownloadCallBack.onDownloading(progress);
sendDownloadProgress(myDownloadCallBack, progress);
}
fos.flush();
//如果下载文件成功,第一个参数为文件的绝对路径
sendSuccessResult(myDownloadCallBack, file.getAbsolutePath());
}
});
}
private void sendDownloadProgress(final MyCallBack myDownloadCallBack, final int progress) {
mDelivery.post(new Runnable() {
@Override
public void run() {
myDownloadCallBack.onDownloading(progress);
}
});
}
/**
* 根据请求地址判断下载文件的名称
* @param path
* @return
*/
private String getFileName(String path) {
int separatorIndex = path.lastIndexOf("/");
return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
}
/**
* 在主线程返回成功结果
* @param myCallBack
* @param response
*/
private void sendSuccessResult(final MyCallBack myCallBack, final String response) {
mDelivery.post(new Runnable() {
@Override
public void run() {
if (myCallBack.mType == String.class) {
myCallBack.onSuccess(response);
} else {
mGson = new Gson();
Object o = mGson.fromJson(response, myCallBack.mType);
myCallBack.onSuccess(o);
}
}
});
}
/**
* 在主线程返回失败结果
*/
private void sendFailResult(final MyCallBack myCallBack, final String err) {
mDelivery.post(new Runnable() {
@Override
public void run() {
myCallBack.onFail(err);
}
});
}
}
这里有一点需要说明:
如果你的后台返回的格式是统一的:
{
"status": "0",
"mag": "1",
"data": ""
}
那么你可以在sendSuccessResult对数据进行第一次处理,将处理后的数据在返回给调用方。
其实通过上述代码,就能很好的看出来所封装的东西了,不过大家应该看到了,我在构建OkHttpClient添加了一个HttpParamInterceptor拦截器,因为OkHttp最核心的东西就是Interceptor(拦截器)。不要误以为它只是负责拦截请求的一些额外的处理(例如cookie),实际上他把实际的网路请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个Intercepter,他们再连接成一个Intercepter.Chain。最终圆满完成了一次网络请求。这里不在过多介绍,想了解的可以查阅相关文章。
/**
* Created by RF
* on 2017/11/28.
* 请求时数据处理拦截器
*/
public class HttpParamInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl.Builder httpUrl = request.url().newBuilder()
//添加同一参数 如手机唯一标识符,token等
.addQueryParameter("city","CHSH000000");
Request newrequest = new Request.Builder()
//添加公共的头部
.addHeader("User-Agent", "test")
.method(request.method(),request.body())
.url(httpUrl.build())
.build();
// printReponseMessage(chain.proceed(newrequest),httpUrl.build().toString());
return chain.proceed(newrequest);
}
/**
* 打印网络请求日志
* @param response
*/
private void printReponseMessage(Response response, String url) throws IOException{
ResponseBody body = response.body();
BufferedSource source = body.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
Charset charset = Charset.defaultCharset();
MediaType contentType = body.contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
String bodyString = buffer.clone().readString(charset);
Log.e("OKHttp",url+ bodyString);
}
}
这里有一点需要说明:之前在网上见过,有人说添加公共参数的时候需要先判断GET请求和POST请求,然后在对应的添加公共参数,意思是GET请求和POST请求添加公共参数的方式不一样,必须通过判断分别对应添加,这里我们看一下addQueryParameter的源码:
/** Encodes the query parameter using UTF-8 and adds it to this URL's query string.
使用UTF-8编码的查询参数并将其添加到这个URL的查询字符串。
*/
public Builder addQueryParameter(String name, @Nullable String value) {
if (name == null) throw new NullPointerException("name == null");
if (encodedQueryNamesAndValues == null) encodedQueryNamesAndValues = new ArrayList<>();
encodedQueryNamesAndValues.add(
canonicalize(name, QUERY_COMPONENT_ENCODE_SET, false, false, true, true));
encodedQueryNamesAndValues.add(value != null
? canonicalize(value, QUERY_COMPONENT_ENCODE_SET, false, false, true, true)
: null);
return this;
}
其实不需要过多的了解方法内的代码,只需要通过注释就会发现,该方法是把传入的key-value解析之后拼在url中,而该类的的注释中也有详细说明:
有兴趣的可以去看一下这个类:okhttp3包下的HttpUrl。
不完善之处:没有加入网络缓存,起初是打算使用第三方的Hawk数据库,不过OkHttp本身有负责读取缓存直接返回、更新缓存的CacheInterceptor;所以缓存处理还没有加上。
不足之处请多多指教。
项目地址为:https://github.com/fengxiaobing/MyOKHttp
(觉得还不错的希望给一个star。 QAQ)