基于Kotlin Multiplatform的鸿蒙跨平台开发实践

一、 背景

在 2023 年的华为开发者大会(HDC)上,华为预告了一个全新的鸿蒙系统 Harmony Next 版本。与之前的鸿蒙系统不同,Harmony Next完全摒弃了对 AOSP 的兼容,彻底基于 OpenHarmony 开源鸿蒙实现。这意味着该系统将仅支持鸿蒙原生应用,Android 应用将不再允许在其上运行。

华为用户在哔哩哔哩的用户生态中一直占据着较大的比例。为了提供更好的用户体验,支持更多的应用生态,哔哩哔哩在去年年底启动了哔哩哔哩鸿蒙原生应用的开发。在对 Harmony Next 系统进行初步调研后,我们发现其从开发语言到运行环境到开发方式,都与 Android 平台完全不同。适配 Harmony Next 就意味着重新开发一个独立的 App 端,无论是短期开发还是长期迭代,这都是一件成本极高的事情。于是我们面临一个问题:是否有跨平台开发的手段来复用现有生态的代码,从而减少开发成本?

二、 方案选择

移动端的跨平台技术一直以来都是热门话题,原因在于移动端的原生开发存在着以下的几个问题:

  1. 编码成本高:相同的业务逻辑需要在多端(iOS,Android 以及未来的 HarmonyOS)用不同的语言各自实现一遍。受限于各端编程语言及系统 API 的差异,业务逻辑逻辑难以做到完全一致。同样的业务逻辑仅靠口头上或文档上的方案对齐,导致最终代码实现上可能会有较大差别。这种业务逻辑实现的差异也会使多端体验上存在较大差别,数据不一致性导致难以解释其合理性(交互体验、收益回顾)。

  2. 后期迭代维护、测试成本高:UI 代码没有很好地和业务逻辑代码解耦合,导致业务逻辑代码复用困难,不方便做单元测试,组件间的循环依赖增多。业务逻辑变更需要需要各端研发对齐并各自实现,这无意间也增加了大量隐性的沟通成本。测试的回归验证也需要在各端分别完成。

从研发成本的角度看,研发希望以更低的成本在多个平台上发布应用程序;采用一套代码可以减少多端业务逻辑的差异,提升多端体验的一致性。

2.1、什么是 Kotlin Multiplatform?

Kotlin Multiplatform(以下简称 KMP) 是由 JetBrains 开发的基于 Kotlin 语言的跨平台开发解决方案。KMP 允许开发者使用一套 Kotlin 代码来构建适用于多个平台的应用程序,包括移动端应用、前端、后端服务和嵌入式系统等。

2.2、KMP 实现原理

KMP 基于 Kotlin K2 编译器,采用多阶段编译架构,其核心包括编译前端和编译后端两个部分:

1.  编译前端:

  • 语法解析:将 Kotlin 源代码解析成抽象语法树(AST)。

  • 语义分析:对 AST 进行语义检查,确保代码符合 Kotlin 语言规范,并进行类型推断和检查。

  • 中间表示(IR)生成:将 AST 转换为中间表示,便于不同编译后端进行进一步处理。

2.  编译后端:

  • JVM 后端:将中间表示转换为 JVM 字节码,以运行在 JVM 平台上(如 Android)。

  • Native 后端:将中间表示转换为 LLVM bitcode,再通过 LLVM 工具链生成适用于不同平台(如 iOS、Linux、Windows 等)的本地二进制代码。

  • JavaScript 后端:将中间表示转换为 JavaScript 代码,以运行在浏览器或 Node.js 环境中。

  • WebAssembly 后端:将中间表示转换为 WebAssembly 代码,以在浏览器或其他支持 WebAssembly 的环境中运行。

KMP 通过多平台模块(Multiplatform Modules)来组织跨平台代码。多平台模块由以下三种部分组成:

1.  Common Module:

  • 包含跨平台的共享代码和 API 定义。

  • 使用 expect 关键字声明平台特定的 API 接口。

2.  Platform-specific Module:

  • 实现各个平台特定的代码和 API。

  • 使用 actual 关键字实现 expect 声明的接口。

3.  Intermediate Module(可选):

  • 提供中间层,包含部分跨平台代码和部分平台特定代码,用于简化平台特定模块的实现。

expect / actual 机制是 KMP 实现平台特定代码的关键。它允许在 common 模块中声明平台特定的接口,并在各个平台特定模块中提供具体实现。

  • expect:在 common 模块中声明需要在不同平台实现的接口或类。

// commonMain
expect val Platform: String
  • actual:在平台特定模块中提供具体实现。

// androidMain
actual val Platform: Stringget() = "Android"// iosMain
actual val Platform: Stringget() = "iOS"

KMP 支持在 common 模块中编写跨平台通用代码,并在平台特定模块中复用这些代码。通过 expect / actual 机制,开发者可以在 common 模块中定义接口和公共逻辑,在平台特定模块中实现特定平台的功能。

2.3、为什么选择 KMP?

跨平台开发一直以来存在以下几个问题:

  1. 平台差异性:不同平台的接口、UI 界面的开发方式和工程架构存在极大差异,开发者需要处理这些差异以保证代码在不同平台上正常运行。

  2. 学习及集成难度:开发者需要学习理解不同的开发环境和相关工具链,学习曲线较为陡峭,快速上手难度较大。

目前行业内使用率最高的两个跨平台方案是 React Native 和 Flutter。虽然它们在设计及原理上有很大区别,但为了减小平台差异性带来的问题,它们在设计思想上都是在平台框架之上搭建一个自己的 Runtime 环境,并采用非原生开发语言开发,与原生开发语言交互时需要编写特殊的桥接层来实现。当所依附平台框架发生变更时,需要双方模块架构共同协调处理。

而 KMP 则与这种设计思路不同,它并不会创建一个自己的运行环境,而是将 Kotlin 代码编译为平台对应语言的代码或机器码,让平台可以直接执行。从这个角度看,React Native 和 Flutter 更适合包含 UI 层的跨平台开发,而 KMP 则更适合纯逻辑层的跨平台开发。

对于鸿蒙 App 来说,我们的需求是尽量复用逻辑层的代码,UI 层更多的跟随原生,以提供更好的性能和设备适配性,较低的上手学习成本。因此,我们最终选择了 KMP 来进行跨平台尝试。

三、 KMP 在鸿蒙 App 中的应用

我们先来看一下鸿蒙支持哪些语言进行原生开发。鸿蒙官方推荐的开发方式使用的主要开发语言是 ArkTS,同时也支持 JavaScript、TypeScript 开发代码逻辑,并且以 napi 的形式提供了与 C 的互操作能力。所以理论上只要能通过 Kotlin 生成 JS 或者 C 的编译产物,都可以实现使用 Kotlin 代码开发鸿蒙。

前面已经提到过,本着给用户带来最优的交互体验,哔哩哔哩鸿蒙原生应用使用官方推荐的 ArkUI 来实现,而 ArkUI 使用的是 ArkTS 作为开发语言。ArkTS 支持与 JS / TS 高效的互操作,这使我们很容易想到将 Kotlin 代码编译成为 JS,无缝的衔接到整个 ArkTS 的生态中。

3.1、Kotlin 与 JavaScript 的数据结构

下面的表格是 Kotlin 类型在 JS 中的映射。

我们可以看到,在 Kotlin 和 JS 里,大部分常用的类型都能找到对应的映射关系。

但是,Kotlin 作为一门静态类型的语言,光有上面的这些类型,还是难以满足其与一些动态类型语言的互操作能力。

3.2、Dynamic 类型

Kotlin 中的 dynamic 类型主要用于与动态语言(尤其是 JavaScript)进行互操作。使用 dynamic 类型,Kotlin 代码可以与没有静态类型定义的 JavaScript 代码交互。这在使用没有类型定义的 JavaScript 库和 API 时尤其有用。

主要特点:

  • 无类型检查:使用 dynamic 时,Kotlin 在编译时不会进行类型检查。所有类型检查都推迟到运行时。

  • 互操作性:dynamic 类型主要用于 Kotlin/JS,允许将 Kotlin 编译成 JavaScript 并在 JavaScript 环境中运行。

  • 灵活性:可以在 dynamic 对象上调用方法和访问属性,而无需编译时的类型安全。

fun whatIsDynamic(dyn: dynamic) {dyn.doSomething()println(dyn.someProp)
}

在上述示例中:

  • 在 dyn(类型为 dynamic)上调用 doSomething()。

  • 访问 dyn 的 someProp 属性,无编译时类型检查。

注意事项:

  • 安全性:由于 dynamic 绕过了 Kotlin 的类型系统,因此应谨慎使用。不当使用可能导致运行时错误。

  • 性能:由于类型检查在运行时进行,与静态类型的代码相比,使用 dynamic 会影响性能。

  • 代码维护:使用 dynamic 的代码由于缺少类型信息,可能更难维护和理解。

再看下面这个例子:

@JsExport
fun isDynamic() {val dyn: dynamic = js("{}")dyn["a"] = "A"console.log(JSON.stringify(dyn.a))
}

这是 Kotlin 代码。

​​​​​​​

function isDynamic() {var dyn = {};dyn['a'] = 'A';console.log(JSON.stringify(dyn.a));
}

这是编译生成的 JS 代码。

可以看到通过 dynamic 类型,Kotlin 可以很轻松的与 JavaScript进行交互。

3.3、Kotlin 协程

Kotlin 协程是一种用于简化异步编程的强大工具。它们能够使异步代码看起来像同步代码,从而提高代码的可读性和可维护性。

使用协程的优势:

  • 简化异步代码:将回调和异步任务简化为顺序代码,使代码更加直观。

  • 提高可读性:消除回调地狱,代码结构更加清晰。

  • 便于错误处理:使用标准的 try/catch 块处理异常,而不是在每个回调中处理错误。

在 JS 中,Promise 是 ES6 引入用于处理异步操作的工具。在 Kotlin/JS 中,Kotlin 协程可以与 JS 的 Promise 无缝操作,这使得无论是 Kotlin 调用 JS 的异步操作,还是 JS 调用 Kotlin 的异步操作都变得非常的简单。

Promise -> suspend fun

通过 Promise 的扩展方法 await,可以很方便的将一个 JS Promise 转化成 Kotlin 的 suspend fun。

​​​​​​​

suspend fun fetchDataFromJS(): String {val promise = js("fetchSomeData()") as Promise<String>return promise.await()
}

上图就是一个简单的从 Kotlin 调用 JS 的 fetchSomeData() 的异步方法,并将其转化为一个 suspend fun 的例子。

suspend fun -> Promise

通过 CoroutineScope 的扩展方法 promise,可以很方便的将一个 suspend fun 转化成 JS Promise。

suspend fun fetchData(): String {delay(1000)    // 模拟异步操作return "Hello from Kotlin/JS!"
}@JsExport
fun fetchDataAsPromise(): Promise<String> {return MainScope().promise {fetchData()}
}

上图就是一个简单的例子,Kotlin 中模拟实现一个用协程完成的异步操作,并将其转为 JS Promise,同时将这个 fetchDataAsPromise 导出。

export declare function fetchDataAsPromise(): Promise<string>;

这个就是 Kotlin 编译生成的 .d.ts 声明文件中刚才导出的 fetchDataAsPromise。

function fetchDataAsPromise() {    var tmp = MainScope();    return promise(tmp, VOID, VOID, fetchDataAsPromise$slambda_0(null));}
function fetchDataAsPromise$slambda_0(resultContinuation) {    var i = new fetchDataAsPromise$slambda(resultContinuation);    var l = function ($this$promise, $completion) {        return i.invoke_t04clr_k$($this$promise, $completion);    };    l.$arity = 1;    return l;}

这个就是 Kotlin 编译生成 JS 中 fetchDataAsPromise 的实现,可以看到因为有使用到协程,整个编译出来的 JS 产物还是比较晦涩难懂的,这里就不展开分析了。

3.4、如何在 Kotlin 代码中使用鸿蒙 API

Harmony OS SDK 通过 .d.ts 的声明文件向外提供了系统 API。

如果想要在 Kotlin 代码中直接使用系统 API,需要将 .d.ts 的声明文件转化为 Kotlin 的声明文件。

external关键字

通过使用 external 关键字来声明在外部环境中定义的函数。

我们以 hilog 为例:

这是 hilog 的 .d.ts 的声明。

这是对应转化成的 Kotlin 的声明。

可以看到,通过 @JsModule 来制定这个该 Kotlin 声明对应的是 @ohos.hilog 模块,模块中的 hilog namespace 通过 external object 来声明。

Kotlin 官方提供了 Dukat 工具(https://github.com/Kotlin/dukat)帮助我们方便快速的进行 .d.ts 到 Kt 的转换。

同时还有一些三方工具,如 karakum(https://github.com/karakum-team/karakum),也提供了将 .d.ts 转换成 Kt 的能力。

在实际的使用过程中,由于目前 Jetbrains 官方团队并没有精力放在 Dukat 上,在 ESModule 的模式下几乎不可用,导致我们最后选择使用 karakum 来进行 Harmony OS API 的自动生成。

karakum 目前对 .d.ts 声明中的一些边界问题处理的比较糟糕,所以当前,我们还需要对生成的 Kt 手动的做一些边界 case 的处理:

  • karakum夹带了一些私货 seskar(https://github.com/turansky/seskar) ,因此我们需要对 seskar 中所有的sugar进行注释操作。

  • 未知类型的处理,例如 Object, BigInt Readonly等。karakum 使用了私有的这些对象的定义,我们需要擦除类型改为Any。

  • 部分类型例如 Uint8Array 的 import 缺失 Kotlin import org.khronos.webgl.*。

  • typealias 的处理,由于 Kotlin/JS Module 并不支持 external 中内嵌 typealias,karakum默认实现是会定义在对应的 .d.ts 的层级里,我们需要把 typealias 提到当前文件顶层。

  • 带默认值的范型处理,在 TypeScript 中可以对范型定义默认类型例如 export interface AsyncCallback,但是 Kotlin 中并没有对应的能力,所以我们需要把范型根据范型个数展开,并且在使用的地方进行替换。

  • 由于 Kotlin 映射了 Error 和 Throwable 的关系,同时 Kotlin 的 Error 是具体的 type,所以我们需要对 Error 进行特殊处理,擦除 Error 的类型,并实现辅助方法来通过 dynamic 获取到对应的属性。

  • 在同时使用 union-types 和 literal-types 时,例如 on(type: 'connect' | 'close'): void; 由于 Kotlin 没有联合类型会导致生成 duplicate 的方法定义。

  • 与kotlin built-in 冲突,例如 fun toString(): String 需要修改成 override toString(): String。

最终的流程如下:

3.5、如何在鸿蒙 App 使用 Kotlin 代码

KMP 在 鸿蒙版 bilibili 的开发流程大致如下图所示:

  1. 首先找到需要使用的 API 的声明文件,并通过 karakum将其转化为 .kt 的声明,导入 KMP 工程。

  2. 编写 Kotlin 逻辑代码,并通过 @JsExport 暴露鸿蒙项目所以要使用的接口。

  3. 通过 Kotlin/JS IR compiler 将 KMP 项目中的 kotlin 代码编译成为 .js + .d.ts。

  4. 将编译产物 .js + .d.ts 导入鸿蒙项目中。

  5. 在鸿蒙项目中就可以愉快的使用 @JsExport 导出的相关代码了。

目前鸿蒙版 bilibili 架构大致如下图:

图片

其中 Framework 层大部分代码均使用 KMP 实现,包括但不限于上图列出的模块。通过使用 KMP 使得大量的基础框架逻辑代码得以在多端复用,从而提升了大量开发效率,并降低了后期的维护成本。

3.6、遇到的一些问题

调试成本有所上升

虽然 DevEco Studio 支持对 js 文件的断点调试功能,但是由于没有办法将 js 代码直接映射到 kt 代码上,这就会增加一些调试的成本。同时,在使用了 kotlin 协程后,KMP 生成的 js 代码会变得异常的”难懂“。

目前更多的只能通过日志大法,和 js 断点来进行调试,虽然没有直接调试 kt 代码来的直观,但是也能够满足日常的开发需要。

三方库的一些坑

在使用一些三方库(诸如 Ktor 等)的时候,发现大部分的 KMP 项目中的 jsMain 默认是被运行在 Node 或者 Browser 环境中的,这就导致了一些三方库的实现代码中,会去访问一些诸如 Window、TextEncoder、TextDecoder 等只有在 Web 或 Node 环境特有的 API。而这些 API 在鸿蒙环境中是不存在的。直接在鸿蒙项目中引入这些三方库,可能在运行时会产生一些非预期的错误。

对于这个问题目前的解决办法是检查三方库的 jsMain 中是否使用了一些鸿蒙环境中不支持的 API,看看有没有办法可以尽可能的不使用这些 API,或者将这些 API 替换成为鸿蒙支持的 API。

编译出的 .js 文件过大

目前 KMP 项目中所有的代码都会导出到一个 .js 文件中,这就使得整个 .js 产物变得比较大,整个产物有 10m 多。

图片

而目前 Ark runtime 在执行超大 js 文件时,可能存在一些性能偏慢的情况。

Kotlin 代码难以使用 ArkTS 提供的多线程能力

Ark runtime 的多线程主要是通过 Actor 并发模型实现的,线程间内存不共享,通过序列化和反序列化消息的方式进行线程间的同步。

ArkTS 虽然也提供了基于 Sendable 协议的可共享对象体系,但也仅能在 .ets 文件中使用,难以在 KMP 中实现基于 Sendable 协议的数据类型。

这就导致 KMP 代码在处理一些 CPU 密集的逻辑时,会产生一定的卡顿。

3.7、有没有更好的办法

在上文中提到了,虽然鸿蒙官方推荐使用 ArkTS 作为鸿蒙 APP 的开发语言,但本身鸿蒙也支持使用 Native (C / C++)进行开发。如果将 KMP 代码直接编译生成为 Native 的产物,是不是上面提到的几个问题就会变得比较容易解决。

Native 代码的运行不依赖 Ark runtime,这也就减少了由 Ark runtime 所带来的一些额外的性能开销。

同时在前文提到的多线程问题,在 Native 的世界里也将变得非常容易处理,无论是使用鸿蒙 NDK 中提供的 libuv、ffrt,亦或是直接使用 posix 的 pthread,都可以很轻易的实现多线程异步处理逻辑。

关于 Kotlin Native 这部分的进展,目前已经实现了基于 Kotlin 2.0 的 KN Compiler 对 Harmony Target 的支持。同时也完成了部分 kotlinx 中部分 library (如 coroutines 等)在鸿蒙平台上的适配。相信在不久的将来就可以正式投入实际生产过程中了。

四、 总结

到目前为止,我们已完成了视频内容生态基础体验相关的开发,并发布至鸿蒙原生应用 Beta 市场。欢迎有资格的同学下载体验。

通过这次实践,我们验证了 Kotlin Multiplatform 在鸿蒙应用开发中的可行性。它不仅实现了代码的跨平台复用,降低了开发成本,还提升了开发效率,为我们提供了一个高效的开发工具链。未来,我们将继续探索和优化这一方案,为用户带来更优质的应用体验。

-End-

作者丨Vicky的饲养员、狒狒

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

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

相关文章

在idea中的git选择某一次记录拉出一个新分支

一 创建新分支 1.1 操作步骤 需求&#xff1a;需要在图中标红的历史记录&#xff0c;从此记录拉出一个分支 1.右键【new branch】 2.起一个新的名字&#xff1a; 3.新分支代码

《图解设计模式》笔记(四)分开考虑

九、Bridge模式&#xff1a;将类的功能层次结构与实现层次结构分离 类的两个层次结构和作用 类的功能层次结构&#xff1a;希望增加新功能时 父类有基本功能&#xff0c;在子类中增加新功能 Something父类 …├─SomethingGood子类 想要再增加新功能 Something父类 …├─So…

LeetCode.55.跳跃游戏(贪心算法思路)

题目描述&#xff1a; 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 输…

Docker容器镜像及其打包

容器镜像分类 1. 系统类镜像 2. 应⽤镜像 搜索镜像 # 默认docker.hub docker search centos 下载镜像 docker pull centos 默认下载最新版本 1. 打包 [rootdocker001 ~]# systemctl start docker.service [rootdocker001 ~]# docker save -o centos.tar centos:latest [root…

基于SpringBoot的线上教学平台系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言 Java 数据库 MySQL 技术 SpringBoot框架&#xff0c;Java语言 工具 IDEA/Eclipse、Navicat、Maven 系统展示 首页 管理员功能模块 学员功能模块 前台首页…

Ozon在奥伦堡州开设首个配送中心,Ozon还有机会赚钱吗?

Ozon平台成立于1998年&#xff0c;是俄罗斯唯一上市的B2C电商平台&#xff0c;在俄罗斯电商市场中占据着到达62%的市场份额&#xff0c;具有强大的市场影响力和吸引力。Ozon拥有数千万的活跃用户&#xff0c;覆盖了俄罗斯各个年龄段和消费层次的群体&#xff0c;而且Ozon拥有俄…

“精准学”官宣将公布中国首个语音端到端大模型

教育科技公司“精准学”宣布&#xff0c;公司已在AI语音交互技术上取得领先性的突破&#xff0c;成功训练了中国首个语音端到端大模型“心流知镜-s(V02)”&#xff0c;可直接实现语音输入-语音输出的交互&#xff0c;使其更适配辅学场景&#xff0c;使大模型达到“真人老师”级…

当《黑神话:悟空》遇上openKylin,国产力量的极致碰撞!

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 万众瞩目的国产3A游戏巨作《黑神话&#xff1a;悟…

【人工智能】如何在白嫖的阿里云PAI平台上跑模型?

在“交互式建模&#xff08;DSW&#xff09;”中新建实例&#xff0c;阿里云自带的示例镜像是很少的&#xff0c;所以我们只需要筛选出适合你的项目的CUDA版本就好。DSW实例可以看作是一个Linux虚拟机&#xff0c;之后我们在实例中新建另一个Python环境使用即可。 新建完实例后…

DevExpress中Blazor部分学习

DevExpress中Blazor学习 1 DevExpress版本2 学习步骤2.1 查看Dev相应的Demo2.2 创建第一个相关应用2.3 使用XPO进行相关数据操作2.4 Dev Blazor使用XPO操作 3 学习中遇到问题及解决方案3.1 打开Dev相关Demo报错 1 DevExpress版本 安装较新的DevExpress&#xff0c;我这边使用的…

基于FreeRTOS的STM32多功能手表

前言 项目背景 项目演示 使用到的硬件 项目原理图 目前版本实现的功能 设计到的freertos知识 实现思路 代码讲解 初始化GPIO引脚、配置时钟 蜂鸣器初始化以及软件定时器创建 系统默认创建的defaultTaskHandle 创建七个Task&#xff0c;代表七个功能 ShowTimeTask …

京东软件测试岗面试题(干货)含答案+文档

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 前面看到了一些面试题&#xff0c;总感觉会用得到&#xff0c;但是看一遍又记不住&#xff0c;所以我把面试题都整合在一起&#xff0c;都是来自各路大佬的分享&am…

自然语言处理系列三十三》 语义相似度》同义词词林》算法原理

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》&#xff08;人工智能科学与技术丛书&#xff09;【陈敬雷编著】【清华大学出版社】 文章目录 自然语言处理系列三十三同义词词林算法原理代码实战 总结 自然语…

LLama 3 跨各种 GPU 类型的基准测试

2024 年 4 月 18 日&#xff0c;AI 社区对 Llama 3 70B 的发布表示欢迎&#xff0c;这是一款最先进的大型语言模型 &#xff08;LLM&#xff09;。该型号是 Llama 系列的下一代产品&#xff0c;支持广泛的用例。该模型 istelf 在广泛的行业平台上表现良好&#xff0c;并提供了新…

基于STM32开发的智能室内照明系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化光照强度监测与处理照明控制与状态指示Wi-Fi通信与远程控制应用场景 智能家居照明管理办公室和公共场所的智能照明常见问题及解决方案 常见问题解决方案结论 1. 引言 随着智能家居…

探索地理空间分析的新世界:Geopandas的魔力

文章目录 探索地理空间分析的新世界&#xff1a;Geopandas的魔力背景&#xff1a;为何选择Geopandas&#xff1f;这个库是什么&#xff1f;如何安装这个库&#xff1f;五个简单的库函数使用方法场景应用&#xff1a;Geopandas在实际工作中的应用常见bug及解决方案总结 探索地理…

【HarmonyOS NEXT星河版开发学习】综合测试案例-各平台评论部分

目录 前言 功能展示 整体页面布局 最新和最热 写评论 点赞功能 界面构建 初始数据的准备 列表项部分的渲染 底部区域 index部分 知识点概述 List组件 List组件简介 ListItem组件详解 ListItemGroup组件介绍 ForEach循环渲染 列表分割线设置 列表排列方向设…

“游戏开发效率革命:AI绘画案例分享,大专生如何实现工作效率十倍提升与副业拓展“

一、游戏开发者的日常 我叫李明&#xff0c;是一名计算机专业的大专生。自从毕业以来&#xff0c;我就一直在一家游戏开发公司工作&#xff0c;转眼间&#xff0c;已经五年了。五年的时光&#xff0c;我从一个职场小白成长为了一名熟练的游戏开发者。但随之而来的&#xff0c;是…

GROUP_CONCAT 用法详解(Mysql)

GROUP_CONCAT GROUP_CONCAT 是 MySQL 中的一个聚合函数&#xff0c;用于将分组后的多行数据连接成一个单一的字符串。 通常用于将某个列的多个值合并到一个字符串中&#xff0c;以便更方便地显示或处理数据。 GROUP_CONCAT([DISTINCT] column_name[ORDER BY column_name [ASC…

Android SDK 遇到的坑之 AIUI(星火大模型)

目录 一、AIUI 二、常见错误 2.1 唤醒无效 2.2 错误码:600103 1、存放唤醒词等资源的路径 2、aiui_phone.cfg 文件配置 3、vtn.ini 文件配置 2.3 错误码:600022 相关推荐 一、AIUI 需要给桌面机器人(医康养)应用做语音指引/控制/健康咨询等功能&#xff0c;根据调研选择A…