okhttp3 源码解析笔记整理——同步、异步请求

OkHttp使用踩坑记录总结(二):OkHttp同步异步请求和连接池线程池 说明 在项目中对第三方服务的调用,使用了OkHttp进行http请求,这当中踩了许多坑。本篇博文将对OkHttp使用过程遇到的问题进行总结记录。 正文 同步请求SyncRequest 异步请求AsyncRequest 通过简单示例了解OkHttp如何进行http请求: SyncRequest: private static void syncRequest(String url) throws ... 阅读详情

系列文章:

  1. okhttp3 源码解析笔记整理——同步、异步请求
  2. okhttp3 源码解析笔记整理——拦截器与请求的实现
  3. okhttp3 读书笔记——默认的线程池,addNetworkInterceptor() 与 addInterceptor() 的区别,复用连接池

额外参考文章:
【Android】OkHttp源码分析/#CallServerInterceptor


1、同步请求

okhttp 可以使用 RealCall # execute() 方法来进行一个同步的请求。

try {
    Response response = mOkHttpClient.newCall(request).execute();
} catch (IOException e) {
    e.printStackTrace();
}

RealCall # execute() 方法如下:

@Override public Response execute() throws IOException {
  // 限制 call 只能执行一次请求
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  // 使用超时机制
  timeout.enter();
  eventListener.callStart(this);
  try {
    // 利用调度器 dispatcher 将当前请求添加到一个收集正在运行的同步请求的队列 runningSyncCalls 中
    client.dispatcher().executed(this);
    // 利用拦截器链来实现真正的请求
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    e = timeoutExit(e);
    eventListener.callFailed(this, e);
    throw e;
  } finally {
    // 最后,将当前请求从 runningSyncCalls 中移除
    client.dispatcher().finished(this);
  }
}

在上述方法中,涉及到两个重点的点。

第一个就是调度去 Dispatcher,其内部的成员变量如下:

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;
  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;
  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  ...

  public synchronized ExecutorService executorService() {
  if (executorService == null) {
    executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
        new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}
}

其中就有 runningSyncCalls,用于收集正在运行的同步请求。而 readyAsyncCalls 用于收集准备运行的异步请求,runningAsyncCalls 用于收集正在运行的异步请求。

executorService 为异步请求时要用到的线程池。executorService() 方法中表明了其的默认实现。

第二个是 getResponseWithInterceptorChain() 方法,在方法中,会通过构造一系列的拦截器,来组成一个拦截器链来实现真实的请求。


2、异步请求

okhttp 实现异步请求:

mOkHttpClient.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }
    @Override
    public void onResponse(Call call, Response response) {
    }
});

RealCall # enqueue()

@Override public void enqueue(Callback responseCallback) {
  // 限制 call 只能执行一次请求
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  // 添加进 dispatcher 的 readyAsyncCalls 中
  // 会把 responseCallback 包装成 AsyncCall
  //(AsyncCall是 RealCall 的内部类,实现了 NamedRunnable)
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

Dispatcher

void enqueue(AsyncCall call) {
  synchronized (this) {
    // 先将包装成 AsyncCall 的 call 添加进 readyAsyncCalls
    readyAsyncCalls.add(call);
    // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
    // the same host.
    if (!call.get().forWebSocket) {
      // 如果不是 WebSocket,则在 runningAsyncCalls 中和 readyAsyncCalls 中寻找是否有连接相同主机的请求 existingCall
      AsyncCall existingCall = findExistingCallWithHost(call.host());
      // 如果有找到,则将 existingCall 的 callsPerHost 引用赋值给当前 call 的 callsPerHost
      // 这样做的目的是在于,对于异步请求,okhttp 会对有着相同主机的 call 在请求时进行计数,
      // 默认限制能够同时请求同一主机的 call 的最大个数为 Dispatcher # maxRequestsPerHost(5)
      // 而实现计数的关键就在 callsPerHost,它是 AtomicInteger 的实例对象
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
    }
  }
  // 调用 promoteAndExecute() 方法来推动和执行存放在 readyAsyncCalls 中的异步请求
  promoteAndExecute();
}

@Nullable private AsyncCall findExistingCallWithHost(String host) {
  for (AsyncCall existingCall : runningAsyncCalls) {
    if (existingCall.host().equals(host)) return existingCall;
  }
  for (AsyncCall existingCall : readyAsyncCalls) {
    if (existingCall.host().equals(host)) return existingCall;
  }
  return null;
}

private boolean promoteAndExecute() {
  assert (!Thread.holdsLock(this));
  List<AsyncCall> executableCalls = new ArrayList<>();
  boolean isRunning;
  synchronized (this) {
    // 从 readyAsyncCalls 中取出可执行的请求先存放到临时的 executableCalls 中,
    // 以及转移到 runningAsyncCalls 中
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall asyncCall = i.next();
      // 如果正在运行的请求达到了最大值,则先退出
      if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
      // 如果请求 asyncCall 对应的主机的连接数达到了最大值,则先跳过该请求
      if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
      i.remove();
      // 将 asyncCall 的 callsPerHost 自增 1,callsPerHost 的作用在上面讲过
      asyncCall.callsPerHost().incrementAndGet();
      executableCalls.add(asyncCall);
      runningAsyncCalls.add(asyncCall);
    }
    isRunning = runningCallsCount() > 0;
  }
  // 执行 executableCalls 中存放的请求
  for (int i = 0, size = executableCalls.size(); i < size; i++) {
    AsyncCall asyncCall = executableCalls.get(i);
    // 通过 executorService 线程池来执行 asyncCall 请求
    asyncCall.executeOn(executorService());
  }
  return isRunning;
}

可以看到,对于一个异步请求,会包装成 AsyncCall,然后通过 Dispatcher 中的线程池 executorService 来执行。

AsyncCall 继承自 NamedRunnable,NamedRunnable 则实现了 Runnable。 而在 NamedRunnable#run() 方法中,会执行 NamedRunnable#execute() 这一抽象方法。AsyncCall 则实现 execute() 方法。

AsyncCall # executeOn()

/**
 * Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up
 * if the executor has been shut down by reporting the call as failed.
 */
void executeOn(ExecutorService executorService) {
  ...
  // 通过线程池来执行当前 realCall,因为 realCall 本质上就是 Runnable
  // 这样,就切换到了子线程中,从而实现了异步
  executorService.execute(this);  
  ...
}

线程池 executorService 的默认实现在前面有说(即通过 Dispatcher # executorService() 方法)。

因此,executorService.execute(this) 实际上会触发 AsyncCall 的 execute() 方法(因为其父类 NamedRunnable#run() 的缘故)

@Override protected void execute() {
  boolean signalledCallback = false;
  // 用于监测超时的
  timeout.enter();
  try {
    Response response = getResponseWithInterceptorChain();
    if (retryAndFollowUpInterceptor.isCanceled()) {
      signalledCallback = true;
      responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
    } else {
      signalledCallback = true;
      responseCallback.onResponse(RealCall.this, response);
    }
  } catch (IOException e) {
    e = timeoutExit(e);
    if (signalledCallback) {
      // Do not signal the callback twice!
      Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
    } else {
      eventListener.callFailed(RealCall.this, e);
      responseCallback.onFailure(RealCall.this, e);
    }
  } finally {
    client.dispatcher().finished(this);
  }
}

可以看到,尽管是异步请求,但是最终也是通过 getResponseWithInterceptorChain() 来实现请求的。

有关 getResponseWithInterceptorChain() 可以参见:okhttp 源码解析笔记整理——拦截器与请求的实现


另外,对于 readyAsyncCalls 也许会有一个疑问,那就是怎么保证该队列中的请求都能执行完呢?

这里可以追溯源码,根据前面的内容可以直到,Dispather # promoteAndExecute() 方法会推动 readyAsyncCalls 中的请求的执行(使其转移到 runningAsyncCalls 中)。

而 Dispather # promoteAndExecute() 方法会在多个地方被调用,其中就有在 Dispather # finished() 中被调用:

private <T> void finished(Deque<T> calls, T call) {
  Runnable idleCallback;
  synchronized (this) {
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    idleCallback = this.idleCallback;
  }
  boolean isRunning = promoteAndExecute();
  if (!isRunning && idleCallback != null) {
    idleCallback.run();
  }
}

而该方法又有在 Dispather 的两个重载方法中被调用:

/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
  call.callsPerHost().decrementAndGet();
  finished(runningAsyncCalls, call);
}

/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
  finished(runningSyncCalls, call);
}

而第一个重载方法会在 RealCall # executeOn() 和 RealCall.Async # execute() 中被调用;

第二个重载方法会在 RealCall # execute() 被调用。

这样,就可以保证 Dispather # promoteAndExecute() 方法重复不断的被触发,最终保证 readyAsyncCalls 中的请求都被执行。

Okhttp面试简答,Android开发自学教程 1、支持SPDY、HTTP2.0 2、无缝支持GZIP来减少数据流量 3、支持同步、异步(异步使用较多) 4、缓存响应数据来减少重复的网络请求 5、可以从很多常用的连接问题中自动恢复 1.2、Okhttp的核心类有哪些?简单讲一下 Dispatcher类: Interceptor类: 1.3、OkHttp方面的其他面试题 1、如何使用OkHttp进行异步网络请求,并根据请求结果刷新UI 2、可否介绍一下OkHttp的整个异步请求流程 3、OkHttp对于网络请求都有哪些优化,如何实现的 4、OkHttp 阅读详情

相关推荐

OkHttp请求与响应流程深度解析:从同步异步到拦截器链

HTTP客户端是Android网络通信的核心组件,其请求发送与响应获取流程涉及线程调度、连接复用与责任链模式等关键技术。OkHttp通过Dispatcher实现同步与异步请求的并发控制,利用拦截器链完成Header补全、缓存判断、连接建立与数据读写,确保请求高效可靠。理解这一流程有助于排查请求超时、连接池泄漏及拦截器顺序错误等常见问题,提升网络层性能与稳定性。

weixin_33023873的博客 204

OKHttp3源码解析(一)同步请求的源码分析

OKHTTP是什么? 百度一搜:”一个处理网络请求的开源项目,是安卓端最火热的轻量级框架,由移动支付Square公司贡献(该公司还贡献了Picasso)用于替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient,现在已经打不出来)” 正常的Http协议 OKHTTP的开发 同步请求 OkHttpClient okHttpClient = new OkHttpClient.Builder().build();//.

万子的博客 567

基于Autoware制作高精地图(一)

基于Autoware制作高精地图(一) ​开始进入正题,也是最近在忙的一件事,制作高精地图。 高精地图的制作大概分为以下四个流程(不一定完全正确): 1.构建点云地图; 2.点云地图标注; 3.制作OpenDrive地图; 4.地图存储索引。 这篇文章主要讲解如何构建点云地图。 首先我们要有一段自己录制的激光雷达点云数据,这里推荐大家采用Velodyne16线激光雷达,因为这里我们构建的点云底图是三维的,而采用Velodyne而不是速腾聚创(rslidar)等其他品牌的原因是Autoware里面有一个选项是

weixin_44570248的博客 7797

okhttp同步和异步请求的简单使用

第一步,app/build.gradle里添加okhttp3的依赖,分为Okhttp,和logging-interceptor第二步,AndroidManifest.xml里添加网络权限第三步,新建网络请求类Hiokhttpget请求通过OkHttpClient构造client对象通过Request.Builder构建一个request实例(要在子线程里完成)通过client.newCall构建Call实例通过call.execute发起同步请求/通过call.enqueue发起异步请求。

1736

CPU系统级验证——测试激励——imperas公司riscvOVPsimPlus文件分析

本分析重点关注该repo向量扩展部分。 1. repo简介 该repo是指令集验证的仿真环境包括,大部分是指令集的测试用例,也包含了一些模拟器,如riscvOVPsimPlus。 主要介绍的是riscvOVPsimPlus指令集模拟器,riscvOVPsimPlus(下面统称ISS)是imperas公司在riscvOVPsim基础上扩充了向量扩展的指令集模拟器,该repo于今年9月份部分开源 什么叫部分开源呢? 首先该repo必须在imperas官网注册下载,而且必须是企业用户(免费邮箱人家瞧不上.

知识,既贵,又便宜。 4459

二阶FLL辅助三阶PLL环路的代码解读

今天对GNSS接收机里面的载波跟踪环路代码进行了解读 以下是个人的一点见解 % 载波环第二步:环路滤波器 二阶FLL辅助三阶PLL环路 carrLoopS0 = lastCarrLoopS0 + (phaseErr*tau0PLL + freqErr*tau1FLL) * PDIcarr; carrLoopS1 = lastCarrLoopS1 + (tau1PLL*phaseErr + carrLoopS0 + tau2FLL*freqErr) * PD

LWN7321的博客 2629

okhttp3同步请求和异步请求小案例

学习内容:okhttp3同步请求和异步请求 OkHttp开源库进行访问 步骤: 1.app/build.gradle文件在dependencies闭包中添加如下内容: implementation ‘com.squareup.okhttp3:okhttp:4.4.0’ 2.创建OkHttpClient类的实例 OkHttpClient client = new OkHttpClient(); 3.创建Request对象(并设置目标地址、请求方式等) Request request = new Reque

qq_45744200的博客 4663

GNSS接收机性能测试全攻略:从实验室到真实场景

本文系统介绍了GNSS接收机从实验室到真实场景的完整性能测试流程。首先通过GNSS模拟器进行射频传导测试,评估接收机的基础性能;其次进行天线OTA测试,考察整机系统表现;最后通过精心设计的外场路测,在复杂环境中验证其实际定位精度与可靠性。文章强调,只有结合可控的实验室验证与多变的外场挑战,才能对接收机性能做出全面、真实的评价。

weixin_28365523的博客 516

okhttp3同步请求使用总结

官方文档:https://github.com/square/okhttp/wiki/Recipes https://github.com/square/okhttp   以下只考虑小数据量请求,比如请求rest json API     相关注意点 1. okhttp3比较多的地方使用了builder模式,OkHttpClient、Request、FormBody等都可以通过&l...

xuanzhui1215的专栏 1824

二手分析仪器改造实战:如何用PMT搭建荧光检测电路(附完整电路图)

本文详细介绍了如何利用二手分析仪器中的光电倍增管(PMT)核心部件,进行荧光检测电路的实战改造。内容涵盖从PMT工作原理理解、高压供电电路设计、低噪声前置放大(跨阻放大器)到信号调理的完整流程,并提供了关键电路图与调试排错指南,旨在帮助电子爱好者与工程师低成本构建稳定、高灵敏度的专业检测系统。

weixin_29234239的博客 440

HTML流光爱心(完整代码)

HTML语言实现红蓝色流光爱心效果的完整代码。

AIGC 1万+

okhttp3 源码解析笔记整理——拦截器与请求的实现

在第一篇文章 okhttp 源码解析笔记整理——同步、异步请求 中可以知道,不管是同步请求还是异步请求,都最终是通过 RealCall # getResponseWithInterceptorChain() 来实现真实的请求的。 Response getResponseWithInterceptorChain() throws IOException { // Build a full st...

一个有思想的搬运工 356

SAP FICO 内部往来对账功能开发说明书(包括测试样例、程序代码仅作参考,不保证一定可以运行)

SAP FICO 内部往来对账功能开发说明书(包括开发源代码、测试样例、FS)

我好像病了的博客 1223

OkHttp3源码解析(整体流程)

点击上方蓝字关注????作者:秦子帅来源:程序员进阶圈整理:code小生前言今天主要讲一下OkHttp3源码解析(整体流程),主要分为四大块:okhttp的基本用法OkHtt...

weixin_38754349的博客 243

【深度学习基础】多任务学习(Multi-Task Learning)

多任务学习(Multi-task learning)是和单任务学习(single-task learning)相对的一种机器学习方法。在机器学习领域,标准的算法理论是一次学习一个任务,也就是系统的输出为实数的情况。复杂的学习问题先被分解成理论上独立的子问题,然后分别对每个子问题进行学习,最后通过对子问题学习结果的组合建立复杂问题的数学模型。多任务学习是一种联合学习,多个任务并行学习,结果相互影响。一次只学习一个任务(task),大部分的机器学习任务都属于单任务学习。

十二月的猫 1万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

okHttp源码阅读

使用方法 okhttp基本使用方法: // 1、创建OkHttpClient对象 OkHttpClient okHttpClient = new OkHttpClient(); //2、创建Request对象 Request request = new Request.Builder() .url(url) .build(); //3、通过okHttpClient的newCall方法获得一个C...

一个写代码的胖子的博客 2263

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Okhttp3源码

Okhttp3源码 hi,大家好,我是爱吃香蕉的猴子,今天看了很多Okhttp3源码的文章,看云里雾里的,在这我也不贴连接了,整理一篇自己理解的吧。 源码Githup地址 具体的使用过程上面也有介绍,现在步入正题。 从使用切入,再进入源码,最后总结 OkHttpClient client = new OkHttpClient.Builder().build(); Request request = new Request.Builder().

qq_20608169的博客 466

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

2024年最新整理的Android源码解析(内含MMKV ARouter AsyncTask Volley Retrofit Okhttp Binder Handler等等)_安卓源码解析

一、解决问题(BUG)只要是代码,就会有bug,只是说bug的多与少、深与浅罢了二、知其所以然如果我们需要将一个开源项目用到自己的项目中,那么就必须了解这项项目的优缺点,并深知原理,对部分细节(尤其是项目的优势、feature)进行深入研究三、学习看源码也是一种不错的学习方式(虽然不一定不是最佳的方式),尤其对于比较优秀的开源项目,能让人大开眼界四、改造。

2401_87361558的博客 1150

OkHttp 源码解析(4.9.1 版本)

文章目录1、OkHttp 简介2、OkHttp 配置与基本用法2.1 依赖引入与配置2.2 基本用法3、OkHttp 常见对象介绍4、OkHttp 源码解析4.1 当我们调用`okhttpClient.newCall(request).execute()`方法进行同步请求时:4.2 当我们调用`okhttpClient.newCall(request).equeue()`方法进行异步步请求时:4.3、七层拦截器源码解析4.3.1 全局拦截器4.3.2 重定向拦截器4.3.3 桥接拦截器4.3.4 缓存拦截器

u011035026的博客 5843

OkHttp框架入门笔记

本文讲解了我对Android开发现状的一些看法,也许有些人会觉得我的观点不对,但我认为没有绝对的对与错,一切交给时间去证明吧!愿与各位坚守的同胞们互相学习,共同进步!《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!结本文讲解了我对Android开发现状的一些看法,也许有些人会觉得我的观点不对,但我认为没有绝对的对与错,一切交给时间去证明吧!愿与各位坚守的同胞们互相学习,共同进步!

2401_84132589的博客 888

最全OkHttp源码解析(1),最新高频Java笔试题分享

以上就是整个任务队列的实现细节,总结起来有以下几个特点:OkHttp 采用 Dispatcher 技术,类似于 Nginx,与线程池配合实现了高并发,低阻塞的运行Okhttp 采用 Deque 作为缓存,按照入队的顺序先进先出OkHttp 最出彩的地方就是在 try/finally 中调用了 finished 函数,可以主动控制等待队列的移动,而不是采用锁或者 wait/notify,极大减少了编码复杂性。

2301_79099287的博客 697

Android最全Android 网络请求(一):OkHttp3,京东java面试题技术面

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。还有。

2401_84519907的博客 1234

Android 使用Okhttp3(1)

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。还有。

2401_84004012的博客 692

OkHttp面试之--OkHttp的整个异步请求流程

通过上一节,我们已经了解了如何使用OkHttp发送异步请求,下载网络图片信息并显示到ImageView控件上,从这一节开始我们就来开始研究一下其内部的实现流程和原理。因为整个流程相对而言还是比较复杂,因此对于流程的分析我划分成以下几个章节去介绍 流程概述 拦截器的原理 HttpEngine中sendRequest的流程分析 HttpEngine中readResponse的流程分析 这一节我们先来看一

zxm317122667的专栏 1万+

深入剖析你不知道的Retrofit网络请求神器(三)

我们继续上一篇的源码分析: https://blog.youkuaiyun.com/wk_beicai/article/details/104048326 总结RxJavaCallAdapterFactory 理论上: 我们获取一个Call<T> 对象,用这个Call对象执行具体的Http请求,Retrofit调用这个Call请求,其实还是调用okHttp中的call请求,只不过对其进行封装...

wk_先森的博客 1万+
上一篇: HTTP 笔记整理
下一篇: okhttp3 源码解析笔记整理——拦截器与请求的实现
OneDeveloper
博客等级 码龄9年 61粉丝 161原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值