可爱的程序猿 - CoderYuan.com

改变世界的猿类——袁国正的个人网站

为什么是 sharp?

最近在升级 coderyuan-image-server 这个图片服务的项目,主要是想增加对 AVIF、HEIC 图片格式的支持,于是就想找找新的方案。

以前的方案是使用 GraphicsMagick 或者 ImageMagick 来进行图片的转换,但是这两个库对于 HEIC 和 AVIF 这样的编码格式支持的并不好,所以必须要放弃它们了。

偶然间发现 sharp 这个库,从它官方的介绍中看,貌似 API 更简单,而且支持的格式也更多,性能更好,所以就选择它作为图片转换的实现方案了。

先介绍一下 sharp 和它背后的 libvips

简单来说 sharp 是基于 libvips 的 Node.js 图片处理实现,根据 libvips 官方的说明,libvips 具有速度快、占用内存少、支持格式多的特点(具体工作原理参见:https://www.libvips.org/API/current/How-it-works.html),非常适合做静态图片的转换工作,能够在各方面超越传统的 GrapicsMagick 和 ImageMagick。

基于 libvips 出色的性能和丰富的格式支持,也诞生了众多语言的封装,主流的 Java、Python、Go 等语言都有对应的实现,也有一系列基于 libvips 能力扩展的图片处理工具库,sharp 就是其中之一,也几乎是 JavaScript 环境下唯一可用的 libvips 实现。

sharp 底层其实也是 Node C++ 实现的,可以通过在 npm install 后编译 Native 代码,实现多个平台的图片处理功能。

存在的问题

此次升级 coderyuan-image-server 的主要需求是支持 AVIF 和 HEIC 格式的图片,而 sharp 的文档中说明了,sharp 默认使用的 libvips 无法支持真正以 HEVC 编码的图片格式,默认只能以 AV1 编码存储。如果在转换时使用如下代码:

1
2
3
sharp().heif({
compression: 'hevc'
})

则会收到如下错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
/home/yuanguozheng/github/image-utils/node_modules/sharp/lib/output.js:1443
const stack = Error();
^

Error: heifsave: Unsupported compression
at Sharp._read (/home/yuanguozheng/github/image-utils/node_modules/sharp/lib/output.js:1443:19)
at Readable.read (node:internal/streams/readable:737:12)
at resume_ (node:internal/streams/readable:1255:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
Emitted 'error' event on Sharp instance at:
at Object.<anonymous> (/home/yuanguozheng/github/image-utils/node_modules/sharp/lib/output.js:1487:18)

Node.js v20.18.0
阅读全文 »

10.8 日华为正式开启 HarmonyOS NEXT 的公测申请,HarmonyOS NEXT 离消费者越来越近了。

今年和几位小伙伴一起学习了 HarmonyOS NEXT 的开发,略有一些心得整理成了这本《HarmonyOS NEXT启程:零基础构建纯血鸿蒙应用》。

目前本书已经出版上架,欢迎有需要的朋友购买!链接:https://item.jd.com/10119922725923.html

本人有幸参与了该书中三个章节的编写,也希望这本书能对大家有所帮助。

这本书的内容浅显易懂,适合零基础的小伙伴学习 HarmonyOS NEXT 的开发,HarmonyOS NEXT 目前迭代迅速,参与本书编写的小伙伴们难免有所疏漏和理解不到位的地方,各位如发现错误,也欢迎指出!

经过漫长的迭代过程,Kotlin 团队终于在 2022 年 10 月 10 日发布了 KMM(Kotlin Multiplatform Mobile)技术的 Beta 版本(伴随 Kotlin 1.7.20 版本),最明显的改进点无疑是 New Memory Management 技术也进入到 Beta 版本状态,可以说是比较稳定了!

KMM 开发者的苦恼

长期以来,使用 Kotlin Multiplatform 技术进行开发时,Kotlin/Native 程序所使用的内存管理模型(Legacy Memory Management)给开发者带来了很多的困惑,还记得我当时入门 KMM 的时候,经常被 kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozenkotlin.native.IncorrectDereferenceException 异常搞的焦头烂额,当时还没有比较浅显易懂的文档来解决问题。

后来在了解了 freeze 机制并熟悉了 Stately 库以后,异常问题总算知道如何去解决了,但代码中满篇的 freeze、Atomic 类、IsoState 集合类等一些特殊元素的引入,也让人痛苦了许久!

现在的改进

现在,在使用 Kotlin 1.7.20 以上版本开发 KMM 项目时,默认会启用 New Memory Management,多线程之间的状态共享,变得简单多了,可以使用 Kotlin 语言在 JVM 上的基本规则,来处理多线程问题。

再也不需要 freeze、@SharedImmutable、@ThreadLocal 这样的语法了!

另外,Top Level 属性在 Legacy Memory Management 上的初始化问题(by lazy 可能造成 iOS App 崩溃)也得到解决,KMM 上的使用体验更接近 JVM 平台。

Beta 版本的发布,也意味着各类 API 趋于稳定,后续不会再有大幅度的修改了,也意味着可以更多地使用 KMM 技术进行 Android 和 iOS 端 App 的开发!

相关资料

本人有幸在 JetBrains 发布的视频中代表百度分享了百度在使用 KMM 技术上的一些经验,欢迎感兴趣的朋友前往相关网站查看!

Kotlin-Blog

Kotlin 官网英文原版博文:https://blog.jetbrains.com/kotlin/2022/10/kmm-beta/

Kotlin 官网中文翻译博文:https://blog.jetbrains.com/zh-hans/kotlin/2022/10/kmm-beta/

Bilibili 视频:https://www.bilibili.com/video/BV1kV4y1V7qV/

YouTube 视频:https://youtu.be/CngKDGBlFxk

Case

还有一篇之前写的文章,也发布在 Kotlin 官网上:《业务逻辑统一与 KMM 技术在百度的落地探索》

最后

再次祝愿 Kotlin Multiplatform 技术及和相关生态发展得越来越好!

背景

与 Server 的数据交互已经成为 App 必不可少的一个重要部分,常用的方式即 HTTP(S),当然也有 WebSocket、TCP、UDP 等等

在 KMM 模块中,为保证双端逻辑一致,且对 JVM、Native 进行统一兼容,可以使用官方推荐的 Ktor 进行网络通信,Kotlinx.Serialization 来进行数据解析

这篇文章就来介绍在 KMM 中如何发起并处理网络请求,后面的文章再详细介绍 kotlinx.serialization 的使用

Ktor 是什么?

Ktor 是由 JetBrains 开发的一套用于解决各类应用中网络连接的框架,不仅可以用在发起请求的各类客户端(不是所谓的 App),还可以构建微服务

针对客户端能力,通过一系列插件,可以支持 HTTP 的各类特性,如:Cookies、重定向、代理、UA、WebSocket 等,在一定程度上,还可以支持一些简单的 TCP 或 UDP 通信

另外,Ktor 还支持为不同的平台配置不同的 HTTP 引擎,如:为 Android 配置 OkHttp 或 HttpURLConnection,为 iOS 配置 NSURLSession,或者为 JVM 配置 Apache HttpClient、为 JavaScript (Node.js) 配置 node-fetch,以便使用同一套代码逻辑处理网络请求

由于现在的 RESTful API 通常会以 JSON 作为通信数据格式,在 JVM 平台上,Ktor 还支持与 Gson、Jackson 协同工作,而对于 Kotlin Multiplatform(当然包括 KMM)可以与 kotlinx.serialization 进行协作

由于 Ktor 适用的平台广泛,本文只对 KMM 平台上的使用进行说明

阅读全文 »

数据库在 App 中的作用

移动 App 的数据库与 Server 数据库不同,其主要目的是为了缓存一些数据,如:历史消息、数据打点、列表数据缓存等,宗旨都是为优化用户体验建立一套简单的数据基础

由于 SQLite 完全开源,且比较轻量(不需要像 MySQL 这样建立一个单独的进程,直接操作 DB 文件),目前,在各类移动端操作系统(包括不限于 Android、iOS、Windows)当中,都会内置 SQLite,以便开发者存取结构化数据

于是,围绕 SQLite 展开的开发框架也越来越多,比如:iOS 上的 FMDB、以及 Apple 官方的 CoreData,Android 上的 SQLiteOpenHelper,以及基于它构建的 GreenDAO、Android 官方的 Room 等等,这些框架使得开发者不需要关注 SQLite 中 C/C++ 一层的 API,大幅降低了移动端数据库的开发成本,使得数据存取变得容易

虽然 SQLite 用途广泛,但 SQLite 也存在着一些性能问题,这些性能问题在数据量比较庞大时,体现地更为明显,近几年也出现了一些面向移动端,基于 NoSQL 或对 SQLite 进行改进数据库框架,如:Realm、WCDB……

那么在 KMM 中,如果需要操作数据库,使用 SQLDelight 框架,无疑是目前比较好的选择

阅读全文 »

为什么需要在 KMM 中处理多线程?

我们使用 KMM,通常是处理和 UI 无关的业务逻辑,所以多数情况是网络请求、数据缓存、文件读写等操作,为了不影响 UI 绘制,这些操作往往都会在异步线程中执行,而 KMM 模块的线程切换,调用方肯定是不应该去管理的,所以需要探索一种在 KMM 中比较靠谱的多线程处理方式

可行的多线程切换方式

  1. 协程(kotlinx-coroutines

    Kotlin 协程不依赖于 JVM 实现,可以应用在 Kotlin Native 项目当中,不需要再实现平台差异化代码,且协程开销与线程相比较小,可以满足异步任务的需求

    但协程也有一些问题,比如,执行顺序不好控制,如果需要异步且串行地执行一系列任务,使用协程并不有效地、方便地控制执行顺序

    另外,协程对 Kotlin Native 支持的并不像 JVM 上那么完美(尤其是多线程的实现),有待进一步完善

  2. expect + Block

    将需要异步执行的任务包成一个 Kotlin 闭包,实际将其扔进工作线程的方法,由 expect + actual 组合的形式实现,Android 端可以利用线程池,iOS 端可以使用 GCD

    这种方法归根结底还是使用了现有比较成熟的多线程方案,执行顺序比较容易控制,但需要一定的基础能力建设,需要编写一些平台差异化代码

  3. 第三方库

    KMM 官方推荐 CoroutineWorkerReaktive

    CoroutineWorker 是对 Kotlin 协程的封装,迭代比较少,不算比较稳定的方案

    Reaktive 采用 RxJava 的实现思想,Native 底层采用 Kotlin Native 实现,目前看功能还算比较方便,但框架相对较重

阅读全文 »

KMM 的依赖类型

KMM 的依赖根据平台分为三类,分别是 Common 依赖、Android 依赖、iOS 依赖,其中 Common 依赖顾明思议,是用于通用逻辑的,这种依赖只能使用基于最标准的 Kotlin 底层能力(不可以耦合 JVM、JS)构建

例如,在 Android 端上比较常用的 Kotlin Reflect、OkHTTP、GSON、Fresco,在 iOS 上比较常用的 AFNetworking、YYModel 之类的库,都不能直接用在 KMM 模块的 Common 代码库中

那么,上面说的这些库能不能让 KMM 继续使用?或者有没有直接可以使用的库?。。。

答案当然是肯定的!

使用 Common 或 Android 依赖

Common 依赖

首先是需要找到能够使用的第三方库,这里推荐一些比较优秀、可以直接使用的 KMM 库,不过这些库可能还需要在各平台的代码库中添加依赖项,以便实现差异化功能或者平台耦合能力

官方 JSON 解析库:https://github.com/Kotlin/kotlinx.serialization

HTTP 请求库:https://github.com/ktorio/ktor

SQLite 操作库:https://github.com/cashapp/sqldelight

这些库的依赖也非常简单,和普通的 Gradle 依赖类似,只需要在 KMM 模块根目录的 build.gradle.kts 文件中添加即可,如下图所示,在 commonMain 变量后面的闭包中,新建一个 dependencies 闭包,即可以按照常规的 Gradle 依赖形式,添加 ktor 的 Common 依赖

在 Sync 成功后,便可以使用 Common 能力了

阅读全文 »

平台差异化代码的使用场景

由于 KMM 运行在各平台时,实际上是翻译成了各平台专用的库,如:Android 上就会将共享模块编译成 Dalvik Bytecode 然后打包成 AAR 文件,而 iOS 上会打包成 Apple Framework,所以,一些平台相关的、不可共享的具体实现代码,就必须利用各平台的 API 来实现

举个简单的例子,公共模块有一个统一的业务逻辑——获取手机型号,控制逻辑可以在 KMM 的 common 代码库中实现,且它并不关系具体的实现逻辑,而实际需要获取手机型号字符串的方法,Android 需要调用 android.os.Build.MODEL 获取,而 iOS 需要通过 UIDevice.current.model 来获取

类似的平台强相关功能,就需要在 KMM 中利用平台差异化代码实现

差异化代码的基本实现

这里需要再次引用 Kotlin 官方的一张图

阅读全文 »

为什么要在现有工程中集成?

直接用 KMM 插件生成的工程不香嘛?为什么还要讲在原有的工程中集成?

不香,确实不香!

  • 默认工程结构局限性高

    1. 由 KMM 插件建立的工程,默认使用 Kotlin(*.kts 文件)进行 Gradle 配置,虽然用 Android Studio 新建一个 Android 工程会默认使用 kts,但很多项目现在都还在使用 Groovy,要改回去也需要一定工作量和学习成本
    2. 默认建立的 iOS 工程使用 Swift UI,目前大多数 iOS App 都会使用代码开发 UI,并没有使用 Storyboard、IB、Swift UI,而且国内很多公司都还在使用 Objective-C,同时 Swift 与 Objective-C 的交互并不像 Kotlin 和 Java 那样简单,所以彻底迁移到 Swift,还需要一定时间
  • 工程目录不易维护

    默认的工程类似一个独立的 Android App 工程,KMM 模块以 Gradle 模块形式依赖在 Android 主 App Module 当中,而 iOS 工程则是直接放在一个目录下,iOS 工程往往规模都比较庞大,且缺少像 Gradle 这样的工程管理框架,且 Xcode 工程索引文件在多人开发时,经常需要解决冲突,如果将 iOS 工程也放在整个 Android 工程目录中,后续将十分难以维护

阅读全文 »

什么是 KMM?

KMM 全称:Kotlin Multiplatform Mobile,是基于 Kotlin 语言进行多平台开发的一套技术框架,它和 Kotlin Native(简称 KN)有一定联系,但 KMM 主要面相移动端开发,即:Android、iOS、Web,而 KN 则主要面相 Linux、macOS、Windows 等

当然,KMM 在 iOS 平台的实现,离不开 Kotlin Native,Kotlin 代码最终会在 iOS 工程中生成一套 Framework 库,可供 Objective-C、Swift 进行调用

KMM 宗旨是使用 Kotlin 语言和技术栈,开发一套可以在多平台之间共享的代码库,用来构建统一的代码逻辑,而不用针对各个平台都去实现自己的一套,从而导致人力的浪费

这里引用 Kotlin 官网的一张图来说明 Kotlin 多平台的工作原理

Kotlin 多平台
阅读全文 »
0%