谈谈OkHttp源码的同步异步原理_enqueue会新建线程吗,2024年最新HarmonyOS鸿蒙高级工程师进阶学习—HarmonyOS鸿蒙热修复原理

15 阅读3分钟

Call call = okHttpClient.newCall(request); //1.异步请求,通过接口回调告知用户 http 的异步执行结果 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { System.out.println(e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { System.out.println(response.body().string()); } } }); //2.同步请求 Response response = call.execute(); if (response.isSuccessful()) { System.out.println(response.body().string()); }

2、异步请求实现流程

//get的异步请求 public void getAsync(View view) { //定义okhttp对象 OkHttpClient okHttpClient = new OkHttpClient(); Request request = new Request.Builder().url("www.test.com").build(); Call call = okHttpClient.newCall(request); //异步请求:不用创建子线程 //enqueue()并不会阻塞代码的执行,不需要与服务器请求完成之后,才会执行后面的代码 //而且enqueue内部会为我们创建子线程 call.enqueue(new Callback() { @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { } @Override public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { Log.i("TAG", "onResponse: " + (Looper.getMainLooper().getThread() == Thread.currentThread()));//为false 表示这是在子线程,需要切换到主线程才能操作UI if (response.isSuccessful()){ Log.i(TAG,"getAsync:"+response.body().string()); } } }); }

call的异步调用是通过RealCall.enqueue()实现的。而请求结果通过Callback回调到主线程

2.1、RealCall.enqueue()

@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } transmitter.callStart(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }

将用户创建的callback作为参数传入AsyncCall()构造函数。AsyncCall 继承于Runnable

2.2、AsyncCall

final class AsyncCall extends NamedRunnable { private volatile AtomicInteger callsPerHost = new AtomicInteger(0); ... /**

  • 该方法是在dispather需要执行此请求的时候,分配给它线程池,此异步请求便在这个线程池中执行网络请求。 */ void executeOn(ExecutorService executorService) { ... boolean success = false; try { //异步的关键:将请求放到线程池中执行。 executorService.execute(this); success = true; } catch (RejectedExecutionException e) { ... success = false; } finally { if (!success) { client.dispatcher().finished(this); // 执行失败会通过Dispatcher进行finished,以后再也不会用此AsyncCall。 } } } @Override protected void execute() { boolean signalledCallback = false; transmitter.timeoutEnter(); try { Response response = getResponseWithInterceptorChain(); signalledCallback = true; //请求成功时,回调Response给到用户 responseCallback.onResponse(RealCall.this, response); } catch (IOException e) { ... //请求错误时,回调错误接口给到用户 responseCallback.onFailure(RealCall.this, e); } finally { //结束一次请求。 client.dispatcher().finished(this); } } }

AsyncCall继承于Runnable,它提供了将Call放到线程池执行的能力,实现了请求的异步流程

2.3、Dispatcher.enqueue()

//准备进行异步调用的请求。 private final Deque readyAsyncCalls = new ArrayDeque<>(); //正在执行的异步请求。 private final Deque runningAsyncCalls = new ArrayDeque<>(); void enqueue(AsyncCall call) { synchronized (this) { //将异步请求加入到双端队列中 readyAsyncCalls.add(call); // 寻找是否有同Host的请求,如果有进行复用 if (!call.get().forWebSocket) { AsyncCall existingCall = findExistingCallWithHost(call.host()); if (existingCall != null) call.reuseCallsPerHostFrom(existingCall); } } //将符合条件的Ready的异步请求转入runningAsyncCalls,并执行 promoteAndExecute(); }

将此请求登记到Dispatcher的预备双端队列中。
以此次的请求的Host来查找可服用的异步请求,如果存在,进行复用。
尝试将刚刚加入预备队的请求执行

2.4、Dipatcher.finish()

private void finished(Deque calls, T call) { Runnable idleCallback; synchronized (this) { ... //一个请求完成后,检查此时是否有在等待执行的请求,并处理。 boolean isRunning = promoteAndExecute(); if (!isRunning && idleCallback != null) { //通知此时已经没有异步请求任务 idleCallback.run(); } }

  • 调度器结束一次请求;
  • 当一个异步任务完成后,调度器会触发一次预备任务执行流程。让之前因为最大请求数等限制而不能执行的请求有机会得到执行;
  • 通过idleCallback.run()通知此时的调度器空闲状态;

2.5、Dipatcher.promoteAndExecute()

private boolean promoteAndExecute() { ... List executableCalls = new ArrayList<>(); boolean isRunning; synchronized (this) { for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall asyncCall = i.next(); //检查最大请求数限制和 if (runningAsyncCalls.size() >= maxRequests) break; if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; //满足条件,便把预备队的请求提升到执行队列。 i.remove(); asyncCall.callsPerHost().incrementAndGet(); executableCalls.add(asyncCall); runningAsyncCalls.add(asyncCall); } isRunning = runningCallsCount() > 0; } //将可执行的异步请求放进线程池执行 for (int i = 0, size = executableCalls.size(); i < size; i++) { AsyncCall asyncCall = executableCalls.get(i); // asyncCall.executeOn(executorService()); } return isRunning;

  • 该方法是对预备队列里的请求提升至执行队列并执行的一次尝试;
  • 如果不能执行,他启动时机将会延后到其他请求结束;

3、同步请求

public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(json, JSON); Request request = new Request.Builder() .url(url) .post(body) .build(); try (Response response = client.newCall(request).execute()) { return response.body().string(); } }

  • 通过Builder模式构建Request;
  • 调用client.newCall(),通过request生成一个Call对象。他的实现类是RealCall;
  • 随后调用RealCall.execute(),进行同步请求;

3.1、RealCall.execute()

@Override public Response execute() throws IOException { synchronized (this) { // 如果该请求已经执行过,报错。 if (executed) throw new IllegalStateException("Already Executed"); executed = true; } transmitter.timeoutEnter(); transmitter.callStart(); try { //获取 client 里面的调度器 Dispatcher 并记录这个请求。 client.dispatcher().executed(this); //通过责任链的方式将发起请求并返回结果。这里是同步动作,会阻塞。 return getResponseWithInterceptorChain(); } finally { //请求完后需要把这个请求从调度器中移走 client.dispatcher().finished(this); } }

  • 判断Call的合法性;
  • 将RealCall传进Client里面的Dispatcher.executed()里,而Dispatcher是在 OkHttpClient 被的构建函数里被创建并作为成员变量的;
  • 开启责任链模式,进行请求相关逻辑;
  • 执行完成后,调度器对这个请求进行收尾工作;

3.2、Dispatcher.executed()

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque runningAsyncCalls = new ArrayDeque<>(); synchronized void executed(RealCall call) { runningSyncCalls.add(call); }

3.3、RealCall.getResponseWithInterceptorChain()

Response getResponseWithInterceptorChain() throws IOException { // 将请求的具体逻辑进行分层,并采用责任链的方式进行构造。 List interceptors = new ArrayList<>(); // 用户自已的请求拦截器 interceptors.addAll(client.interceptors()); //重试和重定向拦截器 interceptors.add(new RetryAndFollowUpInterceptor(client)); //桥拦截器 interceptors.add(new BridgeInterceptor(client.cookieJar())); //缓存逻辑拦截器 interceptors.add(new CacheInterceptor(client.internalCache())); //网络连接逻辑拦截器 interceptors.add(new ConnectInterceptor(client));

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

img img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取