多喝水:记一次桌面小APP开发的心路历程

645 阅读5分钟

由于自己最近在健身(减肥)嘛,教练告诉一定要多喝水,但是由于从小就没有勤喝水的习惯,导致自己还是经常忘了要喝水,总是渴的不行的时候才想起来喝...

正好之前见到过不少提醒喝水的软件,尝试使用了一波,发现并不是很符合自己的需求,原因有以下几点:

1、好看的大都在手机上,我不希望用手机来提醒,因为平时敲代码并不会看手机,如果震动或者声音,太影响其他人。

2、电脑上的也有,但是提醒的强度不高,都是通过“通知”来进行,强度太小了,存在感太低,我需要一个强提醒,最好是弹到脸上的那种。

3、自己也想试试开发一款桌面APP...

基于以上的一些原因吧,在空余时间,尝试了一把桌面APP的开发,期间的一些思考和了解在下面简单做了一下记录。

技术选型

“工欲善其事,必先利其器”,开始前,首先要想想自己要使用什么顺手的工具...

因为自己首先想的是给自己用,平时Mac占据100%的场景,所以首先想到的是原生开发

1、Objective-C or Swift UI

// Objective-C

@implementation Person  @synthesize name;

- (id)initWithAge:(int)initAge {
  self = [super init];
  if (self) {
    // NOTE: direct instance variable assignment, not property setter     age = initAge;
  }
  return self;
}

- (int)age {
  return age;
}
@end
// Swift UI

extension Player {
    mutating func updateScore(_ newScore: Int) {
        history.append(newScore)
        if highScore < newScore {
            print("(newScore)! A new high score for (name)! 🎉")
            highScore = newScore
        }
    }
}

player.updateScore(50)

简单看了一下,OC的语法写起来相当复杂,学习成本相当高,Swift UI看起来和JS差不太多,不过也有很多语法不一致,并且需要学习很多的原生知识、更换编辑器等等...

总体看,使用原生语言来开发的话,学习成本还是相当高的,为了开发一个小APP,ROI太小了,果断放弃。

2、Electron

这时候,忽然想起之前有简单了解过Electron,前段时间分享的《浅浅析B站桌面端主题色切换效果》中的B站的桌面软件,就是基于Electron来开发的。

Electron是使用JavaScriptHTMLCSS构建跨平台的桌面应用程序框架。 Electron兼容MacWindowsLinux,可以构建出三个平台的应用程序

其有一个非常吸引人的点是:可以使用JS+HTML+CSS来进行页面布局,如果你是一个前端开发er,你可以几乎无门槛上手electron,并且它的官方文档写的很详细,很快就可以搞出来一个可用的桌面app,并且可以打出来多个平台的安装包(如果你需要的话)。

(图片来自https://baijiahao.baidu.com/s?id=1744722241805764527&wfr=spider&for=pc)

毫无疑问,这是一个对我来说相当完美的方案,就它了!

基于Electron的APP架构图

image.png

页面效果

1、安装页

2、提示页

3、数据页

4、设置页

5、顶部托盘展示

image.png

6、演示gif

ezgif-1-7de02c7bc7.gif

期望的效果也都达到了:

  • 重提示(弹到脸上)

  • 方便操作(功能简单)

  • 还算可以入眼

  • 可以统计历史数据


本可以至此就完美结束,但是在打包之后,其安装包大小犹如一块石头一样压在我心里,实在是接受不了这么一个小玩意儿要那么大的体积...

226M... 实在是超出预期太多太多了...

就像这种小功能,我最多能接受的大小就是20M以内

再多就实在是接受不了了

通过一些了解,可以通过调整dependencies与devDependencies内的依赖的位置,将页面需要的依赖都挪到dev下,这样让体积减小了大概30M左右,但是最终的体积还是比较大,比较愁人...

在寻找优化体积的过程中发现了tauri这样一个库,其官方介绍:

发现其有一点深得我心,即:使用系统自带的网页渲染器,其应用的打包体积可以倒 600KB 以下!

和electron对比一下

Tauri App体积非常小,因为它使用操作系统的 webview。它不提供运行时,因为最终的二进制文件是从 Rust 编译的。这使得 Tauri 应用程序的逆向不是一项简单的任务(得亏B站没用这个来编写,不然上次的分享可就做不了了😂)。

可以看到,其中比较难搞就是主进程的代码是使用Rust来开发的,之前没有写过Rust,纠结了好长一段时间。

最后还是决定试一试,大概有下面两点原因吧:

1、前端基建目前在朝go、rust构建发展,比如esbuild(go),Deno(rust),SWC(rust)等等

2、尝试多了解一门语言,为以后创造更多实现场景

基于Tauri的APP架构图

image.png

主要做了定时器、主进程store、主进程和UI进程之间通讯方式的调整

一些主要代码

// main.rs

#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

// 引入依赖
use tauri::{Manager, Size};
use tauri::{PhysicalSize, SystemTray, SystemTrayEvent};

// 激活页面,从react中触发展示
#[tauri::command]
fn active_window(app_handle: tauri::AppHandle) {
    let main_window = app_handle.get_window("main").unwrap();
    main_window.show().unwrap();
    main_window.emit("active_window", "").expect("事件触发异常");
    main_window.set_focus().unwrap();
}

// 更新托盘中的的title展示
#[tauri::command]
fn update_tray_title(app_handle: tauri::AppHandle, count: &str) {
    app_handle
        .tray_handle()
        .set_title(&format!("别忘了饮水!今日已饮 {} 次", count).to_string()[..])
        .expect("title设置异常")
}

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            // 设置激活策略
            // 每次激活时,都在当前页面展示窗口
            #[cfg(target_os = "macos")]
            app.set_activation_policy(tauri::ActivationPolicy::Accessory);

            // 设置窗口的大小为显示器大小
            let main_window = app.get_window("main").unwrap();
            let current_monitor = main_window.current_monitor().unwrap();
            let current_monitor_unwrap = current_monitor.unwrap();
            let size = current_monitor_unwrap.size();

            main_window
                .set_size(Size::Physical(PhysicalSize {
                    width: size.width,
                    height: size.height + 100,
                }))
                .unwrap();

            // 设置展示在最上层
            main_window.set_always_on_top(true).unwrap();
            // 打开dev_tools
            // main_window.open_devtools();
            Ok(())
        })
        .system_tray(SystemTray::new())
        .on_system_tray_event(move |app, event| match event {
            SystemTrayEvent::LeftClick {
                position: _,
                size: _,
                ..
            } => {
                // 点击托盘事件回调
                let main_window = app.get_window("main").unwrap();

                // 派发事件,展示window
                main_window
                    .emit("tray_show_window", "")
                    .expect("事件触发异常");
            }
            _ => {}
        })
        .on_window_event(|event| match event.event() {
            tauri::WindowEvent::CloseRequested { api, .. } => {
                // 当点击关闭按钮时,不退出程序
                // 这里暂时没有使用,因为顶部操作按钮都给隐藏了
                event.window().hide().unwrap();
                api.prevent_close();
            }
            tauri::WindowEvent::Focused(false) => {}
            _ => {}
        })
        // 注册事件(供react中调用的方法)
        .invoke_handler(tauri::generate_handler![active_window, update_tray_title])
        // 执行
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
// 在js中调用rust方法
import { invoke } from "@tauri-apps/api";

invoke("active_window");

这里就可以通过tauri提供的包来从js调用rust中的代码,猜测原理和js-bridge差不多,rust往window上注入对象,调用invoke的时候,其实是执行window对象上的方法(猜测,还没有去确认)。

值得注意的:

tauri目前来看还没有electron的社区活跃,一些功能还是缺失的,比如我想做的开机自启动、dock栏事件监听,目前官方还是没有支持的。

不过用它来支持一些小工具开发是非常合适的,如果是做公司业务的技术选型,需要做更深层次的调研才可以。

反馈

以上就是实现这个小桌面APP的主要经过,目前它正很符合预期的运行在我的电脑中。

今天我也比平时多喝了三杯水,嗯,还是有用的hhh!

参考地址

www.electronjs.org/

tauri.app/zh/

下载地址

安装包程序:bj-mutou-1301404888.cos.ap-beijing.myqcloud.com/water-water…