关于OkHttp的名气,相信每一位Android开发者都了解并且使用过,为我们项目中网络请求提供了很大的便捷。OkHttp提供了对网络请求的封装,我们只要调用其暴露的Api,就可以轻松地实现网络相关功能。那关于它的内部实现原理是怎么样的呢,下面将通过一实现一个简单的OkHttp,带你进入OkHttp的内心世界。
OkHttp的使用
OkHttpClient client = new OkHttpClient();//创建okHttpClient对象
//创建网络请求
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
//创建Call请求对象,通过call开启网络访问
Call call = client.newCall(request);
//1、同步请求
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//2、异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("response",response.toString());
}
});
上面就是关于OkHttp的基本使用,通常情况下结合Retrofit一起使用,效果更佳。
简单版OkHttp
我们知道,在客户端会有多个网络请求,多个请求任务也会在同一时间发起请求,这种情况下OkHttp怎么处理的呢?某一个请求失败后又该怎么处理呢?下面我们就解决这些问题。
项目结构:
整体思路:通过IHttpRequest接口定义网络请求,JSonHttpRequest为其实现类;CallbackListener为网络请求回调接口,JsonCallbacklistener为其实现类;HttpTask为网络任务的封装,里面包含了IHttpRequest、CallbackListener对象;有了网络任务对象后,通过ThreadPoolManager来进行任务管理,创建任务队列管理任务,线程池处理任务;当请求失败后,可以选择将失败任务放入失败队列,再次放入线程池发起请求。
网络请求Request
public interface IHttpRequest<T> {
//请求类型
void setType(String type);
//请求地址
void setUrl(String url);
//请求参数
void setData(T data);
//请求结果回调
void setCallListener(CallbackListener callListener);
//网络请求函数
void execute();
}
public class JSonHttpRequest implements IHttpRequest<HashMap> {
private String type;
private String mUrl;
private HashMap<String,String> data;
private CallbackListener callbackListener;
private HttpURLConnection mHttpUrlCon;
@Override
public void setType(String type) {
this.type = type;
}
@Override
public void setUrl(String url) {
this.mUrl = url;
}
@Override
public void setData(HashMap data) {
this.data = data;
}
@Override
public void setCallListener(CallbackListener callListener) {
this.callbackListener = callListener;
}
@Override
public void execute() {
URL url = null;
try {
url = new URL(mUrl);
mHttpUrlCon = (HttpURLConnection) url.openConnection(); //打开网络连接
mHttpUrlCon.setConnectTimeout(5000);
mHttpUrlCon.setUseCaches(false);
mHttpUrlCon.setInstanceFollowRedirects(true);//是否可以被重定向
mHttpUrlCon.setReadTimeout(5000);//响应超时
mHttpUrlCon.setRequestMethod(type);
mHttpUrlCon.setDoInput(true);//是否可以输入数据
mHttpUrlCon.setDoOutput(true);//是否可以输出数据
mHttpUrlCon.setRequestProperty("content-type","application/x-www-form-urlencoded");//消息类型的设置
mHttpUrlCon.connect();//开始连接
//请求参数
if (data != null && !data.isEmpty()){
String param = "";
for (String key: data.keySet()){
param += key + "=" + data.get(key) + "&";
}
param = param.substring(0,param.length()-1);
OutputStream out = mHttpUrlCon.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out);
osw.write(param);
osw.flush();// 刷新
out.close();
osw.close();
}
if (mHttpUrlCon.getResponseCode() == HttpURLConnection.HTTP_OK){
InputStream in = mHttpUrlCon.getInputStream();
callbackListener.onSuccess(in);
} else {
callbackListener.onFailure();
}
} catch (Exception e){
//e.printStackTrace();
throw new RuntimeException("请求失败");
} finally {
mHttpUrlCon.disconnect();
}
}
}
代码的实现比较简单,主要获取网络请求所需要的一些参数,比如URL、请求类型GET/POST等。主要在实现execute()方法,通过HttpURLConnection开启实际的网络访问,并将访问结果通过callbackListener监听。
**结果回调CallbackListener **
public interface CallbackListener {
//成功回调
void onSuccess(InputStream inputStream);
//请求失败回调
void onFailure();
}
public class JsonCallbacklistener<T> implements CallbackListener {
private Class<T> resquestClass;
private DataCallback dataCallback;
private Handler handler = new Handler(Looper.getMainLooper());
public JsonCallbacklistener(Class<T> resquestClass,DataCallback callback) {
this.resquestClass = resquestClass;
this.dataCallback = callback;
}
@Override
public void onSuccess(InputStream inputStream) {
String reaponse = getResponse(inputStream);
final T clazz = JSON.parseObject(reaponse,resquestClass);
handler.post(new Runnable() {
@Override
public void run() {
dataCallback.onSucess(clazz);
}
});
}
@Override
public void onFailure() {
}
private String getResponse(InputStream inputStream) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null){
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
}
在回调接口的函数里,通过Json处理,将网络请求结果以泛型类的形式返回给客户端。有了上面的请求request、CallbackListenner对象,那么我们就可以创建具体的网络请求任务HttpTask了。
**HttpTask **
public class HttpTask<T> implements Runnable,Delayed {
private IHttpRequest mHttpRequest;
public HttpTask(String url,String type,T requestData,IHttpRequest httpRequest,CallbackListener callbackListener) {
mHttpRequest = httpRequest;
mHttpRequest.setUrl(url);
mHttpRequest.setType(type);
mHttpRequest.setData(requestData);
mHttpRequest.setCallListener(callbackListener);
}
@Override
public void run() {
try {
mHttpRequest.execute();
} catch (Exception e){
//重试机制
ThreadPoolManager.getInstance().addDelayTask(this);
}
}
private long delaytime;
private int reTryCount;
public long getDelaytime() {
return delaytime;
}
public void setDelaytime(long delaytime) {
this.delaytime = System.currentTimeMillis() + delaytime;
}
public int getReTryCount() {
return reTryCount;
}
public void setReTryCount(int reTryCount) {
this.reTryCount = reTryCount;
}
@Override
public long getDelay(@NonNull TimeUnit unit) {
return unit.convert(delaytime - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(@NonNull Delayed o) {
return 0;
}
}
可以看到,HttpTask实现了Runnable接口,为一个新的线程任务。为了重试机制,同时又实现了Delayed 接口,通过setDelaytime方法设置Task在加入到重试队列后的延迟时间。
**ThreadPoolManager **
public class ThreadPoolManager {
private static ThreadPoolManager mInstans;
private ThreadPoolExecutor mThreadPool;//线程池
private LinkedBlockingQueue<Runnable> mTaskQueue = new LinkedBlockingQueue<>();//任务等待队列
private ThreadPoolManager(){
mThreadPool = new ThreadPoolExecutor(3, 5, 15, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
addTask(r);
}
});
mThreadPool.execute(callRun);
mThreadPool.execute(callDelayRun);
}
public static ThreadPoolManager getInstance(){
if (mInstans == null){
synchronized (ThreadPoolManager.class){
if (mInstans == null){
mInstans = new ThreadPoolManager();
}
}
}
return mInstans;
}
//讲网络请求Task加入等待队列
public void addTask(Runnable runnable){
if (runnable != null){
mTaskQueue.add(runnable);
}
}
//创建“叫号”线程,不断的从队列里获取新的任务
public Runnable callRun = new Runnable() {
Runnable runnable = null;
@Override
public void run() {
while (true){
try {
runnable = mTaskQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (runnable != null){
mThreadPool.execute(runnable);
}
}
}
};
//重试队列
private DelayQueue<HttpTask> DelayQueue = new DelayQueue<>();
//将请求失败的Task加入重试队列
public void addDelayTask(HttpTask task){
if (task != null){
task.setDelaytime(5000);
DelayQueue.offer(task);
}
}
//重试机制的“叫号”线程
public Runnable callDelayRun = new Runnable() {
@Override
public void run() {
HttpTask task = null;
while (true){
try {
task = DelayQueue.take();
if (task != null){
//判断Task是否超过了重试的次数
if (task.getReTryCount() < 3){
task.setReTryCount(task.getReTryCount() + 1);
mThreadPool.execute(task);
Log.e("---重试---","第"+ task.getReTryCount() + "次");
} else {
//多次尝试失败,放弃
Log.e("---重试---","重试多次失败,放弃。");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
ThreadPoolManager 是核心的代码,主要制定网络请求线程的执行规则。通过线程池,以及创建等待队列,重试队列等来实现请求任务按照相关规则执行。
有了上面的准备,一个简单版的OKHttp就差不多了。但是我们在使用OkHttp的时候,整个OkHttp是封装好了的,我们只需要调用它暴露的Api去实现我们的具体需求,那我们这里也封装一个MyOkHttp出来,毕竟Java面向对象才是精髓嘛。
MyOkHttp
public class MyOkHttp<E> {
public MyOkHttp(String url,String type,E requestData,IHttpRequest httpRequest,CallbackListener callbackListener) {
HttpTask task = new HttpTask(url,type ,requestData,httpRequest,callbackListener);
ThreadPoolManager.getInstance().addTask(task);
}
static class Builder<T> {
private String url;
private String type;
public T requestData;
private IHttpRequest httpRequest;
private CallbackListener callbackListener;
public Builder() {
}
public Builder setUrl(String url) {
this.url = url;
return this;
}
public Builder setType(String type) {
this.type = type;
return this;
}
public Builder setRequestData(T requestData) {
this.requestData = requestData;
return this;
}
public Builder setHttpRequest(IHttpRequest httpRequest) {
this.httpRequest = httpRequest;
return this;
}
public Builder setCallbackListener(CallbackListener callbackListener) {
this.callbackListener = callbackListener;
return this;
}
public MyOkHttp build(){
return new MyOkHttp(url,type,requestData,httpRequest,callbackListener);
}
}
}
代码比较简单,主要通过构造函数为网络请求需要的参数赋值,客户端通过Builder来进行参数的设置,我们看下客户端:
HashMap<String,String> map = new HashMap();
map.put("location","beijing");
map.put("key","XXXXXXXX'");
new MyOkHttp.Builder()
.setUrl("https://free-api.heweather.net/s6/weather/now")
.setType("POST")
.setHttpRequest(httpRequest)
.setRequestData(map)
.setCallbackListener(callbackListener)
.build();
这里采用的是访问和风天气接口为例,通过Map封装了请求数据,并将请求数据,URL等通过MyOkHttp的Builder传递给MyOkHttp里的HttpTask,并将改Task放入线程池开启线程。
到这里,关于简单版OkHttp就介绍完了。实现的功能不多,逻辑也不复杂,就当练手了吧。比起完整的开源OkHttp来说,很简单了,但是也对我们理解OkHttp起到了帮助。