跳转到内容
Tauri

移动插件开发

插件可以运行使用Kotlin(或Java)和Swift编写的本地移动代码。默认的插件模板包含一个使用Kotlin的Android库项目和一个包含示例移动命令的Swift包,该命令展示了如何从Rust代码中触发其执行。

初始化插件项目

按照插件开发指南中的步骤来初始化一个新的插件项目。

如果您有一个现有的插件并且希望为其添加Android或iOS功能,您可以使用plugin android initplugin ios init来引导移动库项目的启动,并引导您进行所需的变化。

默认的插件模板将插件的实现分成两个独立的模块:desktop.rsmobile.rs

桌面实现使用Rust代码实现功能,而移动实现向本地移动代码发送消息以执行一个函数并返回一个结果。如果需要在两种实现之间共享逻辑,可以在lib.rs中定义。

src/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插件定义为扩展app.tauri.plugin(plugin)的Kotlin类,并带有app.tauri.annotation.TauriPlugin注解。带有app.tauri.annotation.Command注解的每个方法都可以由Rust或JavaScript调用。

Tauri默认使用Kotlin为Android插件实现,但如果您愿意,也可以切换到Java。生成插件后,在Android Studio中右键单击Kotlin插件类,从菜单中选择“将Kotlin文件转换为Java文件”选项。Android Studio将引导您完成项目迁移到Java。

开发iOS插件

Android上的Tauri插件定义为从Tauri包中的Plugin类扩展的Swift类。每个带有@objc属性和(_ invoke: Invoke)参数(例如@objc private func download(_ invoke: Invoke) { })的功能都可以由Rust或JavaScript调用。

该插件定义为Swift包,以便您可以使用其包管理器来管理依赖项。

插件配置

有关插件配置的详细信息,请参阅插件配置部分的插件开发指南。

移动设备上的插件实例具有插件配置的getter。

import android.app.Activity
import android.webkit.WebView
import app.tauri.annotation.TauriPlugin
import app.tauri.annotation.InvokeArg
@InvokeArg
class Config {
var timeout: Int? = 3000
}
@TauriPlugin
class 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
}
}
}

生命周期事件

插件可以钩入几个生命周期事件。

  • 加载:当插件加载到web视图时
  • onNewIntent:仅限Android,当活动再次启动时

插件开发指南中还有额外的 插件生命周期事件

加载

  • 触发时间:插件被加载到网页中的时候
  • 原因:执行插件初始化代码
import android.app.Activity
import android.webkit.WebView
import app.tauri.annotation.TauriPlugin
@TauriPlugin
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
override fun load(webView: WebView) {
// perform plugin setup here
}
}

onNewIntent

注意:此功能仅在Android平台上可用。

  • 触发时间:当活动重新启动时。有关更多信息,请参阅Activity#onNewIntent
  • 原因:处理应用程序的重新启动,例如点击通知或访问深度链接。
import android.app.Activity
import android.content.Intent
import app.tauri.annotation.TauriPlugin
@TauriPlugin
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
override fun onNewIntent(intent: Intent) {
// handle new intent event
}
}

添加移动命令

在相应的移动项目中有一个插件类,其中可以定义由Rust代码调用的命令

import android.app.Activity
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
@TauriPlugin
class 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.Activity
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
// Change to Dispatchers.IO if it is intended for fetching data
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
@TauriPlugin
class 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)
}
}

使用 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.Activity
import android.webkit.WebView
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
@InvokeArg
internal class OpenAppArgs {
lateinit var name: String
var timeout: Int? = null
}
@InvokeArg
internal class OpenArgs {
lateinit var requiredArg: String
var allowEdit: Boolean = false
var quality: Int = 100
var app: OpenAppArgs? = null
}
@TauriPlugin
class 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) { }

Tauri自动为插件实现两个命令:checkPermissionsrequestPermissions。这些命令可以直接从JavaScript或Rust中调用

import { invoke, PermissionState } from '@tauri-apps/api/core'
interface Permissions {
postNotification: PermissionState
}
// check permission state
const 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 permission
if (permission.postNotification.startsWith('prompt')) {
const state = await invoke<Permissions>('plugin:<plugin-name>|requestPermissions', { permissions: ['postNotification'] })
}

插件事件

插件可以使用 trigger 函数在任何时刻发出事件

@TauriPlugin
class 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)
}
}

然后可以使用 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 贡献者。署名 - 相同方式共享 4.0 / MIT