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.mzph.cn/news/635220.shtml

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

相关文章

16k+ start 一个开源的的监控系统部署教程

安装条件 Linux或macOS系统 4GB内存 开放 33014、33174、3183端口 1.安装 1、下载源码 首先使用 git 克隆源码到本地 git clone -b main https://github.com/SigNoz/signoz.git && cd signoz/deploy/ 方式1&#xff1a;运行 install.sh 脚本一键安装 ./install.s…

luffy商城项目(一)

企业项目类型 # 1 面向互联网用户&#xff1a;商城类项目 -微信小程序商城 # 2 面向互联网用户&#xff1a;二手交易类的 -咸鱼 -转转 # 3 公司内部项目&#xff1a;python写的重点 -oa系统 -打卡系统工资核算系统 -第三方公司做的&#xff1a…

leetCode-42.接雨水

&#x1f4d1;前言 本文主要是【算法】——算法模拟的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&#xff…

git提权

实验环境——vulnhub-dc2靶场 git提权 前提&#xff1a;用户可以使用sudo中git权限 查看sudo权限 sudo -l可以发现git命令存在sudo提权 基于此进行权限提升 方式&#xff1a; sudo git help config #在末行命令模式输入 !/bin/bash 或 !sh #完成提权 sudo git -p help…

直接发文!1D-2D-MTF-CNN-GRU-AT多通道图像时序融合的分类/故障识别程序!Excel导入,直接运行

​适用平台&#xff1a;Matlab2023版本及以上 本程序同时结合两篇国内顶级EI的方法&#xff1a;提出1D-2D-MTF-CNN-GRU-AT多通道图像时序融合的分类/故障识别程序&#xff01; ①中文EI期刊《电力自动化设备》12月29号网络首发文献&#xff1a;《基于格拉姆角场与并行CNN的并…

【PyTorch】PyTorch之Reduction Ops

文章目录 前言一、ARGMAX二、ARGMIN三、AMAX和AMIN四、ALL和ANY五、MAX和MIN六、MEAN七、MEDIAN八、NORM九、PROD十、STD十一、SUM十二、UNIQUE十三、VAR 前言 介绍pytorch的Reduction Ops。 一、ARGMAX torch.argmax(input, dim, keepdimFalse) → LongTensor Parameters&a…

突破Android开发瓶颈:6年Android开发者的实用建议

作为一名在Android领域摸爬滚打6年的老手&#xff0c;我想给那些在这个行业工作了3~5年的朋友们提供一些职业和技术上的建议。 许多开发者在职业生涯中都会遇到一个瓶颈期&#xff0c;尤其是当你在一个公司待了很长时间&#xff0c;感觉自己的技术和业务能力都无法得到提升时。…

多输入多输出 | Matlab实现基于LightGBM多输入多输出预测

多输入多输出 | Matlab实现基于LightGBM多输入多输出预测 目录 多输入多输出 | Matlab实现基于LightGBM多输入多输出预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现基于LightGBM多输入多输出预测&#xff08;完整源码和数据&#xff09; 1.data为数据集&a…

近期学习文章

DNSlog在渗透测试中的实战技巧 - 网安隐藏源IP&#xff0c;提高溯源难度的几种方案 - 网安FreeBuf网络安全行业门户 【漏洞公告】某平台一个有意思的CSRF // SecTrain安全博客 浅谈Web源码泄漏-安全客 - 安全资讯平台 红队-C2 Server基础构建 - 先知社区FreeBuf网络安全行业…

【电力电子在电力系统中的应用】2 CCM和DCM模式下Cuk电路的升降压工作状态

【仅供参考】 【2023.03西南交大电力电子在电力系统中的应用】 目录 0 仿真要求 1 仿真电路搭建及波形记录 1.1 CCM工作模式 1.1.1 升压模式 1.1.2 降压模式 1.2 DCM工作模式 1.2.1 升压模式 1.2.2 降压模式 1.3 改变开关频率和电容参数 1.3.1 改变开关频率 1.3.2 …

高清网络视频监控系统技术方案

目 录 一、概述 二、建设目标及需求 &#xff08;一&#xff09;建设总目标 &#xff08;二&#xff09;需求分析 三、设计依据与设计原则 &#xff08;一&#xff09;设计依据 &#xff08;二&#xff09;设计原则 四、建设方案设计 &#xff08;一&…

实用干货:最全的Loading动画合集网站!复制即用

大家好&#xff0c;我是大澈&#xff01; 本文约1000字&#xff0c;整篇阅读大约需要2分钟。 感谢关注微信公众号&#xff1a;“程序员大澈”&#xff0c;免费领取"面试礼包"一份&#xff0c;然后免费加入问答群&#xff0c;从此让解决问题的你不再孤单&#xff01…

01-开始Rust之旅

1. 下载Rust 官方推荐使用 rustup 下载 Rust&#xff0c;这是一个管理 Rust 版本和相关工具的命令行工具。下载时需要连接互联网。 这边提供了离线安装版本。本人学习的机器环境为&#xff1a; ubuntu x86_64&#xff0c;因此选用第②个工具链&#xff1b; 1. rust-1.75.0-x86_…

分布式ID(2):雪花算法生成ID

1 雪花算法简介 这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图(图片来自网络)所示: 41-bit的时间可以表示(1L&l…

【前端设计】流光按钮

欢迎来到前端设计专栏&#xff0c;本专栏收藏了一些好看且实用的前端作品&#xff0c;使用简单的html、css语法打造创意有趣的作品&#xff0c;为网站加入更多高级创意的元素。 css body{height: 100vh;display: flex;justify-content: center;align-items: center;background…

rbash环境变量提权

rbash为一个受限制的bash shell变体&#xff0c;限制用户在交互式环境中可使用的操作&#xff0c;以此提升系统安全性 可通过环境变量提权方式&#xff0c;越过此限制 export -p //查看环境变量 BASH_CMDS[a]/bin/sh;a //把/bin/sh给a /bin/bash export PATH$…

three.js从入门到精通系列教程009 - three.js创建球体和圆柱体

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>three.js从入门到精通系列教程009 - three.js创建球体和圆柱体</title><script src"ThreeJS/three.js"></script><script src"ThreeJS…

人工智能 | 自然语言处理的发展历程

github&#xff1a;https://github.com/MichaelBeechan CSDN&#xff1a;https://blog.csdn.net/u011344545 自然语言处理的发展 方向一&#xff1a;技术进步1. 基于规则的语法&#xff08;1950-1990&#xff09;2. 统计语言处理&#xff08;1990-2010&#xff09;3. 基于深度学…

ubuntu-20.04.6-live-server-amd64安装教程-完整版

简介 Ubuntu 20.04.6 Live Server AMD64 安装教程 - 完整版" 提供了详细的指南&#xff0c;旨在帮助用户在使用 AMD64 架构的服务器上安装 Ubuntu 20.04.6 Live Server 版本。该教程包含全面的步骤和详细说明&#xff0c;使用户能够顺利完成整个安装过程&#xff0c;建立…

要做自动化测试,得了解一下自动化架构是什么

自动化测试架构是啥&#xff0c;怎么理解自动化测试架构&#xff1f;这个问题&#xff0c;我们可以从以下几点来慢慢说。 架构是什么 软件架构&#xff08;software architecture&#xff09;是一系列相关的抽象模式&#xff0c;用于指导大型软件系统各个方面的设计。 软件架…