Android依赖注入与Hilt的使用

news2024/11/19 23:13:02

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

基础知识

一、依赖注入是什么?

二.如果依赖注入这么简单,为什么需要专门开发一个框架?

三.安卓的依赖注入框架Dagger与Hilt

四.Hilt的使用

hilt相关的注解

hilt基本使用

必须配置选项

onlysampletest分支代码详解

complextest分支代码详解(更复杂一些的使用)

场景

hilt的场景实现

provider分支代码

Hilt进阶使用

multipleObjtest分支使用(同一类型提供多实现)

预定义限定符(@ApplicationContext @ActivityContext)

官方提供的类生成组件(抄官方)

组件作用域(抄官方)

组件默认绑定(抄官方)



基础知识


一、依赖注入是什么?

类通常需要引用其他类。例如,Car 类可能需要引用 Engine 类。这些必需类称为依赖项,在此示例中,Car 类依赖于拥有 Engine 类的一个实例才能运行。

类可通过以下三种方式获取所需的对象:

  1. 类构造其所需的依赖项。在以上示例中,Car 将创建并初始化自己的 Engine 实例。
  2. 从其他地方抓取。某些 Android API(如 Context getter 和 getSystemService())的工作原理便是如此。
  3. 以参数形式提供。应用可以在构造类时提供这些依赖项,或者将这些依赖项传入需要各个依赖项的函数。在以上示例中,Car 构造函数将接收 Engine 作为参数。

第三种方式就是依赖项注入!使用这种方法,您可以获取并提供类的依赖项,而不必让类实例自行获取。

下面是一个示例。在不使用依赖项注入的情况下,要表示 Car 创建自己的 Engine 依赖项,代码如下所示:

class Car {

    private Engine engine = new Engine();

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-APaBp33c-1670170591309)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

这段代码直接通过Car类的new方法通过申请一片内存空间创建一个新的engine对象,但是这显然不能不能满足我们要求,

首先,Car 类一旦被实例化,就会伴随着 Engine 类的实例化,也就使得 Car 类在实例化时必须知道 Engine 类的实例化需要哪些条件,在我们的例子中就是 PetrolEngine 类实例化需要的条件。

其次,对 Engine 的强依赖使得测试更加困难。Car 使用 Engine 的真实实例,因此您无法使用测试替身针对不同的测试用例修改 Engine,为了降低耦合,我们通过如下方式创建

public class Car {   

    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RxW9ZHCW-1670170591311)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

现在我们只需要在实例化 Car 对象的时候传入一个 Engine 对象的引用,这就意味着 Car 类和 Engine 类的耦合度降低了。Car 类不再需要知道 Engine 类的实例化条件是什么,Car 类能调用的任何类型的 Engine 类。在这个例子中,因为我们通过 Car 类的构造方法把 Engine 类传递(或者称为注入)给 Car 实例,使得我们完成了构造器注入的实现,我们当然还可以通过使用依赖注入框架的方法直接注入到类的域里。上面就是依赖注入的概念了。它的思想就是将依赖直接传递给类,而不是由类来初始化依赖。

二.如果依赖注入这么简单,为什么需要专门开发一个框架?

现在我们知道依赖注入是什么了,也就知道要怎么在代码中应用依赖注入了,所以我们就看看在我们的构造方法或者调用的方法中需要传递哪些依赖把。对于一些简单的依赖,这部分工作确实很好完成,但依赖越复杂,我们需要完成的工作就越繁复。

在实际项目的代码可能有上万行甚至更多,多人、多团队同时开发,以及人员变动、长期维护等因素,让依赖注入的实现变得比较复杂:
1)数量:大量对象之间的依赖,几十个、上百个,比如对汽车进行建模编程。
2)依赖关系复杂:逻辑和时序两方面,逻辑上存在不同的层次的对象依赖,子对象、孙对象);时序上,如何保证传入的对象已经创建。在复杂工程中,往往由不同的人员和团队开发多个模块,让相互之间的依赖关系更加复杂。
再深入一些,比如要管理对象的声明周期,为了安全或者回收资源,需要及时销毁对象,控制对象的使用范围。后期维护人员,不是最初的开发人员,如何能够正确的使用各个对象,避免引入错误逻辑或者冗余代码(原来代码看不懂,绕过去写新代码。这样做危害也很大。)

还是回到刚刚的例子吧,假设 Engine 类也有它所需要的依赖集,例如:曲柄轴,活塞,块和头。如果我们遵循依赖注入的原则,就会将这些类的实例传递给 Engine 类,这样的情况倒还好,我们只需要先创建这些对象,然后在创建 Engine 类实例的时候把它们传递给 Engine 对象就好了,最后我们还是可以将 Engine 类的实例传递给 Car 类。

现在我们让这个例子变得更复杂些,如果我们想为 Engine 类的每一个部件创建类,那么我们很容易就会因此创建几百个类,这些类甚至呈现为一颗复杂的树状图(准确来说是一张图)结构的依赖关系。

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnVpbB66-1670170591311)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]编辑

​ 从我们的例子里就可以了解到以简单的方式实现依赖注入的坏处在哪里了:复杂的依赖关系、大量的模板代码。也正是如此,依赖注入之前没有流行起来。但不可否认,依赖注入确实是值得使用的,也正因如此,有几个大牛开发了依赖注入框架来解决传统依赖注入用法存在的问题。这些框架大大简化了配置依赖以及生成工厂和建造者对象的过程,是之变得直观和简单。

三.安卓的依赖注入框架Dagger与Hilt

1.Dagger是适用于 Java、Kotlin 和 Android 的热门依赖项注入库,由 Google 进行维护。Dagger 为您创建和管理依赖关系图,从而便于您在应用中使用 DI。它提供了完全静态和编译时依赖项,解决了基于反射的解决方案(如 Guice)的诸多开发和性能问题。

2.Hilt是推荐用于在 Android 中实现依赖项注入的 Jetpack 库。Hilt 通过为项目中的每个 Android 类提供容器并自动为您管理其生命周期,定义了一种在应用中执行 DI 的标准方法。

Hilt 在热门 DI 库 Dagger 的基础上构建而成,因而能够受益于 Dagger 提供的编译时正确性、运行时性能、可伸缩性和 Android Studio 支持。

下文我们为将介绍Hilt的使用

四.Hilt的使用

1.在项目根级目录的build.gradle文件中添加hilt-android-gradle-plugin插件

plugins {
  
  id 'com.google.dagger.hilt.android' version '2.44' apply false
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OGFPNYM0-1670170591312)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

2.应用 Gradle 插件并在 app/build.gradle 文件中添加以下依赖项:

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

android {
  ..
}

dependencies {
  implementation "com.google.dagger:hilt-android:2.44"
  kapt "com.google.dagger:hilt-compiler:2.44"
}

//允许自动更新代码
kapt {
  correctErrorTypes true
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0EAprxq4-1670170591313)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

hilt相关的注解

@HiltAndroidApp:所有使用hilt的应用都需要使用这个注解,被使用在Application类上

@AndroidEntryPoint: Hilt可以为带有 @AndroidEntryPoint 注释的其他 Android 类提供依赖项,@AndroidEntryPoint可以被用在四大组件以及View上面

@Inject:获取依赖

hilt基本使用

必须配置选项

Application中添加注解

@HiltAndroidApp
class App: Application() {
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fo14hAvs-1670170591314)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

onlysampletest分支代码详解

这个分支实现的功能是在MainActivity中注入Car对象

1、首先加入必须配置选项

2、Car对象代码:

class Car @Inject constructor() {
    fun drive(name: String) {
        println("小汽车嘟嘟嘟")
    }
}
//Car的构造使用@Inject表示Car对象是一个可被注入的对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fEqQ0njn-1670170591315)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

3、MainActivity代码:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var car: Car//Car对象使用@Inject注解,会自动被实例化,这里必须使用lateinit延迟初始化才会有效
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        car.drive("mage")
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4sj06GCP-1670170591316)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

complextest分支代码详解(更复杂一些的使用)

场景

onlysampletest分支中我们简单的将Car注入到了MainActivity中,但是实际的业务场景我们面临的业务肯定是远比这个负责的,比如我们现在想给Car分配一个司机。正常情况下我们会创建一个Driver司机对象,然后再Car对象的构造函数中传入Driver司机对象。

但是这明显不是hilt所想要的

hilt的场景实现

下面说一下hilt的实现方式:

首先我们需要创建多个类

App Car Driver DriverImpl DriverModule MainActivity

Car的构造中会传入Driver的实现,

Driver是司机的抽象实现,是一个接口。

DriverImple是Driver的实现类可以是任何一个具体的司机。

DriverModule负责提供Driver类的注入规则

下面是代码展示:

1、添加必须配置选项

2、Car实现

class Car @Inject constructor(val driver: Driver) {//Car的构造函数中增加了Driver司机对象的实现
    fun drive() {
        println("老司机${driver.name} 在线开车")
    }
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tJ7HprRF-1670170591317)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

2、Driver司机接口

interface Driver {
    val name :String
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dh49Nq1D-1670170591317)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

3、DriverImple司机接口的实现

class DriverImpl @Inject constructor() : Driver {//因为DriverImpl需要被注入到Car的构造中,所以DriverImpl本身一个是被注入着,他的构造中也需要使用@Inject注解
    override val name: String
        get() = "mage"
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1vqCh7Or-1670170591318)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

4、DriverModule 配置Driver的注入规则

@Module//必须配置的注解,表示这个对象是Module的配置规则
@InstallIn(ActivityComponent::class)//表示这个module中的配置是用来注入到Activity中的
abstract class DriverModule {
    @Binds
    abstract fun bindDriver(driver: DriverImpl): Driver//形参中的DriverImple表示真实要注入Car构造方法中的Driver实现,返回值Driver表示DriverImple所实现的抽象接口
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vtt8aMRr-1670170591319)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

5、MainActivity代码

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var car: Car//这里必须使用lateinit延迟初始化才会有效
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        car.drive()
    }
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-umDgnbT0-1670170591320)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

provider分支代码

provider分支是对@Provides注解的解释

complextest中我们使用@Binds注解将我们自定义的对象共享出去,那么如果要共享的对象并非是我们自己创建的(例如OkhttpClient和Request)我们该如何处理呢,这个时候就可以使用@Provides了。代码示例如下:

1、添加必须配置选项

2、创建OkhttpModule

@Module
@InstallIn(ActivityComponent::class)
object OkhttpModule {
    @Provides
    fun provideOkhttpClient(
        // Potential dependencies of this type
    ): OkHttpClient {
        return OkHttpClient()
    }

    @Provides
    fun providdeRequest():Request{
        return Request.Builder()
            .get()
            .url("https://developer.android.google.cn/training/dependency-injection/hilt-android")
            .build()
    }
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vgOJXjgz-1670170591320)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

3、MainActivity代码

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var client: OkHttpClient
    @Inject
    lateinit var request: Request
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn.setOnClickListener {
            client.newCall(request).enqueue(object :Callback{
                override fun onFailure(call: Call, e: IOException) {
                    println("请求失败")
                }
                override fun onResponse(call: Call, response: Response) {
                    println("请求成功")
                }
            })
        }
    }
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t1xaDGcI-1670170591321)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

Hilt进阶使用

multipleObjtest分支使用(同一类型提供多实现)

有些场景,同一类型的对象我们可能会提供多个实例,这个时候就需要特殊处理。举例:

比如我们现在提供一个Car的接口抽象,然后同时给Car提供两个接口实现TruckCar和TaxtCar。在MainActivity中代码如下:

@Inject
lateinit var truck: Car

@Inject
lateinit var taxi: Car
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QWHLo80Y-1670170591321)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

因为注入的对象类型都是Car,所以编译过程中就无法区分我们到底想注入TruckCar还是TaxtCar,所以编译过程中代码就好报错。要解决这个问题就需要使用自定义注解,下面晒代码:

1、必须配置选项

2、Car、TruckCar、TaxiCar代码

interface Car {
    fun drive(name:String)
}
class TruckCar @Inject  constructor():Car{
    override fun drive(name: String) {
        println("$name 卡车老司机开车,呜呜")
    }

}
class TaxiCar @Inject  constructor():Car{
    override fun drive(name: String) {
        println("$name 出租车老司机开车,呜呜")
    }

}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDzLva9x-1670170591322)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

3、CarModule代码

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Truck//卡车类注入标记

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Taxi//出租车类注入标记

@Module
@InstallIn(ActivityComponent::class)
class CarModule {

    @Truck//卡车类生成规则加上这个注解,注入的时候也是用该注解编译器就可以知道我们真实想注入的类型是TruckCar
    @Provides
    fun bindTruckCar(truckCar: TruckCar): Car {
        return TruckCar()
    }

    @Taxi//出租车类生成规则加上这个注解,注入的时候也是用该注解编译器就可以知道我们真实想注入的类型是TaxiCar
    @Provides
    fun bindTaxtCar(taxiCar: TaxiCar): Car {
        return TaxiCar()
    }
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bVSS35qb-1670170591323)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

预定义限定符(@ApplicationContext @ActivityContext)

抄一下官方的解释,Hilt 提供了一些预定义的限定符。例如,由于您可能需要来自应用或 Activity 的 Context 类,因此 Hilt 提供了 @ApplicationContext@ActivityContext 限定符。

代码示例:

1、必须配置选项

2、BaseInfo类

class BaseInfo @Inject constructor(@ActivityContext private val context: Context) {
    fun printPackageName(){
        println("应用包名 ${context.packageName}")
    }
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xnTwShZA-1670170591324)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

3、MainActivity类

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var baseInfo: BaseInfo
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn.setOnClickListener {
            baseInfo.printPackageName()
        }
    }
}
复制代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BFI0JKEE-1670170591324)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

官方提供的类生成组件(抄官方)

对于您可以从中执行字段注入的每个 Android 类,都有一个关联的 Hilt 组件,您可以在 @InstallIn 注释中引用该组件。每个 Hilt 组件负责将其绑定注入相应的 Android 类。

所有组件如下,组件的生命周期和是依赖于注入组件类的生命周期的

ApplicationComponentApplication
ActivityRetainedComponentViewModel作用于ViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponent带有 @WithFragmentBindings 注释的 View
ServiceComponentService

组件作用域(抄官方)

默认情况下,Hilt 中的所有绑定都未限定作用域。这意味着,每当应用请求绑定时,Hilt 都会创建所需类型的一个新实例。

不过,Hilt 也允许将绑定的作用域限定为特定组件。Hilt 只为绑定作用域限定到的组件的每个实例创建一次限定作用域的绑定,对该绑定的所有请求共享同一实例。

比如当组件作用域为@Singleton的时候,这个组件就是一个全局单例的

比如当组件作用域为@ActivityScoped的时候,这个组件在同一个Activity内都是同一个对象实例

所以默认的组件和作用域

Android 类生成的组件作用域
ApplicationApplicationComponent@Singleton
View ModelActivityRetainedComponent@ActivityRetainedScope
ActivityActivityComponent@ActivityScoped
FragmentFragmentComponent@FragmentScoped
ViewViewComponent@ViewScoped
带有 @WithFragmentBindings 注释的 ViewViewWithFragmentComponent@ViewScoped
ServiceServiceComponent@ServiceScoped

组件默认绑定(抄官方)

每个 Hilt 组件都附带一组默认绑定,Hilt 可以将其作为依赖项注入您自己的自定义绑定。请注意,这些绑定对应于常规 Activity 和 Fragment 类型,而不对应于任何特定子类。这是因为,Hilt 会使用单个 Activity 组件定义来注入所有 Activity。每个 Activity 都有此组件的不同实例。

Android 组件默认绑定
ApplicationComponentApplication
ActivityRetainedComponentApplication
ActivityComponentApplicationActivity
FragmentComponentApplicationActivityFragment
ViewComponentApplicationActivityView
ViewWithFragmentComponentApplicationActivityFragmentView
ServiceComponentApplicationService

作者:李笑鑫
原文链接:Android依赖注入与Hilt的使用_lixiaoxin-12的博客-CSDN博客

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

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

相关文章

[附源码]计算机毕业设计基于Springboot甜品购物网站

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

如何恢复文件夹隐藏文件?超实用的2种技巧来了

案例:电脑中毒,文件夹数据自动隐藏,现在全都找不到了怎么办?——在日常办公使用电脑过程当中,总会遇到这样那样的问题,比如文件夹数据隐藏问题,当我们需要这些数据时如何恢复正常呢?…

java计算机毕业设计ssm图书馆管理系统z3z90(附源码、数据库)

java计算机毕业设计ssm图书馆管理系统z3z90(附源码、数据库) 项目运行 环境配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)…

[附源码]计算机毕业设计基于SpringBoot的疫苗接种管理系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

算法leetcode|19. 删除链表的倒数第 N 个结点(rust重拳出击)

文章目录19. 删除链表的倒数第 N 个结点:样例 1:样例 2:样例 3:提示:原题传送门:分析题解rustgocpythonjava19. 删除链表的倒数第 N 个结点: 给你一个链表,删除链表的倒数第 n 个结…

专享策略No.3 | 商品截面交易策略

量化策略开发,高质量社群,交易思路分享等相关内容 『正文』 ˇ 大家好,2022松鼠俱乐部临近收官。前面发布了专享策略01V3 | 小短波策略,专享策略No.2 | 套利策略-自动换仓-出场加速。今天我们交付第三个专享策略:商品…

C语言—详解库函数中常用的字符串函数

C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串中或者字符数组中。字符串常量适用于那些对它不做修改的字符串函数。本篇文章为大家详解八个常用的字符串操作函数,同时我们尝试模拟实现该函…

OpenCV入门(C++/Python)-使用OpenCV裁剪图像(四)

裁剪是为了从图像中删除所有不需要的物体或区域。甚至突出显示图像的特定功能。 使用OpenCV裁剪没有特定的功能,NumPy数组切片是工作。读取的每个图像都存储在2D数组中(对于每个颜色通道)。只需指定要裁剪区域的高度和宽度(以像素…

Linux安装 dotnet sdk 6.0

一、离线安装(推荐) 1、下载安装包 官网下载地址 2、安装 (1)将安装包上传到linux服务器上的指定目录,如:/root/dotnet/sdk,运行下面指令解压即可 # cd /root/dotnet/sdk # mkdir -p $HOM…

毕设项目 - SSM留学生交流互动论坛系统(含源码+论文)

文章目录1 项目简介2 实现效果2.1 界面展示3 设计方案3.1 概述3.2 系统流程3.2.1 系统开发流程3.2.2 学生登录流程3.2.3 系统操作流程3.3 系统结构设计4 项目获取1 项目简介 Hi,各位同学好呀,这里是M学姐! 今天向大家分享一个今年(2022)最新…

微服务的好处(优点)有哪些?

显然,随着系统复杂度的提升,以及对系统扩展性的要求越来越高,微服务化是一个很好的方向,但除此之外,微服务还会给我们带来哪些好处? 独立,独立,还是独立 我们说微服务打响的是各自…

虹科方案|具有RDMA复制写入日志的高可用性HDD存储集群可提供出色的性能

一、引言 Open-E、Western Digital 和 ATTO 数据存储专家和工程师联手推出了基于 HDD 的数据存储设备,该设备结合Western Digital Ultrastar Data60 JBOD 的海量容量和Ultrastar 系列 HDD 以及加速的性能,这得益于ATTO HBA SAS 控制器和 100GbE NIC&…

制作一个简单HTML大学生抗疫感动专题网页(HTML+CSS)

🎉精彩专栏推荐 💭文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业: 【📚毕设项目精品实战案例 (10…

一文刨析C/C++全局常量的定义

目录 文件特性常量 文件常量 堆常量 EOF, WEOF errno 常量 数学常量 数学错误常量 共享常量 转换模式常量 文件读/写访问常量 NULL (CRT) stdin, stdout, stderr 文件特性常量 语法 #include <io.h> 备注 这些常量指定由函数指定的文件或目录的当前特性。…

软件杂志软件杂志社软件编辑部2022年第9期目录

基金项目论文 基于Blockly和树莓派的多模态控制智能车 杨杰;宋俊炜;吴思玮;魏苏州;赖晓晨; 1-313 基于神经网络的网络异常研究与实现 郭稼逸; 4-6《软件》投稿&#xff1a;cnqikantg126.com 计算机视觉技术在电力系统自动化中的应用 陈海远;顾雅青; 7-9 基于优…

【吴恩达机器学习笔记】十二、降维

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为学习吴恩达机器学习视频的同学提供的随堂笔记。 &#x1f4da;专栏简介&#xff1a;在这个专栏&#xff0c;我将整理吴恩达机器学习视频的所有内容的笔记&…

PM说 | 一文全方位解析C端用户的评论功能!

随着各种自媒体、资讯平台的肆意发展&#xff0c;评论区逐渐成为各路英雄豪杰的必争之地。在评论区&#xff0c;我们可以与”“杠精”来一场遭遇战&#xff0c;邂逅文人墨客&#xff0c;看一场舌战群雄大戏。 评论区越热闹&#xff0c;说明产品越活跃&#xff0c;用户粘性高&a…

iwebsec靶场 文件上传漏洞通关笔记1-第01关 前端js过滤绕过

目录 第01关 前端js过滤绕过 1.禁用js法 &#xff08;1&#xff09;禁用js &#xff08;2&#xff09;刷新页面使生效 &#xff08;3&#xff09;上传脚本 &#xff08;4&#xff09;开启js并刷新页面 2.修改页面法1 &#xff08;1&#xff09;右键查询元素 &#xff…

docker安装和info信息

docker 容器的历史&#xff1a; 1、Chroot Jail 就是我们常见的 chroot 命令的用法。它在 1979 年的时候就出现了&#xff0c;被认为是最早的容器化技术之一。 它可以把一个进程的文件系统隔离起来。 2、The FreeBSD Jail Freebsd Jail 实现了操作系统级别的虚拟化&#…

csapp 读书笔记

第二章&#xff1a;信息的表示和处理 历史上因程序bug导致重大事故的情况很多是由数字溢出&#xff0c;缓冲区溢出导致的。 本章主要讲的是基本数据类型的(IEEE)表示&#xff0c;大小端&#xff0c;以及二进制位运算的骚操作案例。 第三章&#xff1a;程序的机器级表示 本章…