当前位置: 首页 > news >正文

kotlin的dagger hilt依赖注入

依赖注入(dependency injection, di)是设计模式的一种,它的实际作用是给对象赋予实例变量

基础认识

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 初始化变量val person = Person("Miirym")setContent {DiStudyTheme {}}}
}class Person(val name: String)

以上就是一个基本的di过程:我们创建了一个对象person,并传递了名为Miirym的Test实例。

在此情形下,Miirym的名称是依赖,因为类Person依赖于这个名字,在此之后才有了实例化的变量。

那么di的好处体现在什么地方?我们使用错误的方式展开:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 初始化变量val person = Person()setContent {DiStudyTheme {}}}
}class Person {// 去除了主构造函数val name = "Miirym"
}

我们移除了传递name的主构造函数,在此情形下,我们无法向构造函数传递参数,例如name。显然,对于每个Person的实例化对象,它们都会包含相同的名称:Miirym。

更复杂的情形是,我们可能有一个依赖于特定repository(存储库)实例的viewmodel,此时这个repository就成为了我们注入这个viewmodel的依赖。

可能我们会有疑问,为什么在一个viewmodel中,我们会需要不同类型的repository?最常见的一个理由就是测试,同时为了保持灵活性,你需要能够传递任何类型的repository。在我给出的例子中,我传递的是简单的name,但是对于viewmodel而言,传入的repository可能会是任何类型的。

简而言之,传入viewmodel的内容由外部决定。对于这个传入的操作,最简单的方式就是构造函数注入(constructor inject),而这正是我们在第一个示例中做的操作。

dagger hilt简介

dagger hilt帮助我们以简单的方式注入了依赖,省去了构造函数注入的操作(如大量的声明传参的依赖),因此我们能更为集中地管理依赖。

更为重要的一点是,我们可以更便捷的控制依赖的寿命。以单例为例,在一个完整的项目中,例如数据库的单例只能存在一个,但是我们不会只有一个单例;同时我们可以确定特定依赖项的范围,比如限定某个依赖只能依附于某个activity,这样当activity被销毁时,该依赖的内存也会被清理,进而用于其他地方。

接下来我们尝试创建依赖于一个repository的viewmodel,对于app级别的build.gradle引入viewmodel和dagger hilt依赖如下。

    // ViewModel Composeimplementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1"//Dagger - Hiltimplementation "com.google.dagger:hilt-android:2.40.5"kapt "com.google.dagger:hilt-android-compiler:2.40.5"implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"kapt "androidx.hilt:hilt-compiler:1.0.0"implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'

同时需要引入kapt和dagger hilt插件。

plugins {...id 'kotlin-kapt'id 'dagger.hilt.android.plugin'
}

引入kapt插件是为了让kotlin处理注解,因为在使用dagger hilt生成代码时,会用到注解。dagger hilt是在编译时注入的,因此在app被启动时、也就是在编译时,我们就知道依赖流向何处了。

对于项目级别的build.gradle我们需要添加依赖。

buildscript {...dependencies {...classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5"}
}

项目结构示例

项目结构如下。

此处我们模拟一个请求网络的api。

interface MyApi {@GET("test")suspend fun doNetworkCall()
}

在domain层(包含业务实体,用于封装、传输数据)创建 MyRepository接口,用于抽象具体功能。

interface MyRepository {suspend fun doNetworkCall()
}

接着我们创建一个MyRepositoryImpl类,用于在data层实现MyRepository接口。

class MyRepositoryImpl : MyRepository {override suspend fun doNetworkCall() {TODO("Not yet implemented")}}

在此处我希望从MyApi中调用api请求,将内容传递到repository中,这就是di开始的地方:我们希望把MyApi的实例化对象,传递到实现了MyRepository接口的MyRepositoryImpl类中。此处我们继续使用构造方法。

class MyRepositoryImpl(private val api: MyApi // 主构造方法传递依赖
) : MyRepository {override suspend fun doNetworkCall() {TODO("Not yet implemented")}}

 但是在dagger hilt中,它是如何知道我们想将api委托给repository的呢?

创建Module提供依赖项

在app中,在涉及到di时,我们使用多种多样的modules(模块)来作为特定类型依赖项的容器。因此当我们要将api交给repository时,我们要做的操作是新建一个模块来实现这一内容。这样做的好处是,存储在模块中的依赖项会和application存活时长相同,这也使得它们成为效率极高的单例。

因此在实际的app中,如果我们使用较多的依赖,我们可以为其创建相应的模块。例如包含某种广播功能的广播模块,或者是用于提供身份验证存储库的身份验证模块。总而言之,最好是每个模块都能拥有其清晰的职能。

根据以上理论,我们创建一个AppModule。

@Module
@InstallIn(SingletonComponent::class)
object AppModule {}

在此处我们添加了属于dagger hilt的注解:Module和InstallIn。前者表明这是一个用于装载依赖项的容器,后者可以设置这个模块的存活时长。在此处我们设置的是SingletonComponent,代表它的存活时长会如同被注入的application,同时还有其他多种多样的component如下。

SingletonComponent和被注入的application的存活时长相同
ActivityComponent和被注入的activity的存活时长相同
ViewModelComponent和被注入的view model的存活时长相同
ActivityRetainedComponent当activity被recreate、如做旋转屏幕的操作时,该模块不会被销毁
ServiceComponent和被注入的service的存活时长相同

基于我们试图注入MyApi接口,dagger hilt需要知晓如何创建一个retrofit的实例化对象并返回,这就是我们在模块中需要做的事情。

@Module
@InstallIn(SingletonComponent::class)
object AppModule {@Provides@Singletonfun provideMyApi() : MyApi {return Retrofit.Builder().baseUrl("https://test.com").build().create(MyApi::class.java)}
}

根据以上代码,dagger hilt知道了如何创建一个MyApi的实例化对象,每当我们向MyApi请求一个对象时(如在MyRepositoryImpl的主构造方法中),dagger hilt会在模块中尝试寻找一个这样的对象。

我们加入了两个注解:前者告诉dagger hilt该方法提供了依赖项,后者表示这个方法是一个单例。

此处的Singleton和类注解中的SingletonComponent的区别在于:

类注解的决定了该模块下的依赖项的寿命,所有模块下的依赖项的存活时长都如同application;

方法注解的被称为作用域(scope),在此处我们声明该方法返回的MyApi对象是单例的,如果没有这个注解,那么有多个repository要调用这个方法时,返回的MyApi对象就是不唯一的了。

注入依赖项到ViewModel

在一般情况下,创建一个viewmodel可能需要使用ViewModelProvider下的factory类,用于更详细的定义一个viewmodel,但是这样较为复杂。使用dagger hilt可以避免这一情况。

首先创建ViewModel如下,传入依赖项repository。

class MyViewModel(private val repository: MyRepository
) : ViewModel() {}

若使用dagger hilt来寻找主构造函数中需要的依赖项,需要改造如下。

@HiltViewModel
class MyViewModel @Inject constructor(private val repository: MyRepository
) : ViewModel() {}
  1. 在viewmodel外部添加注解@HiltViewModel,声明我们需要使用dagger hilt为该viewmodel注入依赖;
  2. 在主构造函数添加注入注解@Inject和构造函数关键字constructor,向dagger hilt表明我们需要在构造函数中注入这些依赖

经过如上处理,dagger hilt便会从module中寻找是否能提供这些依赖项。所以接下来我们需要在module中添加能够提供viewmodel的方法。

首先在module中创建一个不完整的提供MyRepository方法,因为构建MyRepository需要传入一个MyApi的实例,所以出现了报错;同时注意到,我们先前写的provideMyApi方法,正好返回了MyApi实例。

那么我们是否需要在某个地方调用provideMyApi方法,以此方式来传入MyApi实例呢?

@Module
@InstallIn(SingletonComponent::class)
object AppModule {@Provides@Singletonfun provideMyApi(): MyApi {return Retrofit.Builder().baseUrl("https://test.com").build().create(MyApi::class.java)}@Provides@Singletonfun provideMyRepository(api: MyApi): MyRepository {return MyRepositoryImpl(api)}
}

实际上是不需要的,我们只需要简单的声明需要传入MyApi实例,而dagger hilt会在后台查询是否有能够提供该实例的方法,最终会定位到provideMyApi方法。

经过以上步骤,dagger hilt会将它作为参数传递到我们的provideMyRepository方法,并且创建我们需要的repository;同样的,provideMyRepository方法因为提供了MyRepository实例,同时在我们先前创建viewmodel的主构造函数中请求了MyRepository实例、并使用了依赖注入,所以这个方法也会在后台被dagger hilt调用。

将ViewModel注入UI层

在使用dagger hilt创建viewmodel时,简单的直接创建是不会生效的,我们需要做以下两件事情:

  1. 添加注解@AndroidEntryPoint:当我们需要为安卓组件类(如activity、fragment、service等来自Android Framework的东西)注入依赖时,需要添加注解@AndroidEntryPoint
  2. 创建application类:当repository需要获取context的时候,dagger hilt需要从application获取之,因为我们无法直接在module获取这个context。此时我们需要创建application并为其添加注解@HiltAndroidApp
@AndroidEntryPoint
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {DaggerHiltCourseTheme {val viewModel = hiltViewModel<MyViewModel>()}}}
}
@HiltAndroidApp
class MyApp: Application()

 对于后者dagger hilt是否能真正获取到这一context,我们可以修改MyRepositoryImpl这一实现类来打印context。

@Module
@InstallIn(SingletonComponent::class)
object AppModule {...@Provides@Singletonfun provideMyRepository(api: MyApi, app: Application): MyRepository {return MyRepositoryImpl(api, app)}
}class MyRepositoryImpl(private val api: MyApi,private val appContext: Application
) : MyRepository {init {val appName = appContext.getString(R.string.app_name)Log.i("MyRepositoryImpl", "Hello from the repository: this app name is $appName")}...}

可以看到我们正确的打印了AndroidManifest.xml中的正确默认名称,这也代表我们正确获取了app的context。 

dagger hilt对于相同类型依赖项的区分

如果有两个相同类型的依赖项,dagger hilt是怎么区分我们需要哪一个的?

@Module
@InstallIn(SingletonComponent::class)
object AppModule {...@Provides@Singletonfun provideMyRepository(api: MyApi, app: Application, hello1: String): MyRepository {return MyRepositoryImpl(api, app)}@Provides@Singletonfun provideString1() = "Hello 1"@Provides@Singletonfun provideString2() = "Hello 2"
}

对AppModule修改如上,模拟两个获取字符串的方法,并在provideMyRepository中注入依赖。

此时直接运行会报错,提示该字符串被绑定多次。

 对此情况我们可以添加注解@Named。

@Module
@InstallIn(SingletonComponent::class)
object AppModule {...@Provides@Singletonfun provideMyRepository(api: MyApi,app: Application,@Named("hello1") hello1: String): MyRepository {return MyRepositoryImpl(api, app)}@Provides@Singleton@Named("hello1")fun provideString1() = "Hello 1"@Provides@Singleton@Named("hello2")fun provideString2() = "Hello 2"
}

更为直接的方式:绑定抽象类

在AppModule中我们使用了MyRepositoryImpl类来提供MyRepository接口的实现,但实际上如果想要注入一个接口或抽象类,我们有更简便的方法。

首先去除AppModule中提供MyRepository的方法,并新建一个抽象Module用于直接提供MyRepository。这样做的好处在于dagger hilt会生成较少的代码而获得更佳的性能。

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {// 这是提供依赖项的另一种方式,所以实际上不命名为provide,而是bind@Binds@Singletonabstract fun bindMyRepository(myRepositoryImpl: MyRepositoryImpl) : MyRepository
}

其次修改MyRepositoryImpl类,即MyRepository接口的实现类。为其添加注解@Inject和构造方法关键字。

只要dagger hilt知道如何创建构造函数中的这些依赖项,它也会自动地知道如何创建这个抽象类或结构的实现类。

class MyRepositoryImpl @Inject constructor(private val api: MyApi,private val appContext: Application
) : MyRepository {init {val appName = appContext.getString(R.string.app_name)Log.i("MyRepositoryImpl", "Hello from the repository: this app name is $appName")}override suspend fun doNetworkCall() {TODO("Not yet implemented")}}

因此,无论app中任何类型的类,只要它有一个包含了@Inject注解的构造方法,我们都不需要为它创建provide方法(例如我们去除掉的)。

因此,在抽象module中,我们传入抽象的bind方法中的依赖项,只是为了在每次注入一个抽象类时,dagger hilt可以确定是使用哪一个特定的实现类。

Service注入的方式——字段注入

通常情况下的注入是这样的。

@AndroidEntryPoint
class MyService @Inject constructor(private val myRepository: MyRepository
): Service() {override fun onBind(p0: Intent?): IBinder? {TODO("Not yet implemented")}}

在activity中,我们会为它写一个注入构造函数,在其中传入repository;但是对于Service来说,它不能拥有构造函数。那么我们要如何在活动中获取repository?

@AndroidEntryPoint
class MyService: Service() {@Injectlateinit var repository: MyRepositoryoverride fun onCreate() {super.onCreate() // 我们的repository注入实际上就发生在super.onCreate()中repository.doNetworkCall()}override fun onBind(p0: Intent?): IBinder? {TODO("Not yet implemented")}}

答案是使用字段注入。我们使用lateinit关键字定义它、并使用@Inject注解,此后在super.onCreate方法中,dagger hilt就会完成这个变量的依赖注入,无需我们手动初始化。

Lazy Injection

指的是延迟注入依赖项。

修改MyViewModel,使用dagger包下的lazy修饰依赖项。

通常情况下,依赖项会在我们注入时立刻构建,但在使用lazy修饰后,它会在我们第一次使用时创建。

修改后我们去运行代码,会发现 MyRepositoryImpl类的init代码块中的打印不出现了,这是因为我们没有使用它。我们可以简单地在MyViewModel的init代码块获取注入的依赖项repository,这样打印就会重新出现了。

@HiltViewModel
class MyViewModel @Inject constructor(private val repository: Lazy<MyRepository>
) : ViewModel() {init {repository.get() // 获取MyRepositoryImpl的实例化对象,此时被使用了}}

对于惰性注入的常见场景是身份验证,比如用户在输入用户名和密码之后,我们才开始启用repository的获取。

参考文献:The Ultimate Dagger-Hilt Guide (Dependency Injection) - Android Studio Tutorial

相关文章:

kotlin的dagger hilt依赖注入

依赖注入&#xff08;dependency injection, di&#xff09;是设计模式的一种&#xff0c;它的实际作用是给对象赋予实例变量。 基础认识 class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceSta…...

速通Docker === 常用命令

目录 Docker命令 镜像操作 容器操作 基础操作 启动参数 容器内部操作 打包成指定文件 发布镜像 总结 镜像操作 容器操作 启动容器参数 容器内部操作 打包镜像 启动指定镜像的容器 发布镜像 Docker命令 启动一个nginx,并将它的首页改为自己的页面&#xff0c;发布…...

【redis】键的全局命令

Redis提供了一系列用于管理和操作键的全局命令。这些命令允许你查看、删除、迁移键&#xff0c;以及执行其他与键相关的操作。 有关全局通用类型的命令可以通过help generic命令来查看。有关命令的使用可以通过help 命令来查看&#xff0c;例如help keys。 KEYS keys&#x…...

深度学习-卷积神经网络实战文档注释

1、call 方法 是一个特殊的方法&#xff0c;它允许类的实例表现得像函数一样。也就是说&#xff0c;你可以使用圆括号 () 来调用一个实例&#xff0c;就像调用普通函数一样。 当你调用 model(input_data) 时&#xff0c;实际上是调用了模型的 __ call __ 方法&#xff0c;其会自…...

GR2103高压半桥栅极驱动芯片

产品简介 GR2103封装和丝印 GR2103是一款高性价比的高压半桥栅极驱动专用芯片&#xff0c;设计用于高压、高速驱动N型大功率 MOS管、IGBT管。内置欠压&#xff08;UVLO&#xff09;保护功能&#xff0c;防止功率管在过低的电压下工作&#xff0c;提高效率。内置防止直通功能…...

学习threejs,使用OrbitControls相机控制器

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.OrbitControls 相机控…...

说说Babylon.js中scene.deltaTime的大坑

诡异的问题 下面是给一个材质设置发光颜色周期变化和纹理偏移的代码&#xff0c;你能感觉到这里面可能出现的问题吗&#xff1f; var passTime 0;var uOffset 0;var deltaTime 0;function SetEmissiveColor() {passTime scene.deltaTime * 0.05;if(passTime > 6.2…...

【React】win系统环境搭建

动图更精彩 方案如下 在Visual Studio Code&#xff08;VSCode&#xff09;中搭建React开发环境是一个相对简单但非常重要的步骤&#xff0c;可以帮助你更高效地进行前端开发。以下是详细的步骤和配置指南&#xff1a; 一、准备工作 安装Visual Studio Code (VSCode)&#x…...

ThinkPHP 8的一对一关联

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…...

Linux 下配置 Golang 环境

go sdk 下载环境&#xff1a;https://golang.google.cn/dl/选择对应的版本&#xff1a; 使用 wget 直接拉包下载到服务器中 wget https://golang.google.cn/dl/go1.23.4.linux-amd64.tar.gz如果找不到 wget 命令&#xff0c;yum 下载 wget yum -y install wget配置 go 的环境…...

爬虫后的数据处理与使用(使用篇--实现分类预测)

&#xff08;&#xff09;紧接上文&#xff0c;在完成基本的数据处理后&#xff0c;接下来就是正常的使用了。当然怎么用&#xff0c;确实需要好好思考一下~ 上文&#xff1a;爬虫后的数据处理与使用&#xff08;处理篇&#xff09; 前言&#xff1a; 一般来说&#xff0c;我…...

arcgis提取不规则栅格数据的矢量边界

效果 1、准备数据 栅格数据:dem或者dsm 2、栅格重分类 分成两类即可 3、新建线面图层 在目录下选择预先准备好的文件夹,点击右键,选择“新建”→“Shapefile”,新建一个Shapefile文件。 在弹出的“新建Shapefile”对话框内“名称”命名为“折线”,“要素类型”选…...

python milvus 如何检查有多少个collection 以及多少个index,多少个database

在 Milvus 中,可以通过 Python 客户端(`pymilvus`)来检查当前有多少个集合(Collection)、索引(Index)和数据库(Database)。以下是具体的方法: --- ### 1. 检查有多少个集合(Collection) 使用 `list_collections()` 方法可以列出当前连接的所有集合。 ```python…...

2006-2020年各省工业增加值数据

2006-2020年各省工业增加值数据 1、时间&#xff1a;2006-2020年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区名称、年份、工业增加值 4、范围&#xff1a;31省 5、指标解释&#xff1a;工业增加值是指工业企业在一定时期内以货币形式…...

【MySQL】使用C语言链接

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;MySQL 目录 一&#xff1a;&#x1f525; MySQL connect &#x1f98b; Connector / C 使用&#x1f98b; mysql 接口介绍&#x1f98b; 完整代码样例 二&#xff1a;&#x1f525; 共勉 一&#…...

Vue篇-07

Vue UI组件库 一、移动端常用的UI组件库 1.1、Vant 1.2、Cube UI 1.3、Mint UI 二、PC端常用的UI组件库 2.1、Element UI Element - The worlds most popular Vue UI framework 安装&#xff1a; 按需引入&#xff1a; 135_尚硅谷Vue技术_element-ui按需引入_哔哩哔哩_b…...

使用 LLaMA-Factory 微调大模型

本文将介绍如下内容&#xff1a; 一、搭建 Docker Container 环境二、配置大模型训练环境三、构建、配置数据集四、训练大模型 一、搭建 Docker Container 环境 笔者此前多篇文章说明&#xff0c;此处不再赘述&#xff0c;可参考&#xff1a;NGC容器中快速搭建Jupyter环境 E…...

数据仓库的复用性:模型层面通用指标体系、参数化模型、版本化管理

在数据仓库设计中&#xff0c;复用性 是一个关键原则&#xff0c;它不仅能提升数据资产的使用效率&#xff0c;还能降低开发成本、优化系统运维。下面将从 模型层面的复用性、通用指标体系、参数化模型、版本化管理 四个方面进行详细介绍&#xff0c;并提供可落地的设计方案。 …...

Web APP 阶段性综述

Web APP 阶段性综述 当前&#xff0c;Web APP 主要应用于电脑端&#xff0c;常被用于部署数据分析、机器学习及深度学习等高算力需求的任务。在医学与生物信息学领域&#xff0c;Web APP 扮演着重要角色。在生物信息学领域&#xff0c;诸多工具以 Web APP 的形式呈现&#xff…...

某国际大型超市电商销售数据分析和可视化

完整源码项目包获取→点击文章末尾名片&#xff01; 本作品将从人、货、场三个维度&#xff0c;即客户维度、产品维度、区域维度&#xff08;补充时间维度与其他维度&#xff09;对某国际大型超市的销售情况进行数据分析和可视化报告展示&#xff0c;从而为该超市在弄清用户消费…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 &#xff1a;HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09; 默认端口 &#xff1a;HTTP 使用 80 端口&#xff0c;HTTPS 使用 443 端口。 请求方法 &#xff1a; GET &#xff1a;用于获取资源&#xff0c;…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...

AI语音助手的Python实现

引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...

uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)

UniApp 集成腾讯云 IM 富媒体消息全攻略&#xff08;地理位置/文件&#xff09; 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型&#xff0c;核心实现方式&#xff1a; 标准消息类型&#xff1a;直接使用 SDK 内置类型&#xff08;文件、图片等&#xff09;自…...

stm32wle5 lpuart DMA数据不接收

配置波特率9600时&#xff0c;需要使用外部低速晶振...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...