Flutter异步编程详解

Flutter异步编程详解,oppoAndroid面试 onPressed: calculator, tooltip: 'Increment', child: Icon(Icons.add), ) * 修改代码 1. 新建一个`calculatorByComputeFunction`方法,用`compute`调用`bigCompute`方法: void calculatorByComputeFunction() async { // 使用`compute`调用`bigCompute`方法,传参0 int result = aw. 阅读详情

不知道大家有没有一个疑问:Dart是单线程执行,那它是如何实现异步操作的呢?

本文将对Dart/Flutter提供的Isolate,Event Loop,Future,async/await等进行异步操作相关的知识点进行分析。

Isolate

什么是Isolate?

An isolate is what all Dart code runs in. It’s like a little space on the machine with its own, private chunk of memory and a single thread running an event loop.

  • Isolate相当于Dart语言中的线程Thread,是Dart/Flutter的执行上下文环境(容器);
  • Isolate有自己独立的内存地址和Event Loop,不存在共享内存所以不会出现死锁,但是比Thread更耗内存;

  • Isolate间不能直接访问,需凭借Port进行通信;

Main Isolate

当执行完main()入口函数后,Flutter会创建一个Main Isolate。一般情况下任务都是在这个Main Isolate中执行的。

多线程

一般情况下在Main Isolate执行任务是可以接受的,但是把一些耗时操作放在Main Isolate中执行,会造成掉帧的现象,这会对用户体验造成严重影响。此时,选择将耗时任务分发到其他的Isolate中就是一个很好的实现方式了。

所有的Dart Code都是在Isolate中执行的,代码只能使用同一个Isolate中的内容,不同的 Isolate是内存隔离的,因此只能通过 Port 机制发送消息通信,其原理是向不同的 Isolate 队列中执行写任务。

案例

我们做了个简单的Demo,屏幕中间有一个心在不停的动画(由小变大,再由大变小)。当我们点击右下角对的加号按钮,会进行一个耗时的运算。如果耗时操作在Main Isolate执行,将会造成界面的丢帧,动画将会出现卡顿的情况。

我们目前就是需要解决这个掉帧的问题。

1.compute 方法

Flutter封装了一个compute这个高级API函数可以让我们方便的实现多线程的功能。

Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String? debugLabel }) async {
}

compute接收两个必传参数:1,需要执行的方法;2,传入的参数,这参数最多只能是1个,所以多个参数需要封装到Map中;

  • 最开始的代码
// 耗时操作的方法:`bigCompute`
Future<int> bigCompute(int initalNumber) async {
    int total = initalNumber;
    for (var i = 0; i < 1000000000; i++) {
      total += i;
    }
    return total;
}

// 点击按钮调用的方法:`calculator`
void calculator() async {
    int result = await bigCompute(0);
    print(result);
}

// FloatingActionButton的点击事件
FloatingActionButton(
    onPressed: calculator,
    tooltip: 'Increment',
    child: Icon(Icons.add),
)
  • 修改代码
  1. 新建一个calculatorByComputeFunction方法,用compute调用bigCompute方法:
void calculatorByComputeFunction() async {
    // 使用`compute`调用`bigCompute`方法,传参0
    int result = await compute(bigCompute, 0);
    print(result);
}
  1. 修改FloatingActionButton的点击事件方法为calculatorByComputeFunction
FloatingActionButton(
    onPressed: calculatorByComputeFunction,
    tooltip: 'Increment',
    child: Icon(Icons.add),
)

咱点击试试?

[VERBOSE-2:ui_dart_state.cc(186)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function 'bigCompute':.)

  1. 解决Error:将bigCompute改为为static方法(改为全局函数也是可行的)
static Future<int> bigCompute(int initalNumber) async {
    int total = initalNumber;
    for (var i = 0; i < 1000000000; i++) {
      total += i;
    }
    return total;
}

警告:还有一个需要注意的是所有的Platform-Channel的通信必须在Main Isolate中执行,譬如在其他Isolate中调用rootBundle.loadString("assets/***")就掉坑里了。

2. 直接使用Isolate

上面我们用compute方法,基本上没有看到Isolate的身影,因为Flutter帮我们做了很多工作,包括Isolate创建,销毁,方法的执行等等。一般情况下我们使用这个方法就够了。

但是这个方法有个缺陷,我们只能执行一个任务,当我们有多个类似的耗时操作时候,如果使用这个compute方法将会出现大量的创建和销毁,是一个高消耗的过程,如果能复用Isolate那就是最好的实现方式了。

多线程Isolate间通信的原理如下:

  1. 当前Isolate接收其他Isolate消息的实现逻辑: Isolate之间是通过Port进行通信的,ReceivePort是接收器,它配套有一个SendPort发送器, 当前Isolate可以把SendPort发送器送给其他Isolate,其他Isolate通过这个SendPort发送器就可以发送消息给当前Isolate了。

  2. 当前Isolate给其他Isolate发消息的实现逻辑: 其他Isolate通过当前Isolate的SendPort发送器发送一个SendPort2发送器2过来,其他的Isolate则持有SendPort 2发送器2对应的接收器ReceivePort2接收器2,当前Isolate通过SendPort 2发送消息就可以被其他Isolate收到了。

是不是很绕!我再打个比喻:市面上有一套通信工具套件,这套通信工具套件包括一个接电话的工具和一个打电话的工具。A留有接电话的,把打电话的送给B,这样B就可以随时随地给A打电话了(此时是单向通信)。 如果B也有一套工具,把打电话的送给A,这样A也能随时随地给B打电话了(此时是双向通信了)。

上代码:

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
    // 1.1 新建的isolate
    Isolate isolate;
    // 1.2 Main Isolate的接收器
    ReceivePort mainIsolaiteReceivePort;    
    // 1.3 Other Isolate的发送器
    SendPort otherIsolateSendPort;

    // 新建(复用)Isolate
    void spawnNewIsolate() async {
        // 2.1 建一个接收Main Isolate的接收器
        if (mainIsolaiteReceivePort == null) {
          mainIsolaiteReceivePort = ReceivePort();
        }
        try {
          if (isolate == null) {
            // 2.2 新建的isolate, 把Main Isolate发送器传给新的isolate,calculatorByIsolate是需要执行的任务
            isolate = await Isolate.spawn(
                calculatorByIsolate, mainIsolaiteReceivePort.sendPort);
            // 2.3 Main Isolate 通过接收器接收新建的isolate发来的消息    
            mainIsolaiteReceivePort.listen((dynamic message) {
              if (message is SendPort) {
                // 2.4 如果新建的isolate发来的是一个发送器,就通过这个发送器给新建的isolate发送值过去(此时双向通讯建立成功)
                otherIsolateSendPort = message;
                otherIsolateSendPort.send(1);
                print("双向通讯建立成功,主isolate传递初始参数1");
              } else {
                // 2.5 如果新建的isolate发来了一个值,我们知道是耗时操作的计算结果。
                print("新建的isolate计算得到的结果$message");
              }
            });
          } else {
            // 2.6 复用otherIsolateSendPort
            if (otherIsolateSendPort != null) {
              otherIsolateSendPort.send(1);
              print("双向通讯复用,主isolate传递初始参数1");
            }
          }
        } catch (e) {}
    }

    // 这个是新的Isolate中执行的任务
    static void calculatorByIsolate(SendPort sendPort) {
        // 3.1 新的Isolate把发送器发给Main Isolate
        ReceivePort receivePort = new ReceivePort();
        sendPort.send(receivePort.sendPort);

        // 3.2 如过Main Isolate发过来了初始数据,就可以进行耗时计算了
        receivePort.listen((val) {
          print("从主isolate传递过来的初始参数是$val");
          int total = val;
          for (var i = 0; i < 1000000000; i++) {
            total += i;
          }
          // 3.3 通过Main Isolate的发送器发给Main Isolate计算结果
          sendPort.send(total);
        });
    } 

    @override
    void dispose() {
        // 释放资源
        mainIsolaiteReceivePort.close();
        isolate.kill();
        super.dispose();
    }
}

代码注释的很详细了,就不再解释了。是不是代码好多的感觉,其实如果理解流程了逻辑倒不复杂。

关于Isolate的概念和使用我们就介绍到这里,接下来我们来介绍Isolate中的一个重要知识点Event Loop.

Event Loop

Loop这个概念绝大部分开发者都应该很熟悉了,iOS中有NSRunLoop,Android中有Looper, js中有Event Loop,名字上类似,其实所做的事情也是类似的。

Event Loop的官方介绍如下图:

  • 静态示意图

执行完main()函数后将会创建一个Main Isolate。

  • 动态示意图
  • Event Loop会处理两个队列MicroTask queue和Event queue中的任务;
  • Event queue主要处理外部的事件任务:I/O,手势事件,定时器,isolate间的通信等;
  • MicroTask queue主要处理内部的任务:譬如处理I/O事件的中间过程中可能涉及的一些特殊处理等;
  • 两个队列都是先进先出的处理逻辑,优先处理MicroTask queue的任务,当MicroTask queue队列为空后再执行Event queue中的任务;
  • 当两个队列都为空的时候就进行GC操作,或者仅仅是在等待下个任务的到来。

为了比较好的理解 Event Loop 的异步逻辑,我们来打个比喻:就像我去长沙某网红奶茶品牌店买杯“幽兰拿铁”(由于是现做的茶,比较耗时)的过程。

  1. 我来到前台给服务员说我要买一杯你们店的“幽兰拿铁”,然后服务员递给了我一个有编号的飞盘(获取凭证);
  2. 奶茶店的备餐员工就将我的订单放在订单列表的最后面,他们按照顺序准备订单上的商品,准备好一个就让顾客去领取(Event queue 先进先出进行处理),而我就走开了,该干啥干啥去了(异步过程,不等待处理结果);
  3. 突然他们来了个超级VIP会员的订单,备餐员工就把这个超级VIP订单放在了其他订单的最前面,优先安排了这个订单的商品(MicroTask优先处理)---此场景为虚构;
  4. 当我的订单完成后,飞盘开始震动(进行结果回调),我又再次回到了前台,如果前台妹子递给我一杯奶茶(获得结果),如果前台妹子说对不起先生,到您的订单的时候没水了,订单没法完成了给我退钱(获得异常错误错误)。

我们常用的异步操作Future,async,await都是基于Event Loop,我们接下来就来介绍他们异步操作背后的原理。

Future

我们接下来用代码总体说明一下Future背后的逻辑:


final myFuture = http.get('https://my.image.url');
myFuture.then((resp) {
    setImage(resp);
}).catchError((err) {
    print('Caught $err'); // Handle the error.
});
// 继续其他任务
...
  1. http.get('https://my.image.url')返回的是一个未完成状态的Future, 可以理解为一个句柄,同时http.get('https://my.image.url')被丢进了Event queue中等待被执行,然后接着执行当前的其他任务;
  2. 当Event queue执行完这个get请求成功后会回调then方法,将结果返回,Future为完成状态 ,就可以进行接下来的操作了;
  3. 当Event queue执行完这个get请求失败后会回调catchError方法,将错误返回,Future为失败状态 ,就可以进行错误处理了。

我们接下来分别介绍下Future的一些相关函数:

构造函数

  • Future(FutureOr<T> computation())
final future1 = Future(() {
    return 1;
});

computation被放入了Event queue队列中

  • Future.value
final future2 = Future.value(2);

值在MicroTask queue队列中返回

  • Future.error(Object error, [StackTrace? stackTrace])
final future3 = Future.error(3);

这个error表示出现了错误,其中的值不一定需要给一个Error对象

  • Future.delay
final future4 = Future.delayed(Duration(seconds: 1), () {
    return 4;
});

延迟一定时间再执行

Future结果回调then

final future = Future.delayed(Duration(seconds: 1), () {
    print('进行计算');
    return 4;
});
future.then((value) => print(value));
print('继续进行接下来的任务');

// flutter: 继续进行接下来的任务
// flutter: 进行计算
// flutter: 4

Future出现错误后的回调onError

final future = Future.error(3);
future.then((value) => print(value))
.onError((error, stackTrace) => print(error));

print('继续进行接下来的任务');

// flutter: 继续进行接下来的任务
// flutter: 3

Future完成的回调whenComplete

final future = Future.error(3);
future.then((value) => print(value))
.onError((error, stackTrace) => print(error))
.whenComplete(() => print("完成"));

print('继续进行接下来的任务');

// flutter: 继续进行接下来的任务
// flutter: 3
// flutter: 完成

做过前端开发的对这两个关键字应该很熟悉,Flutter中async/await本质上只是Future的语法糖,使用方法也很简单。

Future<String> createOrderMessage() async {
  var order = await fetchUserOrder();
  return 'Your order is: $order';
}
  1. await放在返回值为Future的执行任务前面,相当于做了个标记,表示执行到此为止,等有结果后再往下执行;
  2. 使用了await必须在方法后面加上async;
  3. async方法必须在返回值上封装上Future。

FutureBuilder

Flutter为我们封装了一个FutureBuilder这个Widget,可以方便的构造UI, 以获取图片进行展示为例:

FutureBuilder(
    future: 加载图片的Future
    uilder: (context, snapshot) {
    // 未完成
    if (!snapshot.hasData) {
        // 使用默认的占位图
    } else if (snapshot.hasError) {
        // 使用加载失败的图
    }  else {
        // 使用加载到的图
    }
}

Flutter学习进阶

2019 年无疑是 Flutter 技术如火如荼发展的一年。

每一个移动开发者都在为 Flutter 带来的“快速开发、富有表现力和灵活的 UI、原生性能”的特色和理念而痴狂,从超级 App 到独立应用,从纯 Flutter 到混合栈,开发者们在不同的场景下乐此不疲的探索和应用着 Flutter 技术,也在面临着各种各样不同的挑战。

知识要点:
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter Dart语言系统入门
…

 

由于篇幅原因,如有需要以上完整学习笔记PDF,可以点击这里免费自取!!

告别手动复制粘贴!用Python脚本+pywinauto实现PC微信消息自动发送(附完整源码) 本文详细介绍了如何利用Python和pywinauto实现PC微信消息的自动化发送,告别繁琐的手动操作。从环境搭建、UI控件定位到构建健壮的发送流程,再到高级功能扩展和项目打包部署,全面解析了自动化办公的实现方案,显著提升工作效率。 阅读详情

相关推荐

【Proteus】NE555纯硬件实现LED呼吸灯效果,附源文件,效果展示

本设计通过NE555与三极管的巧妙配合,仅用基础元件实现了呼吸灯效果。核心在于利用RC积分电路将PWM信号转换为控制电压,并通过负反馈动态调节占空比。Proteus仿真验证了方案的可行性,读者可进一步优化参数或扩展功能,探索更多硬件设计可能性。

2201_75797484的博客 2979

Invalid argument(s): Illegal argument in isolate message: object is unsendable

yanhaijunyan的博客 729

RC滤波电路工作原理:【带图+公式计算】

容抗Xc和电容C成反比,和频率f也成反比,C越大,交流电容易通过电容,说明电容量大,电容的阻碍作用小;交流电的频率高,交流电也容易通过电容,说明频率高,电容的阻碍作用也小。我们可以看到,一个2V的纹波,经常RC电路后,幅值下降到mv级别,基本可以忽略不计,这就是RC滤波电路的工作原理。一个频率为1Khz的信号,通过一个容值为0.1uf的电容,计算电容对该信号产生的阻抗。将RC滤波器电路中的电阻,取值为1KΩ,电容取值为0.47uf,纹波幅值为2V。从公式1可以看到,Xc越大,Vout值就越接近Vin的值。

weixin_66299220的博客 2万+

Flutter:多线程Isolate的简单使用

Flutter:多线程Isolate的简单使用

weixin_41897680的博客 4255

最优化学习笔记1——关于拟牛顿法推导

从2020年3月份到现在,一年的时间里断断续续自学最优化,结合豆瓣读书、知乎等网站上的推荐,翻阅了以下书和课程: Stephen Boyd和Lieven Vandenberghe的《Convex Optimization》,这本书被公认为学习凸优化的必读经典著作,其中大量的篇幅讲应用,讲理论和算法的篇幅稍少一些。在B网站上有Boyd的视频课程,油管上也有,不过只有英文字幕,如果是零基础接触,直接看Boyd的视频稍显费劲,B站上也有凌青老师以《Convex Optimization》为教材的讲解,推荐作为入

xiaofei473的博客 1768

基于非接触式微波雷达设计的跌倒检测预警系统【短信报警】

本项目选择基于非接触式微波雷达的跌倒检测预警装置作为技术核心,采用云帆瑞达的R60AFD1跌倒检测模组。这一模组能够根据人体的运动变化,如速度、距离和姿态等特征,判断是否发生跌倒,并可以持续监控老年人是否处于危险状态。此外,本项目还结合4G通信技术,通过Air724UG模块实现短信报警功能,一旦检测到跌倒事故,系统将自动通过4G网络向紧急联系人发送短信报警,及时通知亲属或医护人员进行救援。

1751

Flutter学习之 Delay(延迟)与sleep(休眠)的区别

Delay:线程延迟,该线程还在运行,占用CPU。 sleep:线程休眠,它会被挂起,把CPU让给其他线程,不占用CPU。 1、sleep时间低于delay时间 print("===========开始时间${DateTime.now()}==============="); Future.delayed(Duration(seconds: 10),(){ print("===========延迟10秒后时间${DateTime.now()}==============="); }); Futu

qq_34707150的博客 9289

全覆盖路径规划开源项目Clean-robot-turtlebot3关键函数解析(上)

本系列文章主要介绍全覆盖路径规划开源项目Clean-robot-turtlebot3的相关内容,包含如下四篇文章,分别介绍了开源项目Clean-robot-turtlebot3流程的概括总结、ROS坐标系常用坐标及其变换、Clean-robot-turtlebot3关键函数解析等内容。

慕羽★的博客 2778

flutter 之 Dart 异步编程【详解】

Dart是一种单线程模型的语言,通过将程序中耗时的任务尽量放在运行后期执行实现异步编程,具体依赖事件循环机制。 事件循环 共两个异步队列,分别是事件队列(eventqueue)和微任务队列(microtaskqueue) 每当遇到耗时任务,就将它们放入异步队列,等其他任务执行完后,事件循环就会循环获取两个队列中的异步任务来执行。 事件循环机制如下图: 执行同步任务 执行微任务(微任务清空前,事件将一直等待执行) 执行事件 微任务队列 用来放入一些耗时相对较短的异步任务。 通过调用async包下

朝阳39的博客 795

3分钟学会SQL中的断点去重技术,轻松搞定连续重复数据去重问题?

传感器采集的温度数据中,存在连续重复的值,需仅在数值变化时保留记录(例如:温度从 25°C 变为 26°C 时记录一次)。:判断逻辑,current_value!若当前温度与前一行的温度不同(或为第一条记录),则保留该行。的技术,常用于时间序列或状态日志中。:将连续相同值的记录合并,只保留断点处的记录。保留断点处的记录(即温度变化时的第一条)。原始数据表 sensor_data。最终仅保留值变化的断点记录。获取前一行的温度值。

石榴姐yyds 1019

配置适合Gurobi的机器硬件环境需要考虑的因素

介绍了在为gurobi选择合适的硬件环境时,需要考虑三个方面的因素;

Linshaodan520的博客 1063

Flutter异步编程详解,android插件化原理

/ 2.2 新建的isolate, 把Main Isolate发送器传给新的isolate,calculatorByIsolate是需要执行的任务。// 2.4 如果新建的isolate发来的是一个发送器,就通过这个发送器给新建的isolate发送值过去(此时双向通讯建立成功)每一个知识点,都有对应的导图,学习的资料,视频,面试题目。每一个知识点,都有对应的导图,学习的资料,视频,面试题目。是内存隔离的,因此只能通过 Port 机制发送消息通信,其原理是向不同的 Isolate 队列中执行写任务。

asnmd1283的博客 680

uniapp [全端兼容] - 实现省市区选择器三四级联动组件,uniapp选择地址自定义省市区三级联动插件,弹框式多级联动选择器区域选择(全平台兼容苹果安卓App、支付宝微信小程序、移动端H5网页)

uni-app省市区选择器,省市区街道三级四级联动选择器,uniapp自定义多列选择器省市两级联动,uniapp picker组件实现多级联动,h5移动端网页,微信小程序,支付宝小程序,Android安卓,苹果ios,app,nvue,uniApp选择器省市区县街道乡镇,uniapp实现省市区选择联动,uniapp城市三级联动(自定义组件)_uniapp三级联动,picker插件省市区联动,uniapp小程序省市区选择器,uni-app 省市区街道的四级联动选择,uniapp怎样实现省市区地址三级联动uni

🏆 前端领域优质创作者 1万+

Flutter异步编程详解(1)

Future compute(isolates.ComputeCallback callback, Q message, { String? debugLabel }) async {}// 耗时操作的方法:Future bigCompute(int initalNumber) async {int total = initalNumber;for (var i = 0; i < 1000000000; i++) {total += i;}return total;}// 点击按钮调

2401_84407867的博客 1003

android 蓝牙通讯demo

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

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Flutter异步编程详解,GitHub标星过万

1.compute方法封装了一个compute这个高级函数可以让我们方便的实现多线程的功能。compute接收两个必传参数:1,需要执行的方法;2,传入的参数,这参数最多只能是1个,所以多个参数需要封装到Map中;calculator// FloatingActionButton的点击事件computebigCompute// 使用compute调用bigCompute方法,传参0咱点击试试?

m0_60452141的博客 1669

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

flutter异步编程源码详解

前面讲到耗时任务不适合放到 microtask 队列或 event 队列中执行,会导致 UI 卡顿。那么在 Flutter 中有没有既可以执行耗时任务又不影响 UI 绘制呢,其实是有的,前面提到 microtask 队列和 event 队列是在 main isolate 中运行的,而 isolate 是在线程中运行的,那我们开启一个新的 isolate 就可以了,相当于开启一个新的线程,使用多线程的方式来执行任务,Flutter 也为我们提供了相应的 Api。

Android 开发架构 433

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

flutter Widget、状态管理、Obx、GetX、dio、异步编程 、GetXController 详解

flutter Widget、状态管理、Obx、GetX、dio、GetXController、异步编程 详解

maoning20080808的专栏 250

Flutter for OpenHarmony 实战:Dart异步编程基础Future与async-await详解

本文深入讲解Dart异步编程中的Future和async-await机制。首先对比了同步与异步代码的执行差异,介绍了Dart的事件循环模型。然后详细解析Future的创建方式、then回调链、辅助方法及超时处理。最后重点讲解了async函数和await表达式的使用技巧,包括顺序执行与并行执行的实现方式。通过代码示例展示了异步编程在Flutter应用中的实际应用场景,帮助开发者理解如何避免UI卡顿并提高程序响应性。

2502_91523773的博客 562

Flutter Dart事件循环与异步编程详解

Dart事件循环与异步编程摘要: Dart采用单线程事件循环模型,包含微任务和事件任务两个队列,执行顺序为同步代码→微任务→事件任务。异步编程通过Future和async/await实现,Future支持链式调用(then/catchError),async/await让异步代码更直观。高级用法包括Future.wait并行执行、Stream数据流处理,以及全面的错误处理机制。Flutter中常用FutureBuilder结合异步操作,性能优化需注意操作取消、防抖节流等技巧。核心特点是单线程非阻塞模型,通过

油墨香^-^的博客 481
上一篇: Android开发前景及现状分析
下一篇: 作为一名Android开发者,你有过迷茫吗?
懂你的大海
博客等级 码龄6年 9049粉丝 1381原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值