Kotlin Compose Multiplatform 跨平台开发实践之加入 iOS 支持
前言
几个月前 Compose Multiplatform 的 iOS 支持就宣布进入了 Alpha 阶段,这意味着它已经具备了一定的可用性。
在它发布 Alpha 的时候,我就第一时间尝鲜,但是只是浅尝辄止,没有做过多的探索,最近恰好有点时间,于是我又重新开始学习 Compose Multiplatform ,并且尝试移植我已有的项目使其支持 iOS,并且将移植过程整理记录了下来,即为本文。
这次移植我选择的依旧是这个使用 Compose 写的计算器项目 calculator-Compose-MultiPlatform 。本来这次我想着移植一个涉及技术稍微多一点的项目的比如这个 githubAppByCompose,但是我仔细研究了一下,毕竟现在 Compose Multiplatform 还处于实验阶段,好多对应的功能和库都还没有,所以只能选择移植前者。
对于这个计算器项目,最开始只是一个使用 Compose 实现的纯 Android 项目,后来移植到了支持 Android 和 桌面 端,所以其实现在再给它添加上 iOS 支持,也算是补齐了最后一个平台了,哈哈。
在开始阅读本文之前,我会假设你已经了解并且知道 Compsoe 的基本使用方法。
为了更好的理解本文,可能需要首先阅读这两篇前置文章:
- 【译】快速开始 Compose 跨平台项目
- Kotlin & Compose Multiplatform 跨平台(Android端、桌面端)开发实践之使用 SQLDelight 将数据储存至数据库
前言的最后看一下运行效果:
Android 端:
ios 端:
桌面端:
开始移植
准备工作
首当其冲,我们需要为 iOS 的支持更改编译配置文件和添加对应的平台特定代码。
在我的这个项目中,我通过以下几个步骤为其添加了对 iOS 的支持:
更改共享代码模块名称
把公用代码模块由 common
改为 shared
,其实这里不用改也行,只是模板配置文件中写的 iOS 使用的公用代码路径是 shared
,但是直接改模块名比改配置文件简单多了,所以我们直接把模块名改了就好了。
改完之后切记要检查一下其他模块引用的名字是否改了,以及注意检查一下包名是否正确。
添加 native.cocoapods 插件
在 shared
模块的 build.gradle.kts
文件的 plugins
增加 native.cocoapods
插件:
plugins {kotlin("native.cocoapods")// ……
}
添加 cocoapods 配置
在 shared
模块的 build.gradle.kts
文件的 kotlin
下增加 cocoapods
相应的配置:
kotlin {// ……iosX64()iosArm64()iosSimulatorArm64()cocoapods {version = "1.0.0"summary = "Some description for the Shared Module"homepage = "Link to the Shared Module homepage"ios.deploymentTarget = "14.1"podfile = project.file("../iosApp/Podfile")framework {baseName = "shared"isStatic = true}extraSpecAttributes["resources"] = "['src/commonMain/resources/**', 'src/iosMain/resources/**']"}// ……
}
配置 iOS 源集
在 shared
模块的 build.gradle.kts
文件的 kotlin
中的 sourceSets
下增加 iOS 的源集配置:
kotlin {// ……sourceSets {// ……val iosX64Main by gettingval iosArm64Main by gettingval iosSimulatorArm64Main by gettingval iosMain by creating {dependsOn(commonMain)iosX64Main.dependsOn(this)iosArm64Main.dependsOn(this)iosSimulatorArm64Main.dependsOn(this)}}
}
添加其他插件
在项目根目录下的 settings.gradle.kts
文件的 pluginManagement
中的 plugins
增加插件配置:
pluginManagement {//……plugins {kotlin("jvm").version(extra["kotlin.version"] as String)// ……}
}
添加 iOS 项目文件
直接把官方模板中的 iosAPP
模块整个目录复制到项目根目录来。
需要注意的是,其实这个 iosAPP
目录并不是一个 idea 模块,而是一个 Xcode 项目。但是目前暂时不需要知道这是什么,只需要把相应的文件整个复制到自己项目中就行了。
然后把官方模版中的 sahred
-> iosMain
文件夹整个复制到 我们项目的 sahred
模块根目录中。
适配代码
在这一节中,主要需要适配的有两种类型的代码:
一是之前就已经在项目中声明了的 expect
函数,需要为 iOS 也加上对应的 actual
函数。
二是需要将原本使用到的 jvm 相关或者说所有使用 java 实现的库和相关代码都需要重新编写或适配。
因为不同于 Android 和 桌面端,kotlin 最终会被编译成 jvm 代码,在 iOS 端,kotlin 会编译成 native 代码,所以所有使用 java 写的代码将无法再使用。
这也就是我前言中说的为啥不选择移植更复杂的项目的原因,就是因为我在其中引用了大量的使用 java 编写的第三方库,而这些第三方库又暂时没有使用纯 kotlin 实现的可用替代品。
下面,我们就开始适配代码。
更改入口
为了保证三端界面一致,我们将原本的UI界面再额外的抽出一个统一的入口函数 APP()
,将其放到 shared
模块的 common
包下:
@Composable
fun APP(standardChannelTop: Channel<StandardAction>? = null,programmerChannelTop: Channel<ProgrammerAction>? = null,
) {val homeChannel = remember { Channel<HomeAction>() }val homeFlow = remember(homeChannel) { homeChannel.consumeAsFlow() }val homeState = homePresenter(homeFlow)val standardChannel = standardChannelTop ?: remember { Channel() }val standardFlow = remember(standardChannel) { standardChannel.consumeAsFlow() }val standardState = standardPresenter(standardFlow)val programmerChannel = programmerChannelTop ?: remember { Channel() }val programmerFlow = remember(programmerChannel) { programmerChannel.consumeAsFlow() }val programmerState = programmerPresenter(programmerFlow)CalculatorComposeTheme {val backgroundColor = MaterialTheme.colors.backgroundSurface(modifier = Modifier.fillMaxSize(),color = backgroundColor) {HomeScreen(homeChannel,homeState,standardChannel,standardState,programmerChannel,programmerState)}}
}
并且,因为不同平台需要差异化实现部分功能,以及目前我还没找到一个好使的支持跨平台的依赖注入库,所以我索性将所有 控制(channel
) 和 状态(state
) 都提升到了最顶层,作为参数传递给下面的 Compose 函数。
然后,更改三端各自的入口函数:
Android (android
模块下的 MainActivity.kt
文件)
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {APP()}}
}
desktop (dektop
模块下的 Main.kt
文件)
fun main() = application {val state = if (Config.boardType.value == KeyboardTypeStandard) {rememberWindowState(size = defaultWindowSize, position = defaultWindowPosition)} else {rememberWindowState(size = landWindowSize, position = defaultWindowPosition)}val standardChannel = remember { Channel<StandardAction>() }val programmerChannel = remember { Channel<ProgrammerAction>() }Window(onCloseRequest = ::exitApplication,state = state,title = Text.AppName,icon = painterResource("icon.png"),alwaysOnTop = Config.isFloat.value,onKeyEvent = {if (isKeyTyped(it)) {val btnIndex = asciiCode2BtnIndex(it.utf16CodePoint)if (btnIndex != -1) {if (Config.boardType.value == KeyboardTypeStandard) {standardChannel.trySend(StandardAction.ClickBtn(btnIndex))}else {programmerChannel.trySend(ProgrammerAction.ClickBtn(btnIndex))}}}true}) {APP()}
}
iOS ( shared
模块 下的 main.ios.kt
文件)
fun MainViewController() = ComposeUIViewController {APP()
}
注意,不同于其他平台,iOS 的入口函数在 shared
模块 中。
当然,你要是想直接改 iosAPP 目录中的代码,那也不是不行,只是对于我们安卓开发来说,还是直接改 shared
更方便点。
实现 iOS 的 平台代码
之前我们的项目中有几个地方的实现依赖于平台,所以写了一些 expect
函数,现在我们需要给 iOS 实现对应的 actual
函数。
首先在 shared
模块的 iosMain
包中创建一个包路径,保持和 commonMain
的 expect
函数包一致:
注意: 包路径一定要一致,不然会编译失败,我就在这里踩了坑,没注意到包名不一样, debug 了好久。
这个项目中的平台差异函数主要有四个:
控制振动:
因为我对 iOS 一窍不通,所以不知道怎么写,索性直接留空了:
actual fun vibrateOnClick() {}actual fun vibrateOnError() {}actual fun vibrateOnClear() {}actual fun vibrateOnEqual() {}
控制屏幕旋转和显示小窗:
这里同上,不知道怎么写,直接留空:
actual fun showFloatWindows() {}actual fun changeKeyBoardType(changeTo: Int) {}
数据库(sqldelight)
actual fun createDriver(): SqlDriver {return NativeSqliteDriver(HistoryDatabase.Schema, "history.db")
}
关于使用 sqldelight
的详细介绍,可以看前言中的前置文章了解。
其实这里这样写是编译不通过的,因为还没加 sqldelight 依赖,下面介绍一下怎么加依赖,这里又是一个大坑。
给 iOS 添加 sqldelight 支持
首先,在 shared
模块下的 build.gradle.kts
文件中的 kotlin
-> sourceSets
-> iosMain
添加 sqldelight
的 驱动依赖:
kotlin {// ……sourceSets {// ……val iosMain by creating {// ……dependencies {implementation("app.cash.sqldelight:native-driver:2.0.0")}}}
}
此时如果你直接 sync gradle 后编译运行,大概率会报错:
Undefined symbols for architecture arm64:
"_sqlite3_bind_text16", referenced from:
_SQLiter_SQLiteStatement_nativeBindString in app(combined.o)
"_sqlite3_bind_int64", referenced from:
_SQLiter_SQLiteStatement_nativeBindLong in app(combined.o)
"_sqlite3_last_insert_rowid", referenced from:
_SQLiter_SQLiteStatement_nativeExecuteForLastInsertedRowId in app(combined.o)
"_sqlite3_reset", referenced from:
_SQLiter_SQLiteConnection_nativeResetStatement in app(combined.o)
"_sqlite3_changes", referenced from:
_SQLiter_SQLiteStatement_nativeExecuteForChangedRowCount in app(combined.o)
_SQLiter_SQLiteStatement_nativeExecuteForLastInsertedRowId in app(combined.o)
"_sqlite3_open_v2", referenced from:
_SQLiter_SQLiteConnection_nativeOpen in app(combined.o)
"_sqlite3_db_config", referenced from:
_SQLiter_SQLiteConnection_nativeOpen in app(combined.o)
"_sqlite3_busy_timeout", referenced from:
_SQLiter_SQLiteConnection_nativeOpen in app(combined.o)
"_sqlite3_trace", referenced from:
_SQLiter_SQLiteConnection_nativeOpen in app(combined.o)
"_sqlite3_bind_parameter_index", referenced from:
_SQLiter_SQLiteConnection_nativeBindParameterIndex in app(combined.o)
"_sqlite3_column_bytes", referenced from:
_SQLiter_SQLiteConnection_nativeColumnGetString in app(combined.o)
_SQLiter_SQLiteConnection_nativeColumnGetBlob in app(combined.o)
"_sqlite3_finalize", referenced from:
_SQLiter_SQLiteStatement_nativeFinalizeStatement in app(combined.o)
"_sqlite3_column_text", referenced from:
_SQLiter_SQLiteConnection_nativeColumnGetString in app(combined.o)
"_sqlite3_column_name", referenced from:
_SQLiter_SQLiteConnection_nativeColumnName in app(combined.o)
"_sqlite3_bind_double", referenced from:
_SQLiter_SQLiteStatement_nativeBindDouble in app(combined.o)
"_sqlite3_profile", referenced from:
_SQLiter_SQLiteConnection_nativeOpen in app(combined.o)
"_sqlite3_close", referenced from:
_SQLiter_SQLiteConnection_nativeClose in app(combined.o)
_SQLiter_SQLiteConnection_nativeOpen in app(combined.o)
"_sqlite3_prepare16_v2", referenced from:
_SQLiter_SQLiteConnection_nativePrepareStatement in app(combined.o)
"_sqlite3_column_type", referenced from:
_SQLiter_SQLiteConnection_nativeColumnIsNull in app(combined.o)
_SQLiter_SQLiteConnection_nativeColumnType in app(combined.o)
"_sqlite3_column_count", referenced from:
_SQLiter_SQLiteConnection_nativeColumnCount in app(combined.o)
"_sqlite3_bind_blob", referenced from:
_SQLiter_SQLiteStatement_nativeBindBlob in app(combined.o)
"_sqlite3_db_readonly", referenced from:
_SQLiter_SQLiteConnection_nativeOpen in app(combined.o)
"_sqlite3_column_int64", referenced from:
_SQLiter_SQLiteConnection_nativeColumnGetLong in app(combined.o)
"_sqlite3_bind_null", referenced from:
_SQLiter_SQLiteStatement_nativeBindNull in app(combined.o)
"_sqlite3_extended_errcode", referenced from:
android::throw_sqlite3_exception(sqlite3*) in app(combined.o)
android::throw_sqlite3_exception(sqlite3*, char const*) in app(combined.o)
"_sqlite3_column_double", referenced from:
_SQLiter_SQLiteConnection_nativeColumnGetDouble in app(combined.o)
"_sqlite3_column_blob", referenced from:
_SQLiter_SQLiteConnection_nativeColumnGetBlob in app(combined.o)
"_sqlite3_step", referenced from:
_SQLiter_SQLiteConnection_nativeStep in app(combined.o)
_SQLiter_SQLiteStatement_nativeExecute in app(combined.o)
_SQLiter_SQLiteStatement_nativeExecuteForChangedRowCount in app(combined.o)
_SQLiter_SQLiteStatement_nativeExecuteForLastInsertedRowId in app(combined.o)
"_sqlite3_clear_bindings", referenced from:
_SQLiter_SQLiteConnection_nativeClearBindings in app(combined.o)
"_sqlite3_errmsg", referenced from:
android::throw_sqlite3_exception(sqlite3*) in app(combined.o)
android::throw_sqlite3_exception(sqlite3*, char const*) in app(combined.o)
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
这是因为 ios 的 Xcode 项目没有添加 sqlite 依赖,我们还需要为 ios 单独添加 sqlite 依赖。
ios 使用的是 cocoapods 进行依赖管理,我们需要使用 pod 添加依赖。
我们有两种选择:
一是在 shared
模块的 build.gradle.kts
中相应的位置添加 pod 依赖配置。
二是直接在 pod 配置文件中添加。
这里我们就选择直接改 pod 的配置文件。
打开项目根目录下的 iosAPP
目录中的 Podfile
文件,在其中添加 sqlite3 依赖:
target 'iosApp' do# ……pod 'sqlite3', '~> 3.42.0'# ……end
添加完记得需要 sync 一下 gradle。
此时再编译运行,大概率还是会报错:
ld: file not found: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphonesimulator.a
不用担心,再在刚才的配置文件中加上这么一段:
# iosApp's podfile
post_install do |installer|installer.pods_project.targets.each do |target|target.build_configurations.each do |config|config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.1'endend
end
此时应该就不会有任何问题了。
适配 jvm 相关代码
正如我们在上一节所说,由于 iOS 使用 native 代码,所以项目中就不能再使用 java 代码,包括引用的第三方库也是。
在我这个项目中涉及到需要适配的主要有两个地方。一个是进制转换时使用到了 java 的 Long
类的方法;另一个就是运算时使用的是 BigInteger
和 BigDecimal
。
进制转换
之前的代码使用的是 java 中的 java.lang.Long.toXXXString
。
这里适配起来其实很简单,要么自己使用 kotlin 实现一个进制转换工具类,要么就像我一样,直接把 Long.java 中需要的部分 CV 一下,然后使用 Android studio 的 java 转 kotlin 一键转换就行了。
下面就是我转好的工具类:
package com.equationl.common.utilsimport kotlin.math.maxobject LongUtil {val digits = charArrayOf('0', '1', '2', '3', '4', '5','6', '7', '8', '9', 'a', 'b','c', 'd', 'e', 'f', 'g', 'h','i', 'j', 'k', 'l', 'm', 'n','o', 'p', 'q', 'r', 's', 't','u', 'v', 'w', 'x', 'y', 'z')fun toBinaryString(i: Long): String {return toUnsignedString0(i, 1)}fun toHexString(i: Long): String {return toUnsignedString0(i, 4)}fun toOctalString(i: Long): String {return toUnsignedString0(i, 3)}fun toUnsignedString0(`val`: Long, shift: Int): String {// assert shift > 0 && shift <=5 : "Illegal shift value";val mag: Int = Long.SIZE_BITS - numberOfLeadingZeros(`val`)val chars: Int = max((mag + (shift - 1)) / shift, 1)//if (COMPACT_STRINGS) {val buf = ByteArray(chars)formatUnsignedLong0(`val`, shift, buf, 0, chars)return buf.map { it.toInt().toChar() }.toCharArray().concatToString()
// } else {
// val buf = ByteArray(chars * 2)
// java.lang.Long.formatUnsignedLong0UTF16(`val`, shift, buf, 0, chars)
// return String(buf, UTF16)
// }}private fun formatUnsignedLong0(`val`: Long,shift: Int,buf: ByteArray,offset: Int,len: Int) {var `val` = `val`var charPos = offset + lenval radix = 1 shl shiftval mask = radix - 1do {buf[--charPos] = digits[`val`.toInt() and mask].code.toByte()`val` = `val` ushr shift} while (charPos > offset)}fun numberOfLeadingZeros(i: Long): Int {val x = (i ushr 32).toInt()return if (x == 0) 32 + numberOfLeadingZeros(i.toInt()) else numberOfLeadingZeros(x)}fun numberOfLeadingZeros(i: Int): Int {// HD, Count leading 0'svar i = iif (i <= 0) return if (i == 0) 32 else 0var n = 31if (i >= 1 shl 16) {n -= 16i = i ushr 16}if (i >= 1 shl 8) {n -= 8i = i ushr 8}if (i >= 1 shl 4) {n -= 4i = i ushr 4}if (i >= 1 shl 2) {n -= 2i = i ushr 2}return n - (i ushr 1)}
}
然后更改我们的代码中使用到的地方即可,例如:
Long.toBinaryString
改为 LongUtil.toBinaryString(long)
。
记得把导入的包也改了:
import java.lang.Long
改为 import com.equationl.common.utils.LongUtil
当然,如果你的工具类直接取名叫 Long
的话,那么调用代码就不用改了,改导入包就行了。
BigInteger 和 BigDecimal
接下来就是 BigInteger 和 BigInteger,同样的思路,我们可以选择自己使用 kotlin 写一个功能相同的工具类,但是显然,这两个类可不同于进制转换,它涉及到的代码量可要大多了。
好在已经有大神写好了纯 kotlin 的支持跨平台的 BigInteger 和 BigDecimal: kotlin-multiplatform-bignum 。我们只需要简单的引用它就可以了。
在 shared
模块下的 build.gradle.kts
文件中的 kotlin
-> sourceSets
-> commonMain
-> dependencies
添加依赖
kotlin {sourceSets {val commonMain by getting {dependencies {implementation("com.ionspin.kotlin:bignum:0.3.8")}}}
}
sync gradle 后,依次修改项目中使用到 BigInteger 和 BigDecimal 地方的代码即可。
需要注意的是,这个库的 API 和 java 的 BigInteger 以及 BigDecimal 并非完全一致,因此需要我们逐个检查并修改。
例如,在 java 的 BigDecimal 中,除法的 API 是: divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
而在这个库中则变为了 divide(other: BigDecimal, decimalMode: DecimalMode? = null)
除此之外,还有一些小地方的代码可能引用的是 java 代码,这里就不再赘述了,按照上述两种思路逐个适配即可。
总结
自此,我们的项目就完全移植到了完整形态的 Compose Multiplatform 中了!现在它已经完全支持 Android、iOS 和 desktop 了!
不知道你们有没有发现,在全文中,我几乎都是在说怎么适配和移植逻辑代码,并没有说到有关 UI 的代码。
哈哈,不是因为我忘记说了,而是因为 Compose Multiplatform 代码真的做到了一套代码,多平台通用。新增加 iOS 支持完全不用动 UI 部分的代码。
完整项目代码: calculator-Compose-MultiPlatform
相关文章:

Kotlin Compose Multiplatform 跨平台开发实践之加入 iOS 支持
前言 几个月前 Compose Multiplatform 的 iOS 支持就宣布进入了 Alpha 阶段,这意味着它已经具备了一定的可用性。 在它发布 Alpha 的时候,我就第一时间尝鲜,但是只是浅尝辄止,没有做过多的探索,最近恰好有点时间&…...

【小黑嵌入式系统第四课】嵌入式系统硬件平台(二)——I/O设备、通信设备(UARTUSB蓝牙)、其他(电源时钟复位中断)
上一课: 【小黑嵌入式系统第三课】嵌入式系统硬件平台(一)——概述、总线、存储设备(RAM&ROM&FLASH) 文章目录 一、I/O设备1. 定时器/计数器2. ADC和DAC3. 人机接口设备3.1 键盘3.2 LCD显示器3.3 触摸屏 二、通信设备1. 通…...

报错:AttributeError: module ‘tensorflow‘ has no attribute ‘flags‘
改成如下: 报错原因:tensorflow1.x与2.x版本问题不兼容...

Android--Retrofit2执行多个请求任务并行,任务结束后执行统一输出结果
场景:后端上传文件接口只支持单个文件上传,而业务需求一次性上传多个图片,因此需要多个上传任务并发进行,拿到所有的返回结果后,才能进行下一个流程。 1、使用Java并发工具 private List<Response<JSONObject>…...
面试算法30:插入、删除和随机访问都是O(1)的容器
题目 设计一个数据结构,使如下3个操作的时间复杂度都是O(1)。 insert(value):如果数据集中不包含一个数值,则把它添加到数据集中。remove(value):如果数据集…...

Qt/C++开源作品45-CPU内存显示控件/和任务管理器一致
一、前言 在很多软件上,会在某个部位显示一个部件,专门显示当前的CPU使用率以及内存占用,方便用户判断当前程序或者当前环境中是否还有剩余的CPU和内存留给程序使用,在不用打开任务管理器或者资源查看器的时候直接得知当前系统的…...

win32汇编-使用子程序
当程序中相同功能的一段代码用得比较频繁时,可以将它分离出来写成一个子程序,在主程序中用call指令来调用它。这样可以不用重复写相同的代码, 仅仅用call指令就可以完成多次同样的工作了。Win 32汇编中的子程序也采用堆栈来传递参数ÿ…...

【论文阅读】 Cola-Dif; An explainable task-specific synthesis network
文章目录 CoLa-Diff: Conditional Latent Diffusion Model for Multi-modal MRI SynthesisAn Explainable Deep Framework: Towards Task-Specific Fusion for Multi-to-One MRI Synthesis CoLa-Diff: Conditional Latent Diffusion Model for Multi-modal MRI Synthesis 论文…...

ShareMouse for Mac(多台电脑鼠标键盘共享软件)
ShareMouse mac版是一款Mac平台上可以在多台电脑间共享鼠标的工具软件,sharemousefor Mac支持 Windows 与 Mac,并可以在不同电脑间共享剪贴板。只需要移动鼠标指针的到想控制的显示器那里去、鼠标光标就会神奇地“跨越”到邻近的电脑屏幕上。每个计算机都…...

中文编程开发语言工具开发案例:多种称重方式编程实际例子
中文编程开发语言工具开发案例:多种称重方式编程实际例子 上图为 计价秤,使用串口通讯线连接电脑的主机,软件自动读取称的重量,自动计算金额。这种方式称重快速,不需再打印条码。 上图这个称重方式为 一体称称重&#…...
国密sm2的Vue、Python、Java互通使用
目录 一、Vue 二、Python 三、Java 一、Vue # npm install --save sm-cryptoimport {sm2} from sm-crypto const cipherMode 1 const private_key d9d37f4f46e8514c6f9398a984e74f3eead994e8f4ac5f92e5deb313cb5ad6a6 const public_key 04 e332ee43ac37be458550652fb9…...

如何通过SK集成chatGPT实现DotNet项目工程化?
智能助手服务 以下案例将讲解如何实现天气插件 当前文档对应src/assistant/Chat.SemanticServer项目 首先我们介绍一下Chat.SemanticServer的技术架构 SemanticKernel 是什么? Semantic Kernel是一个SDK,它将OpenAI、Azure OpenAI和Hugging Face等大…...
DRM中render-node编号的分配
DRM系统 DRM是direct rendering manager的简称。DRM是linux kernel中与负责video cards功能的GPU打交道的子系统。DRM给出了一组API,可以供用户程序来发送命令和数据给GPU设备从而来控制比如display、render等功能。 render-node由来 在以前,DRM子系统…...

将输入对象转换为数组数组的维度大于等于1numpy.atleast_1d()
【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 将输入对象转换为数组 数组的维度大于等于1 numpy.atleast_1d() 选择题 使用numpy.atleast_1d()函数,下列正确的是? import numpy as np a1 1 a2 ((1,2,3),(4,5,6)) print("…...
js 删除树状图无用数据,如果子级没有数据则删除
有一个需求,当你从后端拿到一个树状图的时候,有些子级没数据,这时就需要我们处理一下数据,当然了,如果第一层底下的第二层没数据,第二层底下的所有都没数据,那这一层都不需要。 我的写法&#x…...

Docker 容器化(初学者的分享)
目录 一、什么是docker 二、docker的缺陷 三、简单的操作 一、首先配置一台虚拟机 二、安装Docker-CE 一、安装utils 二、 将 Docker 的软件源添加到 CentOS 的 yum 仓库中。这样可以通过 yum 命令来安装、更新和管理 Docker 相关的软件包。 三、将 download.docker.co…...
LCS 01.下载插件
题目来源: leetcode题目,网址:写文章-CSDN创作中心 解题思路: 假设需要 n 分钟下载插件,前 n-1 分钟将带宽加倍,最后一分钟下载时总时间最少。 解题代码: class Solution { public:int l…...

架构-设计原则
1、面向对象的SOLID 1.1 概述 SOLID是5个设计原则开头字母的缩写,其本身就有“稳定的”的意思,寓意是“遵从SOLID原则可以建立稳定、灵活、健壮的系统”。5个原则分别如下: Single Responsibility Principle(SRP)&am…...

在 Python 3 中释放 LightGBM 的力量:您的机器学习大师之路
机器学习是 Python 占据主导地位的领域,它一直在给全球各行各业带来革命性的变化。要在这个不断变化的环境中脱颖而出,掌握正确的工具是关键。LightGBM 就是这样一个工具,它是一个强大且快速的梯度提升框架。在这份综合指南中,我们将通过实际示例和示例数据集从基础知识到高…...

Spring学习笔记(2)
Spring学习笔记(2) 一、Spring配置非定义Bean1.1 DruidDataSource1.2、Connection1.3、Date1.4、SqlSessionFactory 二、Bean实例化的基本流程2.1 BeanDefinition2.2 单例池和流程总结 三、Spring的bean工厂后处理器3.1 bean工厂后处理器入门3.2、注册Be…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...