现有 H5 应用集成 Tauri 快速打包为 APP,并添加扫码 +打开文件功能

1,588 阅读6分钟

背景

最近想给自己的 H5 应用做一个 APP 端,调研了下发现前不久大火的 Tauri 刚好也可以打包成 web,简单尝试一下(Rust 和 Kotlin 都不会,还好有AI),主要想加扫码功能和打开文件和功能。

初始化

安装 Rust 与 Android Studio

Tauri 使用 Rust 开发,需要装一下Rust,不会也不要紧,配个环境就行,后续需要写 Rust 的场景不多。

这里主要想打包为 Android, 所以只下了个 Android Studio,网上和 Tauri 文档里都有教程就不赘述了,注意检查一下提到的SDK是不是都安装了。 Prerequisites | Tauri

为项目初始化 Tauri

Create a Project | Tauri 来到自己已有的 H5 项目下,首先安装 Tauri 的脚手架,并初始化。

pnpm add -D @tauri-apps/cli@latest
pnpm tauri init

根据提示填入一下信息,填错了也不要紧,可以在 src-tauri/tauri.conf.json 中修改

  1. What is your app name? tauri-app APP 名称
  2. What should the window title be? tauri-app 窗口标题的名称
  3. Where are your web assets located? ../dist H5 打包后的文件路径
  4. What is the url of your dev server? http://localhost:5173 H5 运行开发服务后的地址
  5. What is your frontend dev command? pnpm run dev H5 运行开发环境的命令
  6. What is your frontend build command? pnpm run build H5 运行打包项目的命令

之后可以执行 pnpm tauri android init 初始化安卓项目,生成的文件在 src-tauri/gen/android

运行androidpnpm tauri android dev, 第一次运行可能比较久,大概率还会报错,遇到报错只能搜一下了,我折腾了好几次最后才运行成功。

个性化设置

启动 APP 后发现 APP 的图标和名称都不太对,需要自己改一下,直接搜下 Android 项目如何修改图标和名称就行,图标可以去 Iconfont 啥的找一个,使用 App Icon Generator 生成各种大小的图标,直接将生成后下载的文件拖到 src-tauri/gen/android/app/src/main/res 下就行。

安装插件

要实现扫码与打开文件功能需要用到以下插件:

安装直接照着文档装就行,试了下里面的自动安装命令,直接报错了,还是老老实实手动安装吧。

image.png

顺便可以再安装一个 pnpm install @tauri-apps/api 很多方法都在里面,文档里也没说需要按,琢磨好久。

安装完后的 src-tauri/src/lib.rs 文件

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
  tauri::Builder::default()
    .setup(|app| {
      #[cfg(mobile)]
      // 注册插件
      app.handle().plugin(tauri_plugin_barcode_scanner::init())?;
      app.handle().plugin(tauri_plugin_fs::init())?;
      app.handle().plugin(tauri_plugin_shell::init())?;
      if cfg!(debug_assertions) {
        app.handle().plugin(
          tauri_plugin_log::Builder::default()
            .level(log::LevelFilter::Info)
            .build(),
        )?;
      }
      Ok(())
    })
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

扫码功能

扫码功能比较简单,直接调方法就行,有个坑就是没办法自动弹出让用户授权的弹窗得手动去应用管理授权,否则会扫码失败。只能说暂时接受吧,后面看能不能写个插件去弹窗。

import Taro from '@tarojs/taro';
import { Format, scan } from '@tauri-apps/plugin-barcode-scanner';
import { isValidUrl } from './request';

export const scanOrderProduct = () => {
  scan({
    windowed: false, // 为true时不会跳转到摄像头拍摄到的内容,停留在当前页面但是能正常扫
    formats: [Format.QRCode], // 扫描的类型 二维码
  })
    .then((res) => {
      // 跳转到扫码的内容
      if (isValidUrl(res.content)) {
      }
      // 使用URL对象来解析URL
      const urlObj = new URL(res.content);
      // 提取路径和查询参数部分
      const pathAndQuery = urlObj.pathname + urlObj.search;
      Taro.navigateTo({
        url: pathAndQuery,
      });
      // window.location.href = res.content;
    })
    .catch(() => {
      // 手动提示下没有权限
      Taro.showToast({
        title: '请先在应用设置中增加【相机】权限',
        icon: 'none',
      });
    });
};

打开文件功能

想实现的功能:保存 jsPDF 生成的 pdf 并且下载后马上让用户选择制定的应用打开PDF。

之前 H5 是直接下载文件。但是发现在 Webview 中,不会正常触发下载文件,看文档是要监听什么 WebView 的下载事件,但是实测了也没用,只能先手动保存了。

流程:获取下载文件的 ArrayBuffer -> 使用 File System 插件保存文件 -> 使用 shell 插件打开文件。

完整代码

import { resolve } from '@tauri-apps/api/path';
import { exists, mkdir, remove, writeFile } from '@tauri-apps/plugin-fs';
import { open } from '@tauri-apps/plugin-shell';

const saveAndOpenFileByTauri = async (
  dirPath: string,
  fileName: string,
  data: Uint8Array,
) => {
  // 如果文件夹不存在先创建文件夹
  if (!await exists(dirPath)) {
    await mkdir(dirPath);
  }

  // 组合最终文件的路径
  const filePath = await resolve(dirPath, fileName);

  // 写入文件
  await writeFile(filePath, data);

  console.log('保存成功' + filePath);

  // 打开文件
  open(filePath);
};

export default saveAndOpenFileByTauri;

经过多次调试发现保存文件功能正常,但是打开文件功能一点反应都没有,搜了下基本没有解决办法。无奈只能去源码看下。

打开源码发现 Android 部分的实现极其简单,但是是 Kotlin 写的,这下把我一个 Android 只会 hello world,KT完全不会的拦住了。

好在还有AI,用了DeepSeek,一步一步帮我改好了,最终修改后的源码文件

// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

package app.tauri.shell

import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.core.content.FileProvider
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Invoke
import app.tauri.plugin.Plugin
import java.io.File

@TauriPlugin
class ShellPlugin(private val activity: Activity) : Plugin(activity) {
    @Command
    fun open(invoke: Invoke) {
        try {
            // 确保 path 是 String 类型
            val path: String = invoke.parseArgs(String::class.java) as String

            // 创建 File 对象
            val file = File(path)

            // 检查文件是否存在
            if (!file.exists()) {
                invoke.reject("File does not exist")
                return
            }

            // 使用 FileProvider 生成 content:// URI,只有这个格式的URL才能被别的应用识别
            val fileUri = FileProvider.getUriForFile(
                activity,
                "${activity.packageName}.fileprovider", // 必须与 AndroidManifest.xml 中的 authorities 一致
                file
            )

            // 创建 Intent
            val intent = Intent(Intent.ACTION_VIEW)
            intent.setDataAndType(fileUri, getMimeType(file)) // 动态获取 MIME 类型
            // 给其他应用读取该文件的权限?
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

            // 使用 Intent.createChooser 创建选择器
            val chooser = Intent.createChooser(intent, "选择打开应用")
            activity.startActivity(chooser)

            invoke.resolve()
        } catch (ex: Exception) {
            invoke.reject(ex.message)
        }
    }

    // 根据文件扩展名获取 MIME 类型,这步很重要!如果没有文件类型则找不到可以打开该文件的应用
    private fun getMimeType(file: File): String {
        return when (file.extension.lowercase()) {
            "pdf" -> "application/pdf"
            "jpg", "jpeg" -> "image/jpeg"
            "png" -> "image/png"
            "txt" -> "text/plain"
            "mp4" -> "video/mp4"
            else -> "application/octet-stream" // 默认类型
        }
    }
}

打包

折腾完之后就可以打包了,打包前需要设置签名,可以直接问AI【安卓打包设置签名】,我们项目中的build.gradle是kts的,不能直接粘过去,需要追问一下build.gradle.kts文件如何设置

设置好签名就直接用 pnpm tauri android build --apk 命令打包就行

感受

目前开发下来,坑感觉非常的多,先不说中途遇到各种报错排查半天,对移动端的生态也很差,基础提供的功能很少,问题也很多。

如果没有 APP 的开发经验还是不要轻易尝试。我也是之前开发过 RN, 有些问题才能想到怎么解决。