不瞒大家说,我之前从来没碰过 Flutter,也没搞过移动端开发。但前几天我硬是靠 Cursor + 一股蛮劲,把一个 App 从 idea 到 APK/IPA 塞进了自己的 iPhone 和安卓备用机里。过程中被各种环境依赖折磨到怀疑人生,也被 AI 的代码生成能力惊到下巴掉下来。这篇文章就是完整的流水账,踩过的坑一个不落。


一、 起因:我想要一个手机上能用的"Word 瘦身"工具

事情是这样的。之前我用 Python 写了个桌面端的 Word 文档压缩工具(PC 版的文章改天单独聊),效果还不错,能把一个 80MB 的标书压到十几兆。然后就有朋友问:“能不能手机上也用?有时候临时收到个大文件,想压一下再转发。”

我心想,行吧,顺手做个移动端呗。再加个证件照功能——毕竟打印店拍个证件照 20 块,手机自己搞不也挺好?

于是就有了这个叫**「良辰百宝箱」**的项目。名字起得比较中二,但我觉得挺上口的。

图1: 我的app做成的样子
在这里插入图片描述


二、技术选型:为什么是 Flutter?说实话,不是我选的

我一开始其实想用 React Native,毕竟 JavaScript 我比较熟。但 Cursor 给我推荐了 Flutter,理由很直接:一套代码真正编译成原生,性能更好,Google ML Kit 的 Flutter 插件也很成熟。

我想了想,反正代码不是我手写的😂,用啥框架差别不大,就听 AI 的了。

最终的技术栈长这样:

在这里插入图片描述

这里有个我当时不知道的坑:ML Kit 的插件会拉一整个 NDK(1.2GB)作为编译依赖。后面会聊到这事儿有多折磨人。


三、Word 压缩:手机上给 docx 做"外科手术"

这部分其实是整个项目里我最满意的功能,因为思路很巧妙(当然是 AI 想的,不是我)。

你可能不知道,.docx 文件其实就是个换了皮的 ZIP 压缩包。你把任何一个 .docx 后缀改成 .zip,就能解压出来看到里面的结构。图片都老老实实躺在 word/media/ 目录下。

所以压缩思路就很清晰了:解压 → 找到图片 → 压缩图片 → 塞回去 → 重新打包。说白了就是拆快递、换里面的东西、再封上。

// 核心就这几步,比想象中简单
final archive = ZipDecoder().decodeBytes(docxBytes);

for (final file in archive.files) {
  if (file.name.startsWith('word/media/') && _isImage(file.name)) {
    // 解码图片 → 缩放 → 重新编码为 JPEG
    // 这里用 quality: 70 是个经验值,再低就肉眼可见糊了
    final compressed = img.encodeJpg(resizedImage, quality: 70);
    file.content = compressed;  // 直接替换原文件内容
  }
}

// 重新打包,就这么简单粗暴
final newZip = ZipEncoder().encode(archive);

实测下来,一个塞了 50 张高清图的投标文件,从 87MB 压到 13MB,打开看跟原来几乎没区别。当然,"几乎"这个词很关键——如果你压的是摄影集那种对画质极度敏感的文档,我建议还是别用这功能😁。


四、AI 证件照:这部分确实有点意思

这是我觉得技术含量最高的功能,虽然代码也是 AI 写的,但整个流程串起来还挺让人兴奋的。

简单说就是三步:

第一步:人脸检测——用 Google ML Kit 的 Face Detection,在照片里找到人脸的位置。它会返回一个"框"(Bounding Box),告诉你脸在哪儿、多大、有没有歪。如果检测到零张脸或者多张脸,直接弹提示让用户换照片。

第二步:人像抠图——这步是真 AI 在干活。ML Kit 的 Selfie Segmentation 会给图片的每个像素打一个分数(0 到 1),分数越高说明越可能是人物前景。我们取 0.5 为阈值,高于的保留,低于的替换成纯色背景。

for (int y = 0; y < height; y++) {
  for (int x = 0; x < width; x++) {
    final confidence = mask.getConfidence(x, y);
    if (confidence > 0.5) {
      result.setPixel(x, y, original.getPixel(x, y));  // 人物:保留
    } else {
      result.setPixelRgba(x, y, bgR, bgG, bgB, 255);  // 背景:换色
    }
  }
}
// 这段代码的坑在于:逐像素遍历在大图上会很慢
// 1920x1080 的图大概要 2-3 秒,可以接受
// 但如果处理 4K 图片就卡得明显了,这个后面再优化吧

第三步:裁剪到标准尺寸——根据人脸位置计算证件照的裁剪区域(头顶留白、下巴到胸部),然后缩放到标准的一寸(295×413px)或二寸(413×579px)。

效果嘛,说实话比不上专业修图,但日常用用足够了。在白墙前拍的照片效果最好,复杂背景下边缘偶尔会有些毛糙。这个问题我目前还没想到好的解决办法——有搞过图像处理的朋友可以在评论区指点一下,ML Kit 的分割精度就到这个程度了,是不是得上更重的模型才行?


五、内购系统:免费试用 + 付费解锁

商业化这块我做得比较简单——免费版每天限制使用次数(压缩 3 次、证件照 2 次),超了就弹窗引导购买专业版。免费版的证件照还会带个半透明水印,逼你掏钱😁。

技术实现上就是一个单例的 IapService,用 shared_preferences 存本地状态:

bool canUse(String feature) {
  if (_isPro) return true;  // 氪金玩家无限制,尊贵就完事了
  
  final todayUsed = _getUsageCount(feature, _todayKey());
  return todayUsed < ProConfig.getLimit(feature);
}

这里说一句,iOS 的内购和 Android 的内购是两套体系。iOS 走 App Store 的 StoreKit,Android 走 Google Play Billing。好在 Flutter 的 in_app_purchase 包帮你屏蔽了大部分差异。但如果要上国内安卓商店(华为、小米、OPPO),那还得对接各家的支付 SDK,这个坑我还没踩,留给下一步了。


六、真机部署:我人生中最漫长的一天

好,重头戏来了。代码写完到手机上能跑,中间隔了一个银河系的距离。

6.1)iOS:比想象中顺利(才怪)

iOS 部署的前置条件:

  • 一台 Mac(这个没得选)
  • Xcode(得是最新版,旧版各种报错)
  • CocoaPods(Ruby 的包管理器,用来管理 iOS 的原生依赖)

图:flutter的iOS 模拟器
在这里插入图片描述

CocoaPods 安装就是第一个坑。默认源在国外,sudo gem install cocoapods 跑了半天没动静。后来换了清华的 RubyGems 镜像才搞定:

gem sources --remove https://rubygems.org/
gem sources --add https://mirrors.tuna.tsinghua.edu.cn/rubygems/
sudo gem install cocoapods

然后 flutter build ios 又报错:ML Kit 要求 iOS 最低版本 15.5,但项目默认配的是 13.0。改 Podfile:

platform :ios, '15.5'  # 不改这行,pod install 会直接翻脸

编译倒是一次过了(大概 10 分钟),但往手机上装的时候又出幺蛾子——我手机锁屏了,Flutter 报了一个 “Your device is locked”。我说怎么一直失败呢,原来手机得保持亮屏解锁状态。这种坑搜都搜不到,纯靠踩。

最后装上去了,打开 App 提示"不受信任的开发者"。去 iPhone 的设置 → 通用 → 设备管理里信任一下就行。不需要花 $99 买开发者账号,免费 Apple ID 就能装自己手机上测试。

6.2)Android:一场与环境依赖的持久战

如果说 iOS 是小坑密集,Android 就是一个巨坑。原因在于它的依赖链太长了:

Flutter → Gradle 8.14 → JDK 17 → Android SDK 36 → NDK r28c(1.2GB)→ CMake

一环扣一环,少一个都编译不了。

JDK 的故事:我电脑上装的是 JDK 11,但 Android SDK 最新的命令行工具是用 Java 17 编译的(字节码版本 61.0),JDK 11 根本加载不了。这是硬伤,绕不过去,只能老老实实装 JDK 17。我从 Adoptium 的清华镜像下了个 OpenJDK 17 的 tar.gz,解压扔到 ~/Library/Java/ 下面就完事了。

Gradle 的故事:Flutter 需要 Gradle 8.14,默认从 services.gradle.org 下载,国内速度感人。第一次下载的 zip 因为网络问题还损坏了(ZipException: zip END header not found)。最后换成腾讯云镜像秒下:

# gradle-wrapper.properties
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14-all.zip

NDK 的故事,这个才是真正的 boss:NDK r28c 是 ML Kit 的 native 代码编译要用的,体积 1.2GB。用 sdkmanager 下载,速度大概 120KB/s——算了下得两个多小时。Google 中国 CDN googledownloads.cn 也差不多,浏览器单线程就这速度。

最后是怎么解决的呢?**aria2 多线程下载。**这是整篇文章最值得记住的一个技巧:

brew install aria2

# 16 线程下载,速度直接起飞
aria2c -x 16 -s 16 -k 1M \
  --max-connection-per-server=16 \
  -o android-ndk-r28c-darwin.dmg \
  "https://googledownloads.cn/android/repository/android-ndk-r28c-darwin.dmg"

结果呢?浏览器 120KB/s,aria2 跑出了 20MB/s。1.2GB 的文件 51 秒下完。差了 170 倍。我当时的表情大概是这样的:🤯

下载完是个 DMG 镜像,挂载后把 NDK 目录复制到 SDK 路径下:

hdiutil attach android-ndk-r28c-darwin.dmg -mountpoint /tmp/ndk-mount
cp -R /tmp/ndk-mount/AndroidNDK*.app/Contents/NDK/* \
      ~/Library/Android/sdk/ndk/28.2.13676358/

然后还有 Gradle 依赖下载慢的问题,在 build.gradle.kts 里加上阿里云镜像:

repositories {
    maven { url = uri("https://maven.aliyun.com/repository/google") }
    maven { url = uri("https://maven.aliyun.com/repository/central") }
    google()
    mavenCentral()
}

最终 flutter build apk --release 一次通过。107.8MB 的 APK 生成了。 我把它拷到桌面,微信发到安卓手机上,安装打开,看到「良辰百宝箱」四个字出现在屏幕上那一刻——说不激动是假的。虽然这是个很简单的 App,但从零到真机跑通的整个过程,确实有一种"我真的做出来了"的感觉。

在这里插入图片描述


七、国内开发者环境加速速查表

这张表是我这次踩坑踩出来的,建议收藏:

要下的东西 原始源(慢) 国内方案(快)
Gradle Wrapper services.gradle.org mirrors.cloud.tencent.com/gradle/
Maven 依赖 google() / mavenCentral() maven.aliyun.com 阿里云镜像
CocoaPods rubygems.org mirrors.tuna.tsinghua.edu.cn/rubygems/
Android NDK dl.google.com(120KB/s) aria2c -x16 + googledownloads.cn(20MB/s)
JDK 17 oracle.com / adoptium.net 清华镜像 Adoptium Temurin
Flutter Pub 包 pub.dev PUB_HOSTED_URL=https://pub.flutter-io.cn

说真的,国内做移动开发,一半时间在写代码,另一半时间在跟网络环境斗智斗勇


八、AI 到底帮了多少忙?说几个让我印象深刻的

我不想搞那种"AI 完成了 95% 的工作"这种标题党(虽然确实差不多)。具体说几个让我觉得"这玩意儿真行"的瞬间吧:

1. 我说"把 Word 里的图片压缩",它直接理解了 docx 是 ZIP 的事。 我自己之前都不知道这个原理,AI 不光知道,还给出了完整的解包-替换-重新打包方案。

2. ML Kit 的集成一次搞定。 人脸检测、人像分割、背景替换的 Dart 代码,几百行一口气生成出来,能跑。我只改了一个 null check 的警告。

3. 环境问题的排查能力。 CocoaPods 报错、Gradle 版本不对、JDK 版本太低——每次我把报错信息贴给它,它都能准确定位原因并给出修复方案。这个能力我觉得比写代码还实用。

4. 它知道 aria2 NDK 下载慢这事儿,我本来打算硬等两小时的,结果 AI 建议用 aria2 多线程下载。51 秒搞定。

当然也有不行的时候。比如 Xcode 里怎么找 “Signing & Capabilities” 这种图形界面的操作,它只能用文字描述,我还是对着屏幕找了好一会儿。AI 处理 CLI 和代码是一把好手,但 GUI 操作目前还得靠人。


九、几个还没解决的问题(求指教)

坦白说,这个 App 目前还很粗糙,有几个我暂时没想明白的:

  1. 证件照边缘质量——ML Kit 的 Selfie Segmentation 精度有限,头发丝边缘经常有锯齿。有没有更好的端侧抠图方案?还是说得上服务端的大模型?
  2. APK 体积太大——107MB,主要是 ML Kit 的模型文件占了大头。能不能做成按需下载?
  3. 国内安卓商店的支付——in_app_purchase 只支持 Google Play 和 App Store,国内安卓商店怎么办?有踩过这个坑的朋友吗?

如果你有经验,真心求评论区指点。


十、写在最后

这次折腾下来,我最大的感受是:AI 编程助手最大的价值,不是帮你省掉写代码的时间,而是让你有勇气去做以前不敢碰的事。

我以前觉得移动端开发门槛很高——Swift、Kotlin、Xcode、Android Studio、签名证书、上架审核——光听名词就头大。但有了 AI 之后,这些变成了"遇到问题问一下就行"的事情。真正阻碍你的不再是技术,而是你愿不愿意迈出第一步。

当然,你说 AI 写的代码能不能直接上生产?我个人觉得——小工具类的 App 完全可以,但复杂业务还是得人审。至少这次的代码,我拿给一个做了 5 年 Flutter 的朋友看,他说"代码结构还挺清晰的,没什么明显的坑"。

那就先这样吧。App 装到手机上了,下一步准备上架 App Store 和国内安卓商店,到时候再写一篇上架踩坑记。

如果这篇流水账对你有点用,点个赞呗。有问题评论区聊,我看到都会回。


文中提到的工具和版本: Cursor IDE / Flutter 3.41 / Dart 3.11 / Google ML Kit / Xcode 26.2 / JDK 17.0.18 / Android SDK 36 / NDK r28c(截至 2026 年 2 月)

标签:#Flutter实战 #AI辅助开发 #Cursor #移动端开发 #MLKit #跨平台 #踩坑记录

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐