news 2026/9/2 22:58:49

用Delphi打造跨平台日程管理APP:SQLite存储与通知机制实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Delphi打造跨平台日程管理APP:SQLite存储与通知机制实战

简介:一份基于 Delphi FMX 框架开发的 Android 日程管理 APP 完整工程,围绕 SQLite 数据持久化、自定义 ListView 和手势交互展开,适合有 Pascal 基础、希望学习跨平台移动开发的读者。项目以轻量级 SQLite 为存储核心,包含建表 SQL 与数据库文件,并演示了半透明对话框、系统分享、虚拟键盘辅助组件等移动端常见交互,另附自定义 ListView 外观演示包,便于直接复用。资源共二十七个文件,以 pas 源码、fmx 界面、vlb 布局、sql/db 数据库及 dproj/deployproj 工程配置为主,压缩包约四十六 KB,结构清晰,便于按模块对照学习。已有四百七十四人学习浏览,可作为课程设计或企业移动原型参考。借助该工程可重点研究 FMX 中 ListView 的样式定制与滑动/长按手势处理,梳理 Delphi 调用 Android 系统分享的 Intent 流程,同时理解部署配置与资源文件在真实项目中的用途。 很多老程序员对 Delphi 的感情其实挺复杂的,早年靠它吃饭,后来又被各种新语言冲击,但真到要做一些工具类、内部系统、或者快速上手的跨平台小应用时,又总会想起它。这次的项目就是一个典型的例子——用 Delphi 做一个简单的日程管理 APP。不复杂,但是五脏俱全,从数据存储、界面布局到提醒通知,样样都得自己动手。这篇文章就围绕这个项目,把我在开发过程中踩过的坑、用到的技巧、以及一些关键代码的思考逻辑完整写一遍,给那些还在用 Delphi 或者想重新捡起 Delphi 的朋友一个参考。

1. 项目整体设计与思路拆解

1.1 为什么用 Delphi 做日程管理 APP

先回答一个最直接的问题:现在做 APP 有 Flutter、React Native、Kotlin 一堆方案,为什么要回头用 Delphi?我的理由很简单,一是团队里老同事对 Delphi 的熟练度远高于其他语言,二是这个项目本身是典型的“内部工具型”应用,核心诉求是开发速度快、维护成本低、打包体积小。Delphi 在这类场景下反而很合适,尤其是 RAD Studio 的跨平台能力,一套代码同时出 Windows 和 Android 版本,节省的时间不是一点半点。

日程管理这个领域本身也不复杂。无非是记录事件、设置提醒、查看某一天或某个月有哪些安排。业务逻辑清晰,数据量不大,不需要服务端实时同步,甚至可以做成单机应用。用 Delphi 来做,刚好避开那些框架本身的复杂度,把精力集中在业务实现上。

另外还有一个很实际的因素:Delphi 对系统底层的掌控能力强。比如在 Windows 端做开机自启、托盘提醒、读取系统日历、和 Outlook 交互等等,Delphi 的生态里都有现成的组件。而在 Android 端,通过 FireMonkey 框架也能实现本地通知和后台定时任务,虽然配置起来比原生麻烦一些,但胜在代码复用率高。

1.2 功能边界与数据模型设计

这个项目的定位是“简单”,所以功能边界我一开始就划清楚了,不做复杂的东西:

  • 日程的增删改查,支持按日期浏览。
  • 设置提醒时间,到点触发系统通知。
  • 支持农历和节假日的简单标注(这个是中国用户比较刚需的功能)。
  • 数据本地存储,不依赖网络。

功能定义好之后,数据结构其实就很好设计了。日程表的核心字段包括:ID、标题、内容、日期、开始时间、结束时间、是否全天事件、提醒提前量、备注。我用了一张数据表来存所有信息,ID 用自增整数,日期和时间分开存储,方便查询。

T_Schedule - ID: Integer (主键) - Title: string - Content: string - EventDate: TDate - StartTime: TTime - EndTime: TTime - IsAllDay: Boolean - RemindOffset: Integer // 提前多少分钟提醒,-1表示不提醒 - Remark: string

这种设计看起来简单,但在实际使用中会有一个问题:当数据量超过几千条,按日期范围查询的性能就会下降。所以我在 EventDate 上加了索引,并且在查询时统一用参数化 SQL,避免每次都全表扫描。对于这种轻量级应用,SQLite 完全够用,没必要上 MySQL 或者 PostgreSQL。

1.3 技术选型:VCL 还是 FireMonkey

这是用 Delphi 做跨平台 APP 时绕不开的选择题。VCL 是 Windows 专属的,组件丰富,第三方控件多,开发桌面端体验非常好。FireMonkey(FMX)是跨平台框架,支持 Windows、macOS、Android、iOS,但有些组件行为和原生还是有差距。

我这个项目选择的是 FMX,因为目标是 Android 端为主的移动应用。但说实话,FMX 在 Android 上跑起来,性能比不过真正的原生开发,界面渲染也偶尔会出现一些奇怪的问题。如果只是做一个给几十个人用的内部工具,FMX 完全撑得住;如果你想上应用市场,对用户体验要求很高,那还是要做原生优化,或者干脆换个框架。

开发时有个细节需要特别注意:FMX 在 Android 上的 ListView 和原生 ListView 不一样,FMX 的 TListView 是自绘的,它的 ItemAppearance 设置为 ListItem 时才能显示标题和副标题。如果你直接拖一个 TListView 进去,什么都不设置,运行时只会看到一片空白。这个坑新手很容易踩。

2. 核心细节解析与实操要点

2.1 日期处理:判断周六日、节假日与农历

日程管理最核心的就是时间计算。用户需要区分工作日和休息日,还要在节假日(尤其是春节、国庆这种长假)前提前安排日程。所以判断周六日只是基础,农历节日的支持才是中国用户真正需要的东西。

Delphi 没有内置农历转换函数,需要自己写算法或者引入三方库。我用了网上的一个农历算法单元,原理是维护一个农历数据表,然后用天干地支和月日信息来计算农历日期。这里给出一段判断周六日的代码,非常简单:

function IsWeekend(const ADate: TDate): Boolean; var Dow: Integer; begin Dow := DayOfWeek(ADate); Result := (Dow = 1) or (Dow = 7); // 1=Sunday, 7=Saturday end;

但这里有个坑要注意:DayOfWeek 返回的数值在不同区域设置下可能不一样。在 Delphi 中,DayOfWeek 返回 1 代表周日,7 代表周六。如果项目的区域设置改成从周一开始计算,结果就会错乱。稳妥的做法是用 ISO 8601 的 Monday=1, Sunday=7 标准,需要自己封装一层:

function IsISOWeekend(const ADate: TDate): Boolean; begin Result := (DayOfWeek(ADate) = 1) or (DayOfWeek(ADate) = 7); end;

代码虽然一样,但注释里要写明依赖项。农历部分的算法比较大,暂不贴全,核心思路是“查表法”——用一个长数组保存从 1900 年到 2100 年每一年的农历月大月小、闰月信息,然后通过天数累加计算出当前日期对应的农历日期。

2.2 数据持久化方案:SQLite 还是 TClientDataSet

日程数据量虽然不大,但我还是推荐用 SQLite,而不是把数据存在 JSON 或 XML 文件。原因有两点:一是 SQL 查询方便,按日期范围、关键字搜索都很方便;二是 SQLite 事务性好,写入过程中如果程序崩溃,数据不容易损坏。

Delphi 操作 SQLite 的方式很多,可以直接用 FireDAC,也可以引入第三方组件如 DISQLite3。FireDAC 是 RAD Studio 自带的数据库访问框架,支持 SQLite、MySQL、SQL Server 等,语法统一,而且支持参数化查询,推荐直接使用。

以下是用 FireDAC 连接 SQLite 的关键代码:

FDConnection1.DriverName := 'SQLite'; FDConnection1.Params.Database := 'schedule.db'; FDConnection1.Connected := True; FDQuery1.SQL.Text := 'SELECT * FROM T_Schedule WHERE EventDate = :EventDate ORDER BY StartTime'; FDQuery1.ParamByName('EventDate').AsDate := SelectedDate; FDQuery1.Open;

值得一提的是 TClientDataSet。热词里有人提到clonecursor函数,这个函数的作用是创建当前游标的副本,让两个 TDataSet 共享同一份数据。在某些场景下确实有用,比如你需要同时以两种不同顺序浏览同一份数据的时候。但在日程管理里,我们直接通过 SQL 排序就够了,不需要 clone cursor 这种操作,知道了但用不上,也算是一种知识储备。

2.3 提醒机制:本地通知与 Timer 轮询

这是这个项目的核心亮点。提醒机制在 Android 上实现有两种方式:

  • 本地通知(Notification):通过系统的 NotificationManager 发送,即使 APP 在后台也能弹出。
  • 应用内轮询:APP 在前台运行时,用 TTimer 定时检查当前时间是否有日程需要提醒。

我最终采用了“双轨制”:前台用 Timer 轮询,后台用系统通知。FireMonkey 在 Android 上没法直接调用 NotificationManager,需要借助 Java 桥接(JNI)或者使用第三方库。我用了Androidapi.JNI.App类来实现本地通知,核心代码如下:

procedure SendNotification(const ATitle, AContent: string); var NotificationManager: JNotificationManager; Builder: JNotificationBuilder; Intent: JIntent; begin NotificationManager := TJNotificationManager.Wrap( TAndroidHelper.Context.getSystemService(TJContext.JavaClass.NOTIFICATION_SERVICE) ); Intent := TJIntent.Create; Intent.setClass(TAndroidHelper.Context, TAndroidHelper.Context.getClass); Builder := TJNotificationBuilder.JavaClass.init( TAndroidHelper.Context, 'schedule_channel' ); Builder.setContentTitle(StringToJString(ATitle)); Builder.setContentText(StringToJString(AContent)); Builder.setSmallIcon(TAndroidHelper.Context.getApplicationInfo.icon); NotificationManager.notify(1, Builder.build); end;

这段代码的核心逻辑是获取系统的通知服务,然后构建一个通知对象并发送。第二参数是通知 ID,如果重复使用同一个 ID,会覆盖之前的通知,如果不同 ID,则会显示多条通知。在日程提醒中,我用日程 ID 作为通知 ID,这样多条提醒可以并行显示。

2.4 界面布局:ListView、日期选择器与侧滑菜单

日程管理 APP 的界面不需要太花哨,核心就是三个页面:日程列表、日程详情/编辑、日历视图。我用 FMX 的 TListView 展示日程列表,通过设置ItemAppearance := TListViewAppearance.ListItem来显示标题和副标题(时间、地点),同时在每个 Item 右侧添加一个删除按钮,方便快速操作。

日期选择器我用的是 FMX 自带的 TCalendarEdit。在桌面上运行没什么问题,但在 Android 上点击弹出来的日历选择器样式比较原生,有些安卓机型会渲染异常,解决方案是换成自定义的弹窗,用 TCalendar 组件嵌到 TPopup 里,这样能在所有机型上保持一致的体验。

侧滑菜单实现起来比较费劲,FMX 没有现成的侧滑控件。我当时参考了 Demo 里的一个实现,用TLayout包裹左右两个面板,然后通过手势事件OnPan来修改Position.X,实现侧滑效果。具体代码如下:

procedure TMainForm.HandlePan(Sender: TObject; const EventInfo: TGestureEventInfo; var Handled: Boolean); begin if EventInfo.GestureId = giPan then begin MenuLayout.Position.X := MenuLayout.Position.X + EventInfo.Distance; if MenuLayout.Position.X > 0 then MenuLayout.Position.X := 0; end; end;

思路很简单,就是通过手势的 X 轴位移量来平移菜单布局。这里要注意的是,手势结束后要判断菜单是应该完全展开还是收起,如果手指松开时菜单位置还在某个阈值(比如屏幕宽度的 30%)之外,就用动画平滑滑到目标位置,否则回弹。

3. 实操过程与核心环节实现

3.1 从零开始创建项目:环境准备与工程结构

在动手写代码之前,先把环境准备好。我用的版本是 RAD Studio 11 Alexandria,针对 Android 平台开发时,需要先安装 Android SDK 和 NDK 并配置好 Java JDK。

步骤很简单,依次操作:

  1. 下载安装 RAD Studio 11,安装时勾选 Android 和 Windows 平台支持。
  2. 打开 Tools > Options > Deployment,配置 Android SDK 路径。默认情况下 Delphi 会自己下载一部分 SDK 组件。
  3. 在 Project Manager 中右键 Target Platforms,选择 Android 64-bit。
  4. 创建新项目:File > New > Multi-Device Application。

工程结构上,我建了一个干净的目录:

ScheduleApp/ ├── Main.pas ├── ScheduleData.pas ├── Notifications.pas ├── Lunar.pas └── ScheduleApp.dproj

Main.pas负责界面和交互,ScheduleData.pas负责数据访问和业务逻辑,Notifications.pas封装本地通知的接口,Lunar.pas是农历算法单元。分这么细的好处是,逻辑清晰,后续如果要在 Windows 端复用同样的数据逻辑,可以直接引用 ScheduleData.pas,不用动界面代码。

3.2 数据层开发:建表、查询与缓存

我用 FireDAC 连接 SQLite 时,遇到过一个小坑:FireDAC 的 SQLite 驱动默认不开启外键支持,如果多张表之间有关联关系,需要对连接参数做额外设置。这个项目单表结构,不存在外键问题,所以没有踩雷。

建表语句很简单:

CREATE TABLE IF NOT EXISTS T_Schedule ( ID INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, Content TEXT, EventDate DATE NOT NULL, StartTime TIME, EndTime TIME, IsAllDay INTEGER DEFAULT 0, RemindOffset INTEGER DEFAULT -1, Remark TEXT ); CREATE INDEX IF NOT EXISTS idx_eventdate ON T_Schedule(EventDate);

在代码里,我把对数据的操作封装成了一个TScheduleDataModule类,提供以下方法:

  • LoadByDate(const ADate: TDate): TDataSet;
  • LoadBetween(const AStartDate, AEndDate: TDate): TDataSet;
  • AddSchedule(ASchedule: TSchedule);
  • UpdateSchedule(ASchedule: TSchedule);
  • DeleteSchedule(const AID: Integer);

其中TSchedule是对数据行的对象封装,字段和表结构一一对应。这么做的好处是,界面层可以直接绑定对象列表,不需要和 DataSet 强耦合,后面如果要从 SQLite 切换到其他数据库,只改数据层就够了。

查询时有一个性能优化点:如果你在日程列表页要展示整整一个月的日程,不建议一次性把整个月的数据全查出来,而是分页加载。理想情况是每页只加载 20 条,用户滚动到底部时再加载新的 20 条。在 SQLite 里用LIMIT 20 OFFSET 20就可以实现分页。

3.3 日程提醒核心逻辑:Timer 启动、后台限制与省电策略

日程提醒的核心是 Timer 轮询机制。前端界面启动时,启动一个全局的 TTimer,间隔设置为 30 秒(不要低于 10 秒,否则 Android 会认为你的应用占用过多资源,降低你的进程优先级)。

每个计时周期做这几件事:

  1. 查当前时间到当前时间 + 1 小时之间是否有日程开始。
  2. 如果有,并且没有发过提醒,则触发SendNotification,然后将该日程标记为“已提醒”。
  3. 已提醒标记存在本地表里,用IsNotified字段(Integer,0/1)控制。

伪代码如下:

procedure TScheduleDataModule.CheckReminders; var NowTime: TDateTime; CurrentSchedule: TSchedule; begin NowTime := Now; FDQuery1.SQL.Text := 'SELECT * FROM T_Schedule WHERE EventDate = :Date AND IsNotified = 0'; FDQuery1.ParamByName('Date').AsDate := DateOf(NowTime); FDQuery1.Open; while not FDQuery1.Eof do begin CurrentSchedule := GetScheduleFromQuery(FDQuery1); if (CurrentSchedule.StartTime > NowTime) and (MinutesBetween(CurrentSchedule.StartTime, NowTime) <= CurrentSchedule.RemindOffset) then begin SendNotification(CurrentSchedule.Title, '日程即将开始'); MarkAsNotified(CurrentSchedule.ID); end; FDQuery1.Next; end; end;

这里注意,查询条件是EventDate = :Date,而不是EventDate = DateOf(Now)。因为某些日程可能是跨天的,比如 23:00 开始、次日凌晨 1 点结束。为了保证不漏提醒,查询时应该用EventDate BETWEEN :Today AND :Tomorrow这种方式,把跨天的情况也算进去。

后台提醒是另一个大坑。Android 系统为了省电,会杀掉后台进程,如果你的 APP 在后台放久了,Timer 未必能继续触发。解决方案是使用前台服务(Foreground Service),但前台服务需要常驻通知栏,这需要在启动时创建一条“APP 正在运行”的常驻通知。对内部工具来说,这样做好用,但如果是要上架应用商店,又得考虑用户是否会反感这种常驻通知。

3.4 导入导出功能:读取 Excel 实现批量日程录入

热词里提到delphi 读取 excel,这个在日程管理里也有实际应用场景——用户可能会在 Excel 里整理好一堆日程,需要批量导入到 APP。Delphi 读 Excel 有几种方式:

  • 使用 Excel 的 COM 接口(仅限 Windows)。
  • 使用第三方库如 FlexCel、SMExport。
  • 使用 TFileStream 直接解析 XLSX 文件(XLSX 本质是 ZIP 压缩的 XML 文件)。

第一种方式最简单,但只适用于 Windows 端。考虑到 Android 端也有导入需求,我用的是第三种方案,用 TZipFile 解压 XLSX,然后读取xl/worksheets/sheet1.xml中的内容,用 Xml.XMLDoc 解析单元格数据。具体实现有点繁琐,但好处是不依赖外部组件,跨平台可用。

procedure ImportFromExcel(const AFileName: string); var Zip: TZipFile; Stream: TMemoryStream; XMLDoc: IXMLDocument; RootNode, RowNode, CellNode: IXMLNode; I, J: Integer; begin Zip := TZipFile.Create; try Zip.Open(AFileName, TZipMode.zmRead); Zip.Read('xl/worksheets/sheet1.xml', Stream); XMLDoc := LoadXMLDocumentFromStream(Stream); RootNode := XMLDoc.DocumentElement; for I := 0 to RootNode.ChildNodes.Count - 1 do begin RowNode := RootNode.ChildNodes[I]; if RowNode.NodeName = 'row' then begin for J := 0 to RowNode.ChildNodes.Count - 1 do begin CellNode := RowNode.ChildNodes[J]; // 解析单元格值,跨单元格的 t 属性处理 end; end; end; finally Zip.Free; end; end;

这里要注意,XLSX 中的日期存储方式有两种:一种是数字序列号,一种是字符串。解析时需要根据单元格的s属性和t属性来判断。数字序列号需要转换成 Delphi 的 TDate,转换公式是:TDate(25569) + 序列号天数值。25569 是 Delphi 的 TDate 基准日期(1899-12-30)与 Excel 的基准日期(1900-01-01)之间的差,这个数是从实践中试出来的,不同的 Excel 版本可能略有差异,所以本地化测试不可少。

4. 常见问题与排查技巧实录

4.1 中文乱码:文件编码、SQLite 连接串与组件显示

Delphi 开发的中文乱码问题,十个人有九个人会遇到。我这次主要踩了两个坑。

第一个坑是源码文件的编码。Delphi 7 和 RAD Studio 高版本对代码文件的默认编码不同。Delphi 7 默认是 ANSI,而 RAD Studio 默认是 UTF-8。如果代码中直接写中文,并且保存时用了错误的编码,编译器会报错或者运行时出现乱码。解决方案很简单:在 IDE 中把源码文件另存为 UTF-8 格式。Tools > Options > IDE > Environment Variables 里可以设置默认文本编码。

第二个坑是 SQLite 连接字符串的编码设置。FireDAC 的 SQLite 驱动默认使用 UTF-8 编码存储字符串,如果你从旧数据库迁移过来,里面存的是 GBK 编码,就要在连接串中加上CharacterSet=utf8,并且在查询后手动转换字符串。我的建议是,所有新写的代码统一用 UTF-8,不要混用编码。

组件的显示问题也可能导致乱码。比如 FMX 的 TLabel 在 Android 上显示中文时,默认字体可能不支持某些生僻字,这个时候你需要给 Label 设置一个中文字体(如 Noto Sans CJK SC)。这个不难,但要提前想到,否则界面在特定机型上就会出现方块字。

4.2 TClientDataSet 的 CloneCursor 在数据展示中的作用

热词里有人在问TClientDataSetCloneCursor函数是干什么的。这里结合日程管理的实际场景解释一下。CloneCursor的作用是给当前数据集创建一个共享数据的游标,两个游标看到的是同一份内存数据,但是各自可以有不同的排序、过滤条件、以及当前记录指针。

在日程管理里,有一个很好的用法:你需要在两个界面上展示同一份日程数据,一个按日期排序,一个按提醒时间排序。如果直接开两个查询,会加载两次数据,内存浪费且不同步。用CloneCursor就解决这个问题:

FDQuery1.SQL.Text := 'SELECT * FROM T_Schedule'; FDQuery1.Open; FDQuery2.CloneCursor(FDQuery1, True); FDQuery2.IndexFieldNames := 'EventDate'; FDQuery1.IndexFieldNames := 'StartTime';

这里的第二个参数Reset为 True 会重置克隆游标的当前记录位置。两个游标修改数据会同步,非常适合列表和详情页联动。不过有一点需要提醒:CloneCursor只能在同一个TDataSet类型之间使用,不能把一个FDQuery克隆到另一个TClientDataSet上,类型必须匹配。

4.3 Timer 不触发:进程被系统杀死的排查思路

Android 后台的 Timer 不触发,是最多人问的问题。我排查的思路是分三步走:

  1. 先确认进程是否活着。在开发者选项里打开“正在运行的服务”,看看你的 APP 是否仍在后台运行。
  2. 如果进程被杀,说明需要提升进程优先级。方案是开启前台服务,或者让用户手动设置“允许后台运行”和“不受电池优化限制”。
  3. 如果进程活着但 Timer 不触发,检查是否在 Android 6.0 之后的 Doze 模式下被限制。Doze 模式会在设备静止时停止网络访问和延迟任务,TTimer 的机制是运行在主线程消息队列里的,在 Doze 模式下可能会被延迟数分钟才执行一次。

我的解决方案是:在 APP 被切换到后台时,启动一个TNotificationCenter计划通知服务,把未来要提醒的事件一次性注册到系统通知中心。这样即便 APP 进程被杀,通知仍然由系统按时发出。

Android 上注册计划通知的 API 是AlarmManager,但在 FireMonkey 里直接调用比较复杂,我用了一个封装好的第三方库Grijjy.Android.AlarmManager,它简化了调用步骤,核心代码如下:

AlarmManager.SetAlarm(AlarmTime, Intent, RequestCode);

这个方法接收三个参数:闹钟触发时间、需要发送的 Intent、用于区分不同闹钟的 RequestCode。我把日程 ID 作为 RequestCode,这样在每个闹钟触发时,可以通过 RequestCode 找回日程信息。

4.4 WebBrowser 控件控制放大缩小

这个项目的日程详情页里,我引入了 Delphi 自带的TWebBrowser控件来渲染富文本内容——比如用户复制粘贴了一段带格式的网页内容作为日程备注。在 Windows 上这个控件表现良好,但在 Android 上缩放行为有差异。

默认情况下,Android 的 WebView 控件不会主动显示缩放按钮,即使用户双指缩放,网页内容也不会跟随缩放。解决方案是在页面加载完后执行一段 JavaScript,允许网页内容根据自己的视口宽度进行缩放:

var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=3.0, user-scalable=yes'); document.getElementsByTagName('head')[0].appendChild(meta);

在 Delphi 中执行这段 JS 的方式是:

WebBrowser1.EvaluateJavaScript( 'var meta = document.createElement(''meta'');' + 'meta.setAttribute(''name'', ''viewport'');' + 'meta.setAttribute(''content'', ''width=device-width, initial-scale=1.0, maximum-scale=3.0, user-scalable=yes'');' + 'document.getElementsByTagName(''head'')[0].appendChild(meta);' );

这里有个引号嵌套的问题,JS 里的字符串用单引号,而 Delphi 的字符串也是单引号,所以内部要用双单引号转义,否则编译就会报错。另外要注意的是,WebBrowse 的在 Windows 上使用的是 IE 内核,在 Android 上是 WebView,两者对 JavaScript 的支持程度不一样。如果页面的 JS 很复杂,在两种平台上可能出现渲染差异,需要单独调试。

4.5 编译打包:DCU 版本不匹配与发布签名

Delphi 开发中常见的编译错误是Unit1.pas和编译后的.dcu文件版本不匹配。例如热词里有人提到devexpress delphi 7 dcu,许多第三方组件在不同 Delphi 版本下需要重新编译 DCU。我的建议是,项目的所有第三方组件源码和 DCU 文件都统一放在同一个Library路径下,每次升级 Delphi 版本时都重新编译这些组件,不要让 IDE 自动扫描多个版本的 DCU。

另外,Android 应用发布到真机调试或上架应用商店,都需要签名。Debug 模式下 Delphi 会自动生成一个调试证书,直接安装到手机没问题。但发布(Release)模式必须要用正式签名,否则即使打包成功,安装到其他设备上也会提示“应用未签名”或“安装失败”。

签名操作是在 Project > Options > Provisioning 里配置,选择 Release 配置,然后点击“Signing”页签,选择证书文件。如果没有证书,可以用 Delphi 自带的证书创建工具生成一个新证书。

发布时还有一个细节要注意:Android 的包名(Package Name)在第一次发布后就不能改了,改包名相当于一个全新的应用,用户无法通过旧包无缝升级。所以项目创建时就要把包名定义好,格式是com.yourcompany.scheduleapp,不要用默认的com.embarcadero.ScheduleApp

4.6 真机调试抓包:Fiddler 配置与常见问题

移动端开发经常需要调试网络请求,但日程管理 APP 本身不依赖网络,一般不需要抓包。不过如果你在日程管理里集成了同步功能(比如将日程同步到云端),那抓包工具就很有必要了。

用 Fiddler 抓手机 APP 的包,常规配置是:

  1. 电脑和手机连同一个 Wi-Fi。
  2. Fiddler 开启 HTTPS 解密(Tools > Options > HTTPS > Decrypt HTTPS traffic)。
  3. 手机上设置代理,指向电脑的 IP 和端口(Fiddler 默认 8888)。
  4. 手机浏览器访问http://你的IP:8888安装 Fiddler 的根证书。

听起来简单,但实际操作中经常会出现“抓不到包”的情况。最常见的原因有三个:

  • Fiddler 没有监听正确的端口,或者防火墙拦截了 8888 端口。
  • Android 7.0 及以上版本默认不再信任用户安装的 CA 证书,应用如果使用了 HTTPS 并且不走系统信任的证书链,Fiddler 就解密不了。
  • 手机 App 本身使用了证书固定(Certificate Pinning),明文证书替换之后 App 直接拒绝连接。

第二个问题的解决办法是:将 Fiddler 的根证书作为系统证书安装,需要 root 权限;或者修改 App 的网络安全配置,允许在 debug 模式下信任用户证书。第三个问题没法在设备端解决,只能从代码层面暂时关闭证书固定,或者用Frida这类工具做动态注入,但这就涉及更深的逆向知识了,一般内部工具开发用不上。

写在最后的操作心得

做这个项目最大的感触是,Delphi 它不新,但真做小型应用时,反而比一些新框架省心。同样是跨平台,Flutter 还得装 Dart SDK 配环境,Delphi 这边装好 RAD Studio 就能写代码,而且控件拖拽绑事件,几十行就能把界面搭好,速度是真的快。

但也要承认不足,FMX 的生态和原生开发比还是差了不少。真遇到一些冷门的手机机型,界面渲染和系统交互偶尔会有奇怪的兼容问题,排查起来比较费时间。我的实用建议是:这种小工具类 APP,优先保证功能逻辑正确和核心路径的数据一致性,界面效果别太花哨,能稳定跑起来比一次性把 UI 做漂亮重要得多。所有数据操作统一走数据层,界面层只负责展示和交互,这样后续出了 bug 定位起来会很轻松。如果你也准备用 Delphi 做类似的内部工具,这套思路可以少走不少弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 22:57:07

擦亮眼!不是随便一个 AI 就能搞定毕业论文,2026 教授认可工具推荐

每一年毕业季&#xff0c;无数同学深陷论文难题&#xff1a;开题毫无思路、搭建框架耗费数日、初稿逻辑松散、查重标红泛滥、AI检测超标、格式反复被导师驳回。现如今市面上通用型AI工具遍地开花&#xff0c;但绝大多数通用大模型存在编造虚假参考文献、学术语句口语化、AI生成…

作者头像 李华
网站建设 2026/9/2 22:56:03

MFC按钮美化利器CButtonST实战指南

简介&#xff1a;这一MFC增强按钮类库CButtonST面向Windows桌面应用开发者&#xff0c;用于替代默认CButton&#xff0c;快速实现自定义颜色、图标、热键与多状态视觉反馈&#xff0c;提升界面专业度与交互性。压缩包共120个文件&#xff0c;内含22个cpp源文件、23个h头文件、3…

作者头像 李华
网站建设 2026/9/2 22:50:35

Grok绑定X送$100积分:开发者API接入与成本控制全攻略

很多人看到“Grok 绑定 X 送 $100 开发者积分”这个消息&#xff0c;第一反应是&#xff1a;这就是平台拉新发的优惠券&#xff0c;跟打车平台送补贴差不多&#xff0c;领完就完事。但如果你真的在做 AI 应用开发&#xff0c;这个判断其实漏掉了最有价值的部分。xAI 通过这 $10…

作者头像 李华
网站建设 2026/9/2 22:50:17

Spring Boot + Vue 实现个人视频管理:从上传到播放的完整链路

在实际开发里&#xff0c;“我的视频”这类个人视频管理项目经常被低估。很多开发者以为只要有一个 HTML5 <video> 标签&#xff0c;把视频地址填进去就能播放&#xff1b;真正动手做时才发现&#xff0c;视频文件存在哪里、接口怎么返回、浏览器用什么编码格式播放、上…

作者头像 李华