--- START OF POST --- URL: https://blog.kingsword.tech/2025/09/27/a-gradle-plugin-for-high-performance-material-symbols-in-kmp/ Published At: 2025-09-27T00:00:00.000Z # Kotlin Multiplatform 项目的高性能 Material Symbols 生成插件 最近在开发 Kotlin Multiplatform App 时,需要使用一些图标。很自然地想到了 `material-icons-extended` 库,但集成后发现了一个严重的问题:无论怎么使用 `Proguard` 进行混淆压缩,App 的图标包体积始终会增加 11.3 MB。这对于追求轻量化的现代应用来说,是难以接受的。 为了解决这个问题,我决定寻找一个可以按需引入、只包含项目中实际使用到的图标的解决方案。调研中发现 Google 在 2022 年推出了新的图标系列 [Material Symbols](https://fonts.google.com/icons),它提供了更丰富的样式和配置。于是,我开发了一款名为 **SymbolCraft** 的 Gradle 插件,旨在彻底解决 KMP 项目中的图标体积问题。 [SymbolCraft](https://github.com/kingsword09/SymbolCraft) 是一个为 Kotlin Multiplatform 设计的、支持按需生成 Material Symbols 图标的 Gradle 插件。它通过分析您的配置,自动从 CDN (借助 [esm.sh](https://esm.sh)) 下载指定图标库`@material-symbols/svg-[100-700]`的 SVG 文件,并利用 [DevSrSouza/svg-to-compose](https://github.com/DevSrSouza/svg-to-compose) 库将其转换为高性能的 `ImageVector`。 除了解决体积问题,SymbolCraft 还着力于优化开发体验和构建性能。它内置了 **智能缓存** 机制,避免重复下载;利用 Kotlin 协程实现 **并行下载**,加快生成速度;并保证 **确定性构建**,生成的代码完全一致,对版本控制和 Gradle 的构建缓存都非常友好。 当然,它也全面支持 Material Symbols 丰富的样式选项(粗细、风格、填充),并能生成高质量的矢量图形。其中我最喜欢的功能之一是 **Compose 预览**,插件可以自动为所有生成的图标创建 `@Preview` 函数,让你在 IDE 里就能一目了然,极大提升了开发效率。 ### 那么,如何开始使用? 集成 SymbolCraft 的过程非常流畅。首先,像添加其他 Gradle 插件一样,在 `libs.versions.toml` 中定义它: ```toml [plugins] symbolCraft = { id = "io.github.kingsword09.symbolcraft", version = "0.1.1" } ``` 接着在你的模块 `build.gradle.kts` 文件中应用这个插件: ```kotlin plugins { alias(libs.plugins.symbolCraft) } ``` 最关键的一步来了:通过 `materialSymbols` DSL 配置块告诉 SymbolCraft 你需要哪些图标。它的配置非常灵活,既可以为单个图标精细定义样式,也支持便捷的批量操作。如果你不确定某个图标的名称,或者想浏览所有可用的图标,可以访问 [Material Symbols Demo](https://marella.github.io/material-symbols/demo/) 这个网站,它非常方便: ```kotlin materialSymbols { // 基础配置 packageName.set("com.app.symbols") outputDirectory.set("src/commonMain/kotlin") // 支持多平台项目 cacheEnabled.set(true) // (可选) 开启 Compose 预览生成 generatePreview.set(true) // 配置单个图标,比如 "search" symbol("search") { style(weight = 400, variant = SymbolVariant.OUTLINED, fill = SymbolFill.UNFILLED) style(weight = 500, variant = SymbolVariant.OUTLINED, fill = SymbolFill.FILLED) } // 使用便捷的批量配置方法 symbol("home") { standardWeights() // 自动添加 400, 500, 700 三种粗细 } symbol("person") { allVariants(weight = 400) // 添加所有变体 (outlined, rounded, sharp) } // 批量配置多个图标 symbols("favorite", "star", "bookmark") { weights(400, 500, variant = SymbolVariant.OUTLINED) } } ``` ### 从配置到代码:完整的工作流是怎样的? 配置完成后,我们就可以让 SymbolCraft 大显身手了。只需在终端运行一个简单的 Gradle 命令: ```bash ./gradlew generateMaterialSymbols ``` > 如果遇到缓存问题或想强制重新生成所有图标,可以加上 `--rerun-tasks` 参数。 插件便会开始工作,你会看到它高效地下载 SVG、命中缓存、并最终生成代码。任务成功后,在你指定的输出目录(例如 `src/commonMain/kotlin/com/app/symbols`)下,就会出现所有图标对应的 Kotlin 文件。 接下来,在你的 Composable 函数中就可以直接引用这些新鲜出炉的 `ImageVector` 了: ```kotlin import com.app.symbols.MaterialSymbols import com.app.symbols.materialsymbols.SearchW400Outlined import com.app.symbols.materialsymbols.HomeW700Rounded import androidx.compose.material3.Icon import androidx.compose.runtime.Composable @Composable fun MyScreen() { // 你可以直接导入使用 Icon( imageVector = SearchW400Outlined, contentDescription = "Search" ) // 或者通过 MaterialSymbols 对象访问,方便查找 Icon( imageVector = MaterialSymbols.HomeW700Rounded, contentDescription = "Home" ) } ``` 但我们如何确认生成的图标就是我们想要的样式呢?总不能每次都运行 App 查看吧。这正是 `generatePreview` 功能的用武之地。开启后,SymbolCraft 会额外生成一个预览文件,让你在 Android Studio 或 IntelliJ IDEA 的预览面板中就能一览所有图标的实际效果,极大地方便了调试和验证。 ### 总结 SymbolCraft 通过按需生成和智能缓存的策略,成功解决了在 KMP 项目中使用 Material Symbols 导致的包体积膨胀问题。它不仅提供了强大的功能和灵活的配置,还通过 Compose 预览等特性优化了开发体验。如果你也在为 KMP 项目的图标方案而烦恼,不妨试试 SymbolCraft!


#### 参考文档 - [SymbolCraft GitHub Repository](https://github.com/kingsword09/SymbolCraft) - [Material Symbols - Google Fonts](https://fonts.google.com/icons) - [Material Symbols Demo - by marella](https://marella.github.io/material-symbols/demo/) --- END OF POST --- --- START OF POST --- URL: https://blog.kingsword.tech/2025/04/19/github-commit-gpg-signing/ Published At: 2025-04-19T00:00:00.000Z # GitHub提交签名验证 最近在 `GitHub` 贡献 `Pull Request` 时,发现贡献者需要提交签名验证,因此记录下这个过程。 以 `Mac` 电脑为例: #### GPG ##### 1. 安装 `GPG` 程序 ```bash brew install gnupg ``` > 如果是 windows 则安装 [Gpg4win](https://www.gpg4win.org/)。 ##### 2. 生成 `GPG` 密钥 (1) 生成密钥 ```bash gpg --full-generate-key ``` > 使用默认加密即可(椭圆曲线ECC) > > 电子邮箱需要填写经过 `GitHub` 验证的邮箱 (2) 先获取 `GPG` 密钥 `ID` ```bash $ gpg --list-secret-keys --keyid-format=long /Users/hubot/.gnupg/secring.gpg ------------------------------------ sec 4096R/3AA5C34371567BD2 2016-03-10 [expires: 2017-03-10] uid Hubot ssb 4096R/4BB6D45482678BE3 2016-03-10 ``` (3) 获取公钥 ```bash gpg --armor --export 3AA5C34371567BD2 # Prints the GPG key ID, in ASCII armor format ``` (4) 复制以 `-----BEGIN PGP PUBLIC KEY BLOCK-----` 开头并以 `-----END PGP PUBLIC KEY BLOCK-----` 结尾的 GPG 密钥。 (5) [将 `GPG` 密钥新增到 `GitHub` 帐户](https://docs.github.com/zh/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account) - 在 GitHub 任意页面的右上角,单击个人资料照片,然后单击 “设置”****。 - 在边栏的“访问”部分中,单击 “SSH 和 GPG 密钥”。 - 在“GPG 密钥”标头旁边,单击“新建 GPG 密钥”。 - 在“标题”字段中键入 GPG 密钥的名称。 - 在“密钥”字段中,粘贴生成 GPG 密钥时复制的 GPG 密钥。 - 单击“添加 GPG 密钥” - 若要确认操作,请向 GitHub 帐户进行身份验证以确认操作。 (6) `git` `commit` 配置 `signing` - 全局配置 ```bash git config --global user.signingkey 4874E921904AC200 git config --global commit.gpgsign true ``` - 项目配置 ```bash git config user.signingkey 4874E921904AC200 git config commit.gpgsign true ``` ##### 3. 配置 `GPG` 因为平时使用 `GitHub Desktop` 提交代码,需要有一个可以输入 `GPG` `passphrases` 的 `GUI` 程序,如果使用命令行则不需要,因为默认的 `pinentry` 可以在命令行中出现 `dialog` 来输入。 (1) 安装 `pinentry` `GUI` 程序 ```bash brew install pinentry-mac ``` (2) 配置 `gpg-agent.conf` 用于控制弹出 `pinentry` 的频次和程序 `gpg-agent.conf` 位置: - `Mac`:`~/.gnupg/gpg-agent.conf` ```bash # Set the default cache time to 1 day. default-cache-ttl 86400 # Set the max cache time to 30 days. max-cache-ttl 2592000 # Set pinentry GUI program (Only MacOS) pinentry-program /opt/homebrew/bin/pinentry-mac ``` - `Windows`: `C:\Users\\AppData\Roaming\gnupg\gpg-agent.conf` > powershell可以使用 `cd $env:AppData/gnupg` 进入 `gnupg` 配置目录 ```bash # Set the default cache time to 1 day. default-cache-ttl 86400 # Set the max cache time to 30 days. max-cache-ttl 2592000 ``` ##### 4. 重启 `gpg-agent` ```bash gpgconf --kill gpg-agent gpg-connect-agent reloadagent /bye ``` #### QA - `gpg: signing failed: Inappropriate ioctl for device` 出现这个错误是因为没有弹出 `pinentry` `GUI` 程序输入 `passphrases`。 - 如何备份和导入 `GPG` 配置 [备份和恢复 `GPG` `key`](https://www.jwillikers.com/backup-and-restore-a-gpg-key)


#### 参考文档 - [生成 `GPG` 密钥](https://docs.github.com/zh/authentication/managing-commit-signature-verification/generating-a-new-gpg-key) - [将 `GPG` 密钥添加到 `GitHub` 帐户](https://docs.github.com/zh/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account) - [备份和恢复 `GPG` `key`](https://www.jwillikers.com/backup-and-restore-a-gpg-key) - [How can I get GPG Agent to cache my password?](https://askubuntu.com/questions/805459/how-can-i-get-gpg-agent-to-cache-my-password) --- END OF POST --- --- START OF POST --- URL: https://blog.kingsword.tech/2025/04/13/tauri2-plugin-developerment-pitfall-record-3/ Published At: 2025-04-13T00:00:00.000Z # Tauri 2.0 插件开发踩坑记录(3) 最近使用 `Tauri 2.0` 封装 `web` 前端为 `app` 时,有个需求就是在 `Android` 应用内授权安装应用,但是在开发过程中遇到了一些问题,本文将详细介绍这些问题以及解决方案。 假设要开发的插件为: [tauri-plugin-android-package-install](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-android-package-install)。 在 `TypeScript` 的接口定义中就可以以下面的方式进行定义: `guest-js/index.ts`: ```ts import { invoke } from "@tauri-apps/api/core"; export async function install(installPath: string): Promise { await invoke("plugin:android-package-install|install", { installPath, }); } ``` 在 `Rust` 中定义的命令如下: ```rust #[command] pub(crate) async fn install(app: AppHandle, install_path: String) -> Result<()> { app.android_package_install().install(install_path) } ``` 在这里就遇到了第一个问题:因为 `Rust` 中定义的参数名是 `install_path`,则 `TypeScript` 中定义必须使用 `installPath`,即 `invoke` 的 `args` 参数名必须使用 `installPath`,否则会出现以下错误: ```shell Msg: Uncaught (in promise) invalid args `installPath` for command `install`: command install missing required key installPath ``` 代码如下: ```ts // ❌错误 import { invoke } from "@tauri-apps/api/core"; export async function install(install_path: string): Promise { await invoke("plugin:android-package-install|install", { install_path, }); } // ✅正确 import { invoke } from "@tauri-apps/api/core"; export async function install(installPath: string): Promise { await invoke("plugin:android-package-install|install", { installPath, }); } // ✅正确 import { invoke } from "@tauri-apps/api/core"; export async function install(install_path: string): Promise { await invoke("plugin:android-package-install|install", { installPath: install_path, }); } ``` 第二个可能遇到的问题是安装问题,即: ```shell FileProvider Issue: Failed to find configured root that contains ``` 需要在 `res/xml/file_paths.xml` 配置 `files-path`,即: ```diff ``` 具体其它细节就不再赘述了,可以看 [tauri-plugin-android-package-install](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-android-package-install) 详细代码。


#### 参考文档 - [Tauri 官方文档](https://v2.tauri.app/) - [tauri-plugin-android-package-install](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-android-package-install) --- END OF POST --- --- START OF POST --- URL: https://blog.kingsword.tech/2025/04/06/tauri2-deno-starter-record-1/ Published At: 2025-04-06T00:00:00.000Z # Tauri 2.0 项目deno workspace管理依赖(1) 最近在开发 `Tauri 2.0` 应用时,需要开发自己的插件,但是插件开发需要使用 `TypeScript` 开发之后还需要打包才能使用,这很麻烦。于是想着要利用 `deno` 的能力,直接就可以将 `TypeScript` 包直接使用在前端项目中,最后统一打包就好了,而不需要多次打包,而且可以利用 `JSR` 发布 `deno` 包,这样之后不管是直接继续使用 `deno`,或者想改为使用 `node` 环境使用都能直接用。 #### 创建项目 ##### 1. 初始化项目 `Tauri 2.0` 官方文档有提供使用 `deno` 进行初始化项目,即: ```bash mkdir tauri2-deno-starter cd tauri2-deno-starter deno run -A npm:create-tauri-app ``` ##### 2. 改造项目 按照官方指令创建出来的项目并不能直接利用 `deno workspace` 的功能来直接使用 `TypeScript` 代码在前端中使用。初始化出来的项目只是利用了 `deno install` 的能力安装依赖,实际上还是使用的 `package.json` 来管理依赖,而不是使用 `deno.json` 的方式管理依赖。 以创建前端项目为 `React + TypeScript` 为例: (1) 在 `tauri2-deno-starter` 目录下创建 `deno.json` 管理全局依赖; ```json { "nodeModulesDir": "auto", "workspace": [ "./app" // 假设创建的 tauri 项目为 app ], "imports": { "@tauri-apps/api": "npm:@tauri-apps/api@^2", "@tauri-apps/cli": "npm:@tauri-apps/cli@^2" } } ``` 设置 `nodeModulesDir` 为 `auto` 是为了 deno 安装依赖时会生成 node_modules 目录。 `imports` 中添加 `@tauri-apps/api` 和 `@tauri-apps/cli` 依赖方便开发插件和应用时都可以使用。 (2) 迁移 `app` 为使用 `deno` 管理依赖 删除各种 `tsconfig` 文件和 `package.json` 文件,添加 `deno.json` 文件。 `app/deno.json` 文件如下: ```json { "name": "@kingsword/app", "version": "0.1.0", "exports": {}, "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "npm:react@^19.0", "jsxImportSourceTypes": "npm:@types/react@^19.0", "lib": ["ES2020", "DOM", "DOM.Iterable"] }, "tasks": { "dev": "deno run -A npm:vite", "build": "deno check src/main.ts && deno run -A npm:vite build", "preview": "deno run -A npm:vite preview", "tauri": "deno run -A npm:@tauri-apps/cli" }, "imports": { "@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0", "@vitejs/plugin-react": "npm:@vitejs/plugin-react", "react": "npm:react", "react-dom": "npm:react-dom", "react-router-dom": "npm:react-router-dom", "vite": "npm:vite@^6.0.3" } } ``` 修改 `vite.config.ts` ```ts import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import deno from "@deno/vite-plugin"; import process from "node:process"; const host = process.env.TAURI_DEV_HOST; // https://vitejs.dev/config/ export default defineConfig(() => ({ plugins: [ deno(), // react(), ], // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // // 1. prevent vite from obscuring rust errors clearScreen: false, // 2. tauri expects a fixed port, fail if that port is not available server: { port: 1420, strictPort: true, host: host || false, hmr: host ? { protocol: "ws", host, port: 1421, } : undefined, watch: { // 3. tell vite to ignore watching `src-tauri` ignored: ["**/src-tauri/**"], }, }, })); ``` (3) 创建完整项目结构 添加 `plugins` 目录用于管理 `Tauri 2.0` 的插件,添加 `packages` 目录用于管理前端工具 `TypeScript` 包。完整项目结构如下: ```text tauri2-deno-starter/ ├── app/ # 应用主目录 │ ├── src/ # 前端源代码 │ └── src-tauri/ # Tauri相关配置和Rust代码 ├── plugins/ # 自定义Tauri插件 ├── packages/ # 自定义Deno TS包 ├── deno.json # Deno配置文件 ``` #### 开发 `deno` 包 ##### 1. 创建 lib ```bash cd packages deno init [package_name] --lib ``` ##### 2. 添加到 `workspace` `tauri2-deno-starter/deno.json` ```json { "nodeModulesDir": "auto", "workspace": [ "./app" // 假设创建的 tauri 项目为 app "./packages/[package_name]" ], "imports": { "@tauri-apps/api": "npm:@tauri-apps/api@^2", "@tauri-apps/cli": "npm:@tauri-apps/cli@^2" } } ``` ##### 3. 发布 `deno` 包 如果需要发布 deno 包,提供给 deno 和 npm 用户使用,则可以查看 [JSR](https://jsr.io) 的文档,发布指令为: ```bash deno publish ``` #### 开发 `Tauri 2.0` 插件 ##### 1. 创建插件 ```bash cd plugins deno run -A npm:@tauri-apps/cli plugin new [plugin_name] ``` ##### 2. 添加到 `workspace` 在 `tauri-plugin-[plugins_name]` 和 `tauri-plugin-[plugins_name]/examples/tauri-app` 下都要添加 `deno.json` 文件。 `tauri-plugin-[plugins_name]/deno.json`: ```json { "name": "@kingsword/tauri-plugin-[plugins_name]-api", "version": "0.1.0", "exports": "./guest-js/index.ts" } ``` `tauri-plugin-[plugins_name]/examples/tauri-app/deno.json` 则参考 `app/deno.json` 的设置,`examples/tauri-app` 中的修改也和 `app` 的修改一致,需要去掉 `tsconfig` 和 `package.json` 等。 `tauri2-deno-starter/deno.json` ```json { "nodeModulesDir": "auto", "workspace": [ "./app" // 假设创建的 tauri 项目为 app "./plugins/tauri-plugin-[plugins_name]", "./plugins/tauri-plugin-[plugins_name]/examples/tauri-app", ], "imports": { "@tauri-apps/api": "npm:@tauri-apps/api@^2", "@tauri-apps/cli": "npm:@tauri-apps/cli@^2" } } ``` ##### 3. 发布插件 发布插件通常需要将 `guest-js` 目录下的 `TypeScript` 包发布到 `JSR` 以及 `rust` 包发布到 `crates`,deno 包发布已经在前面解释过,也可看文档,rust 包则使用如下命令: ```bash cargo publish # 不要通过构建它们来验证内容则: cargo publish --no-verify ``` #### 运行项目 到此可以运行桌面端程序了: ```bash deno task --filter 'app' tauri dev ``` #### 添加移动端支持 如果需要添加移动端的支持,还需要创建移动端项目,即 ##### 1. 初始化移动端项目 ```bash cd app deno run -A npm:@tauri-apps/cli android init deno run -A npm:@tauri-apps/cli ios init ``` ##### 2. 添加到 `workspace` 在 `android` 目录下添加 `deno.json` ```json { "name": "@kingsword/app-android", "exports": {}, "tasks": { "tauri": "deno run -A npm:@tauri-apps/cli" } } ``` 在 `apple` 目录下添加 `deno.json` ```json { "name": "@kingsword/app-ios", "exports": {}, "tasks": { "tauri": "deno run -A npm:@tauri-apps/cli" } } ``` 之后在 `tauri2-deno-starter/deno.json` 中添加即可: ```json { "nodeModulesDir": "auto", "workspace": [ "./app" // 假设创建的 tauri 项目为 app "./app/src-tauri/gen/android", "./app/src-tauri/gen/ios" ], "imports": { "@tauri-apps/api": "npm:@tauri-apps/api@^2", "@tauri-apps/cli": "npm:@tauri-apps/cli@^2" } } ``` ##### 3. 运行移动端项目 ```bash # android deno task --filter 'app' tauri android dev # ios deno task --filter 'app' tauri ios dev ``` 以上就是开发一个使用 `deno workspace` 来管理 `Tauri 2.0` 应用依赖的过程。


#### 参考文档 - [Tauri 官方文档](https://v2.tauri.app/) - [deno 官方文档](https://deno.com) - [jsr publish](https://jsr.io/docs/publishing-packages) - [cargo publish](https://doc.rust-lang.org/cargo/commands/cargo-publish.html) - [tauri2-deno-starter](https://github.com/kingsword09/tauri2-deno-starter) --- END OF POST --- --- START OF POST --- URL: https://blog.kingsword.tech/2025/03/30/react-native-repack-use-1/ Published At: 2025-03-30T00:00:00.000Z # React Native 使用 Repack 创建项目(1) #### 1. 创建项目 最近在使用 [Repack](https://github.com/callstack/repack) 创建 `react native` 项目。 由于 `react native` 项目都默认是 `yarn` 的支持更好,但是我想要使用 `pnpm` 初始化这个项目,即: ```bash # repack 5.0.5 pnpm dlx @callstack/repack-init ``` #### 2. 安装依赖 创建完之后使用 `pnpm i` 安装以来之后,启动项目会失败,缺少了很多依赖,需要自行安装依赖才行。 缺少了以下依赖: ```bash @react-native/gradle-plugin @react-native/codegen babel-plugin-syntax-hermes-parser @babel/plugin-syntax-typescript @react-native/babel-plugin-codegen ``` 之后使用 `pnpm add -D` 安装这些依赖就行了。 #### 3. 运行 ###### 1. Android ```bash pnpm android ``` ###### 2. iOS ```bash # 安装 cocoapods 依赖 pnpm dlx pod-install # 运行 pnpm ios ``` #### 4. QA ###### 1. 使用 `Android Studio` 打开 `android` 目录出现 `Cannot run program "node"` 异常时怎么解决? (1)查询 `node` 指令位置 ```bash which node ``` (2)替换 `node_modules/@callstack/repack/android/build.gradle` 中的 `node` 指令 ```diff - commandLine("node", "--print", "require.resolve('react-native/package.json')") # 比如 node 位置是 /usr/local/bin/node + commandLine("/usr/local/bin/node", "--print", "require.resolve('react-native/package.json')") ``` ###### 2. 出现 `Missing ExternalProject for :` 异常? 替换 `android/settings.gradle` 中的如下内容: ```diff - pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } + pluginManagement { includeBuild(file('../node_modules/@react-native/gradle-plugin').toPath().toRealPath().toAbsolutePath().toString()) } - includeBuild('../node_modules/@react-native/gradle-plugin') + includeBuild(file('../node_modules/@react-native/gradle-plugin').toPath().toRealPath().toAbsolutePath().toString()) ```


#### 参考文档 - [React Native 官方文档](https://reactnative.dev/) - [repack](https://re-pack.dev/) - [Missing ExternalProject for :](https://github.com/react-native-community/cli/issues/1207#issuecomment-2232732690) --- END OF POST --- --- START OF POST --- URL: https://blog.kingsword.tech/2025/03/28/tauri2-plugin-developerment-pitfall-record-2/ Published At: 2025-03-28T00:00:00.000Z # Tauri 2.0 插件开发踩坑记录(2) 开发 `Tauri 2.0` 的插件时,可能会有异步返回数据的需求,这时候就需要使用到 [addpluginlistener](https://v2.tauri.app/reference/javascript/api/namespacecore/#addpluginlistener)。 假设要开发的插件为: [tauri-plugin-mobile-onbackpressed-listener](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-mobile-onbackpressed-listener)。 在 `TypeScript` 的接口定义中就可以以下面的方式进行定义: `guest-js/index.ts`: ```ts import { addPluginListener, invoke, PluginListener, } from "@tauri-apps/api/core"; export async function registerBackEvent( goBack: () => void ): Promise { await invoke("plugin:mobile-onbackpressed-listener|register_back_event"); return await addPluginListener( "mobile-onbackpressed-listener", "mobile-onbackpressed-goback", (result) => { goBack(); } ); } ``` 定义了一个插件 `command`:`register_back_event` 和一个监听 `event`:`mobile-onbackpressed-goback`。接下来就可以在各个平台进行实现相关代码了。 - 在 `Android` 端可以通过 `onBackPressedDispatcher` 和 `onBackInvokedDispatcher` 来监听设备的返回事件,`registerBackEvent` 这个事件处理时可以直接通过 `invoke.resolve()` 返回就可以了。 - 在 `iOS` 端,则需要做一些特殊处理,因为 `iOS` 本身没有返回监听相关的功能,需要具体实现,也可以参考 [MobileOnBackPressedListenerPlugin.swift](https://github.com/kingsword09/tauri-plugins-workspace/blob/main/packages/tauri-plugin-mobile-onbackpressed-listener/ios/Sources/MobileOnBackPressedListenerPlugin.swift) 此处代码来实现。 在这之后,你就可以通过监听到返回,在 `Android` 端和 `iOS` 端分别触发:`trigger("mobile-onbackpressed-goback", JSObject())` 来实现通知到前端代码来执行 `goBack` 方法。 当你以为一切都完成时,坑就出现了,你会发现 `trigger` 怎么都无法通知到前端,查阅官方文档也没有看到有相关的说明,而且官方似乎没有相关的示例可以查看。 后来是在看到第三方的插件时,通过对比才发现问题所在。需要将 `addPluginListener` 的 `command` 也添加到你的插件的 `command` 中,即 `tauri-plugin-mobile-onbackpressed-goback/build.rs`: ```rust const COMMANDS: &[&str] = &[ "register_back_event", "registerListener", "remove_listener" ] ``` 这样才能够实现 `trigger` 可以通知到前端。之后在使用插件时,也需要在 `app` 中的 `capabilities/*.json` 添加上相关的权限: ```json "permissions": [ "mobile-onbackpressed-listener:allow-register-back-event", "mobile-onbackpressed-listener:allow-registerListener", "mobile-onbackpressed-listener:allow-remove-listener" ] ``` 如此,这个插件就可以正常使用了。


#### 参考文档 - [Tauri 官方文档](https://v2.tauri.app/) - [tauri-plugin-mobile-onbackpressed-listener](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-mobile-onbackpressed-listener) --- END OF POST --- --- START OF POST --- URL: https://blog.kingsword.tech/2025/03/28/tauri2-plugin-developerment-pitfall-record-1/ Published At: 2025-03-28T00:00:00.000Z # Tauri 2.0 插件开发踩坑记录(1) 最近在开发 `Tauri 2.0` 的插件的时候,遇到了一个问题,就是我在 `TypeScript` 中定义了一个对象参数用来传递给插件,在 `Android` 端会出问题,定义如下: `guest-js/index.ts` ```ts export interface Options { privacyUrl: string; isPrivacyDialogRequired: boolean; } ``` 在 `Android` 中定义如下: ```kotlin @InvokeArg class Options { lateinit var privacyUrl: String var isPrivacyDialogRequired: Boolean = true } ``` 按照这种方式定义之后,如果在调用这个插件的时候,`isPrivacyDialogRequired` 设置为 `false`,在 `Android` 的结果依然会是:`true`。 这个问题在 `[阿里巴巴Java开发手册](https://github.com/alibaba/p3c)` 中有提到:[【强制】POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。](https://github.com/alibaba/p3c/blob/6c59c8c36ecd8722c712d5685b8c3822c1c8b030/p3c-gitbook/%E7%BC%96%E7%A8%8B%E8%A7%84%E7%BA%A6/%E5%91%BD%E5%90%8D%E9%A3%8E%E6%A0%BC.md?plain=1#L22) 而 `Tauri 2.0` 默认使用的序列化框架 [jackson](https://github.com/FasterXML/jackson) 就会出现这个问题。两种方式解决这个问题: 1. 不使用 `is` 开头的属性来定义布尔类型变量; 2. 则是使用 `JsonProperty` 注解: ```kotlin import com.fasterxml.jackson.annotation.JsonProperty @InvokeArg class Options { lateinit var privacyUrl: String @JsonProperty("isPrivacyDialogRequired") var isPrivacyDialogRequired: Boolean = true } ``` 通过这样修改之后,就可以正常处理反序列化了。


#### 参考文档 - [Tauri 官方文档](https://v2.tauri.app/) - [Jackson 官方文档](https://github.com/FasterXML/jackson) - [阿里巴巴 Java 开发手册](https://github.com/alibaba/p3c) --- END OF POST ---