Kotlin 40. Dependency Injection 依赖注入以及Hilt在Kotlin中的使用,系列3:Hilt 注释介绍及使用案例
一起来学Kotlin:概念:27. Dependency Injection 依赖注入以及Hilt在Kotlin中的使用,系列3:Hilt 注释介绍及使用案例
此系列博客中,我们将主要介绍:
Dependency Injection
(依赖注入) 概念介绍。网上看了许多关于 DI 的介绍,云里雾里。这里,我们通过通俗易懂地方式对其进行介绍。- 手动依赖注入介绍。为了让大家更容易理解 Hilt,我们先介绍如何通过手动的方式实现依赖注入效果。
- Hilt 注释(annotations)介绍及使用案例
- MVVM 案例中如何使用 Hilt
此博客基于一个非常简单的 Kotlin 项目来解释 Hilt 的使用方式。
文章目录
- 一起来学Kotlin:概念:27. Dependency Injection 依赖注入以及Hilt在Kotlin中的使用,系列3:Hilt 注释介绍及使用案例
- 前言
- 1 回顾
- 2 Hilt 的相关注释(annotations)
- 2.1 `@HiltAndroidApp` 注释
- 2.2 `@AndroidEntryPoint` 注释
- 2.3 `@Inject` 注释
- 2.4 `@ViewModelInject` 注释
- 2.5 `@Module` 注释
- 2.6 `@Binds` 注释
- 2.7 `@InstallIn` 注释
- 2.8 `@Provides` 注释
- 3 Hilt 的简单案例
- 3.1 `AndroidManifest.xml`
- 3.2 `build.gradle (Project)`
- 3.3 `build.gradle (Module)`
- 3.4 Application Class `MyApp.kt`
- 3.5 `MainActivity.kt`
- 3.6 `DatabaseAdapter.kt`
- 3.7 `DatabaseService.kt`
前言
1 回顾
在系列的第一篇博客中,我们介绍了依赖注入的概念,以及为什么需要依赖注入。
在系列的第二篇博客中,我们介绍了手动依赖注入。我们并不建议在项目中使用手动依赖注入,但我们可以通过手动依赖注入的介绍,来解释Hilt主要完成的两件事:
- 提供了“containers”(容器)用来装各种依赖;
- 自动管理这些“containers”(容器)的“lifecycles”(生命周期)。
在下面的章节中,我们主要解决一个问题:Hilt 具体应该在项目中怎么使用。
2 Hilt 的相关注释(annotations)
我们先对 Hilt
涉及到的注释进行一一介绍,这些注释包括:
@HiltAndroidApp
@AndroidEntryPoint
@Inject
@ViewModelInject
@Module
@Binds
@InstallIn
@Provides
2.1 @HiltAndroidApp
注释
在我们的安卓工程项目中,如果要使用Hilt,必须要有一个自定义的Application才行。这里我们的 MyApp.kt
文件中需在该类上方标注 @HiltAndroidApp
:
@HiltAndroidApp
class MyApp : Application() {
...
}
2.2 @AndroidEntryPoint
注释
此注释可以将成员注入到各安卓组件中,例如活动(activities)、片段(fragments)、视图(views)、服务(services)和广播接收器(broadcast receivers)。
我们必须要使用 @AndroidEntryPoint
注释来注释安卓组件,才能接下来其中继续注入字段或方法(比如 @Inject 注释)。Hilt 目前支持以下安卓类:
- Activity(使用
@HiltAndroidApp
注释标注) - Fragment(使用
@AndroidEntryPoint
注释标注) - View(使用
@AndroidEntryPoint
注释标注) - Service(使用
@AndroidEntryPoint
注释标注) - Broadcast Receiver(使用
@AndroidEntryPoint
注释标注)
使用 @AndroidEntryPoint
注释一个安卓类需要注释所有依赖于它的类。每当我们注释一个 fragment 时,我们还必须注释使用该 fragment 的任何 activity。
比如,Activity:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)}
}
对于 Fragment:
@AndroidEntryPoint
class CountriesListFragment : Fragment() {
...
}
2.3 @Inject
注释
当项目中的构造函数被 @Inject
注释时,它就可以作为依赖项在任何地方使用。比如上面 MainActivity.kt
中的 databaseAdapter
。也比如下面的例子:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBinding@Injectlateinit var repository: DbRepository@Injectlateinit var taskAdapter: TaskAdapter@Injectlateinit var task: TaskEntityoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding= ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)... }
}
我们之前提到依赖注入的三种形式:
- constructor injection(类的构造函数注入):依赖项是通过类构造函数提供的。
- setter/field injection(类字段注入):客户端公开一个 setter 方法,注入器使用它来注入依赖项。
- interface injection:依赖项提供了一个注入器方法,可以将依赖项注入传递给它的任何客户端。 客户端必须实现一个接口,该接口公开一个接受依赖项的 setter 方法。
上面这个例子就是属于第二种。这里要明确说明一点,在使用 Hilt 做类字段注入的时候,变量名前面是不能加 private 关键字的。因为加了 private 之后,这个变量就变成了类私有的变量,Hilt 就没有权限访问了,也无从谈起依赖注入了。
这也就是我们在使用了 Hilt 之后,不需要像手动依赖注入时那样,在 Activity
类里面的 onCreate()
函数对这个变量进行赋值。而是使用 @inject
注释这个变量以后,直接在代码里使用。赋值的事情,是由 Hilt
帮我们代劳了。
2.4 @ViewModelInject
注释
和 @Inject
注释类似的,viewModel 里面的构造函数被 @ViewModelInject
注释时,它就可以作为依赖项在任何地方使用。@ViewModelInject
不是来自 Hilt
框架,而是来自 Hilt
支持的 Jetpack
库,它向 Jetpack
发出 ViewModel
已准备好注入的信号。这个注释是专门为 ViewModel
组件设计的。比如下面的例子:
@HiltViewModel
class CountriesListViewModel
@Inject constructor(private val repository: ApiRepository) : ViewModel() {
...
}
2.5 @Module
注释
对于接口来说,接口又没有构造函数,怎么 Inject 呢?这个时候,我们就需要 @Module
注释。
Hilt module
也是一个类,但是它需要在类名前面加上注释(annotation)@Module;同时,还需要加上 @InstallIn
,顾名思义,我们拆开来看这个注释:Install In,后面肯定跟的是一个作用域范围(scope),意思就是告诉Hilt,我们新建的这个 “Module” 类,作用范围是什么。这个范围(scope)的分类,我们后续将会提到,在这暂且先不用深究。
@Module
object ApiModule {
...
}
2.6 @Binds
注释
前面我们说过,接口是没有构造函数的,所以也无法使用 constructor injection
的方式进行依赖注入,那么我们怎么样才能让 Hilt
知道我们需要这个接口的实例作为依赖呢?这时候需要使用注释(annotation)@Binds
来告诉 Hilt
。不过有一项准备工作我们还要先做,那就是先得有一个接口的实现类。这样 Hilt
才会知道使用哪个实现类去实现这个接口,相关示例代码如下:
interface AnalyticsService {<!-- -->fun analyticsMethods()
}/*接口AnalyticsService 的实现类需要 Constructor-injected,
Hilt 需要使用到*/
class AnalyticsServiceImpl @Inject constructor(...
) : AnalyticsService {<!-- --> ... }@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {<!-- -->@Bindsabstract fun bindAnalyticsService(analyticsServiceImpl: AnalyticsServiceImpl): AnalyticsService
}
AnalyticsService
是一个接口,AnalyticsServiceImpl
是 AnalyticsService
接口的实现类,同理 AnalyticsServiceImpl
的构造函数前也需要加上注释 @Inject
告诉 Hilt
在哪可以找到它。
最后就到我们的 Hilt Module 了,它前面有两个注释:@Module
和 @InstallIn
(ActivityComponent::class)。这两个注释总结起来就是告诉 Hilt
,这个 Module
的可见范围是所有的Activity类。因为返回的是 AnalyticsService
接口类型,所以我们这里使用抽象类和抽象函数bindAnalyticsService
来实现这个 Hilt Module。
抽象函数 bindAnalyticsService
前面是 @Binds
注释,它的参数就是接口 AnalyticsService
的实现类 AnalyticsServiceImpl
,函数返回类型就是接口 AnalyticsService
。这样,Hilt就可以准确地把它的实例注入到依赖于它的地方了。
总结一下,就是对于接口实例的依赖注入,需要使用三个注释:@Module
,@InstallIn
和 @Binds
。
2.7 @InstallIn
注释
在 @Module
注释的使用过程中,我们已经提到了 @InstallIn
注释的使用。通过添加 @InstallIn
注释,我们将此模块类提供的依赖对象的使用限制为特定的安卓组件。以下是另一个例子:
@Module
@InstallIn(SingletonComponent::class)
object ApiModule {
...
}
Hilt
提供了七个与 @InstallIn
注释兼容的组件。@Module
注释告诉 hilt 框架,它提供的依赖对象只能在 @InstallIn
注释中命名的特定组件的生命周期内被注入和使用。
- @InstallIn(ApplicationComponent) — present for the lifetime of the application.
- @InstallIn(ActivityComponent) — present for the lifetime of the Activity.
- @InstallIn(ActivityRetainedComponent) — present for the lifetime of a configuration surviving activity (i.e) surviving orientation changes just like the ViewModel.
- @InstallIn(FragmentComponent) — present for the lifetime of the fragment.
- @InstallIn(ServiceComponent) — present for the lifetime of the service.
- @InstallIn(ViewComponent) — present for the lifetime of the view that is directly inside an activity.
- @InstallIn(ViewWithFragmentComponent) — present for the lifetime of the view inside a fragment.
2.8 @Provides
注释
当我们需要对第三方库进行依赖注入的时候,就不能使用注释(annotation)@Binds
了,为了以示区分,Hilt
提供了一个新的注释 @Provides
。
其实 Hilt
的注释取名还是很考究的,接口是需要“绑定”(@Binds
)的,而第三方库是需要“提供”(@Provides
)的。
假如一个 AnalyticsService
类实现了一个第三方库 Retrofit
,这时候,我们只需要在 Hilt
的 Module
中实现一个函数,函数名可以任意起,目的是要告诉 Hilt
,怎么来构建这个 AnalyticsService
类的实例对象,示例代码如下:
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {<!-- -->@Providesfun provideAnalyticsService(): AnalyticsService {<!-- -->return Retrofit.Builder().baseUrl("https://example.com").build().create(AnalyticsService::class.java)}
}
与前面的注解 @Binds
例子相比,在这个例子中主要是把 @Binds
改成了 @Provides
,函数 provideAnalyticsService
返回的是一个使用 Retrofit
构建的 AnalyticsService
类的实例。
这样,Hilt
就可以在需要 AnalyticsService
类实例(依赖)的地方,把它的实例注入进去。
3 Hilt 的简单案例
在这里,我们介绍一个非常简单的 Hilt 案例,大家可以跟着我们一起在 Android Studio 上新建一个安卓的应用,然后按照下面的步骤一步步添加相关依赖和代码。
3.1 AndroidManifest.xml
Hilt 框架会在我们首次构建项目时为项目创建一个基类 Hilt_MyApp。 我们不需要直接扩展基类,因为它会自动创建带注释的类 MyApp。这里我们需要在 AndroidManifest.xml
文件中添加 android:name=".MyApp"
,如下所示:
<applicationandroid:name=".MyApp"... ><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>
</application>
3.2 build.gradle (Project)
我们需要在 build.gradle (Project)
中添加 Hilt
对应的 classpath。代码如下:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {dependencies {classpath 'com.google.dagger:hilt-android-gradle-plugin:2.43.2'}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {id 'com.android.application' version '7.2.0' apply falseid 'com.android.library' version '7.2.0' apply falseid 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}task clean(type: Delete) {delete rootProject.buildDir
}
3.3 build.gradle (Module)
我们需要在 build.gradle (Module)
中添加 Hilt
对应的依赖
implementation 'com.google.dagger:hilt-android:2.43.2'
kapt 'com.google.dagger:hilt-compiler:2.43.2'
并且 plugins 中也需要添加:
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
代码如下:
plugins {id 'com.android.application'id 'org.jetbrains.kotlin.android'id 'kotlin-kapt'id 'dagger.hilt.android.plugin'
}android {compileSdk 32defaultConfig {applicationId "com.example.hiltex1"minSdk 27targetSdk 32versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}kotlinOptions {jvmTarget = '1.8'}
}dependencies {implementation 'androidx.core:core-ktx:1.7.0'implementation 'androidx.appcompat:appcompat:1.3.0'implementation 'com.google.android.material:material:1.4.0'implementation 'androidx.constraintlayout:constraintlayout:2.0.4'testImplementation 'junit:junit:4.13.2'androidTestImplementation 'androidx.test.ext:junit:1.1.3'androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'//Hiltimplementation 'com.google.dagger:hilt-android:2.43.2'kapt 'com.google.dagger:hilt-compiler:2.43.2'
}
3.4 Application Class MyApp.kt
在我们的安卓工程项目中,如果要使用Hilt,必须要有一个自定义的Application才行。这里我们的 MyApp.kt
文件中需在该类上方标注 @HiltAndroidApp
:
@HiltAndroidApp
class MyApp : Application() {
...
}
3.5 MainActivity.kt
在这里,我们写一个很简单的 MainActivity.kt
文件,代码如下:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {@Injectlateinit var databaseAdapter: DatabaseAdapteroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)Log.d(TAG,"DatabaseAdapter : $databaseAdapter")databaseAdapter.log("Hey Hilt")}
}
这里我们使用到了 @AndroidEntryPoint
注释。AndroidEntryPoint 也就是上是其所相关联的依赖项的入口点(EntryPoint)。 我们需要告诉 Hilt
我们希望在何处注入依赖项。这就是AndroidEntryPoint的作用。它定义了将在何处提供依赖项。
Hilt 在这里的主要目的是尝试标准化注入功能,并消除尽可能多的组件耦合。AndroidEntryPoint 会自动为系统构建这些依赖项和组件。 所以我们不必再像前一章节那样去进行手动的依赖注入。当然,需要再强调一次,如果我们使用 AndroidEntryPoint 注释 fragment 并且它包含依赖项,则还必须注释使用到该 fragment 的 Activities。即,任何依赖于我们正在注释的这个类的相关类,依赖项,组件等,同样需要被注释。
在我们往下看其他代码之前,关注一下这行代码:
@Inject
lateinit var databaseAdapter: DatabaseAdapter
看似平平无奇,但实际上,这就是前面提到依赖注入的三种形式之一:setter/field injection(类字段注入)。在这个例子中,我们希望将 databaseAdapter 注入到 main activity 中。
3.6 DatabaseAdapter.kt
这里有提到依赖注入的三种形式之一:constructor injection(类的构造函数注入)。在 DatabaseAdapter.kt
中,我们就要使用到:
class DatabaseAdapter @Inject constructor(var databaseService: DatabaseService) {fun log(msg: String){Log.d(TAG,"DatabaseAdapter : $msg")databaseService.log(msg)}
}
创建依赖(dependency)的最基本、最简单的方法是通过构造函数注入。 一个组件(component)本质上是一个不带参数的类,我们只需要添加 inject 注解,这样就可以随时将其注入到需要的地方(又比如 class SampleClass @Inject constructor()
)。
3.7 DatabaseService.kt
这也是一个类的构造函数注入的例子。
class DatabaseService @Inject constructor() {fun log(msg: String){Log.d(TAG,"DatabaseService msg : $msg")}
}
大家有没有发现,在用了 Hilt
之后,依赖注入的使用变得非常简单,代码也变得非常简洁(我们可以对比一下系列2中的手动依赖注入案例)。
相关文章:
Kotlin 40. Dependency Injection 依赖注入以及Hilt在Kotlin中的使用,系列3:Hilt 注释介绍及使用案例
一起来学Kotlin:概念:27. Dependency Injection 依赖注入以及Hilt在Kotlin中的使用,系列3:Hilt 注释介绍及使用案例 此系列博客中,我们将主要介绍: Dependency Injection(依赖注入)…...

1000亿数据、30W级qps如何架构?来一个天花板案例
1000亿级存储、30W级qps系统如何架构?来一个天花板案例 说在前面 在尼恩的(50)读者社群中,经常遇到一个 非常、非常高频的一个架构面试题,类似如下: 千万级数据,如何做系统架构?亿…...

人工智能及其应用(蔡自兴)期末复习
人工智能及其应用(蔡自兴)期末复习 相关资料: 人工智能期末复习 人工智能复习题 人工智能模拟卷 人工智能期末练习题 1 ⭐️绪论 人工智能:人工智能就是用人工的方法在机器(计算机)上实现的智能࿰…...

openpnp - configure - 矫正里程碑
文章目录openpnp - configure - 矫正里程碑概述备注ENDopenpnp - configure - 矫正里程碑 概述 进入矫正里程碑了 查找问题 现在第一个问题是X轴的齿隙矫正 根据提示, 将顶部相机移动到主基准点上, 选择容差(就选用默认的0.025), 开始矫正. 正好开机后, 使能了视觉原点归零. …...

JavaScript高级程序设计读书分享之8章——8.2创建对象
JavaScript高级程序设计(第4版)读书分享笔记记录 适用于刚入门前端的同志 创建Object的实例 let person new Object(); person.name "Nicholas"; person.age 29; person.job "Software Engineer"; person.sayName function() { console.log(this…...

关于Could not build wheels for opencv-python-headless, which is...报错的解决方案
在通过最新版pip在线安装package:opencv-python-headless的时候,会产生报错信息,主要为 ERROR: Failed building wheel for opencv-python-headless ERROR: Could not build wheels for opencv-python-headless, which is required to insta…...

【K3s】第1篇 K3s入门级介绍及架构详解
1、什么是 K3s? K3s 是一个轻量级的 Kubernetes 发行版,它针对边缘计算、物联网等场景进行了高度优化。K3s 有以下增强功能: 打包为单个二进制文件。使用基于 sqlite3 的轻量级存储后端作为默认存储机制。同时支持使用 etcd3、MySQL 和 PostgreSQL 作…...
Java学习--反射
1. 反射 1.1 反射的概述: **专业的解释(了解一下):**是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法ÿ…...
应用和迭代(名词解释)
应用和迭代是什么意思 应用: ● 一个完整的前端应用,一般用应用脚手架创建,包含路由,页面,状态等 ● 一个应用对应一个代码仓库 ● 应用的分组(业务中心,数据中台等)只用于逻辑分类&…...

HTMLCollection 和 NodeList 区别
Node 和 Element DOM 是一棵树,所有节点都是 NodeNode 是 Element 的基类Element 是其他 HTML 元素的基类,如 HTMLDivElement HTMLCollection 和 NodeList HTMLCollection 是 Element 的集合NodeList 是 Node 的集合 <body><p id"p1&qu…...

fork()出来一个进程,这个进程的父进程是从哪来的?
基本概念fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.fork()是一个系统调用,不是一个函数。详细信息可以,man…...

结构体内存对齐
结构体相信大家已经了解过了,现在我们深入讨论一个问题,计算结构体的大小 也是很热门的一个考点:结构体内存对齐 先看看下面结构体的大小 typedef struct Test {char a;char b;char c; }Test; 很容易看出答案为3,结构体的大小位…...

【C语言进阶】指针进阶
今日所做之事勿候明天,自我所做之事勿候他人。 --歌德 目录 指针进阶(更深层次的理解): 一.字符指针 二.指针数组 三.数组指针 1.数组指针的定义: 2.&数组名和数组名: 3.数组指针的使用: 四.数组参数,指针参数 1.一维数组传参:…...
java:Class的isPrimitive方法使用
java:Class的isPrimitive方法使用 1 前言 java中Class类的isPrimitive方法,用于检查类型是否为基本类型。java虚拟机创建了int、byte、short、long、float、double、boolean、char这8种基础信息,以及void,一共9种。为这9种类型时…...

TCP 握手过程 三次 四次
蛋老师视频 SYN 同步 ACK 确认 FIN 结束 核心机制是确定哪些请求或响应需要丢弃 SYN、ACK、FIN 通过 1/0 设置开启/关闭 开启SYN后,报文中会随机生成 Sequence序号 用于校验 (应用可能发起多个会话,可以区分) 服务器的同步序…...

windows 下 安裝mysql 5.7.41 (64位) 超简单方式
文章目录1. 安装包下载2.安装步骤3. 服务卸载方式4. 配上 my.ini 常用配置1. 安装包下载 注意,截至2023年2月23日,MySQL所有版本不提供ARM芯片架构的Windows版本(8.0.12开始支持Red Hat系统的ARM版本),所以ARM架构的Windows无法安装MySQL&am…...

二叉树——二叉树的最近公共祖先
二叉树的最近公共祖先 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一…...

数据结构与算法基础-学习-14-线性表之串
一、串的定义由0-n个字符组成的有限序列。(n>0)二、串的相关术语1、子串串中任意个连续字符组成的子序列成为该串的子串。2、主串包含子串的串成为主串。3、字符位置字符在序列中的序号为该字符在串中的位置。4、子串位置子串第一个字符在主串中的位置…...
Mac 快捷键
目录 命令行快捷键 命令行快捷键 control d 命令行中代表发送EOF终止输入 control u 删除光标之前到行首的字符 control k 删除光标之前到行尾的字符(比较常用) control a 移动光标到行首(常用) control e 移动光标到行尾 control l 清屏,相当于clear命令 con…...

【微服务】-微服务环境搭建
目录 2.1 技术选型 2.2 模块设计 2.3 微服务调用 2.4 创建⽗⼯程 2.5 创建商品微服务 2.6 创建订单微服务 2.1 技术选型 持久层: SpingData Jpa 数据库: MySQL5.7 其他: SpringCloud Alibaba 技术栈 2.2 模块设计 --- shop-parent ⽗⼯程 --- shop-product-api 商品微服…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...