ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Delphi 跨平台开发实战:日程管理 App 从 FMX 到 Android 打包全复盘

Delphi 跨平台开发实战:日程管理 App 从 FMX 到 Android 打包全复盘 简介一款基于DELPHI FireMonkey框架的Android日程管理APP源码面向移动开发初学者与中级开发者完整演示了轻量级SQLite数据库在资源受限移动设备上的嵌入应用以及FMX跨平台界面构建与事件交互的常见手法。资源共27个文件压缩包仅46KB包含pas源文件、fmx界面文件、vlb布局文件、SQLite数据库文件db、SQL脚本及dpr/dproj工程配置等目录结构能清晰对应各功能模块。已有474人学习浏览适合作为快速上手的参考实例。核心实现覆盖了SQLite建表与增删查改、自定义ListView结合滑动与长按等手势识别完成日程操作、通过Intent调用系统分享功能、半透明背景对话框保证操作连贯性以及vkbdhelper虚拟键盘辅助组件优化输入体验同时展示了AndroidManifest.template.xml等部署配置做法。通过这些源码可直观理解DELPHI FMX移动开发中数据层、界面层与系统能力集成的整体思路。 “现在还有人用 Delphi 开发 APP那不是上个时代的东西吗”——每次我在群里发这个日程管理小工具的截图总有人这么问。事实上我用 Delphi 已经断断续续好几年了最近用两个月里零散的周末时间做了一款同时跑在 Windows 和 Android 的简单日程管理 APP过程比想象中顺畅踩的坑也比想象中多。这篇就把完整项目复盘写下来从技术选型、功能设计、核心代码到打包发布的坑全部摊开讲。这个项目听起来很小但如果你认真做它基本覆盖了 Delphi 跨平台开发的全部关键环节FMX 界面布局、SQLite 本地存储、Android 通知权限、日期处理、APK 签名打包。适合刚接触 Delphi 的新手也适合想把手头工具从桌面扩展到移动端的老开发。读完你完全可以照着做出一份能在自己手机上跑的日程管理应用而不是只能躺在 IDE 里的示例工程。1. 项目整体设计与技术选型1.1 为什么 2025 年还会用 Delphi先说结论Delphi 没有死它只是不营销。Embarcadero 一直在更新版本Delphi 10.4、11.3、12 Athens 到现在还在迭代对高版本 Android、iOS 的支持也越来越好。对于日程管理这种以表单、列表、本地数据库为主的小工具FMXFireMonkey框架完全够用性能不是瓶颈开发效率反而很高。我之前也考虑过 Flutter 和 React Native但这类方案需要引入完整的 JS/Dart 工具链工程体积大而且桌面端支持往往是后补的。Delphi 最大的优势是一份 Object Pascal 代码同一套 UI直接跨 Windows、macOS、Android、iOS 编译。写的是原生应用不是套壳 WebView。日程管理这个项目从新建工程到跑出第一个 Android APK我实际只花了一个周末这速度在传统原生开发里很难做到。还有一个很实际的点Delphi 的第三方生态对于老手艺开发者极度友好。Devexpress 的 Delphi 控件、各种 VCL/FMX 组件库依然活跃遇到问题在 Stack Overflow 和 Delphi 社区基本都能找到现成答案。1.2 日程管理 APP 的最小可用功能拆解我并不想做一个功能堆叠的“全家桶”应用而是先从最小可用版本MVP开始。最终圈定的功能只有五个日程列表页按日期倒序展示所有日程已完成项置灰显示。日程详情页查看标题、详细内容、开始和结束时间。新增/编辑页填写标题、内容用 TDateTimePicker 选择起止时间支持标记完成。提醒设置给每条日程设置提醒时间偏移提前 5 分钟、15 分钟、1 小时等。本地通知到点后弹出系统通知栏提醒。数据库没有用远程服务全部存在本地 SQLite。这正好符合“简单日程管理”的定位——不需要账号体系不需要多端同步打开 App 就能记到点就能提醒。2. 工程搭建与跨平台环境准备2.1 Delphi 版本选择与环境配置我这次使用的是 Delphi 11.3因为 10.4 对 Android 12 以上权限模型的支持偏弱部分 API 需要额外适配而 11.3 和 12 Athens 在高版本 Android 的权限处理上已经比较成熟。如果你手头是 10.4我建议至少升级到 11.3 再做 Android 目标可以少踩很多兼容性的坑。环境配置方面最核心的就是 Android SDK 和 JDK。在 IDE 的 Tools Options Deployment Android SDK 里指定 SDK 路径。这里有个经验不用纠结用最新的 API Level建议用 33 或 34 稳定版别追 35 及以上因为 FMX 对新 API 的封装往往滞后半拍。需要安装的组件Android SDK Platform 33/34Android SDK Build-Tools 33.0.x 或 34.0.xJDK 11 或 17Delphi 11.3 用 JDK 11 即可2.2 创建 FMX 多设备应用工程新建工程时选 Multi-Device Application模板选 Blank Application。默认的 Target Platforms 会同时列出 Windows、Android、iOS、macOS。这就是 Delphi 跨平台的直观体现——同一个工程文件你不需要复制任何代码只需要在 Project Manager 里切换平台再点 Run。不过要注意FMX 在不同平台的文件会带不同后缀比如 Android 平台编译出来的是.so动态库和 APKWindows 平台输出.exe。代码层面虽然可以共用但涉及文件路径、权限、通知等系统功能时仍然需要按平台分开处理。后面我会详细说这些差异点。3. 核心功能实现数据层与界面代码3.1 日程数据模型与 SQLite 建表数据存储选用 FireDAC SQLite。FireDAC 是 Delphi 自带的数据库访问框架对 SQLite 支持得很好关键是不需要外部安装驱动代码里设置好驱动名和路径就能直接干活。我的日程表结构很简单核心字段如下CREATE TABLE IF NOT EXISTS schedules ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, start_time DOUBLE, end_time DOUBLE, done INTEGER DEFAULT 0, remind_offset INTEGER DEFAULT 0 );start_time和end_time字段我直接存TDateTime的浮点值。这样做的原因是 Delphi 的TDateTime本质就是 Double排序直接用 SQLORDER BY start_time DESC后面做日期比较、计算提醒时间点时也能直接做算术运算省去字符串解析的麻烦。在 Delphi 中初始化数据库连接的方式很简单uses System.SysUtils, System.IOUtils, FireDAC.Comp.Client; procedure TForm1.InitDatabase; var LDBPath: string; begin FDConnection1.DriverName : SQLite; LDBPath : TPath.Combine(TPath.GetDocumentsPath, schedule.db); FDConnection1.Params.Values[Database] : LDBPath; FDConnection1.Connected : True; FDConnection1.ExecSQL(CREATE TABLE IF NOT EXISTS schedules ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, start_time DOUBLE, end_time DOUBLE, done INTEGER DEFAULT 0, remind_offset INTEGER DEFAULT 0)); end;注意Android 上不要尝试把数据库文件写到程序安装目录安卓应用沙盒机制下这里通常不可写。使用TPath.GetDocumentsPath对应 Android 的/storage/emulated/0/Android/data/包名/files才是安全位置。Windows 上它对应的是“我的文档”目录两边语义一致跨平台代码完全不用改。3.2 列表页的数据展示与刷新日程列表我用TListView组件这是 FMX 里最常用的列表控件Windows 和 Android 上的视觉效果几乎一致滚动体验也能接受。TListView有两种主流用法一种是用 LiveBindings 绑定数据源另一种是手动填数据。我个人倾向在中小型项目里手动填数据代码更可控排查问题也直观。核心逻辑就是从查询结果里逐条读出数据创建列表项procedure TForm1.ReloadScheduleList; var Item: TListViewItem; begin ListView1.Items.Clear; FDQuery1.SQL.Text : SELECT id, title, start_time, done FROM schedules ORDER BY start_time DESC; FDQuery1.Open; try while not FDQuery1.Eof do begin Item : ListView1.Items.Add; Item.Text : FDQuery1.FieldByName(title).AsString; Item.Detail : FormatDateTime(yyyy-mm-dd hh:nn, FDQuery1.FieldByName(start_time).AsDateTime); // 用 Data 字典保存 ID方便点击时直接取用 Item.Data[id] : FDQuery1.FieldByName(id).AsInteger; if FDQuery1.FieldByName(done).AsInteger 1 then Item.TextColor : TAlphaColorRec.Gray; FDQuery1.Next; end; finally FDQuery1.Close; end; end;TListViewItem的Data属性很方便可以把主键 ID 直接塞进去用户点击列表项时从当前 Item 的 Data 里取 ID再查详情比额外做索引数组要干净得多。有一点经验要提如果在更新列表时发现界面总是闪烁或者残留旧数据优先检查是不是忘了Items.Clear以及是不是在循环里频繁重建了 TListViewItem。FMX 的列表控件重新赋值时尽量一次性 Clear 再重建不要频繁穿插修改。4. 提醒通知与日期处理4.1 Android 本地通知的正确姿势日程 APP 的核心价值就是“到点提醒”这部分也是跨平台差异最大的地方。FMX 自带了TNotificationCenter组件理论上可以做到一套代码同时跑 Windows 和 Android但实际上Windows 上的通知支持很弱经常无效所以我只在 Android 和 iOS 目标上依赖它Windows 版干脆不做提醒只做列表展示。基本实现代码var Notif: TNotification; begin NotificationCenter1.CancelAll; // 清掉旧的 Notif : NotificationCenter1.CreateNotification; try Notif.Name : ScheduleReminder; Notif.Title : 日程提醒; Notif.AlertBody : 你有日程安排 FCurrentTitle; Notif.FireDate : FRemindTime; // TDateTime 类型直接赋触发时间 Notif.EnableSound : True; NotificationCenter1.PresentNotification(Notif); finally Notif.DisposeOf; end; end;TNotification的FireDate是TDateTime类型这一点很方便——你可以直接用上一节中start_time - remind_offset计算出来的结果赋值不用处理时间戳字符串。但这里坑很多尤其是 Android 高版本Android 13 需要 POST_NOTIFICATIONS 权限。在 Delphi 工程 Project Options Applications Permissions 里手动勾选Notification权限。如果漏了通知会静默失败没有任何报错。Android 8 需要通知渠道Notification Channel。Delphi 自带的TNotificationCenter在创建通知时会尝试使用默认渠道但部分国产 ROM 会把默认渠道关闭。我踩过这个坑后直接在初始化时创建并设置一个TNotificationChannelvar LChannel: TNotificationChannel; begin LChannel : NotificationCenter1.CreateChannel(schedule_channel); LChannel.Title : 日程提醒; LChannel.Importance : TNotificationImportance.High; NotificationCenter1.PresentNotification(...); end;不要依赖通知的“精准定时”。实际测试发现Android 系统对不活跃 App 的后台执行做了很多限制FireDate定的时间经常会被延迟几分钟甚至不触发。对于“简单日程管理”这个定位我的做法是打开 App 时检查一下数据库里有没有到点但未提醒的日程配合一个一分钟的TTimer做轮询兜底。这不是完美的方案但对轻量级使用场景足够。4.2 日期计算与周末判断做日程管理绕不开日期处理。Delphi 的DateUtils单元提供了几乎所有你需要的日期函数但要格外注意DayOfWeek的返回值习惯——很多中国开发者第一次用都会栽在这里。System.DateUtils.DayOfWeek的规则是1 Sunday2 Monday……7 Saturday。也就是说周日是一周的第一天。如果你的界面要显示“周一”“周日”或者判断是不是周末直接拿返回值跟 [1, 7] 比较uses System.DateUtils; function IsWeekend(const ADate: TDateTime): Boolean; var D: Word; begin D : DayOfWeek(ADate); // 1 Sunday, 7 Saturday Result : (D 1) or (D 7); end;如果你需要“本周一”作为起始点做列表周分组那就得做一个小转换。我习惯用DateOf去掉时间部分再用下面的函数找到周一function GetMondayOfWeek(const ADate: TDateTime): TDateTime; var D: Word; begin D : DayOfWeek(ADate); // 将 Sunday1 转换为 Monday0Tuesday1... Saturday5 Result : DateOf(ADate) - ((D 5) mod 7); end;另一个常用组合是StartOfTheDay和EndOfTheDay判断某条日程是否属于今天时直接比较(ScheduleTime StartOfTheDay(Now)) and (ScheduleTime StartOfTheDay(Now) 1)就行比拆成年月日分别比较优雅得多。4.3 启动自检与 TTimer 兜底提醒前面提到通知可能被系统延迟所以我加了一个非常简单的“启动自检 轮询”机制。在 Form 的OnCreate里启动一个TTimer间隔设 60000 毫秒procedure TForm1.Timer1Timer(Sender: TObject); var LNow: TDateTime; begin LNow : Now; FDQuery1.SQL.Text : SELECT id, title FROM schedules WHERE done 0 AND start_time :Now AND remind_offset 0 AND reminded 0; FDQuery1.Params.ParamByName(Now).AsDateTime : LNow; FDQuery1.Open; try while not FDQuery1.Eof do begin // 触发通知 NotifySchedule(FDQuery1.FieldByName(id).AsInteger, FDQuery1.FieldByName(title).AsString); // 标记已提醒避免重复触发 MarkAsReminded(FDQuery1.FieldByName(id).AsInteger); FDQuery1.Next; end; finally FDQuery1.Close; end; end;这里我在表里加了一个reminded字段通知触发后立即置 1防止 Timer 每分钟轮询时重复弹通知。这个字段对于稳定运行很重要不然用户会收到连环轰炸。5. 编译打包与发布要点5.1 Android 签名与 APK 生成在 Android 上安装应用必须签名。Delphi 的做法是在 Project Options Provisioning 里配置。切换到 Release 配置勾选 “Use custom keystore”填上 Keystore 文件路径、密码和别名。我建议从一开始就生成自己的签名文件不要用 Delphi 自带的调试签名发布测试版。因为如果用户已经装了你用调试签名打的包后面你想升级正式签名版本系统会提示“签名不一致”装不上——除非卸载重装这会丢数据很尴尬。生成 keystore 的标准命令在命令行执行JDK 自带keytool -genkey -v -keystore schedule.keystore -alias schedule -keyalg RSA -keysize 2048 -validity 10000然后在 Delphi 的 Provisioning 页面填入对应信息即可。打包时选择 Android 目标平台点 RunDebug或 BuildReleaseDelphi 会直接生成 APK。10.4 以上版本还支持生成 AABAndroid App Bundle如果你准备上架 Google Play用 AAB 格式会更合适。5.2 Windows 平台的无缝切换这是 Delphi 最爽的部分。在 Project Manager 里把 Target Platforms 从 Android 切到 Windows 64-bit然后点 Run一个原生 Windows 程序直接弹出来代码一行不改。我用这个特性做了开发调试的主力路径——Windows 上调试 UI 和业务逻辑效率远高于在真机上测试查通知、查数据库都方便得多改完再切回 Android 打包。唯一的注意点是Windows 上 FMX 默认的高分屏适配有时不到位字体发虚或者控件偏小。可以在 Project Options Application 里勾选 “High DPI support”并设置窗体FormFactor.Width和Height的适应策略。遇到文字模糊优先检查是不是 TListView 的 ItemAppearance 设置不对换用 DynamicAppearance 并手动设置字体尺寸通常能解决。5.3 权限配置与多平台注意点在 Project Options Applications Permissions 页面建议把 Android 权限精简到最少。日程管理实际只需要两项Notification通知栏提醒如果需要读取系统日历可以加Calendar但我这个项目没用系统日历所以没勾。不要勾选一堆用不到的权限。权限越多上架审核和用户信任度都会受影响。iOS 平台则需要在 Deployment 配置里额外声明通知权限描述文案否则首次弹权限框时 App 会直接崩溃。6. 常见问题与排查技巧实录整个开发过程踩了不少坑整理成速查表按出现频率排序希望能帮你少走弯路。问题现象可能原因解决办法Android 上通知不弹出没有 POST_NOTIFICATIONS 权限Project Options Applications Permissions 勾选 NotificationAndroid 上通知偶尔不弹或延迟系统对后台 App 的限制App 启动时扫描到点日程立即触发TTimer 每分钟兜底轮询SQLite 文件中文字符变成乱码数据读取/写入时字符集不一致确保 FDConnection 的 Params 里设置 CharacterSet 为 utf8建库时用 UTF-8 编码TListView 刷新时旧数据残留或闪烁没有正确清理 Items 或重建了 Item 对象Items.Clear后重新 Add避免在循环中频繁操作同一 ItemAPK 安装失败提示“签名不一致”之前装过调试签名的包卸载旧包重新安装正式测试统一使用同一个 keystoreDayOfWeek 结果跟预期日期对不上Delphi 中周日返回 1周一返回 2根据业务调整参考文中的 GetMondayOfWeek 实现FireDAC 在 Android 启动时报找不到 SQLite 驱动没有部署 sqlite3 动态库Project Deployment 里添加对应平台的原生库文件或检查 Deploy 配置Windows 下打开程序字体发虚未适配高分屏开启 High DPI support设置 TListView 的 ItemAppearance这里重点说两个最常见的问题。第一个是 SQLite 的 UTF-8 乱码。Delphi 的 FireDAC 对 SQLite 默认字符集处理不算友好如果你在 Windows 上建了一个中文数据库直接丢到 Android 上跑很容易读出乱码。解决办法是在每次连接后手动执行FDConnection1.ExecSQL(PRAGMA encoding UTF-8);以及在FDConnection1.Params中加一行CharacterSet为UTF8。实测这样设置后Windows 和 Android 之间拷贝数据库文件中文都能正常显示。第二个是 TListView 手动刷新时的细节。很多时候列表刷新异常是因为你在while not Query.Eof循环里面直接操作了 TListViewItem 的Data属性而 FMX 的虚拟化列表可能复用了 Item 对象。安全做法是每次统一Clear再统一重建。如果列表数据超过几百条再考虑用 LiveBindings 优化性能小数据量场景手动填充反而更稳定。排查问题时我习惯在 Delphi 里用OutputDebugString输出调试信息再用 Android 的 logcat 过滤标签查看。如果你对 adb 操作熟悉也可以连上真机后打开adb logcat按包名过滤。最后再分享一点个人体会这个项目从零到能装进手机大概花了我两周左右的业余时间。最深的感受是用 Delphi 做跨平台应用最大的价值不是某个具体控件或语法糖而是能用最小的认知负担同时维护多个平台。我在 Windows 上写业务逻辑调试完毕切 Android 打包再回头改 UI 细节整个开发循环非常短。如果你手里有现成的 Delphi 桌面工具想搬到手机上建议就拿日程管理、记账本、待办清单这类小项目练手一轮走下来FMX 的坑基本能踩完一半。最后再分享一个小技巧如果你打算长期做这类个人工具把日期处理、数据库初始化和通知封装抽成独立的单元文件Unit不要全堆在 Form 里。我这个项目前期偷懒把数据库逻辑写在主窗体里后来加设置页时改得想砸键盘。模块拆开之后后面每一步都轻松很多。本文还有配套的精品资源点击获取
返回列表