Kotlin 移动端多平台

支持多平台编程是 Kotlin 的主要优势之一。它减少了为不同平台编写和维护相同代码所花费的时间,同时保留了本机编程的灵活性和优势。

1. 基本概念

  • KMM:Kotlin Multiplatform for mobile(移动设备的 Kotlin 多平台)

    • KMM 多平台的主要用例之一是在移动平台之间共享应用程序逻辑代码

    • 如果要实现本机 UI 或使用平台 API 时,需要编写特定于平台的代码

    • KMM 当前处于 beta 阶段,已经几乎稳定

    • Kotlin roadmap

          以下为 Kotlin 团队的优先事项

      • K2 compiler:对 Kotlin 编译器的重写,针对速度、并行性和统一性进行了优化。它还将让我们介绍许多预期的语言功能。在 1.9.0 中,K2 编译器已经达到了 JVM 的 Beta 阶段。此版本还增加了对 Kotlin/Native 的支持,并改进了对 Kotlin/JS 的支持,下一步是使 K2 编译器稳定并发布 Kotlin 2.0

      • K2-based IntelliJ plugin:基于 K2 的 IntelliJ 插件:更快的代码完成、突出显示和搜索,以及更稳定的代码分析。

      • Kotlin Multiplatform:通过改进工具链稳定性和文档,并确保兼容性保证,将技术推广到 Stable

        • Kotlin 多平台移动版于 2022 年 10 月进入测试版。到 2023 年底,我们希望将其提升为稳定版,这意味着即使在保守的情况下也可以安全使用。

        • 与 Google 合作开发了新的内存分配器,该分配器应该可以提高运行时性能和内存消耗。开发已经完成,分配器在 Kotlin 1.9.0 中可通过选择加入获得,并将在 Kotlin 1.9.20 (KT-55364) 中默认启用(致力于通过并行进行更多垃圾收集工作来减少垃圾收集暂停)。旧内存管理器在 Kotlin 1.8.20 中已弃用,并将在 Kotlin 1.9.20 中删除

  • Compose Multiplatform

    • Compose Multiplatform 是 JetBrains 基于 Kotlin 和 Jetpack Compose 的声明式 UI 框架

    • iOS 处于 Alpha 状态

2. 环境准备

  • 安装必要的工具

    • Android Studio

    • Xcode

    • JDK(推荐 17 LTS)

    • Kotlin Multiplatform Mobile 插件

      • Android Studio Settings/Preferences | Plugins

      • 搜索 Kotlin Multiplatform Mobile,安装重启

    • Kotlin 插件

           Kotlin 1.9.10 -> compose compiler 1.5.3

           Kotlin 1.9.0 -> compose compiler 1.5.0

      • 通常和 Android Studio 捆绑,建议升级到 1.9.0 或以上

      • 如果使用了 Jetpack Compose,注意 compose compiler 和 Kotlin 版本的兼容,例如:

  • 检查环境的工具 - KDoctor

    • KDoctor 仅适用于 macOS

    • 安装:brew install kdoctor

    • 执行:kdoctor

    • 根据输出结果,安装缺失的工具

      • 通常需要安装:Cocoapods,Ruby

3. 创建工程

  • KMM 工程使用 gradle 来构建

  • 工程整体结构,这里使用 kmm 重写 UpStorage 库为例:

      ├── androidApp

      ├── build.gradle.kts

      ├── gradle

      ├── gradle.properties

      ├── iosApp

      ├── kmmStorageShared

      └── settings.gradle.kts

    • 整个工程分为全局配置部分(build.gradle.kts,gradle,gradle.properties,settings.gradle.kts)和 Modules 部分(kmmStorageShared,androidApp,iosApp)

      • settings.gradle.kts:主要用来引入其他模块

        rootProject.name = "UpStorage"
        include(":androidApp")
        include(":kmmStorageShared")
      • build.gradle.kts:主要用来引入 Maven 源,和 gradle 插件

        buildscript {repositories {maven(url = "https://mdpm.haier.net/nexus/repository/public")。。。}dependencies {classpath(libs.bundles.plugins)// 包含的插件有:// AGP: com.android.tools.build:gradle:8.1.1// Kotlin: org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10}
        }allprojects {repositories {maven(url = "https://mdpm.haier.net/nexus/repository/public")。。。}
        }
        
      • gradle.properties

        # 共享本机代码中使用自定义 cinterop 库
        kotlin.mpp.enableCInteropCommonization=true
        # 新的源码布局
        kotlin.mpp.androidSourceSetLayoutVersion=2
        ###############################################################################################
        *                                                                     common
        *                                                                        |
        *                                                      +-----------------+-------------------+
        *                                                      |                                     |
        *
        *                                                    native                                 ...
        *
        *                                                     |
        *                                                     |
        *                                                     |
        *         +----------------------+--------------------+-----------------------+
        *         |                      |                    |                       |
        *
        *       apple                  linux                mingw              androidNative
        *
        *         |
        *  +-----------+------------+------------+
        *  |           |            |            |
        *
        * macos       ios         tvos        watchos
      • gradle

        建议使用 8.0 或以上版本
        distributionBase=GRADLE_USER_HOME
        distributionPath=wrapper/dists
        distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
        zipStoreBase=GRADLE_USER_HOME
        zipStorePath=wrapper/dists
    • Modules 部分介绍

      • kmmStorageShared:是一个纯的 Kotlin 模块,中包含 Android 和 iOS 应用程序通用的逻辑

              模块结构如下:

              ├── build.gradle.kts

              ├── kmmStorageShared.podspec

              └── src

               ├── androidMain

               ├── commonMain

               └── iosMain

        • build.gradle.kts:

          • 引入插件

          • plugins {kotlin("multiplatform")    // kotlin 多平台插件kotlin("native.cocoapods") // 如果 IOS 使用 cocoapods,引入此插件id("com.android.library")  // 安卓库
            }
          • 其它配置

          • kotlin {targetHierarchy.default() // 新的默认的源码布局// 支持的 IOS 平台iosX64()iosArm64()iosSimulatorArm64()cocoapods {summary = "Some description for the Shared Module"homepage = "Link to the Shared Module homepage"version = "1.0"license = "MIT"ios.deploymentTarget = "11.0"podfile = project.file("../iosApp/Podfile"framework {baseName = "kmmStorageShared"isStatic = true}// 源specRepos {url("https://git.haier.net/uplus/shell/cocoapods/Specs.git")}// IOS 引入其它模块pod("FMDB") {version = "~> 2.7.5"}pod("uplog") {source = git("https://git.haier.net/uplus/ios/uplog.git") {branch = "kmm_1.7.1"}}}sourceSets {val commonMain by getting {dependencies {implementation(libs.kotlinx.coroutines.core)}}val androidMain by getting {dependencies {api("com.haier.uhome:UpLog:3.6.0")api("com.haier.uhome:uplog-core:3.4.0")}}val iosMain by getting {dependencies {}}}
            }
      • androidApp:通常作为库开发的 demo,是一个安卓模块

      • iosApp:通常作为库开发的 demo,是一个 xcode 工程

4. 代码说明

在 kmm 共享模块中

  • src/commonMain 是共享代码

  • src/androidMain 是安卓差异化代码,可以调用 JDK、AndroidSDK 中的 API

  • src/iosMain 是苹果手机的差异化代码,可以调用 IOS 平台的 API

4.1 举例

我们基于现有的日志库,实现一个跨平台的日志接口:安卓和 IOS 平台有各自的 UpLog 库,通过使用 Kotlin 的 expect 和 actual 关键字来实现差异代码和通用逻辑

  • commonMain:

    internal expect object UpLog {fun d(tag: String, msg: String, vararg args: Any)fun d(tag: String, msg: String, t: Throwable)fun i(tag: String, msg: String, vararg args: Any)fun i(tag: String, msg: String, t: Throwable)fun w(tag: String, msg: String, vararg args: Any)fun w(tag: String, msg: String, t: Throwable)fun e(tag: String, msg: String, vararg args: Any)fun e(tag: String, msg: String, t: Throwable)
    }
  • androidMain:

      这里我们可以发现,使用了 UpLoggerManager,其本质上是 UpLog 库中的 API

    internal actual object UpLog {private const val LOGGER_NAME = "UpStorage"private val initialized = AtomicBoolean(false)private lateinit var logger: Loggeractual fun d(tag: String, msg: String, vararg args: Any) {logger.debug("$tag: $msg", *args)}actual fun d(tag: String, msg: String, t: Throwable) {logger.debug("$tag: $msg", t)}actual fun i(tag: String, msg: String, vararg args: Any) {logger.info("$tag: $msg", *args)}actual fun i(tag: String, msg: String, t: Throwable) {logger.info("$tag: $msg", t)}actual fun w(tag: String, msg: String, vararg args: Any) {logger.warn("$tag: $msg", *args)}actual fun w(tag: String, msg: String, t: Throwable) {logger.warn("$tag: $msg", t)}actual fun e(tag: String, msg: String, vararg args: Any) {logger.error("$tag: $msg", *args)}actual fun e(tag: String, msg: String, t: Throwable) {logger.error("$tag: $msg", t)}init {if (initialized.compareAndSet(false, true)) {logger = UpLoggerManager.getInstance().createLogger(LOGGER_NAME)}}
    }
  • iosMain:

      这里使用的是 cocoapods 生成库中的 iOS api

    package com.haier.uplus.kmm.storage.platformimport cocoapods.uplog.*internal actual object UpLog {private const val MODULE_NAME = "KmmStorage"private val logger = UPLog.getInstance()!!.createLogger(MODULE_NAME)!!actual fun d(tag: String, msg: String, vararg args: Any) = logger.logWithLevel(UPLogLevelDebug, msg)actual fun d(tag: String, msg: String, t: Throwable) = logger.logWithLevel(UPLogLevelDebug, msg)actual fun i(tag: String, msg: String, vararg args: Any) = logger.logWithLevel(UPLogLevelInfo, msg)actual fun i(tag: String, msg: String, t: Throwable) = logger.logWithLevel(UPLogLevelInfo, msg)actual fun w(tag: String, msg: String, vararg args: Any) = logger.logWithLevel(UPLogLevelWarning, msg)actual fun w(tag: String, msg: String, t: Throwable) = logger.logWithLevel(UPLogLevelWarning, msg)actual fun e(tag: String, msg: String, vararg args: Any) = logger.logWithLevel(UPLogLevelError, msg)actual fun e(tag: String, msg: String, t: Throwable) = logger.logWithLevel(UPLogLevelError, msg)
    }

如果要使用这个 UpLog 单例,不管在 common 中,还是在 android/ios 中都可以直接引用并调用

4.2 基本原理

Kotlin Native是一种将Kotlin源码编译成不需要任何VM支持的目标平台二进制数据的技术,编译后的二进制数据可以直接运行在目标平台上,它主要包含一个基于LLVM的后端编译器的和一个Kotlin本地运行时库。设计Kotlin Native的目的是为了支持在非JVM环境下进行编程,如在嵌入式平台和iOS环境下,如此一来,Kotlin就可以运行在非JVM平台环境下。

Kotlin Native 内部使用 cinterop 来对 Apple Framework 进行扫描,根据其头文件(.h)获取可以调用的类、方法、变量、常量以及他们的类型,最终生成 klib 文件。而 klib 文件中含着针对不同 CPU 架构所编译的二进制文件,以及可供 Kotlin Native 调用的 knm 文件, knm 文件类似 Jar 包中的 。class 文件,是被编译后的 Kotlin 代码,内部将 cinterop 扫描出来 的 Objective-C 内容转换成了 Kotlin 对应的内容,以便 IDE 可以进行索引,最终在 KMM 模块中使用 Kotlin 代码进行调用。

4.3 模块编译

  • 编译命令:./gradlew clean assemble

  • Android 和 iOS 的产物如下所示

  • 可使用 maven-publish 插件把 kmm 模块的产物上传到 maven 私服:包括安卓端的 aar 文件和 iOS 各平台的 klib 文件

    • 如果单独要把 Framework 导出给 iOS 使用,把编译产物手动上传到 pod 私服即可

5. 可能碰到的问题

  • 使用 pod 引入 iOS 模块的时候,出现找不到头文件的错误,例如:

    > Task :kmmStorageShared:cinteropUplogIosSimulatorArm64
    Exception in thread "main" java.lang.Error: /Users/liuqing.yang/work/haier/kmm/
    UpStorage/kmmStorageShared/build/cocoapods/synthetic/ios/build/
    Release-iphonesimulator/uplog/uplog.framework/Headers/UPLogUpload.h:9:9: 
    fatal error: 'UpLogUploadFileDelegate.h' file not foundat org.jetbrains.kotlin.native.interop.indexer.ModuleSupportKt.getModulesASTFiles(ModuleSupport.kt:74)at org.jetbrains.kotlin.native.interop.indexer.ModuleSupportKt.getModulesInfo(ModuleSupport.kt:14)at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.buildNativeLibrary(main.kt:563)at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.processCLib(main.kt:317)at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.processCLibSafe(main.kt:242)at org.jetbrains.kotlin.native.interop.gen.jvm.MainKt.access$processCLibSafe(main.kt:1)at org.jetbrains.kotlin.native.interop.gen.jvm.Interop.interop(main.kt:100)at org.jetbrains.kotlin.cli.utilities.InteropCompilerKt.invokeInterop(InteropCompiler.kt:45)at org.jetbrains.kotlin.cli.utilities.MainKt.mainImpl(main.kt:23)at org.jetbrains.kotlin.cli.utilities.MainKt.main(main.kt:45)

      或者提示 klib 不存在,例如:

    :kmmStorageShared:iosArm64Main: cinterop file: /Users/liuqing.yang/work/haier/kmm/UpStorage/kmmStorageShared/build/classes/kotlin/iosArm64/main/cinterop/kmmStorageShared-cinterop-uplog.klib does not exist

      如果出现 klib 不存在,需要删除工程中的 .gradle 文件夹,然后重新 sync 工程就会看到找不到头文件的真实原因

      最后根据提示,打开 IOS 工程,修复对应错误即可

  • 根据官方的描述,纯的 Swift 模块,目前还不支持双向互操作,Swift 调用 Kotlin 没有问题。

Kotlin/Native 与 Objective-C 支持双向互操作。见 Interoperability with Swift/Objective-C | Kotlin

  • 安卓 Kotlin 版本不一致问题

    • 测试 Demo 的 Kotlin 版本目前是1.3.61,然而 Kmm 工程的版本为1.9.10

    • 可能会碰到如下错误:

    • e: Incompatible classes were found in dependencies.
      e: /Users/liuqing.yang/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.9.10/dafaf2c27f27c09220cee312df10917d9a5d97ce/kotlin-stdlib-common-1.9.10.jar!/META-INF/kotlin-stdlib-common.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.9.0
    • 升级 Demo 中的 Kotlin 版本到 1.9.10,可能会碰到:

    • > java.lang.NoClassDefFoundError: org/jetbrains/kotlin/gradle/plugin/KotlinBasePlugin

因为 Demo 工程的 AGP 版本太低,建议升级到 4.2.2 或更新,另外 gradle 版本升级到 7.2 或更新

   另外如果提示

Class 'xxxxxxxx' is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler

  在工程根目录的 gradle.properties 中加入

kotlin.experimental.tryK2=true
  • 新的 Kotlin 版本中,kotlin-android-extensions 已移除,建议使用 viewBinding

android {buildFeatures {viewBinding true}
}

6. 单元测试

6.1 集成 Cucumber

Setup
  • 依赖导入

dependencies {androidTestImplementation(libs.androidx.test.core.ktx)androidTestImplementation(libs.androidx.test.rules)androidTestUtil(libs.androidx.test.orchestrator)androidTestImplementation(libs.test.cucumber.android)
}
  • KmmAndroidJUnitRunner

package com.haier.uplus.kmm.storage.android.testimport android.os.Bundle
import io.cucumber.android.runner.CucumberAndroidJUnitRunner
import io.cucumber.junit.CucumberOptions
import java.io.File/*** Created by liuqing.yang* 2023/9/11.*/
@CucumberOptions(features = ["features"],strict = true,
)
class KmmAndroidJUnitRunner : CucumberAndroidJUnitRunner() {override fun onCreate(bundle: Bundle?) {bundle?.putString("plugin", getPluginConfigurationString())//it crashes on Android R without itFile(getAbsoluteFilesPath()).mkdirs()super.onCreate(bundle)}/*** Since we want to checkout the external storage directory programmatically, we create the plugin configuration* here, instead of the {@link CucumberOptions} annotation.** @return the plugin string for the configuration, which contains XML, HTML and JSON paths*/@Suppress("SameParameterValue")private fun getPluginConfigurationString(): String {val cucumber = "cucumber"val separator = "--"return "junit:" + getCucumberXml(cucumber) + separator +"html:" + getCucumberHtml(cucumber)}@Suppress("SameParameterValue")private fun getCucumberHtml(cucumber: String) = "${getAbsoluteFilesPath()}/$cucumber/.html"@Suppress("SameParameterValue")private fun getCucumberXml(cucumber: String) = "${getAbsoluteFilesPath()}/$cucumber/.xml"private fun getAbsoluteFilesPath() =File(targetContext.getExternalFilesDir(null), "reports").absolutePath
}
  • Feature 文件

Feature: Kmm storageScenario Outline: auto insert some data to dbGiven insertKeyValue"<Key>""<Value>"Then insertSuccess"true"Examples:| Key | Value || a   | 1     || b   | 2     || c   | 3     |
  • Steps

package com.haier.uplus.kmm.storage.android.testimport android.util.Log
import com.haier.uplus.kmm.storage.manager.UpStorage
import io.cucumber.java.en.Given
import io.cucumber.java.en.Then
import org.junit.Assert/*** Created by liuqing.yang* 2023/9/11.*/
class StorageStep {companion object {private const val TAG = "StorageStep"}@Volatileprivate var insertRet: Boolean = false@Given("insertKeyValue{string}{string}")fun insertkeyvalue(key: String, value: String) {insertRet = UpStorage.putIntValue(key, value.toInt())Log.d(TAG, "insertkeyvalue: $insertRet")}@Then("insertSuccess{string}")fun insertSuccess(ret: String) {Log.d(TAG, "insertSuccess: $insertRet, $ret")Assert.assertEquals(ret.toBoolean(), insertRet)}
}
Running the tests

使用 Android Studio IDE

  1. Run > Edit Configurations

  2. 点击 + 按钮,选择 Android Instrumented Tests

  3. 指定测试名称,选择测试模块,点击 OK,最后点击运行按钮

执行结果和日志可在 build 目录中查看

6.2 集成 Jacoco

  • 引入 jacoco 插件

plugins {id("jacoco")
}
  • 自定义扫描的源码目录和 class 文件目录

tasks.register(kmmJacoco, JacocoReport::class.java) {group = reportingdescription = jacocoDescdependsOn(createDebugAndroidTestCoverageReport)reports {xml.enabled = truehtml.enabled = true}def coverageClassDirs = fileTree(//检测覆盖率的class所在目录(以项目class所在目录为准)dir: '../../UpBluetoothPlugin/build/intermediates/javac/debug',//增加以上目录中不需要检测的文件列表excludes: ['**/BuildConfig.class','**/impl/**.class'])getClassDirectories().setFrom(coverageClassDirs)getSourceDirectories().setFrom(files(coverageSourceDirs))File ecFile = new File("$buildDir/outputs/code_coverage/debugAndroidTest/connected");ecFile.listFiles().each {println it.namegetExecutionData().setFrom(files(it))}
}
  • 执行结果:

    • HTML:build/reports/jacoco/kmmJacoco/html 

    • XML:build/reports/jacoco/kmmJacoco/kmmJacoco.xml 

7. 团队介绍

「三翼鸟数字化技术平台-智家APP平台」通过持续迭代演进移动端一站式接入平台为三翼鸟APP、智家APP等多个APP提供基础运行框架、系统通用能力API、日志、网络访问、页面路由、动态化框架、UI组件库等移动端开发通用基础设施;通过Z·ONE平台为三翼鸟子领域提供项目管理和技术实践支撑能力,完成从代码托管、CI/CD系统、业务发布、线上实时监控等Devops与工程效能基础设施搭建。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试题:简单说一下阻塞IO、非阻塞IO、IO复用的区别 ?

文章目录 前言一、什么是IO二、阻塞IO模型三、非阻塞 IO模型四、IO复用模型总结 前言 在《Unix网络编程》一书中提到了五种IO模型&#xff0c;分别是&#xff1a;阻塞IO、非阻塞IO、IO复用、信号驱动IO以及异步IO。本篇文章主要介绍IO的基本概念以及阻塞IO、非阻塞IO、IO复用三…

配置DNS主从服务器,实现真反向解析

主服务器 [rootbogon ~]# systemctl stop firewalld.service #关闭防火墙 [rootbogon ~]# setenforce 0 #关闭selinux [rootbogon ~]# systemctl restart named #启动dns服务 [rootbogon ~]# vim /etc/named.conf #进入dns配置文件 options {#监听…

Java-NIO篇章(4)——Reactor反应器模式

前面已经讲过了Java-NIO中的三大核心组件Selector、Channel、Buffer&#xff0c;现在组件我们回了&#xff0c;但是如何实现一个超级高并发的socket网络通信程序呢&#xff1f;假设&#xff0c;我们只有一台内存为32G的Intel-i710八核的机器&#xff0c;如何实现同时2万个客户端…

MySQL索引优化:深入理解索引下推原理与实践

随着MySQL的不断发展和升级&#xff0c;每个版本都为数据库性能和查询优化带来了新的特性。在MySQL 5.6中&#xff0c;引入了一个重要的优化特性——索引下推&#xff08;Index Condition Pushdown&#xff0c;简称ICP&#xff09;。ICP能够在某些查询场景下显著提高查询性能&a…

PPT 编辑模式滚动页面不居中

PPT 编辑模式滚动页面不居中 目标&#xff1a;编辑模式下适应窗口大小、切换页面居中显示 调整视图大小&#xff0c;编辑模式通过Ctrl 鼠标滚轮 或 在视图菜单中点击适应窗口大小。 2. 翻页异常&#xff0c;调整视图大小后&#xff0c;PPT翻页但内容不居中或滚动&#xff0c…

革新区块链:代理合约与智能合约升级的未来

作者 张群&#xff08;赛联区块链教育首席讲师&#xff0c;工信部赛迪特聘资深专家&#xff0c;CSDN认证业界专家&#xff0c;微软认证专家&#xff0c;多家企业区块链产品顾问&#xff09;关注张群&#xff0c;为您提供一站式区块链技术和方案咨询。 代理合约&#xff08;Prox…

WordPress怎么禁用文章和页面古腾堡块编辑器?如何恢复经典小工具?

现在下载WordPress最新版来搭建网站&#xff0c;默认的文章和页面编辑器&#xff0c;以及小工具都是使用古腾堡编辑器&#xff08;Gutenberg块编辑器&#xff09;。虽然有很多站长说这个编辑器很好用&#xff0c;但是仍然有很多站长用不习惯&#xff0c;觉得操作太难了&#xf…

JAVA之常用集合框架

java中的常用集合是对数据进行存储以及相关操作的api。常用的有ArrayList、LinkedList、Vector、HashSet、TreeSet、TreeMap、HashMap ArrayList 数据结构 ArrayList的本质是一个数组 &#xff0c;那么它就具有数组的所有特性 可以根据下标快速查找值 ArrayList是如何实现动…

路由器初始化配置、功能配置

实验环境 拓扑图 Ip规划表&#xff08;各组使用自己的IP规划表&#xff09; 部门 主机数量 网络地址 子网掩码 网关 可用ip Vlan 市场部 38 192.168.131.0 255.255.255.0 192.168.131.1 2-254 11 研发部 53 192.168.132.0 255.255.255.0 192.168.132.1 2-2…

弹性调度助力企业灵活应对业务变化,高效管理云上资源

作者&#xff1a;吴昆 什么是弹性调度 云计算时代&#xff0c;企业可以通过云平台获得大量计算资源&#xff0c;并根据业务发展和流量需求的实时变化&#xff0c;灵活调整使用的资源类型与资源量。阿里云提供了多种弹性资源&#xff0c;如云服务器 ECS 和弹性容器实例 ECI&am…

SpringBoot解决Slow HTTP慢速攻击漏洞

项目场景&#xff1a; 扫描到的漏洞截图&#xff1a; 攻击原理&#xff1a; Web应用在处理HTTP请求之前都要先接收完所有的HTTP头部&#xff0c;因为HTTP头部中包含了一些Web应用可能用到的重要的信息。攻击者利用这点&#xff0c;发起一个HTTP请求&#xff0c;一直不停的发送…

SpringCloud Aliba-Sentinel【上篇】-从入门到学废【4】

&#x1f3b5;诗词分享&#x1f3b5; 大江东去&#xff0c;浪淘尽&#xff0c;千古风流人物。 ——苏轼《念奴娇赤壁怀古》 目录 &#x1f37f;1.Sentinel是什么 &#x1f9c2;2.特点 &#x1f9c8;3.下载 &#x1f32d;4.sentinel启动 &#x1f953;5.实例演示 1.Senti…

Java代码审计Shiro反序列化CB1链source入口sink执行gadget链

目录 0x00 前言 0x01 CC链&CB链简介 1. Commons Collections链是什么&#xff1f; 2. Commons BeanUtils链是什么&#xff1f; 0x02 测试Commons BeanUtils1链 0x03 Shiro550 - Commons BeanUtils1链 - 跟踪分析&#xff08;无依赖&#xff09; 1. 前置知识 2. Co…

【每日一题】按分隔符拆分字符串

文章目录 Tag题目来源解题思路方法一&#xff1a;遍历方法二&#xff1a;getline 写在最后 Tag 【遍历】【getline】【字符串】【2024-01-20】 题目来源 2788. 按分隔符拆分字符串 解题思路 方法一&#xff1a;遍历 思路 分隔符在字符串开始和结束位置时不需要处理。 分隔…

设计模式设计原则——依赖倒置原则(DIP)

DIP&#xff1a;Dependence Inversion Principle 原始定义&#xff1a;High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions。 官…

【正点原子STM32】搭建开发环境(安装MDK和器件支持包、DAP仿真器和ST LINK仿真器、CH340串口驱动)

一、常用开发工具简介 MDKDAP 二、安装MDK 1、MDK简介2、如何获取MDK3、安装MDK和器件支持包 三、安装仿真器驱动 DAP仿真器免驱ST LINK仿真器驱动安装方法 ST LINK驱动及教程 四、安装CH340 USB虚拟串口驱动 1、安装CH340 USB虚拟串口驱动2、为什么要安装CH340 USB虚拟…

《WebKit 技术内幕》之八(2):硬件加速机制

2 Chromium的硬件加速机制 2.1 GraphicsLayer的支持 GraphicsLayer对象是对一个渲染后端存储中某一层的抽象&#xff0c;同众多其他WebKit所定义的抽象类一样&#xff0c;在WebKit移植中&#xff0c;它还需要具体的实现类来支持该类所要提供的功能。为了完成这一功能&#x…

蓝桥杯官网填空题(奇怪的分式)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 上小学的时候&#xff0c;小明经常自己发明新算法。一次&#xff0c;老师出的题目是&#xff1a;1/4乘以8/5 小明居然把分子拼接在一起&#xff0c;分母拼接在一起&…

无法找到mfc100.dll的解决方法分享,如何快速修复mfc100.dll文件

在日常使用电脑时&#xff0c;我们可能会碰到一些系统错误提示&#xff0c;比如“无法找到mfc100.dll”的信息。这种错误通常会阻碍代码的执行或某些应用程序的启动。为了帮助您解决这一问题&#xff0c;本文将深入探讨其成因&#xff0c;并提供几种不同的mfc100.dll解决方案。…

【分享】MathWorks中国汽车年会:“软件定义汽车”

从软件赋能到软件定义&#xff0c;汽车行业不仅需要解决诸如错误发现滞后带来的高昂代价、功能融合所需的跨学科知识、功能安全与实施成本之间的权衡等老问题&#xff0c;也面临着新的挑战&#xff1a;软件复杂度的不断提升、利用数据驱动创造价值、人工智能的引入和实现、数字…