flutter 专题二 Flutter状态管理之Riverpod 0.8.4
一 、flutter 有哪些状态管理方式
Flutter的状态管理方式有很多,Redux、 Bloc、 MobX、Provider等等。单单一个Provider,我也见到了各种组合,例如ChangeNotifier + Provider / StateNotifier + Provider( + freezed)。各种方式各有千秋,我们根据自己的习惯和项目的情况去选择就好,这里不做讨论;
二 、Riverpod 介绍
Riverpod和Provider师出同门,都来自作者Remi,Riverpod可以被认为是Provider的重写,来实现原本不可能的功能。就像它的名字一样,字母与Provider相同,但是又不相同。
你可以理解Riverpod是Provider的升级版,解决了Provider的一些痛点:
Provider是InheritedWidget的封装,所以在读取状态时需要BuildContext。这导致了许多的限制,许多新手在不理解InheritedWidget和BuildContext时,跨页面获取状态经常会ProviderNotFoundException。而Riverpod不再依赖Flutter,也就是没有使用InheritedWidget,所以也不需要BuildContext。
读取对象是编译安全的。没有那么多的运行时异常。
能够有多个相同类型的provider。
provider可以是私有的。
当不再使用provider的状态时,将其自动回收。
当然目前Riverpod
也有一些不足(0.14.0+3版本):
- 毕竟诞生不久,它还不能保证是完全稳定的。
- 可能后期会有API的破坏性改动。(比如在0.7.0就有不少Breaking,导致我之前写的部分示例内容就报错了。)
- 目前生产环境中使用需要谨慎。
三、Riverpod
的三种使用方式
Riverpod 提供了
三种使用方式,如下图,可以根据自己的实际项目选择适合自己的方式
本篇不引入flutter_hooks相关内容,这里我就选择flutter_riverpod
。那么将它添加到pubspec.yaml
中
flutter_riverpod: ^0.14.0+3
四 、flutter 基础使用
Provider
这里使用Riverpod
的Provider
需要三步就可以。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';// 1.创建一个全局的provider,里面储存“Hello World!”
final Provider<String> helloWorldProvider = Provider((_) => 'Hello World!');void main() {runApp(// 2.添加“ProviderScope”。所有使用Riverpod的Flutter程序都必须// 在widget tree的根部添加它,用来储存各个provider。ProviderScope(child: MyApp(),),);
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Riverpod Example',theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),home: ProviderExample(),);}
}// 3.使用“ConsumerWidget”,在“build”中获取对应的provider
class ProviderExample extends ConsumerWidget {@overrideWidget build(BuildContext context, ScopedReader watch) {final String value = watch(helloWorldProvider);return Scaffold(appBar: AppBar(title: Text('Provider Example')),body: Center(child: Text(value),),);}
}
这里储存“Hello World!” 使用的是Provider
,它提供一个永远不变的对象。不过大部分场景下状态都是可变的,下面用计数器来举例。
StateProvider
在“Hello World”的基础上,做两点修改即可。
-
定义一个全局常量
StateProvider
。
final StateProvider<int> counterProvider = StateProvider((_) => 0);
2、
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';class StateProviderExample extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('StateProvider Example'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text('You have pushed the button this many times:',),Consumer(builder: (context, watch, _) {/// 使用Consumer(ConsumerWidget的封装),控制刷新的范围。int count = watch(counterProvider).state;return Text('$count',style: Theme.of(context).textTheme.headline4,);},),],),),floatingActionButton: FloatingActionButton(/// 使用read获取counterProvider,操作state。onPressed: () => context.read(counterProvider).state++,tooltip: 'Increment',child: Icon(Icons.add),),);}}
如果你的状态比较复杂可以使用ChangeNotifierProvider,如果习惯使用StateNotifier,可以使用StateNotifierProvider 。其实StateProvider的内部是StateController,也还是StateNotifier。源码如下;
class StateProvider<T>extends AlwaysAliveProviderBase<StateController<T>, StateController<T>> {StateProvider(Create<T, ProviderReference> create, {String name,}) : super((ref) => StateController(create(ref)), name);...
}class StateController<T> extends StateNotifier<T> {StateController(T state) : super(state);@overrideT get state => super.state;@overrideset state(T value) => super.state = value;
}
StateNotifierProvider的用法与StateProvider基本一致,这里就不贴出来了,有兴趣的可以点击这里查看。
2021-04-19更新:
0.14.0对StateNotifierProvider的语法有破坏性变化,避免了StateNotifierProvider的错误使用。具体见文档。
ChangeNotifierProvider
这部分没啥说的,注意ChangeNotifier与StateNotifier的区别,需要自己调用notifyListeners通知变更。
final ChangeNotifierProvider<Counter> _counterProvider = ChangeNotifierProvider((_) => Counter());class Counter extends ChangeNotifier {int _count = 0;int get count => _count;void increment() {_count++;notifyListeners();}void decrement(){_count--;notifyListeners();}
}class ChangeProviderNotifierExample extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('ChangeNotifierProvider Example'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text('You have pushed the button this many times:',),Consumer(builder: (context, watch, _) {int count = watch(_counterProvider).count;return Text('$count',style: Theme.of(context).textTheme.headline4,);},),],),),floatingActionButton: FloatingActionButton(/// 使用read获取counterProvider。onPressed: () => context.read(_counterProvider).increment(),tooltip: 'Increment',child: Icon(Icons.add),),);}
}
FutureProvider
final FutureProvider<String> futureProvider = FutureProvider((_) async {/// 延时3sawait Future.delayed(const Duration(seconds: 3));return 'Riverpod';
});class FutureProviderExample extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('FutureProvider Example'),),body: Center(child: Consumer(builder: (context, watch, _) {AsyncValue<String> futureProviderValue = watch(futureProvider);/// 根据相应状态展示return futureProviderValue.when(loading: () => CircularProgressIndicator(),error: (error, stack) => Text('Oops, something unexpected happened'),data: (value) => Text('Hello $value',style: Theme.of(context).textTheme.headline4,),);},),),);}
}
作者也提供了StreamProvider。用法大同小异,有兴趣的可以查看我的示例代码。
ProviderListener
如果你希望在Widget Tree上监听provider的状态变化,可以使用ProviderListener。用上面的计数器例子,当计数器为5时,触发监听。
ProviderListener<StateController<int>>(provider: counterProvider,onChange: (_, counter) {if (counter.state == 5) {print('当前计数器为5,触发监听。');}},child: Consumer(builder: (context, watch, _) {int count = watch(counterProvider).state;return Text('$count',style: Theme.of(context).textTheme.headline4,);},),
),
ScopeProvider
一般我们在实现一个列表的Item时,需要传入相应的index大致如下:
ListView.builder(itemCount: 50,itemBuilder: (context, index) {return ProductItem(index: index);},
)
如果使用ScopedProvider
并结合 ProviderScope
,就可以简单的获取index,不必从构造方法接收它。使用起来很简单,直接上代码:
/// 定义ScopedProvider
final ScopedProvider<int> currentProductIndex = ScopedProvider<int>(null);class ScopeProviderExample extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('ScopedProvider'),),body: ListView.builder(itemCount: 50,itemBuilder: (context, index) {return ProviderScope(overrides: [/// 修改valuecurrentProductIndex.overrideWithValue(index),],/// 使用'const'关键字实例化了“ProductItem”,/// 但仍然可以在内部动态获取内容。child: const ProductItem(),);},),);}
}class ProductItem extends ConsumerWidget {const ProductItem({Key key}): super(key: key);@overrideWidget build(BuildContext context, ScopedReader watch) {/// 获取相应indexfinal index = watch(currentProductIndex);return ListTile(title: Text('item $index'));}
}
4.修饰符
family
family
的作用是可以在获取provider时可以添加一个参数。直接上例子,一看便知:
/// 使用family,可以在获取provider时传入city
final _weatherProvider = Provider.family<String, String>((ref, city) {return '$city (Sunny)';
});class FamilyExample extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Family')),body: Center(child: Consumer(builder: (context, watch, _) {/// 这里可以传参“London”final String weather = watch(_weatherProvider('London'));return Text('$weather',);},),),);}
}
意: 使用family时传入的参数是有限制的。比如bool 、 int 、 double 、 String 、常量或是重写了==和hashCode的不可变对象。
autoDispose
前面我们的例子中,创建的provider因为保存在Widget Tree的根部。所以即使页面关闭,再次进入页面时会获取之前的状态。
这显然是不灵活的,那么这里就可以使用autoDispose,它可以在我们不再使用provider时,自动将其销毁。那么合理的使用它可以避免内存泄漏。
比如之前的计数器例子,只需加一个autoDispose就可以避免此类问题。
final stateProvider = StateProvider.autoDispose((_) => 0);
如果你需要自定义dispose事件,可以使用onDispose
。比如你的provider中有网络请求(使用Dio):
final myProvider = FutureProvider.autoDispose((ref) async {final cancelToken = CancelToken();// 当provider被销毁时,取消http请求ref.onDispose(() => cancelToken.cancel());// http请求final response = await dio.get('path', cancelToken: cancelToken);// 如果请求成功完成,则保持该状态。ref.maintainState = true;return response;
});
上面代码中出现了ref.maintainState,这个参数默认为false。如果用户离开页面并且请求失败,下次则将再次执行该请求。但是,如果请求成功完成(maintainState为true),则将保留状态,下次重新进入页面时不会触发新的请求。
使用autoDispose可以达到限制provider是全局还是局部作用。这样一来,可以更方便的解决跨页面使用provider的问题。
5.进阶使用
Combining providers
1.如果创建的provider需要另一个provider的状态,这时就需要使用ProviderReference的read方法。
下面的示例是,给予城市和国家的provider,当创建locationProvider时,获取城市和国家的状态。
final Provider<String> cityProvider = Provider((ref) => 'London');
final Provider<String> countryProvider = Provider((ref) => 'England');
final Provider<Location> locationProvider = Provider((ref) => Location(ref));class Location {Location(this._ref);final ProviderReference _ref;String get label {/// read 获取final city = _ref.read(cityProvider);final country = _ref.read(countryProvider);return '$city ($country)';}
}
使用Riverpod就可以提供多个相同类型的Provider,这也是相比Provider的一个优点。
2.如果获取的状态值会发生变化,我们需要监听它。可以使用ProviderReference的watch方法。
下面的示例是,给予城市provider,当城市变化时,天气也相应变化。
final StateProvider<String> cityProvider = StateProvider((ref) => 'London');
final StateProvider<String> weatherProvider = StateProvider((ref) {/// watch监听final String city = ref.watch(cityProvider).state;return '$city (Sunny)';
});class CombiningProviderExample2 extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('CombiningProvider')),body: Center(child: Consumer(builder: (context, watch, _) {final String weather = watch(weatherProvider).state;return Text('$weather',);},),),floatingActionButton: FloatingActionButton(onPressed: () {String city = context.read(cityProvider).state;/// 修改状态if (city == 'London') {context.read(cityProvider).state = "Xi'an";} else {context.read(cityProvider).state = 'London';}},tooltip: 'Refresh',child: Icon(Icons.refresh),),);}
}
refresh
强制provider立即刷新,重新返回创建的值。这种适合列表下拉刷新,或者请求数据错误时重试。
final FutureProvider<List<String>> productsProvider = FutureProvider((_) async {/// 延时3sawait Future.delayed(const Duration(seconds: 3));return List.generate(50, (index) => 'Item $index');
});class RefreshProviderExample extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('RefreshProvider'),),body: Center(child: Consumer(builder: (context, watch, _) {AsyncValue<List<String>> productsProviderValue = watch(productsProvider);return productsProviderValue.when(loading: () => CircularProgressIndicator(),error: (error, stack) => Text('Oops, something unexpected happened'),data: (list) => RefreshIndicator(onRefresh: () => context.refresh(productsProvider), /// 刷新child: ListView(children: [for (final item in list) ListTile(title: Text(item)),],),),);},),),);}
}
select
当状态中某一个值发生变化时,相应Consumer下的builder就会执行,重建widget。如果使用select可以指定某一值更改时进行刷新,精准控制刷新范围,避免不必要的rebuild。
不过目前(0.14.0+3版本),select这种局部监听只支持使用hooks_riverpod包的useProvider。所以这里需要引用hooks_riverpod。
final ChangeNotifierProvider<Person> personProvider = ChangeNotifierProvider((_) => Person());class Person extends ChangeNotifier {int _age = 0;int get age => _age;set age(int age) {_age = age;notifyListeners();}String _name = 'weilu';String get name => _name;set name(String name) {_name = name;notifyListeners();}
}class SelectExample extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Select Example'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[HookBuilder(builder: (_) {String name = useProvider(personProvider.select((p) => p.name));/// 如果使用下面的方式,则age变化时,这里的Text也会刷新。
// String name = useProvider(personProvider).name;return Text('name:$name',);},),HookBuilder(builder: (_) {int age = useProvider(personProvider.select((p) => p.age));return Text('age:$age',);},),],),),floatingActionButton: FloatingActionButton(// 这里age变化时,只有对应的Text会变化。onPressed: () => context.read(personProvider).age = Random.secure().nextInt(255),tooltip: 'Refresh',child: Icon(Icons.refresh),),);}
}
其他
细心的你会发现,在使用read获取provider时还是使用了context。一开始不是说,没有使用InheritedWidget,所以也不需要BuildContext吗?
其实Riverpod本身确实如此,但是在Flutter的应用中,为了便于高效(时间复杂度O(1))的在Widget Tree中获取ProviderContainer(在ProviderScope中隐式创建,用来储存provider),需要在根部使用InheritedWidget,便于最终获取provider。
read、refresh、Consumer、ProviderListener等方法和Widget的内部其实都调用了ProviderScope.containerOf(context, listen = xx);,不同的是listen的值。
static ProviderContainer containerOf(BuildContext context, {bool listen = true,}) {UncontrolledProviderScope scope;if (listen) {scope = context //.dependOnInheritedWidgetOfExactType<UncontrolledProviderScope>();} else {scope = context.getElementForInheritedWidgetOfExactType<UncontrolledProviderScope>().widget as UncontrolledProviderScope;}return scope.container;}
比如read中listen的值为false,使用getElementForInheritedWidgetOfExactType方法,这样在数据发生变化时就不会掉用didChangeDependencies,避免不必要的rebuild。相对的,Consumer、ProviderListener中listen的值为ture,会实现我们需要的widget重建。
我们可以还可以通过Flutter Inspector检查已有的状态,所有状态汇总在ProviderScope下面,这也是Riverpod的一个优点。如下图所示:
发布本篇时,有关Riverpod的资料与讨论很少。本篇也是我在实践完官网文档后的理解,如有错误,欢迎指出!
个人认为Riverpod是相对更轻松便捷的一种状态管理方式,待它稳定时应该能被更多的人喜爱。
Riverpod的相关示例代码我已经上传至Github,有兴趣的可以看看。后面如果Riverpod有变动时,我也会及时更新。大家可以收藏起来,多多点赞支持一下,给我点更新动力!
5、参考
https://riverpod.dev/docs
相关文章:

flutter 专题二 Flutter状态管理之Riverpod 0.8.4
一 、flutter 有哪些状态管理方式 Flutter的状态管理方式有很多,Redux、 Bloc、 MobX、Provider等等。单单一个Provider,我也见到了各种组合,例如ChangeNotifier Provider / StateNotifier Provider( freezed)。各…...

【Linux】从零开始使用多路转接IO --- poll
碌碌无为,则余生太长; 欲有所为,则人生苦短。 --- 中岛敦 《山月记》--- 从零开始使用多路转接IO 1 前言1 poll接口介绍3 代码编写4 总结 1 前言 上一篇文章我们学习了多路转接中的Select,其操作很简单,但有一些缺…...
Docker配置宿主机目录和网络映射
容器挂载宿主机目录 在Docker中,你可以通过-v或--volume选项将宿主机的目录挂载到容器中。这可以让你在容器和宿主机之间共享文件。 例如,如果你想将宿主机的/home/user/data目录挂载到容器的/data目录,你可以使用以下命令: do…...
第十七课 component组件解析
component组件解析 component组件的写法在众多组件写法中算是比较简单的,component组件结构组成如下: 1)组件名 2)组件模板 3)利用Vue对象进行生成 基础示例: <div id"app"><test>…...
求余和求模是不是一样的,就要看看计算机中的 fix 和 floor 区别
在计算机中,fix和floor是两个不同的取整函数,它们各自有不同的取整规则。以下是fix和floor的详细区别: 一、定义与功能 fix函数 定义:fix函数是朝零方向取整的函数,即它会返回小于或等于(对于正数…...
00 递推和递归的核心讲解
递归的步骤 说 f(n)含义返回/xx f(n)等价式子在第二步中观察趋势,发现边界值(分类递归)和终止值(return) 递归优化思路 记忆化 递推/动态规划的步骤 说f(n)含义循环 关系式列 初值 综上,题目分为两类&a…...

深度学习常用开源数据集介绍【持续更新】
DIV2K 介绍:DIV2K是一个专为 图像超分辨率(SR) 任务设计的高质量数据集,广泛应用于计算机视觉领域的研究和开发。它包含800张高分辨率(HR)训练图像和100张高分辨率验证图像,每张图像都具有极高…...

rust编写的系统监测器
系统监测器 技术栈 rusttaurivue3vue-echartsrsbuild 软件介绍 用于查看电脑的硬件信息,实时监测cpu,内存,硬盘,网络,进程等系统资源 图形化,动态化展示,美观实用 软件截图 下载 https:/…...

【MyBatis源码】CacheKey缓存键的原理分析
文章目录 Mybatis缓存设计缓存KEY的设计CacheKey类主体CacheKey组成CacheKey如何保证缓存key的唯一性 Mybatis缓存设计 MyBatis 每秒过滤众多数据库查询操作,这对 MyBatis 缓存键的设计提出了很高的要求。MyBatis缓存键要满足以下几点。 无碰撞:必须保证…...

034_Structural_Transient_In_Matlab结构动力学问题求解
结构动态问题 问题描述 我们试着给前面已经做过的问题上加一点有趣的东西。 结构静力学求解 当时求解这个问题,在最外面的竖直切面加载了一个静态的固定的力。下面我们试试看在上方的表面增加一个脉冲压力载荷。 采用统一的有限元框架,定义问题&…...

项目模块十五:HttpResponse模块
一、模块设计思路 存储HTTP应答要素,提供简单接口 二、成员变量 int _status; // 应答状态码 unordered_map<string, string> _headers; // 报头字段 string _body; // 应答正文 bool _redirect_flag; // 是否重定向信息 stri…...

推荐一款优秀的pdf编辑器:Ashampoo PDF Pro
Ashampoo PDF Pro是管理和编辑 PDF 文档的完整解决方案。程序拥有您创建、转换、编辑和保护文档所需的一切功能。根据需要可以创建特定大小的文档,跨设备可读,还可以保护文件。现在您还能像编辑Word文档一样编辑PDF! 软件特点 轻松处理文字 如 Microso…...
【系统架构设计师】2024年上半年真题论文: 论模型驱动架构设计方法及其应用(包括解题思路和素材)
更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 真题题目(2024年上半年 试题3)解题思路1、模型驱动架构能够为软件开发带来的好处2、模型驱动架构的开发过程论文素材参考真题题目(2024年上半年 试题3) 模型驱动架构设计是一种用于应用系统开发的软件设计方法…...

国内短剧源码短剧系统搭建小程序部署H5、APP打造短剧平台
在当今的互联网时代,短剧作为一种新兴的娱乐形式,受到了越来越多用户的喜爱。为了提供更好的用户体验和满足用户需求,一个好的短剧系统需要具备多元化的功能和优质的界面设计。 本文将介绍国内短剧源码短剧系统搭建小程序部署H5、APP所需的…...

Java集合框架面试指南
Java集合框架面试指南 文章目录 Java集合框架面试指南ArrayList特点:LinkedList特点:ArrayDeque特点:PriorityQueue特点:HashMap特点:HashSet特点:LinkedHashMap特点LinkedHashMap经典用法 TreeMap特点Conc…...
八、MapReduce 大规模数据处理深度剖析与实战指南
MapReduce 大规模数据处理深度剖析与实战指南 一、绪论 在当今的大数据时代背景下,海量数据的处理已然成为企业及科研机构所面临的重大挑战。MapReduce 作为一种高效的分布式计算模型,在大规模数据处理领域中发挥着至关重要的作用。本文将深入阐释 MapR…...

开源免费的API网关介绍与选型
api网关的主要作用 API网关在现代微服务架构中扮演着至关重要的角色,它作为内外部系统通信的桥梁,不仅简化了服务调用过程,还增强了系统的安全性与可管理性。例如,当企业希望将内部的服务开放给外部合作伙伴使用时,直…...

OpenCV视觉分析之目标跟踪(5)目标跟踪类TrackerMIL的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 MIL 算法以在线方式训练分类器,以将目标从背景中分离出来。多重实例学习(Multiple Instance Learning)通过在…...

二级列表联动
介绍 本示例主要介绍了List组件实现二级联动(Cascading List)的场景。 该场景多用于商品种类的选择、照片不同类型选择等场景。 效果图 使用说明: 滑动二级列表侧控件(点击没用),一级列表随之滚动。&…...

「C/C++」C++ 标准库 之 #include<sstream> 字符串流库
✨博客主页何曾参静谧的博客📌文章专栏「C/C」C/C程序设计📚全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...

《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献
Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译: ### 胃肠道癌症的发病率呈上升趋势,且有年轻化倾向(Bray等人,2018&#x…...

PydanticAI快速入门示例
参考链接:https://ai.pydantic.dev/#why-use-pydanticai 示例代码 from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel from pydantic_ai.providers.openai import OpenAIProvider# 配置使用阿里云通义千问模型 model OpenAIMode…...

【Java多线程从青铜到王者】单例设计模式(八)
wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本,sleep也是可以指定时间的,也就是说时间一到就会解除阻塞,继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒),wait能被notify提前唤醒…...