Jetpack技术Hilt【实现解耦 】

news2025/1/31 2:59:02

Hilt是什么?

Hilt 是基于 Dagger2 的针对 Android场景定制化 的框架。

这有点像什么? RxAndroid 是 RxJava 的Android平台定制化扩展。Andorid虽然由Java、Kotlin构成,但是它有很多平台的特性,比如它有 Java开发 所不知道的 Context 等。

Dagger框架虽然很出名,在国外也很流行,但是在国内使用其的App少之又少,列举一些缺点:

  • 上手难,众多Android应用框架中,Dagger必定是最难学的那一档;
  • 它就是一个复杂框架,没有针对任何平台,所以对于所有平台来说会难用;
  • 在Android Studio4.0版本以前,无法追踪Dagger的依赖关系(就类比IDE无法通过快捷键知道一个接口有哪些实现类) 开发者不知道为啥要做依赖注入
  • 对于第三点,Android Studio4.1已经支持了该功能,但是4.1有许多Bug,很多开发者都没有升级 。

Hilt的出现解决前两点问题,因为 Hilt 是 Dagger 针对Android平台的场景化框架,比如Dagger需要我们手动声明注入的地方,而Android声明的地方不都在 onCreate()吗,所以Hilt就帮我们做了,除此之外还做了很多事情,这样一来,相较于Dagger,我们能用更少代码、更容易、更轻松的配置依赖注入。

Hilt使用地方

Google认为移动端应用的架构设计,最重要的 Separation of concerns(分离关注点)。上网找解释,其实它就是 模块解耦。下面是Google官方推荐的Android应用架构图:

依赖注入(DI)概念

而 Hilt 是被定义为 依赖注入框架而被发布。什么?又是依赖注入框架?不是之前已经有了一个 Dagger2 了吗?除了 Dagger2, 还有 ButterKnife ,Kotlin甚至还有轻量级的 Koin。

什么是依赖注入?先来看下面代码:

class MyClass {
    val user = User()
}

我们在一个类 MyClass 中 声明了一个变量 user,并初始化-------调用 User 构造函数,创建一个对象。

上面这段代码就产生了一个依赖关系。

  • 我们要先看懂谁依赖了谁?首先 MyClass 是我们的类,User 可以是我们自己写的类,也可以是通过第三方Jar包或SDK里面的类。 在我们写的 MyClass 的代码里面,我们需要一个 User 对象来完成一些任务,所以我们创建了 User 对象,这就说明 MyClass 依赖了 User。对于 MyClass来说,User是外面之物,但是又需要依赖它。

如果上面这个 User,不是由自己创建,而是由外部创建,然后在本类只做赋值工作 ,这个过程就是 依赖注入。

有一个我们非常熟悉的设计模式,就使用了依赖注入的方法—工厂模式:

class UserFactory {
    fun newUser(): User{
        return User()
    }
}
​
class MyClass {
    val user = UserFactory.newUser()
}

我们的 MyClass 类需要使用 User 类,但是这次没有自己来创建(没有自己new出来),而是交由给 UserFactory 来创建出来,MyClass就做了最后的赋值工作。

对于 MyClass 来说,这就是一次依赖注入,和上面例子相比,把对象创建的过程交由给了别的类。

所以我们通过上面两个例子就能知道依赖注入的本质是什么:借由外部得到对象。依赖注入框架就是这个外部

现在流行的 Dagger2、Koin框架,只是让我们更轻松、更容易的去得到对象。

Dagger的中文翻译是 “刀”,它就像一把刀,插进我们代码中。那相信你也知道 ButterKnife 为什么这么取名了吧。

Hilt使用

2.1 导入

在 app 的 build.gradle 中加入:

plugins {
    ...
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
​
dependencies {
    ...
    implementation 'com.google.dagger:hilt-android:2.28-alpha'
    kapt 'com.google.dagger:hilt-android-compiler:2.28-alpha'
}

在 project的 build.gradle 中加入:

classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'

2.2 一个简单的例子 Hilt 需要 AndroidManifest 使用带有 @HiltAndroidApp 注解的 Application 类,所以我们的 Application需要这样:

@HiltAndroidApp
class HiltApp : Application() {
   ...
}

然后在 AndroidManifest 文件中声明:

<application
        android:name=".HiltApp"
        ...
    </application>

假如我们要在 MainActivity 中注入一个 User 对象, 我们首先编写一个 User 类,User类有两个属性 name 和 age 。 诶,这个时候有同学就会问了:我通过 @Inject 声明一个User,Hilt就能给我创建一个 User 对象,那这个User对象里面的name和age是啥?答案是:编译会报错,因为我们自己都不知道这两个参数是啥,Hilt怎么可能会知道,所以这两个属性也要通过依赖注入的方式来注入。

为了简化这个问题,我定义一个默认的无参构造函数,反正创建之后,里面的值也是可以修改的嘛。

data class User(var name: String, var age: Int) {
    // 定义一个默认的无参构造函数,并使用 @Inject 注解修饰
    @Inject
    constructor() : this("Rikka", 23)
}

接着我们在 MainActivity 中声明一个 User,通过 @Inject 来修饰,并且MainActivity 需要通过 @AndroidEntryPoint 修饰:

// 1
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
​
    // 2
    @Inject
    lateinit var user: User
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        Log.d(TAG, "user name:${user.name} age:${user.age}")
    }
}

Logcat 打印结果如下:

代码解析:

注释1: 为 MainActivity 修饰 @AndroidEntryPoint,该注解表明 该类为需要进行依赖注入的 Android类,是Dagger针对Android场景化的地方。当我们类中需要进行依赖注入,我们为该类加入这个注解,它会帮助创建一个单独的 Hilt组件。它不能修饰Abstract,它只能修饰:

  • ComponentActivity
  • (Support)Fragment
  • View
  • Service
  • BroadcastReceiver

注释2:我们需要注入一个 User,所以我们给它加一个 @Inject 注解,告诉 Hilt。因为Kotlin的语法问题,这里不得不声明 lateinit(而这里Koin的写法更加优雅),接下来步骤大概是这样的:

  • Hilt 会去找User 这个类的构造函数,以此来创建一个对象
  • Hilt 发现 有两个个构造函数,而无参构造函数被@Inject 声明
  • Hilt 会去调用被@Inject 的构造函数,创建一个User(“Rikka”, 23) 对象
  • 返回这个对象, MainActivity 实现外部帮忙创建 User对象,实现 User 的依赖注入。

Inject 的中文翻译是 “注入、注射”,所以可以形象的认为, @Inject 修饰的变量是被外界通过针筒注入进来的。

  • @Inject 可以修饰
  • 构造函数 Constructors
  • 变量 Fields
  • 方法 Methods

构造函数是最先被注解的,然后再是变量和方法。所以它修饰构造函数和修饰变量,其实是不同的作用。但为了便于理解,我们可以把它看成是一个插眼工具,便于Hilt去寻找要注入的地方。

我们上面的 User 类是无参构造函数,这次假设我们要有参数的呢?其实就是参数也要注入嘛,这就是套娃来的,来看看我们给User新增一个 属性:Clothes:

class Clothes @Inject constructor() {
}
​
class User @Inject constructor(var clothes: Clothes){
}
​
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    ...
        Log.d(TAG, "user clothes:${user.clothes}")
        
}

打印结果:

PS:大家不要太拘泥于有参构造函数的创建,我认为注入的作用是创建出一个对象,这个对象里面的内容可以后续再传入,它更多的体现、或者我们需要注意的是 “分离关注点”

2.3 实现接口实例注入

因为接口没有构造函数,所以当我们想要依赖一些接口时,该怎么办。

我们来下面的示例,我们写一个 Profession 接口,代表职业:

interface Profession {
    fun doJob()
}

假设我们有两个实现接口的类:医生类和程序猿类:

class Doctor : Profession{
    override fun doJob() {
        Log.d("Doctor", "doctor do job")
    }
}
​
class Programmer : Profession{
    override fun doJob() {
        Log.d("Programmer", "programmer do job")
    }
}

这个时候我给 User 类添加一个职业的属性,并希望它能够自动注入:

class User @Inject constructor(var clothes: Clothes){
    @Inject
    lateinit var profession: Profession
}

因为 Profession 是一个接口,它有两个实现类,所以这样 Hilt 并不能知道我们要实例化哪个具体的实现类,所以编译的时候就会报错。

而 Hilt 也解决这种问题,首先我们要在每个实现类上注入构造函数:

class Doctor @Inject constructor() : Profession{
    ...
}
​
class Programmer @Inject constructor() : Profession{
    ...
}

接着我们需要实现一个和该接口有关的 XXXModule类,它被 @Module 修饰,这个和 Dagger 中的一样,代表它会为接口提供一个创建实例的工厂,同时需要加上 @InstallIn 注解,用来声明它是被安装到哪个组件中,该注解后面会说到。 代码如下:

@Module
@InstallIn(ActivityComponent::class)
abstract class ProfessionModule {  // 1
​
    // 2
    @Binds
    abstract fun bindDoctor(doctor: Doctor): Profession
}

注释1: 我们写出来的类是一个抽象类,因为我们不需要具体的实现它,而且它没有具体的命名规则,因为我们也不会在代码中直接调用它,但是为了便于理解,我们起名一般叫 接口名 + Module。

注释2: 我们假设该Module提供一个 Doctor 的职业,那我们需要定义一个 抽象方法 来获取一个Doctor类。 并且 该方法需要被 @Binds 注解修饰。这样就能被 Hilt 识别。

这样一来,我们就实现了接口的一个实例化的注入,我们来实验一下,在 User 中去展示它:

class User @Inject constructor(var clothes: Clothes){
    @Inject
    lateinit var profession: Profession
​
    fun showMyself() {
        profession.doJob()
    }
}
​
// MainActivity
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var user: User
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        user.showMyself()
    }
}

打印结果为:

可以看到我们的 Doctor 成功的注入了。

OK,我们了解了接口某一个实现类的注入 (Doctor),那假设这个时候,另外一个类需要注入接口的另一个实现类 Programmer,那我们是不是也得按照同样的做法,在 Module类中添加呢?

@Module
@InstallIn(ActivityComponent::class)
abstract class ProfessionModule {
​
    @Binds
    abstract fun bindDoctor(doctor: Doctor): Profession
    
    @Binds
    abstract fun bindProgrammer(programmer: Programmer): Profession
}

这个时候发现运行,编译也会报错:

提示我们被绑定多次了。

这是因为 Doctor 和 Programmer 都是相同类型,当他们一起被 Binds 注解,那 Hilt 不知道要去绑定哪一个。

这个时候就需要使用 @Qualifier 注解来帮助我们了,它就是为了这种 相同类型 依赖注入而产生的:

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindDoctor
​
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class

我们创建了新的注解 BindDoctor 和 BindProgrammer,他们都被 @Qualifier 修饰,表示他们用来作用在同种类型上, @Retention 选择使用 BINARY类型,表明该注解保留到编译后,但无法通过反射来得到,是比较适合的注解。

接下来,我们要将这些注解作用在被 Binds 注解的抽象方法上:

@Module
@InstallIn(ActivityComponent::class)
abstract class ProfessionModule {
​
    @BindDoctor
    @Binds
    abstract fun bindDoctor(doctor: Doctor): Profession
    
    @BindProgrammer
    @Binds
    abstract fun bindProgrammer(programmer: Programmer): Profession
}

最后,在 User 中声明使用哪一个类型的注入:

class User @Inject constructor(var clothes: Clothes){
    @BindProgrammer  // 这次注入一个 Programmer
    @Inject
    lateinit var profession: Profession
​
    fun showMyself() {
        profession.doJob()
    }
}

打印结果如下所示:

这下我们就实现了具体某个实例的注入啦。

2.4 实现第三方依赖注入

假设一些类不是由我们自己写的,而是由第三方库导入的。比如 OkHttp ,我们在使用网络请求的时候,需要使用它,为了分离关注点,我们需要对他进行依赖注入。

但是 OkHttp 是我们不能修改的类,所以我们不能在它的构造函数上加入 @Inject, 这个时候该怎么办呢?

Dagger 中也有类似的场景,我们需要 @Providers 来帮助我们。除此之外,我们也需要 @Module 注解来声明一个 Module 类, 基于上面的例子,我们可以把这种 Module 类看成是一个工厂:

@Module
@InstallIn(ApplicationComponent::class)
class NetModule { // 1
​
    // 2
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        // 3
        return OkHttpClient.Builder().build()
    }
}

注释1: 声明一个 NetModule,提供网络库相关的组件,并没有和上面例子一样声明为抽象函数,这是因为里面的都有具体的实现方法。

注释2: 编写一个 provideOkHttpClient() 方法,返回一个 OkHttpClient对象。 声明一个 @Providers 注解,表示这个提供的依赖对象,是第三方的类或者系统类,我们因为不能直接更改其构造函数,所以得加上这个注解。

注释3:new 一个对象,并返回。

这样,我们就能在我们代码中直接使用了:

@Inject
    lateinit var okHttpClient:

@Providers 的本质是什么? 第三方类因为其只读性,Hilt不能找到其构造函数,所以需要我们自己手动的创建,创建的方法被 @Providers 修饰, Hilt 找到这个方法,并提供由我们手动创建的对象。

所以 @Providers 的本质,是由我们自己创建对象, Hilt 帮我们注入。

现在大家都不会直接使用 OkHttp,而是使用 Retrofit,所以我们来提供一个 Retrofit 把:

...
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build()
    }

因为 Retrofit的创建需要依赖一个 OkHttpClient 对象,所以我们需要创建一个,但是我们也可以注入一个,因为我们之前已经有 provideOkHttpClient,所以它就能提供一个实例,我们不用在担心什么了。

2.5 Hilt 的内置组件和作用域

2.5.1 @InstallIn 注解

我们之前看到了 @InstallIn 这个注解,它的作用是用来表明 Module 作用的地方,它的参数时 xxxComponent格式,前面xx代表作用域。

因为 Hilt 是Dagger的Android场景化,所以它能作用的地方和我们Android息息相关,有下面几处:

  1. Application ->ApplicationComponent
  2. ViewModel ->ActivityRetainedComponent
  3. Activity ->ActivityComponent
  4. Fragment ->FragmentComponent
  5. View ->ViewComponent
  6. Service ->ServiceComponent
  7. View Annotation with@WithFragmentBindings ->ViewWithFragemntComponent

除了最后一个,别的作用域还是挺常见的。他们都要通过 @InstallIn 注入。

比如我们ProfessionModule是声明成 @InstallIn(ActivityComponent::class),这就说明只有在 Activity 的代码中可以使用它,而在 Service 中是不能使用的。 而 NetModule 则是声明成 InstallIn(ApplicationComponent::class)的,这说明在全局都可以使用它提供的 OkHttpClient 和 Retrofit 对象。

2.5.2 使注入对象单例

像 Retrofit 、 OkHttpClient 这样的全局都需要使用到的对象,我们希望它的作用域是全局,并且单例的。

但是 Hilt 提供的 @Inject 对象并不是单例的,每次 注入时都会重新生成一个新的实例,这就说明,假设我们要使用 Retrofit 来做网络请求, @Providers 每次提供的都是不一样的,这样对性能来说很不友好,而且不符合常规的逻辑设定。

按照以往,我们可以通过给 NetModule 声明一个 @Singleton 注解,来让这个类实现单例,来解决这个问题。

Hilt 也有自己的解决方案,那就是使用 @xxxScope 注解,它和上面的 xxxComponent所对应,表示 在这个作用域内单例,来看看对应关系:

  1. Application ->ApplicationComponent ->@Singleton
  2. ViewModel ->ActivityRetainedComponent ->@ActivityRetainedScoped
  3. Activity ->ActivityComponent ->@ActivityScoped
  4. Fragment ->FragmentComponent ->@FragmentScoped
  5. View ->ViewComponent ->@ViewScoped
  6. Service ->ServiceComponent ->@ServiceScoped
  7. View Annotation with@WithFragmentBindings ->ViewWithFragemntComponent ->@ViewScoped

使用 @xxScoped 来替代 @InstallIn(xxxComponent::class) 声明组件为单例。

因为 Application 是作用于全局,所以它的注解是 @Singleton,比较好理解。

2.5.3 作用域的包含关系

作用域也有自己的包含关系,比如被 @ActivityScoped声明的组件,可以在 Fragment 或者 View 中使用,他们的具体包含关系如下图所示:

2.6 Hilt 预置的 Qualifier

我介绍过 Hilt 是 Dagger针对Android的场景化,所以它低层做了很多事情,使得在Android上更好的使用。除了上面介绍过的那些注解外,还有很多别的东西,可以让我们去探索,同时也了解了Dagger本身。

Context 上下文是Android 独特的存在,它代表着 Application、Activity、Service的一些fwk层的东西。

而我们的代码中经常会需要 Context 来创建一些东西:

class A @Inject constructor(context: Context) {
   ...
}

但是我们知道,它是系统类,我们无法注入 Context。那我们可以通过使用 @Providers 来创建吗?

@Providers
fun provideContext() {
   ???
}

很明显,Context 是由AMS来创建的,我们无法直接创建一个上下文出来。这个问题该如何解决呢?

答案是:我们不用解决,Hilt 为我们提供了它自己预置的注解 @ApplicationContext 和 @ActivityContext,我们直接使用,Hilt会帮我们注入上下文。

class A @Inject constructor(@ApplicaitonContext context: Context)

而现在没有 ServiceContext,可能是用的比较少吧?

@ApplicationContext 提供的类型是 Application, 而不是我们自己的 App 自定义的 Application,加入我们要使用自己的该怎么办呢?答案是也很简单:

@Providers
fun provideApplicaiton(application: Application): MyApplication {
    return  applicaiton as MyApplication
}

直接在 Module 中提供一个,并强转就 OK啦。

注意

ApplicationContext 的作用域是全局, 所以它修饰的类的作用只能是 @InstallIn(Applicaiton) 或 @Singleton,其他的也同理。

以上就是本章节的全部内容,主要讲解Jetpack技术中Hilt去实现解耦 ;更多Android开发技术可以参考《Android核心技术手册》点击查看全部。

之前没有用过 Dagger,因为项目不需要,且难学,问题多。 Hilt 出来之后解决了大部分的痛点,再不上车属实就有点说不过去了。

Hilt 相较与 Dagger,肯定是更好用,更适合Android来使用。 它和 Koin的比较,只是性能上的差异,网上大部分的文章都认为 Hilt 性能更优,但是代码量更多,在大的项目使用 Hilt 会更好,而小的项目两者差别不会太大。具体还请开发者自己研究。

Hilt 作用是 提供依赖注入,帮助程序分离关注点,帮助搭建低耦合高内聚的框架,学习它,有利于我们学习 Android应用架构 方面的技能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/346243.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式开发:AI和Chat GPT改变嵌入式系统的3种方式

嵌入式软件开发是一个已经发展了很多年的领域&#xff0c;它已经见证了许多技术进步。然而&#xff0c;ChatGPT和其他人工智能技术的引入将以前所未有的方式彻底改变该领域。在这篇博文中&#xff0c;我们将探索ChatGPT和AI将改变嵌入式开发的三种方式。方式1 —— CHATGPT/AI自…

Redis之搭建一主多从

搭建redis一主多从的过程 1.在相应位置创建一个文件夹存放redis配置文件 mkdir myredis2.复制redis配置文件到此文件夹中 cp /opt/redis/redis/bin/redis.conf /opt/myredis/redis.conf3.新建三个配置文件 touch redis6379.conf touch redis6380.conf touch redis6381.conf4…

测试开发之Django实战示例 第九章 扩展商店功能

第九章 扩展商店功能在上一章里&#xff0c;为电商站点集成了支付功能&#xff0c;然后可以生成PDF发票发送给用户。在本章&#xff0c;我们将为商店添加优惠码功能。此外&#xff0c;还会学习国际化和本地化的设置和建立一个推荐商品的系统。本章涵盖如下要点&#xff1a;建立…

基于JAVA+SpringBoot+Vue+ElementUI中学化学实验室耗材管理系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 当前&#xff0c;中学…

【js文字大小自适应】原生js实现文字大小自适应(附完整源码)

目录1、先睹为快2、代码实现第一步、搭建页面第二步、设置样式第三步、JS实现字体自适应3、源码直通车&#xff08;百度云下载&#xff09;【写在前面】之前写过一个大屏的项目&#xff0c;其中里面最头疼的应该是图片&#xff0c;文字&#xff0c;图表&#xff08;echarts&…

分布式事务解决方案——TCC

TCC是Try、Confirm、Cancel三个词语的缩写&#xff0c;TCC要求每个分支事务实现三个操作&#xff1a;预处理Try、确认Confirm、撤销Cancel。1、Try 阶段是做业务检查(一致性)及资源预留(隔离)&#xff0c;此阶段仅是一个初步操作&#xff0c;它和后续的Confirm一起才能真正构成…

Win11系统如何安装Ubuntu20.04(WSL版本)并安装docker

终于还是下定决心去换电脑了……这次采用轻量级的WSL&#xff0c;发现虽然没有占内存的GUI界面&#xff0c;但是编码和阅读文档还是非常nice的 1、首先开启Win11的虚拟机服务 2、下载你期望的Ubuntu服务器&#xff08;这里以20.04为例&#xff09; 安装成功后&#xff0c;发现…

发布的软文如何提高阅读量?

软文文章是一种带有宣传性质的文章&#xff0c;旨在宣传某个产品、品牌或者公司。然而&#xff0c;要想有效地宣传&#xff0c;就必须要让读者真正的阅读并了解软文中的内容。因此&#xff0c;提高软文文章阅读量是非常重要的。下面是一些有助于提高软文文章阅读量的方法&#…

简单记录简单记录

目录1.注册Gmail2.注册ChatGPT3.验证“真人”使用4.开始使用1.注册Gmail 第一步先注册一个谷歌邮箱&#xff0c;你也可以使用微软账号&#xff0c;大部分人选择使用gmail。 申请谷歌邮箱 选择个人用途创建账号即可。 &#x1f4cc;温馨提示&#xff1a; 你直接使用guo内的网…

react路由详解

在学习react路由之前&#xff0c;我们肯定需要安装路由。大家先运行如下命令安装路由。安装之后随我一起探索react路由。 安装 版本v6 npm i react-router-dom -S 页面准备 创建两个文件夹 pages和 router pages文件夹里面放的是页面 router文件夹里面是进行路由配置 路由…

【MySQL】聚合函数和GROUP BY

文章目录1、聚合函数2、GROUP BY3、HAVING4、SELECT的执行过程1、聚合函数 聚合函数作用于一组数据&#xff0c;并对一组数据返回一个值。聚合函数类型&#xff1a;AVG()、SUM()、MAX()、MIN()、COUNT()。不能在 WHERE 子句中使用聚合函数。 SELECT AVG(salary), MAX(salary)…

从0到1一步一步玩转openEuler--14 openEuler DNF(YUM)配置管理

文章目录14.1 DNF配置文件14.1.1 配置main部分14.1.2 配置repository部分14.1.3 显示当前配置14.2 创建本地软件源仓库14.3 添加、启用和禁用软件源14.3.1 添加软件源14.3.2 禁用软件源14.3.3 启用软件源DNF是一款Linux软件包管理工具&#xff0c;用于管理RPM软件包。DNF可以查…

论文笔记: Monocular Depth Estimation: a Review of the 2022 State of the Art

中文标题&#xff1a;单目深度估计&#xff1a;回顾2022年最先进技术 本文对比了物种最近的基于深度学习的单目深度估计方法&#xff1a; GPLDepth(2022)[15]: Global-Local Path Networks for Monocular Depth Estimation with Vertical CutDepthAdabins(2021)[1]: Adabins:…

操作系统:文件系统的实现

一、文件系统结构 磁盘的逻辑单元为块&#xff0c;内存和磁盘之间的I/O传输以块为单位执行。 磁盘的特点 1可以原地重写&#xff0c;可以从磁盘上读一块儿&#xff0c;修改该块&#xff0c;并将它写回到原来的位置可以直接访问磁盘上的任意一块。因此&#xff0c;可以方便地…

浅析SCSI协议(2)命令模型

SCSI命令模型 SCSI协议对链路传输没有进行严格的限制&#xff0c;但约束了SCSI的命令模型。协议允许使用SAS、FC以及以太网等不同的链路实现&#xff0c;但对所有的SCSI传输层实现而言&#xff0c;都必须遵循统一的SCSI命令模型。SCSI命令模型约束了SCSI命令执行过程、命令请求…

《底层逻辑:看清这个世界的底牌》读后感

书名《底层逻辑&#xff1a;看清这个世界的底牌》作者刘润简介如果只教给你各行各业的“干货”&#xff08;方法论&#xff09;&#xff0c;那只是“授人以鱼”&#xff0c;一旦环境出现任何变化&#xff0c;“干货”就不再适用。但如果教给你的是底层逻辑&#xff0c;那就是“…

初入测试如何编写测试用例?从3个方面带你写一个合格的测试用例

前言 作为一个测试新人&#xff0c;刚开始接触测试&#xff0c;对于怎么写测试用例很头疼&#xff0c;无法接触需求&#xff0c;只能根据站在用户的角度去做测试&#xff0c;但是这样情况会导致不能全方位的测试APP&#xff0c;这种情况就需要一份测试用例了&#xff0c;但是不…

亿级高并发电商项目-- 实战篇 --万达商城项目 七(品牌模块、商品类型模块等开发)

专栏&#xff1a;高并发---分布式 在管理商品时&#xff0c;除了商品名、价格、商品介绍等基本参数外。还需 要给商品添加品牌、商品类型、商品规格等参数。比如Iphone13的 品牌是苹果。商品类型属于手机通讯>手机>手机。规格有机身颜色: 星光色、版本:128G。品牌、商品…

暗网与深网:5 个主要区别

在互联网上&#xff0c;深网不会被网络爬虫索引&#xff0c;而暗网是故意隐藏的。 文章目录前言一、暗网与深网二、什么是暗网&#xff1f;什么是深网&#xff1f;暗网和深网之间的 5 个主要区别1. 范围和操作暗网深网2. 尺寸暗网&#xff1a;深网&#xff1a;3. 访问暗网深网4…

数据的分组聚合

1&#xff1a;分组 t.groupby #coding:utf-8 import pandas as pd import numpy as np file_path./starbucks_store_worldwide.csv dfpd.read_csv(file_path) #print(df.head(1)) #print(df.info()) groupeddf.groupby(byCountry) print(grouped) #DataFrameGroupBy #可以遍历…