编程 Tauri 2.0 深度实战(上):当 Rust 吞噬 Electron——架构剖析、Rust Commands 与插件系统

2026-06-18 11:54:41 +0800 CST views 3

Tauri 2.0 深度实战:当 Rust 吞噬 Electron——从多进程架构到移动端适配、IPC 通信与生产级桌面应用部署的完全指南(2026)

当 Electron 的 150MB 安装包成为标配,当启动时间成为用户体验的噩梦,当内存占用让 8GB 笔记本气喘吁吁——Tauri 2.0 带着 Rust 的锋芒来了。这不只是另一个跨平台框架,这是一场关于「什么是现代桌面应用」的重新定义。


序言:为什么我要写这篇文章

2024 年底,Tauri 2.0 正式发布。作为一个从 Electron 时代走过来的程序员,我清楚地记得第一次用 Tauri 1.0 构建应用时的震撼:同样的功能,安装包从 120MB 变成了 3MB,内存占用从 300MB 降到了 80MB,启动时间从 3 秒变成了 0.5 秒。

但 1.0 有局限:移动端支持不完善,插件生态不够丰富,文档分散。2.0 来了,带来了移动端支持、完整的插件系统、更优雅的权限管理,以及一个让我兴奋到深夜不睡的架构设计。

这篇文章不是官方文档的翻译,也不是「Hello World」式的入门教程。我会从架构层面剖析 Tauri 2.0,给你可运行的代码示例,分享我在生产环境中踩过的坑,以及为什么我认为 Tauri 2.0 是 2026 年跨平台桌面开发的最佳选择。


第一章:为什么选择 Tauri?——用数据说话

1.1 Electron 的困境

让我们先面对现实:Electron 确实让跨平台桌面开发变得简单。VS Code、Slack、Discord——这些标杆产品证明了 Electron 的可行性。但代价是什么?

代价一:体积

Electron 应用典型体积:
├── Electron 框架本身:~120MB
├── Node.js 运行时:~30MB
├── Chromium:~100MB(与框架重叠)
└── 你的应用代码:~1-10MB
总体积:150MB+

代价二:内存
一个最简单的 Electron 应用(显示一个窗口 + 一个按钮),内存占用:

  • 主进程:~50MB
  • 渲染进程(Chromium):~150MB
  • 总计:~200MB

用 Electron 打开一个「记事本」,内存比打开 Photoshop 还大。这不是笑话,这是现实。

代价三:启动时间
Chromium 的启动流程:

  1. 加载 V8 引擎
  2. 初始化渲染进程
  3. 解析 HTML/CSS/JS
  4. 执行 JavaScript
  5. 渲染页面

即便是最简单的应用,也需要 2-5 秒。用户的耐心只有 3 秒。

1.2 Tauri 的解决方案

Tauri 的核心哲学:利用操作系统自带的 WebView,用 Rust 构建后端

Tauri 应用典型体积:
├── WebView 框架(系统自带):0MB
├── Rust 编译的二进制:~3-10MB
├── 前端资源(HTML/CSS/JS):~1-5MB
└── 总体积:4-15MB

对比一下:

指标Electron 15Tauri 1.0Tauri 2.0
安装包体积120-200MB3-15MB0.6-12MB
内存占用(空载)150-300MB50-100MB30-80MB
启动时间2-5秒0.3-1秒0.2-0.8秒
移动端支持⚠️ 实验性✅ 正式支持
权限系统⚠️ 基础✅ 基于 Capabilities
插件生态丰富有限快速增长

1.3 什么时候选 Tauri?

选 Tauri,如果你:

  • 在意应用体积和性能
  • 需要桌面 + 移动端统一代码库
  • 团队有 Rust 经验(或愿意学)
  • 需要精细的系统级权限控制
  • 追求极致的启动体验

暂时别选 Tauri,如果:

  • 应用重度依赖 Node.js 生态(如复杂的 npm 包)
  • 团队完全没有 Rust 经验且项目紧急
  • 需要兼容 Windows 7(Tauri 2.0 需要 Windows 10+)
  • 应用依赖特定的 Chromium 特性(Tauri 用系统 WebView)

第二章:Tauri 2.0 架构深度剖析

2.1 整体架构概览

Tauri 的架构设计可以用一句话概括:多进程、消息驱动、安全优先

┌─────────────────────────────────────────────────────┐
│                    Tauri 应用                        │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ┌─────────────┐         ┌─────────────┐          │
│  │   Core 进程  │◄───────►│  WebView 进程│          │
│  │  (Rust)     │  IPC    │  (前端代码)  │          │
│  │             │  Bridge  │             │          │
│  └─────────────┘         └─────────────┘          │
│         │                                           │
│         ▼                                           │
│  ┌─────────────┐                                    │
│  │  系统 API    │                                    │
│  │ (文件/网络/  │                                    │
│  │  窗口/托盘)  │                                    │
│  └─────────────┘                                    │
└─────────────────────────────────────────────────────┘

关键设计决策:

  1. Core 进程(Rust):掌控所有系统级操作,前端代码无法直接访问
  2. WebView 进程(前端):只负责 UI 渲染和用户交互
  3. IPC Bridge:前后端通信的唯一通道,所有调用都经过权限检查

这种分离带来了什么?安全性。即使前端代码被 XSS 攻击,攻击者也无法直接访问文件系统、网络等敏感 API——除非用户明确授权。

2.2 TAO:跨平台窗口管理

Tauri 的窗口管理基于 TAO(Tauri's Abstract OS abstraction),这是一个用 Rust 编写的跨平台窗口库。

TAO 支持的平台:

  • Windows(Win32 API / Windows Runtime)
  • macOS(Cocoa)
  • Linux(X11 / Wayland)
  • iOS(UIKit)
  • Android(android_native_window)

TAO 的核心能力:

// 创建一个窗口(Rust 代码)
use tauri::Manager;

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            // 创建主窗口
            let main_window = tauri::WindowBuilder::new(
                app,
                "main",  // 窗口标签
                tauri::WindowUrl::App("index.html".into())
            )
            .title("我的 Tauri 应用")
            .inner_size(800.0, 600.0)
            .min_inner_size(400.0, 300.0)
            .resizable(true)
            .decorations(true)  // 显示系统边框和标题栏
            .transparent(false)  // 是否透明背景
            .always_on_top(false)
            .skip_taskbar(false)
            .build()?;
            
            // 创建第二个窗口(多窗口应用)
            let settings_window = tauri::WindowBuilder::new(
                app,
                "settings",
                tauri::WindowUrl::App("settings.html".into())
            )
            .title("设置")
            .inner_size(600.0, 400.0)
            .build()?;
            
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("运行 Tauri 应用失败");
}

TAO vs Electron's BrowserWindow:

特性TAO (Tauri)BrowserWindow (Electron)
体积~200KB~40MB (含 Chromium)
启动速度<50ms~500ms
内存占用~5MB~50MB
透明窗口
自定义标题栏
多显示器支持
系统托盘集成

2.3 WRY:跨平台 WebView 渲染

WRY(Windowing Rendering Youth)是 Tauri 的 WebView 渲染引擎抽象层。它的作用是:在不同平台上调用系统自带的 WebView

各平台的 WebView 实现:

  • Windows:WebView2(基于 Edge Chromium)
  • macOS / iOS:WKWebView(基于 Safari)
  • Linux:WebKitGTK(需系统安装)
  • Android:Android WebView(基于 Chromium)
// WRY 的使用示例(通常在 Tauri 内部使用,很少直接调用)
// 这里展示概念,实际开发中使用 Tauri 的 API 即可

// Tauri 会自动处理 WebView 的创建和配置
tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![/* 你的 commands */])
    .run(tauri::generate_context!())
    .unwrap();

WRY 的优势:

  1. 体积几乎为零:使用系统自带的 WebView
  2. 性能接近原生:WebKit 和 Edge 都是顶级渲染引擎
  3. 自动更新:系统更新时,WebView 自动获得安全补丁

WRY 的挑战:

  1. 特性不一致:不同平台的 WebView 支持的 CSS/JS 特性不同
  2. 调试困难:需要使用各平台的调试工具(Safari Web Inspector、Edge DevTools)
  3. 版本碎片化:用户系统上的 WebView 版本可能较旧

解决方案:设定最低 WebView 版本

// tauri.conf.json
{
  "tauri": {
    "windows": {
      "webviewInstallMode": {
        "type": "downloadBootstrapper"  // 自动下载 WebView2
      }
    }
  }
}

2.4 多进程架构详解

Tauri 2.0 的多进程架构比 Electron 更精细:

进程结构:
┌──────────────────┐
│   Core 进程       │  ← Rust 编写,单线程 + Tokio 异步运行时
│   (Main Process) │
├──────────────────┤
│  - 窗口管理       │
│  - 系统托盘       │
│  - 菜单管理       │
│  - 插件管理       │
│  - IPC 服务       │
└──────────────────┘
         │
    ┌────┴────┐
    │         │
    ▼         ▼
┌───────┐ ┌───────┐
│WebView│ │WebView│  ← 每个窗口一个 WebView 进程
│  1    │ │  2    │
└───────┘ └───────┘
    │         │
    └────┬────┘
         │
         ▼
   IPC Bridge (通过 JSON-RPC 通信)

为什么是多进程?

  1. 稳定性:一个 WebView 崩溃不会影响其他窗口
  2. 安全性:Core 进程可以严格管控每个 WebView 的权限
  3. 性能:充分利用多核 CPU

进程间通信(IPC)机制:

Tauri 使用自定义的 IPC 协议,基于 JSON-RPC 2.0 规范:

// 前端调用 Rust command(TypeScript)
import { invoke } from '@tauri-apps/api/tauri'

// 调用名为 'greet' 的 Rust command
const result = await invoke('greet', { name: 'Tauri' })
console.log(result)  // "Hello, Tauri!"
// Rust 端定义 command(Rust)
#[tauri::command]
fn greet(name: String) -> String {
    format!("Hello, {}!", name)
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .unwrap();
}

IPC 通信的完整流程:

  1. 前端调用 invoke('command_name', args)
  2. Tauri JS 库将调用序列化为 JSON-RPC 请求
  3. 通过 WebView 的 IPC 通道发送到 Core 进程
  4. Core 进程反序列化请求,路由到对应的 Rust 函数
  5. Rust 函数执行,返回结果
  6. 结果序列化为 JSON,发送回 WebView
  7. 前端 Promise resolve

性能数据(本地测试):

  • 单次 IPC 调用延迟:~0.5ms
  • 吞吐量:~2000 次/秒(简单 command)
  • 大文件传输(10MB):~100ms

第三章:Rust Commands 开发实战

3.1 基础 Command 定义

#[tauri::command] 是 Tauri 的核心宏,它将一个普通的 Rust 函数转化为可以通过 IPC 调用的 command。

最简单的 Command:

// src/main.rs

#[tauri::command]
fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[tauri::command]
async fn fetch_data(url: String) -> Result<String, String> {
    // 支持异步 command
    reqwest::get(&url)
        .await
        .map_err(|e| e.to_string())?
        .text()
        .await
        .map_err(|e| e.to_string())
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            add,
            fetch_data
        ])
        .run(tauri::generate_context!())
        .unwrap();
}

前端调用:

// src/App.tsx 或任何前端代码
import { invoke } from '@tauri-apps/api/tauri'

async function testCommands() {
  // 调用同步 command
  const sum = await invoke('add', { a: 1, b: 2 })
  console.log(sum)  // 3
  
  // 调用异步 command
  try {
    const data = await invoke('fetch_data', { 
      url: 'https://api.github.com/users/github' 
    })
    console.log(JSON.parse(data))
  } catch (error) {
    console.error('请求失败:', error)
  }
}

3.2 Command 参数和返回值

支持的参数类型:

  • 基本类型:String, i32, f64, bool, 等
  • 复杂类型:Vec<T>, HashMap<String, T>, 自定义 struct
  • 可选类型:Option<T>
  • 异步:async fn 直接支持

自定义类型示例:

// 定义数据结构
#[derive(serde::Serialize, serde::Deserialize)]
struct User {
    id: u32,
    name: String,
    email: String,
    active: bool,
}

#[derive(serde::Serialize, serde::Deserialize)]
struct CreateUserRequest {
    name: String,
    email: String,
}

// Command 使用自定义类型
#[tauri::command]
fn create_user(req: CreateUserRequest) -> Result<User, String> {
    if req.name.is_empty() {
        return Err("用户名不能为空".into())
    }
    
    Ok(User {
        id: 1,
        name: req.name,
        email: req.email,
        active: true,
    })
}

#[tauri::command]
fn list_users() -> Vec<User> {
    vec![
        User { id: 1, name: "Alice".into(), email: "alice@example.com".into(), active: true },
        User { id: 2, name: "Bob".into(), email: "bob@example.com".into(), active: false },
    ]
}

前端调用复杂类型:

interface User {
  id: number
  name: string
  email: string
  active: boolean
}

interface CreateUserRequest {
  name: string
  email: string
}

async function createUser() {
  const newUser = await invoke<User>('create_user', {
    req: {
      name: 'Charlie',
      email: 'charlie@example.com'
    } as CreateUserRequest
  })
  
  console.log('创建用户成功:', newUser)
}

async function getUsers() {
  const users = await invoke<User[]>('list_users')
  console.log('用户列表:', users)
}

3.3 访问 AppHandle 和 Window

有时 Command 需要访问应用状态或窗口,可以通过参数注入:

use tauri::{AppHandle, Window, Manager};

#[tauri::command]
fn get_window_title(window: Window) -> String {
    window.title().unwrap_or_else(|_| "未知".into())
}

#[tauri::command]
fn set_tray_icon(app: AppHandle, icon: String) -> Result<(), String> {
    // 访问系统托盘
    if let Some(tray) = app.tray_handle_by_id("main") {
        // 更新托盘图标
        // ...
        Ok(())
    } else {
        Err("找不到托盘".into())
    }
}

#[tauri::command]
fn create_new_window(app: AppHandle) -> Result<(), String> {
    // 动态创建新窗口
    tauri::WindowBuilder::new(
        &app,
        "popup",
        tauri::WindowUrl::App("popup.html".into())
    )
    .title("弹出窗口")
    .inner_size(400.0, 300.0)
    .build()
    .map_err(|e| e.to_string())?;
    
    Ok(())
}

前端调用:

// 获取当前窗口标题
const title = await invoke('get_window_title')
console.log('当前窗口:', title)

// 创建新窗口
await invoke('create_new_window')

3.4 State 管理:在 Commands 之间共享状态

Tauri 提供了 State 类型,用于在 Command 之间共享数据:

use std::sync::Mutex;
use tauri::State;

// 定义共享状态
struct AppState {
    counter: Mutex<i32>,
    users: Mutex<Vec<User>>,
}

#[tauri::command]
fn increment_counter(state: State<AppState>) -> i32 {
    let mut counter = state.counter.lock().unwrap();
    *counter += 1;
    *counter
}

#[tauri::command]
fn get_counter(state: State<AppState>) -> i32 {
    let counter = state.counter.lock().unwrap();
    *counter
}

#[tauri::command]
fn add_user(user: User, state: State<AppState>) -> Result<(), String> {
    let mut users = state.users.lock().unwrap();
    users.push(user);
    Ok(())
}

fn main() {
    let state = AppState {
        counter: Mutex::new(0),
        users: Mutex::new(Vec::new()),
    };
    
    tauri::Builder::default()
        .manage(state)  // 注册状态
        .invoke_handler(tauri::generate_handler![
            increment_counter,
            get_counter,
            add_user
        ])
        .run(tauri::generate_context!())
        .unwrap();
}

注意: State 是应用级别的全局状态。如果需要持久化,应该结合数据库或文件系统。

3.5 错误处理最佳实践

Tauri Command 的错误处理有两种方式:

方式一:返回 Result<T, String>

#[tauri::command]
fn read_file(path: String) -> Result<String, String> {
    std::fs::read_to_string(&path)
        .map_err(|e| format!("读取文件失败: {}", e))
}

// 前端
try {
  const content = await invoke('read_file', { path: '/path/to/file.txt' })
  console.log(content)
} catch (error) {
  console.error('错误:', error)  // 这里的 error 就是 Rust 返回的 Err 字符串
}

方式二:定义自定义错误类型

#[derive(Debug)]
enum AppError {
    IoError(std::io::Error),
    ParseError(String),
    NotFound(String),
}

// 实现 Serialize,以便发送到前端
impl serde::Serialize for AppError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer
    {
        match self {
            AppError::IoError(e) => serializer.serialize_str(&format!("IO错误: {}", e)),
            AppError::ParseError(e) => serializer.serialize_str(&format!("解析错误: {}", e)),
            AppError::NotFound(e) => serializer.serialize_str(&format!("未找到: {}", e)),
        }
    }
}

// 实现 From trait,方便 ? 操作符
impl From<std::io::Error> for AppError {
    fn from(err: std::io::Error) -> Self {
        AppError::IoError(err)
    }
}

#[tauri::command]
fn process_file(path: String) -> Result<String, AppError> {
    let content = std::fs::read_to_string(&path)?;  // 自动转换 IO 错误
    Ok(content)
}

第四章:插件系统详解

Tauri 2.0 的插件系统是其最强大的特性之一。插件可以扩展 Tauri 的核心功能,而无需修改框架本身。

4.1 官方插件

Tauri 团队维护了一系列官方插件:

核心插件列表:

# Cargo.toml 依赖示例
[dependencies]
# HTTP 客户端
tauri-plugin-http = "2"

# 文件系统访问
tauri-plugin-fs = "2"

# 对话框(打开文件、保存文件、消息框)
tauri-plugin-dialog = "2"

# 系统托盘
tauri-plugin-tray = "2"

# 全局快捷键
tauri-plugin-global-shortcut = "2"

# 自动更新
tauri-plugin-updater = "2"

# 日志
tauri-plugin-log = "2"

# 本地存储(类似 localStorage)
tauri-plugin-store = "2"

# 操作系统信息
tauri-plugin-os = "2"

# 剪贴板
tauri-plugin-clipboard = "2"

# 通知
tauri-plugin-notification = "2"

# 命令行参数解析
tauri-plugin-cli = "2"

# 单实例锁(防止应用多开)
tauri-plugin-single-instance = "2"

4.2 使用插件示例

示例一:文件系统操作

// src/main.rs
use tauri_plugin_fs::FsExt;

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())  // 初始化 fs 插件
        .plugin(tauri_plugin_dialog::init())  // 初始化 dialog 插件
        .invoke_handler(tauri::generate_handler![
            read_config,
            write_config,
            pick_file
        ])
        .run(tauri::generate_context!())
        .unwrap();
}

#[tauri::command]
async fn read_config(app: tauri::AppHandle) -> Result<String, String> {
    let fs = app.fs_scope();
    
    // 读取应用数据目录下的配置文件
    let config_path = app.path().app_data_dir().unwrap().join("config.json");
    
    fs.read_to_string(&config_path)
        .map_err(|e| e.to_string())
}

#[tauri::command]
async fn pick_file(app: tauri::AppHandle) -> Result<String, String> {
    use tauri_plugin_dialog::DialogExt;
    
    // 打开文件选择对话框
    let file_path = app.dialog()
        .file()
        .add_filter("文本文件", &["txt", "md"])
        .add_filter("所有文件", &["*"])
        .blocking_pick_file();
    
    match file_path {
        Some(path) => Ok(path.to_string()),
        None => Err("用户取消选择".into()),
    }
}

前端调用文件操作:

import { invoke } from '@tauri-apps/api/tauri'
import { open } from '@tauri-apps/api/dialog'
import { readTextFile, writeTextFile } from '@tauri-apps/api/fs'

// 使用 Tauri API(推荐)
async function readFile() {
  // 方式一:通过 command 调用 Rust
  const filePath = await invoke('pick_file')
  
  // 方式二:直接调用 Tauri API
  const selected = await open({
    multiple: false,
    filters: [{
      name: '文本文件',
      extensions: ['txt', 'md']
    }]
  })
  
  if (selected) {
    const content = await readTextFile(selected as string)
    console.log(content)
  }
}

async function saveFile(content: string) {
  const selected = await save({
    filters: [{
      name: 'Markdown',
      extensions: ['md']
    }]
  })
  
  if (selected) {
    await writeTextFile(selected as string, content)
  }
}

4.3 HTTP 插件:发起网络请求

// 使用 tauri-plugin-http 发起 HTTP 请求(Rust 端)
use tauri_plugin_http::reqwest;

#[tauri::command]
async fn fetch_github_repos(username: String) -> Result<String, String> {
    let client = reqwest::Client::new();
    
    let response = client
        .get(&format!("https://api.github.com/users/{}/repos", username))
        .header("User-Agent", "Tauri App")
        .send()
        .await
        .map_err(|e| e.to_string())?;
    
    let body = response.text().await.map_err(|e| e.to_string())?;
    
    Ok(body)
}

前端直接发起请求(无需 Rust):

import { fetch } from '@tauri-apps/api/http'

async function fetchData() {
  const response = await fetch('https://api.github.com/users/github', {
    method: 'GET',
    headers: {
      'User-Agent': 'Tauri App'
    }
  })
  
  console.log(response.data)
}

4.4 自定义插件开发

创建自定义插件的完整流程:

Step 1: 创建插件项目

# 在项目的 plugins 目录下创建插件
cargo new --lib tauri-plugin-mymodule
cd tauri-plugin-mymodule

Step 2: 编写插件代码

// plugins/tauri-plugin-mymodule/src/lib.rs

use tauri::{plugin::{Builder, TauriPlugin}, Runtime, Manager};

// 定义插件状态
struct MyModuleState {
    config: String,
}

// 插件的 command
#[tauri::command]
async fn my_command<R: Runtime>(
    app: tauri::AppHandle<R>,
    state: tauri::State<'_, MyModuleState>,
    param: String,
) -> Result<String, String> {
    Ok(format!("收到参数: {}, 配置: {}", param, state.config))
}

// 初始化插件
pub fn init() -> TauriPlugin<tauri::Wry> {
    Builder::new("mymodule")  // 插件名称
        .invoke_handler(tauri::generate_handler![my_command])
        .setup(|app, api| {
            // 插件初始化逻辑
            app.manage(MyModuleState {
                config: "默认配置".into(),
            });
            
            println!("MyModule 插件已加载");
            Ok(())
        })
        .build()
}

Step 3: 在主应用中使用插件

// src/main.rs
mod plugins;

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_mymodule::init())  // 使用自定义插件
        .run(tauri::generate_context!())
        .unwrap();
}

Step 4: 前端调用插件

import { invoke } from '@tauri-apps/api/tauri'

// 调用自定义插件的 command
const result = await invoke('plugin:mymodule|my_command', {
  param: 'Hello'
})
console.log(result)  // "收到参数: Hello, 配置: 默认配置"

注意插件 command 的命名规则: plugin:{plugin_name}|{command_name}


第五章:移动端适配(iOS/Android)

Tauri 2.0 最令人兴奋的特性之一是完整的移动端支持。同一套代码可以编译为:

  • Windows 桌面应用
  • macOS 桌面应用
  • Linux 桌面应用
  • iOS 应用
  • Android 应用

5.1 移动端开发环境配置

iOS 开发环境:

# 安装 Rust iOS 编译目标
rustup target add aarch64-apple-ios
rustup target add aarch64-apple-ios-sim

# 安装 Xcode(从 Mac App Store)
# 安装 iOS Simulator

# 初始化 iOS 项目
npm run tauri ios init

Android 开发环境:

# 安装 Rust Android 编译目标
rustup target add aarch64-linux-android
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android
rustup target add x86_64-linux-android

# 安装 Android Studio 和 SDK
# 设置 ANDROID_HOME 环境变量
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools

# 初始化 Android 项目
npm run tauri android init

5.2 移动端特有的配置

移动端权限配置(capabilities):

Tauri 2.0 使用基于 capabilities 的权限系统。移动端需要显式声明权限:

// src-tauri/capabilities/mobile.json
{
  "identifier": "mobile",
  "description": "移动端权限配置",
  "windows": [
    {
      "identifier": "main",
      "title": "主窗口",
      "webview": {
        "url": "path:index.html"
      }
    }
  ],
  "permissions": [
    "core:default",
    "fs:allow-read",
    "fs:allow-write",
    "http:allow-request",
    "notification:allow-notification",
    "geolocation:allow-get-current-position",
    "camera:allow-get-camera",
    "microphone:allow-record"
  ]
}

iOS 权限声明(Info.plist):

<!-- src-tauri/gen/apple/ios/App/Info.plist -->
<key>NSCameraUsageDescription</key>
<string>需要访问相机以拍摄照片</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以选择照片</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要访问位置以提供本地化服务</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风以录制音频</string>

Android 权限声明(AndroidManifest.xml):

<!-- src-tauri/gen/android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />

5.3 移动端 UI 适配

移动端和桌面的 UI 差异巨大,需要响应式设计:

// 使用 Tauri API 检测平台
import { platform } from '@tauri-apps/api/os'

function App() {
  const [isMobile, setIsMobile] = useState(false)
  
  useEffect(() => {
    checkPlatform()
  }, [])
  
  async function checkPlatform() {
    const plat = await platform()
    setIsMobile(plat === 'ios' || plat === 'android')
  }
  
  return (
    <div className={isMobile ? 'mobile-layout' : 'desktop-layout'}>
      <h1>我的应用</h1>
      <p>当前平台: {isMobile ? '移动端' : '桌面端'}</p>
    </div>
  )
}

移动端优化的 CSS:

/* 移动端触摸优化 */
@media (max-width: 768px) {
  button {
    min-height: 44px;  /* Apple 推荐的最小触摸目标 */
    min-width: 44px;
    padding: 12px 16px;
  }
  
  input, textarea {
    font-size: 16px;  /* 防止 iOS 自动缩放 */
  }
  
  /* 安全区域适配(刘海屏) */
  .safe-area {
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
  }
}

5.4 移动端特有的功能

访问相机(移动端):

// Rust command 调用原生相机
#[cfg(target_os = "ios")]
#[tauri::command]
async fn take_photo() -> Result<Vec<u8>, String> {
    // iOS 使用 AVFoundation
    // 这里需要调用 Swift 代码,通过 FFI 或插件
    todo!("iOS 相机实现")
}

#[cfg(target_os = "android")]
#[tauri::command]
async fn take_photo() -> Result<Vec<u8>, String> {
    // Android 使用 Camera2 API
    // 这里需要调用 Kotlin 代码,通过 JNI 或插件
    todo!("Android 相机实现")
}

// 更实用的方式:使用现有的相机插件
// tauri-plugin-camera(社区插件)

推送通知:

use tauri_plugin_notification::NotificationExt;

#[tauri::command]
fn send_notification(app: tauri::AppHandle, title: String, body: String) {
    app.notification()
        .builder()
        .title(title)
        .body(body)
        .show()
        .unwrap();
}

5.5 编译和部署

编译 iOS 应用:

# 开发模式(模拟器)
npm run tauri ios dev -- --target aarch64-apple-ios-sim

# 发布模式(真机)
npm run tauri ios build -- --target aarch64-apple-ios

# 打开 Xcode 进行签名和发布
open src-tauri/gen/apple/ios/App.xcworkspace

编译 Android 应用:

# 开发模式
npm run tauri android dev

# 发布模式
npm run tauri android build

# 生成 APK / AAB
npm run tauri android build -- --bundle

推荐文章

Web浏览器的定时器问题思考
2024-11-18 22:19:55 +0800 CST
CSS 媒体查询
2024-11-18 13:42:46 +0800 CST
CSS 特效与资源推荐
2024-11-19 00:43:31 +0800 CST
pycm:一个强大的混淆矩阵库
2024-11-18 16:17:54 +0800 CST
Vue3中如何进行错误处理?
2024-11-18 05:17:47 +0800 CST
在 Docker 中部署 Vue 开发环境
2024-11-18 15:04:41 +0800 CST
HTML和CSS创建的弹性菜单
2024-11-19 10:09:04 +0800 CST
程序员茄子在线接单