源代碼
GitHub源代碼
本文目標(biāo)
手寫實現(xiàn)okhttp簡易流程(僅供學(xué)習(xí))
基本使用
public void click(View view) {
//1.1創(chuàng)建okHttpClient
OkHttpClient okHttpClient = new OkHttpClient();
//1.2創(chuàng)建RequestBody對象
RequestBody requestBody = new RequestBody()
.type(RequestBody.FORM)
.addParam("userName", "beijing")
.addParam("password", "123456");
//1.3創(chuàng)建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.發(fā)起異步請求
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.創(chuàng)建okHttpClient和創(chuàng)建Request對象(配置的請求信息封裝)
- 2.把Request對象封裝成call對象
- 3.發(fā)起同步請求或異步請求
我們從okHttpClient開始實現(xiàn)
1.1OkHttpClient
首先先看OkHttpClient類
//1.1創(chuàng)建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 對象和創(chuàng)建Call對象的newCall方法
1.2RequestBody
然后因為我這里用的是form表單,所以我們先看下RequestBody 是怎么寫的
//1.2創(chuàng)建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";
// 參數(shù),文件
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();
}
// 都是一些規(guī)范
public String getContentType() {
return type + ";boundary = " + boundary;
}
// 多少個字節(jié)要給過去,寫的內(nèi)容做一下統(tǒng)計
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;
}
//寫內(nèi)容
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流的方式去寫內(nèi)容
1.3Request
//1.3創(chuàng)建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設(shè)計模式
2.Call
//2.把Request對象封裝成call對象
Call call = okHttpClient.newCall(request);
把Request傳給okHttpClient
client.newCall(request);調(diào)用進去,會發(fā)現(xiàn)是RealCall在調(diào)用
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 {
/**
* 發(fā)起異步請求
* @param callback
*/
void enqueue(Callback callback);
/**
* 發(fā)起同步請求
* @return
*/
Response execute();
}
下面是RealCall 真正發(fā)起請求的Call對象
/**
* Author: 信仰年輕
* Date: 2021-06-23 17:31
* Email: hydznsqk@163.com
* Des: 真正發(fā)起請求的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() {
// 來這里,開始訪問網(wǎng)絡(luò) 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方式不能緩存,需手動設(shè)置為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());//設(shè)置請求頭
}
}
urlConnection.connect();
// 寫內(nèi)容
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()));
}
// 進行一些列操作,狀態(tài)碼 200
} catch (IOException e) {
callback.onFailure(RealCall.this,e);
}
}
}
}
上述代碼是用了HttpURLConnection 這種很原始的方式進行網(wǎng)絡(luò)請求的,因為這里是okhttp的簡易版,在類的結(jié)構(gòu)上是一致的,只是底層引擎不同,真正的OkHttp 是Socket + okio(IO)實現(xiàn)的
3.Call的異步請求
//3.發(fā)起異步請求
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);
}
});
其實就是調(diào)用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: 分發(fā)器,主要是用線程池
*/
public class Dispatcher {
private ExecutorService executorService;
/**
* 用線程池開啟異步請求,然后就會AsyncCall中的run方法
* @param call
*/
public void enqueue(RealCall.AsyncCall call) {
executorService().execute(call);
}
/**
* 創(chuàng)建線程池,而且是單例
*/
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流轉(zhuǎn)成字符串
/**
* Author: 信仰年輕
* Date: 2021-06-23 17:31
* Email: hydznsqk@163.com
* Des: 響應(yīng),通過inputStream解析服務(wù)器返回來的數(shù)據(jù)為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