源代码
GitHub源代码
本文目标
手写实现okhttp简易流程(仅供学习)
基本使用
public void click(View view) {
//1.1创建okHttpClient
OkHttpClient okHttpClient = new OkHttpClient();
//1.2创建RequestBody对象
RequestBody requestBody = new RequestBody()
.type(RequestBody.FORM)
.addParam("userName", "beijing")
.addParam("password", "123456");
//1.3创建Request对象
Request request = new Request
.Builder()
.post(requestBody)
.headers(getHeaderParams())
.url("https://api.devio.org/as/user/login")
.builder();
//2.把Request对象封装成call对象
Call call = okHttpClient.newCall(request);
//3.发起异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("TAG", e.toString());
show(e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String string = response.string();
Log.e("TAG", string);
show(string);
}
});
}
- 1.创建okHttpClient和创建Request对象(配置的请求信息封装)
- 2.把Request对象封装成call对象
- 3.发起同步请求或异步请求
我们从okHttpClient开始实现
1.1OkHttpClient
首先先看OkHttpClient类
//1.1创建okHttpClient
OkHttpClient okHttpClient = new OkHttpClient();
/**
* Author: 信仰年轻
* Date: 2021-06-23 17:30
* Email: hydznsqk@163.com
* Des: OkHttp客户端对象
*/
public class OkHttpClient {
Dispatcher dispatcher;
public OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
}
public OkHttpClient() {
this(new Builder());
}
public Call newCall(Request request) {
return RealCall.newCall(request, this);
}
public static class Builder {
Dispatcher dispatcher;
public Builder() {
dispatcher = new Dispatcher();
}
public OkHttpClient builder() {
return new OkHttpClient(this);
}
}
}
可以看出来该对象中只是持有Dispatcher 对象和创建Call对象的newCall方法
1.2RequestBody
然后因为我这里用的是form表单,所以我们先看下RequestBody 是怎么写的
//1.2创建RequestBody对象
RequestBody requestBody = new RequestBody()
.type(RequestBody.FORM)
.addParam("userName", "beijing")
.addParam("password", "123456");
具体如下
/**
* Author: 信仰年轻
* Date: 2021-06-23 17:31
* Email: hydznsqk@163.com
* Des:请求体
*/
public class RequestBody {
// 表单格式
public static final String FORM = "multipart/form-data";
// 参数,文件
private final HashMap<String, Object> params;
private String boundary = createBoundary();
private String type;
private String startBoundary = "--" + boundary;
private String endBoundary = startBoundary + "--";
public RequestBody() {
params = new HashMap<>();
}
private String createBoundary() {
return "OkHttp"+ UUID.randomUUID().toString();
}
// 都是一些规范
public String getContentType() {
return type + ";boundary = " + boundary;
}
// 多少个字节要给过去,写的内容做一下统计
public long getContentLength() {
long length=0;
Set<Map.Entry<String, Object>> entries = params.entrySet();
for(Map.Entry<String, Object> entry:entries){
String key = entry.getKey();
Object value = entry.getValue();
if(value instanceof String){
String text = getText(key, (String) value);
Log.e("TAG",text);
length+=text.getBytes().length;
}
}
if(params.size()!=0){
length+=endBoundary.getBytes().length;
}
return length;
}
//写内容
public void onWriteBody(OutputStream outputStream) throws IOException {
Set<Map.Entry<String, Object>> entries = params.entrySet();
for(Map.Entry<String, Object> entry:entries){
String key = entry.getKey();
Object value = entry.getValue();
if(value instanceof String){
String text = getText(key, (String) value);
outputStream.write(text.getBytes());
}
}
if(params.size()!=0){
outputStream.write(endBoundary.getBytes());
}
}
/**
startBoundary + "\r\n"
Content-Disposition; form-data; name = "pageSize"
Context-Type: text/plain
1
*/
private String getText(String key, String value) {
return startBoundary+"\r\n"+
"Content-Disposition: form-data; name = \""+key+"\"\r\n"+
"Context-Type: text/plain\r\n"+
"\r\n"+
value+
"\r\n";
}
public RequestBody addParam(String key, String value) {
params.put(key, value);
return this;
}
public RequestBody type(String type) {
this.type = type;
return this;
}
}
都是一些固定格式,然后也是通过IO流的方式去写内容
1.3Request
//1.3创建Request对象
Request request = new Request
.Builder()
.post(requestBody)
.headers(getHeaderParams())
.url("https://api.devio.org/as/user/login")
.builder();
/**
* Author: 信仰年轻
* Date: 2021-06-23 17:31
* Email: hydznsqk@163.com
* Des:把配置的请求信息封装成Request对象,包含url,method请求方式,headers头信息,RequestBody请求体
*/
public class Request {
final String url;//url
final Method method;//请求方式
final Map<String, String> headers;//头信息
final RequestBody requestBody;//请求体,用于post请求
private Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers;
this.requestBody = builder.requestBody;
}
public static class Builder {
String url;//url
Method method;//请求方式
Map<String, String> headers;//头信息
RequestBody requestBody;//请求体,用于post请求
public Builder() {
method = Method.GET;
headers = new HashMap<>();
}
public Builder url(String url) {
this.url = url;
return this;
}
public Builder get() {
method = Method.GET;
return this;
}
public Builder post(RequestBody body) {
method = Method.POST;
this.requestBody = body;
return this;
}
public Builder headers(String key, String value) {
headers.put(key, value);
return this;
}
public Builder headers( Map<String, String> map) {
headers.putAll(map);
return this;
}
public Request builder() {
return new Request(this);
}
}
}
老样子,把配置的请求信息封装成Request对象,包含url,method请求方式,headers头信息,RequestBody请求体,运用了builder设计模式
2.Call
//2.把Request对象封装成call对象
Call call = okHttpClient.newCall(request);
把Request传给okHttpClient
client.newCall(request);调用进去,会发现是RealCall在调用
public class OkHttpClient {
public Call newCall(Request request) {
return RealCall.newCall(request, this);
}
}
下面的是顶层Call接口
/**
* Author: 信仰年轻
* Date: 2021-06-23 17:28
* Email: hydznsqk@163.com
* Des: 请求的Call顶层接口
*/
public interface Call {
/**
* 发起异步请求
* @param callback
*/
void enqueue(Callback callback);
/**
* 发起同步请求
* @return
*/
Response execute();
}
下面是RealCall 真正发起请求的Call对象
/**
* Author: 信仰年轻
* Date: 2021-06-23 17:31
* Email: hydznsqk@163.com
* Des: 真正发起请求的Call对象
*/
public class RealCall implements Call {
private OkHttpClient client;
private Request originalRequest;
public RealCall(Request originalRequest,OkHttpClient client) {
this.client = client;
this.originalRequest = originalRequest;
}
public static Call newCall(Request request, OkHttpClient okHttpClient) {
return new RealCall(request,okHttpClient);
}
//异步请求
@Override
public void enqueue(Callback callback) {
//异步交给线程池
AsyncCall asyncCall = new AsyncCall(callback);
client.dispatcher.enqueue(asyncCall);
}
//同步请求
@Override
public Response execute() {
return null;
}
final class AsyncCall extends NamedRunnable{
Callback callback;
public AsyncCall(Callback callback){
this.callback=callback;
}
@Override
protected void execute() {
// 来这里,开始访问网络 Request -> Response
Log.e("TAG","execute");
// 基于 HttpUrlConnection , OkHttp = Socket + okio(IO)
final Request request = originalRequest;
try {
URL url = new URL(request.url);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
if(urlConnection instanceof HttpsURLConnection){
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) urlConnection;
// https 的一些操作
// httpsURLConnection.setHostnameVerifier();
// httpsURLConnection.setSSLSocketFactory();
}
// urlConnection.setReadTimeout();
// 写东西
urlConnection.setRequestMethod(request.method.name);
urlConnection.setDoOutput(request.method.doOutput());
//Post方式不能缓存,需手动设置为false
urlConnection.setUseCaches(false);
RequestBody requestBody = request.requestBody;
if(requestBody != null){
// 头信息
urlConnection.setRequestProperty("Content-Type",requestBody.getContentType());
urlConnection.setRequestProperty("Content-Length",Long.toString(requestBody.getContentLength()));
}
//自己定义的头信息 header,里面有token和boarding-pass
Map<String, String> headers = request.headers;
if(headers!=null){
Set<Map.Entry<String, String>> entries = headers.entrySet();
for(Map.Entry<String, String> entry:entries){
urlConnection.setRequestProperty(entry.getKey(), entry.getValue());//设置请求头
}
}
urlConnection.connect();
// 写内容
if(requestBody != null){
requestBody.onWriteBody(urlConnection.getOutputStream());
}
int statusCode = urlConnection.getResponseCode();
if(statusCode == 200) {
InputStream inputStream = urlConnection.getInputStream();
Response response = new Response(inputStream);
callback.onResponse(RealCall.this,response);
}else{
InputStream inputStream = urlConnection.getInputStream();
Response response = new Response(inputStream);
callback.onFailure(RealCall.this,new IOException(response.string()));
}
// 进行一些列操作,状态码 200
} catch (IOException e) {
callback.onFailure(RealCall.this,e);
}
}
}
}
上述代码是用了HttpURLConnection 这种很原始的方式进行网络请求的,因为这里是okhttp的简易版,在类的结构上是一致的,只是底层引擎不同,真正的OkHttp 是Socket + okio(IO)实现的
3.Call的异步请求
//3.发起异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("TAG", e.toString());
show(e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String string = response.string();
Log.e("TAG", string);
show(string);
}
});
其实就是调用RealCall的enqueue方法
//异步请求
@Override
public void enqueue(Callback callback) {
//异步交给线程池
AsyncCall asyncCall = new AsyncCall(callback);
client.dispatcher.enqueue(asyncCall);
}
接下来我们来看下线程池
/**
* Author: 信仰年轻
* Date: 2021-06-23 17:35
* Email: hydznsqk@163.com
* Des: 分发器,主要是用线程池
*/
public class Dispatcher {
private ExecutorService executorService;
/**
* 用线程池开启异步请求,然后就会AsyncCall中的run方法
* @param call
*/
public void enqueue(RealCall.AsyncCall call) {
executorService().execute(call);
}
/**
* 创建线程池,而且是单例
*/
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "okhttp");
thread.setDaemon(false);
return thread;
}
});
}
return executorService;
}
}
在onResponse方法中看到了可以把Response通过IO流转成字符串
/**
* Author: 信仰年轻
* Date: 2021-06-23 17:31
* Email: hydznsqk@163.com
* Des: 响应,通过inputStream解析服务器返回来的数据为String
*/
public class Response {
private final InputStream inputStream;// Skin
public Response(InputStream inputStream) {
this.inputStream = inputStream;
}
//IO流解析成字符串
public String string() {
return convertStreamToString(inputStream);
}
public String convertStreamToString(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
}
基本上到这里核心代码就写完了,具体的可以参考Demo
网友评论