移动插件开发
插件可以运行用 Kotlin (或 Java) 和 Swift 编写的本地移动代码。默认插件模板包含一个使用 Kotlin 的 Android 库项目和一个包含示例移动命令的 Swift 包,该命令展示了如何从 Rust 代码触发其执行。
初始化插件项目
按照插件开发指南中的步骤初始化一个新的插件项目。
如果您有一个现有的插件,并希望向其添加 Android 或 iOS 功能,您可以使用 plugin android init
和 plugin ios init
来引导移动库项目,并引导您完成所需的更改。
默认插件模板将插件的实现拆分为两个独立的模块:desktop.rs
和 mobile.rs
。
桌面实现使用 Rust 代码来实现功能,而移动实现向本地移动代码发送消息以执行函数并获取结果。如果跨两个实现都需要共享逻辑,则可以在 lib.rs
中定义它
use tauri::Runtime;
impl<R: Runtime> <plugin-name><R> { pub fn do_something(&self) { // do something that is a shared implementation between desktop and mobile }}
此实现简化了共享 API 的过程,该 API 可以被命令和 Rust 代码使用。
开发 Android 插件
Android 的 Tauri 插件被定义为一个 Kotlin 类,它扩展了 app.tauri.plugin.Plugin
并使用 app.tauri.annotation.TauriPlugin
进行注解。任何使用 app.tauri.annotation.Command
注解的方法都可以被 Rust 或 JavaScript 调用。
Tauri 默认使用 Kotlin 进行 Android 插件实现,但如果您愿意,也可以切换到 Java。生成插件后,在 Android Studio 中右键单击 Kotlin 插件类,然后从菜单中选择 “Convert Kotlin file to Java file” 选项。Android Studio 将引导您完成到 Java 的项目迁移。
开发 iOS 插件
iOS 的 Tauri 插件被定义为一个 Swift 类,它扩展了 Tauri
包中的 Plugin
类。每个带有 @objc
属性和 (_ invoke: Invoke)
参数的函数(例如 @objc private func download(_ invoke: Invoke) { }
)都可以被 Rust 或 JavaScript 调用。
该插件被定义为 Swift 包,以便您可以使用其包管理器来管理依赖项。
插件配置
有关开发插件配置的更多详细信息,请参阅插件开发指南的插件配置部分。
移动设备上的插件实例具有插件配置的 getter
import android.app.Activityimport android.webkit.WebViewimport app.tauri.annotation.TauriPluginimport app.tauri.annotation.InvokeArg
@InvokeArgclass Config { var timeout: Int? = 3000}
@TauriPluginclass ExamplePlugin(private val activity: Activity): Plugin(activity) { private var timeout: Int? = 3000
override fun load(webView: WebView) { getConfig(Config::class.java).let { this.timeout = it.timeout } }}
struct Config: Decodable { let timeout: Int?}
class ExamplePlugin: Plugin { var timeout: Int? = 3000
@objc public override func load(webview: WKWebView) { do { let config = try parseConfig(Config.self) self.timeout = config.timeout } catch {} }}
生命周期事件
插件可以挂钩到几个生命周期事件
- load:当插件加载到 web view 中时
- onNewIntent:仅限 Android,当 activity 重新启动时
插件开发指南中还有其他插件的生命周期事件。
load
- 何时:当插件加载到 web view 中时
- 原因:执行插件初始化代码
import android.app.Activityimport android.webkit.WebViewimport app.tauri.annotation.TauriPlugin
@TauriPluginclass ExamplePlugin(private val activity: Activity): Plugin(activity) { override fun load(webView: WebView) { // perform plugin setup here }}
class ExamplePlugin: Plugin { @objc public override func load(webview: WKWebView) { let timeout = self.config["timeout"] as? Int ?? 30 }}
onNewIntent
注意:这仅在 Android 上可用。
- 何时:当 activity 重新启动时。有关更多信息,请参阅 Activity#onNewIntent。
- 原因:处理应用程序重新启动,例如当通知被点击或深度链接被访问时。
import android.app.Activityimport android.content.Intentimport app.tauri.annotation.TauriPlugin
@TauriPluginclass ExamplePlugin(private val activity: Activity): Plugin(activity) { override fun onNewIntent(intent: Intent) { // handle new intent event }}
添加移动命令
在各自的移动项目中有一个插件类,可以在其中定义可以被 Rust 代码调用的命令
import android.app.Activityimport app.tauri.annotation.Commandimport app.tauri.annotation.TauriPlugin
@TauriPluginclass ExamplePlugin(private val activity: Activity): Plugin(activity) { @Command fun openCamera(invoke: Invoke) { val ret = JSObject() ret.put("path", "/path/to/photo.jpg") invoke.resolve(ret) }}
如果您想使用 Kotlin suspend
函数,则需要使用自定义协程作用域
import android.app.Activityimport app.tauri.annotation.Commandimport app.tauri.annotation.TauriPlugin
// Change to Dispatchers.IO if it is intended for fetching dataval scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
@TauriPluginclass ExamplePlugin(private val activity: Activity): Plugin(activity) { @Command fun openCamera(invoke: Invoke) { scope.launch { openCameraInner(invoke) } }
private suspend fun openCameraInner(invoke: Invoke) { val ret = JSObject() ret.put("path", "/path/to/photo.jpg") invoke.resolve(ret) }}
class ExamplePlugin: Plugin { @objc public func openCamera(_ invoke: Invoke) throws { invoke.resolve(["path": "/path/to/photo.jpg"]) }}
使用 tauri::plugin::PluginHandle
从 Rust 调用移动命令
use std::path::PathBuf;use serde::{Deserialize, Serialize};use tauri::Runtime;
#[derive(Serialize)]#[serde(rename_all = "camelCase")]pub struct CameraRequest { quality: usize, allow_edit: bool,}
#[derive(Deserialize)]pub struct Photo { path: PathBuf,}
impl<R: Runtime> <plugin-name;pascal-case><R> { pub fn open_camera(&self, payload: CameraRequest) -> crate::Result<Photo> { self .0 .run_mobile_plugin("openCamera", payload) .map_err(Into::into) }}
命令参数
参数被序列化为命令,并且可以使用 Invoke::parseArgs
函数在移动插件上解析,该函数接受一个描述参数对象的类。
Android
在 Android 上,参数被定义为一个使用 @app.tauri.annotation.InvokeArg
注解的类。内部对象也必须被注解
import android.app.Activityimport android.webkit.WebViewimport app.tauri.annotation.Commandimport app.tauri.annotation.InvokeArgimport app.tauri.annotation.TauriPlugin
@InvokeArginternal class OpenAppArgs { lateinit var name: String var timeout: Int? = null}
@InvokeArginternal class OpenArgs { lateinit var requiredArg: String var allowEdit: Boolean = false var quality: Int = 100 var app: OpenAppArgs? = null}
@TauriPluginclass ExamplePlugin(private val activity: Activity): Plugin(activity) { @Command fun openCamera(invoke: Invoke) { val args = invoke.parseArgs(OpenArgs::class.java) }}
iOS
在 iOS 上,参数被定义为一个继承 Decodable
的类。内部对象也必须继承 Decodable 协议
class OpenAppArgs: Decodable { let name: String var timeout: Int?}
class OpenArgs: Decodable { let requiredArg: String var allowEdit: Bool? var quality: UInt8? var app: OpenAppArgs?}
class ExamplePlugin: Plugin { @objc public func openCamera(_ invoke: Invoke) throws { let args = try invoke.parseArgs(OpenArgs.self)
invoke.resolve(["path": "/path/to/photo.jpg"]) }}
权限
如果插件需要最终用户的权限,Tauri 简化了检查和请求权限的过程。
首先定义所需的权限列表和一个别名,以在代码中标识每个组。这在 TauriPlugin
注解内完成
@TauriPlugin( permissions = [ Permission(strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "postNotification") ])class ExamplePlugin(private val activity: Activity): Plugin(activity) { }
首先覆盖 checkPermissions
和 requestPermissions
函数
class ExamplePlugin: Plugin { @objc open func checkPermissions(_ invoke: Invoke) { invoke.resolve(["postNotification": "prompt"]) }
@objc public override func requestPermissions(_ invoke: Invoke) { // request permissions here // then resolve the request invoke.resolve(["postNotification": "granted"]) }}
Tauri 自动为插件实现两个命令:checkPermissions
和 requestPermissions
。这些命令可以直接从 JavaScript 或 Rust 调用
import { invoke, PermissionState } from '@tauri-apps/api/core'
interface Permissions { postNotification: PermissionState}
// check permission stateconst permission = await invoke<Permissions>('plugin:<plugin-name>|checkPermissions')
if (permission.postNotification === 'prompt-with-rationale') { // show information to the user about why permission is needed}
// request permissionif (permission.postNotification.startsWith('prompt')) { const state = await invoke<Permissions>('plugin:<plugin-name>|requestPermissions', { permissions: ['postNotification'] })}
use serde::{Serialize, Deserialize};use tauri::{plugin::PermissionState, Runtime};
#[derive(Deserialize)]#[serde(rename_all = "camelCase")]struct PermissionResponse { pub post_notification: PermissionState,}
#[derive(Serialize)]#[serde(rename_all = "camelCase")]struct RequestPermission { post_notification: bool,}
impl<R: Runtime> Notification<R> { pub fn request_post_notification_permission(&self) -> crate::Result<PermissionState> { self.0 .run_mobile_plugin::<PermissionResponse>("requestPermissions", RequestPermission { post_notification: true }) .map(|r| r.post_notification) .map_err(Into::into) }
pub fn check_permissions(&self) -> crate::Result<PermissionResponse> { self.0 .run_mobile_plugin::<PermissionResponse>("checkPermissions", ()) .map_err(Into::into) }}
插件事件
插件可以在任何时间点使用 trigger
函数发出事件
@TauriPluginclass ExamplePlugin(private val activity: Activity): Plugin(activity) { override fun load(webView: WebView) { trigger("load", JSObject()) }
override fun onNewIntent(intent: Intent) { // handle new intent event if (intent.action == Intent.ACTION_VIEW) { val data = intent.data.toString() val event = JSObject() event.put("data", data) trigger("newIntent", event) } }
@Command fun openCamera(invoke: Invoke) { val payload = JSObject() payload.put("open", true) trigger("camera", payload) }}
class ExamplePlugin: Plugin { @objc public override func load(webview: WKWebView) { trigger("load", data: [:]) }
@objc public func openCamera(_ invoke: Invoke) { trigger("camera", data: ["open": true]) }}
然后可以使用 addPluginListener
辅助函数从 NPM 包中调用辅助函数
import { addPluginListener, PluginListener } from '@tauri-apps/api/core';
export async function onRequest( handler: (url: string) => void): Promise<PluginListener> { return await addPluginListener( '<plugin-name>', 'event-name', handler );}
© 2025 Tauri 贡献者。CC-BY / MIT