资讯详情

资讯详情

Delphi EX10 Android 开机自启动实现与避坑指南

简介本资源面向使用 Delphi XE10RAD Studio XE10进行 Android 跨平台开发的开发者聚焦于让应用在设备开机后自动运行这一常见需求。内容围绕 Android 系统的权限管理与启动服务配置展开涉及 BroadcastReceiver 监听 ACTION_BOOT_COMPLETED 广播、Service 后台组件编写以及在 AndroidManifest 模板中声明 receiver、service 与 RECEIVE_BOOT_COMPLETED 权限等关键环节并包含 Delphi 与 Java 混合调用的实现思路适合具备一定 Delphi 与 Android 基础的开发者参考。压缩包为 rar 格式共 15 个文件约 674KB涵盖 dproj、dpr、pas、fmx 等项目源码与界面文件以及 java、xml、dex、jar、class 等 Android 侧编译与配置产物另有 res、bat、deployproj 等资源与部署脚本结构完整。目前已有 777 人学习下载可帮助读者快速理解开机自启动的完整实现路径与工程组织方式。1. 开机自启动在 Delphi EX10 Android 上到底难在哪很多人第一次在 Delphi EX10RAD Studio 10.x 的 Delphi 编译器里做 Android 开机自启动都会卡在同一个地方代码写完了权限也加了手机重启后 App 就是不起来。原因不复杂——Android 从 8.0 开始对后台启动、隐式广播、电池优化做了一轮又一轮收紧而 Delphi 的 Android 运行时又不像原生 Android Studio 那样把 AndroidManifest.template.xml 的细节摊在你面前。你要同时搞定三件事一个能接收BOOT_COMPLETED的广播接收器、一份被正确合并进最终 APK 的清单文件、以及一套让系统愿意放行的权限与厂商白名单策略。这套东西在 Delphi EX10 里能跑通但每一步都有它自己的脾气。这篇笔记面向的是已经能用 Delphi 编译出 Android APK、想让自己的 App 在设备重启后自动拉起的开发者我会把原理、清单改法、代码、参数和踩过的坑按顺序讲清楚。2. 先搞懂 Android 开机广播的机制与 Delphi 的接入点2.1 BOOT_COMPLETED 广播为什么越来越难收到Android 的开机自启动本质是系统在启动完成后发送一个android.intent.action.BOOT_COMPLETED广播任何声明了接收该广播的组件都会被系统拉起。听起来简单但限制是层层叠加的第一层是权限。接收BOOT_COMPLETED必须声明android.permission.RECEIVE_BOOT_COMPLETED这是硬性门槛没有它系统直接忽略你的接收器。第二层是隐式广播限制。Android 8.0API 26起大部分隐式广播不允许在清单里静态注册但BOOT_COMPLETED属于被明确豁免的少数几个之一所以静态注册仍然有效。这一点很关键因为很多人误以为所有隐式广播都被禁了从而放弃了静态注册这条路。第三层是电池优化与后台限制。Android 6.0 引入 Doze 模式9.0 起对后台启动 Activity 加了限制。你的接收器能被唤醒不代表你能顺利启动一个 Activity——如果 App 处于后台且系统判定为「不活跃」直接startActivity可能被静默拦截。第四层是厂商定制。国内主流 ROM 对自启动有独立的白名单管理即便你满足了 AOSP 的所有条件没进厂商的自启动白名单照样起不来。这是最容易被忽略、也最玄学的一层。理解这四层你就明白为什么「代码对了但不起作用」是常态。Delphi EX10 帮你封装了 Java 桥接但它不会帮你绕过这些系统策略。2.2 Delphi EX10 里广播接收器该放在哪Delphi 的 Android 支持里广播接收器有两种落地方式。一种是纯 Java 的 BroadcastReceiver编译成 jar 或 aar 后通过AndroidManifest.template.xml注册。这种方式最贴近原生控制力最强但需要你额外维护一个 Java 工程对纯 Delphi 团队不友好。另一种是用 Delphi 自己的JBroadcastReceiver位于Androidapi.JNI.GraphicsContentViewText和FMX.Helpers.Android相关单元在运行时动态注册。但动态注册有个致命问题App 没运行的时候接收器根本不存在而开机自启动恰恰要求 App 没运行时也能被拉起。所以动态注册这条路在开机自启动场景下是走不通的。结论很明确开机自启动必须用静态注册而 Delphi EX10 里静态注册的载体就是AndroidManifest.template.xml。这个文件在工程目录下编译时 Delphi 会用它作为模板把里面的占位符替换成实际值生成最终的AndroidManifest.xml打进 APK。我一般会先确认这个文件存在。如果工程里没有说明你的 Android 目标平台还没被正确初始化需要在 Project Manager 里右键 Android 平台做一次 Add Android SDK 之类的配置Delphi 才会生成模板。2.3 静态注册的清单结构长什么样在AndroidManifest.template.xml里接收器的声明要放在application节点内部。一个最小可用的结构是这样application android:persistent%persistent% ... activity android:namecom.embarcadero.firemonkey.FMXNativeActivity ... !-- 主 Activity 声明Delphi 自动生成不要动 -- /activity !-- 开机自启动接收器 -- receiver android:namecom.embarcadero.firemonkey.BootReceiver android:enabledtrue android:exportedtrue android:permissionandroid.permission.RECEIVE_BOOT_COMPLETED intent-filter android:priority1000 action android:nameandroid.intent.action.BOOT_COMPLETED/ action android:nameandroid.intent.action.QUICKBOOT_POWERON/ action android:namecom.htc.intent.action.QUICKBOOT_POWERON/ /intent-filter /receiver uses-permission android:nameandroid.permission.RECEIVE_BOOT_COMPLETED/ /application这里有几个点必须说清楚。android:exportedtrue是必须的因为接收器要接收来自系统外部的广播Android 12 起如果 exported 没显式声明会直接编译失败。android:priority1000提高优先级让系统在有序广播里优先派发给你虽然BOOT_COMPLETED本身不是有序广播但部分厂商 ROM 会参考这个值。QUICKBOOT_POWERON是 HTC 等厂商的快速开机广播加上它覆盖面更广。android:name指向的类名如果你用纯 Java 接收器就写你自己的包名加类名如果想让 Delphi 帮你处理可以指向 FireMonkey 自带的接收器类但那个类默认不做自启动逻辑你需要自己扩展。实际项目里我倾向于自己写一个 Java 接收器逻辑最可控。3. 从零把开机自启动跑通清单、接收器与启动逻辑3.1 准备一个最小 Java 广播接收器Delphi EX10 允许你在工程里挂 Java 源文件编译时通过AndroidManifest.template.xml引用。先在工程目录下建一个java文件夹放一个BootReceiver.javapackage com.example.bootdemo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class BootReceiver extends BroadcastReceiver { private static final String TAG BootReceiver; Override public void onReceive(Context context, Intent intent) { String action intent.getAction(); Log.i(TAG, onReceive action action); // 只处理开机相关广播避免误触发 if (Intent.ACTION_BOOT_COMPLETED.equals(action) || android.intent.action.QUICKBOOT_POWERON.equals(action) || com.htc.intent.action.QUICKBOOT_POWERON.equals(action)) { // 用显式 Intent 拉起主 Activity避免被后台启动限制拦截 Intent launch new Intent(); launch.setClassName(context.getPackageName(), com.embarcadero.firemonkey.FMXNativeActivity); launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); launch.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); try { context.startActivity(launch); Log.i(TAG, startActivity success); } catch (Exception e) { Log.e(TAG, startActivity failed: e.getMessage()); } } } }逻辑说明onReceive是广播回调系统在开机完成后调用它。这里先做 action 判断只对开机广播响应避免其他广播误触发。启动 Activity 时用setClassName显式指定组件比用 action 隐式启动更可靠因为 Android 10 起对后台隐式启动 Activity 限制更严。FLAG_ACTIVITY_NEW_TASK是必须的因为接收器没有 Activity 上下文不加这个标志会抛异常。FLAG_ACTIVITY_CLEAR_TOP保证如果 App 已经在栈里不会重复创建。参数说明context.getPackageName()返回当前应用包名com.embarcadero.firemonkey.FMXNativeActivity是 Delphi FireMonkey Android 应用的主 Activity 类名这个类名在 Delphi EX10 里是固定的不要改成你自己的。如果你的工程用了自定义 Activity需要替换成实际类名。3.2 把 Java 接收器接进 Delphi 编译流程光有 Java 文件不够Delphi 不会自动编译它。你需要在工程里配置 Java 编译。常见做法是在AndroidManifest.template.xml同级放一个build.gradle片段或者用 Delphi 的Project Options - Building - Android - Java指定额外的 Java 源目录。更省事的做法是把 Java 文件编译成 jar然后通过Project Options - Building - Android - Libraries把 jar 加进去。编译命令大致是# 在 java 源码目录下执行需先配置好 Android SDK 的 javac javac -source 1.8 -target 1.8 \ -bootclasspath %ANDROID_SDK%\platforms\android-30\android.jar \ -d ./out \ ./com/example/bootdemo/BootReceiver.java # 打包成 jar jar cvf bootreceiver.jar -C ./out .逻辑说明-bootclasspath指向 Android SDK 里的android.jar这是编译 Android Java 代码必须的不能用 JDK 自带的 rt.jar。-source和-target指定 1.8因为 Android 对高版本字节码支持有限。编译产物在out目录打包成 jar 后加到 Delphi 工程。参数说明android-30是 API 级别按你工程实际的目标 API 调整Delphi EX10 默认可能用 android-30 或更高。%ANDROID_SDK%是环境变量指向你的 Android SDK 根目录。如果你的 javac 版本较新-bootclasspath可能被废弃改用--release 8配合-classpath指向 android.jar。3.3 清单里引用自定义接收器并声明权限把 3.1 的接收器类名替换进清单receiver android:namecom.example.bootdemo.BootReceiver android:enabledtrue android:exportedtrue intent-filter android:priority1000 action android:nameandroid.intent.action.BOOT_COMPLETED/ action android:nameandroid.intent.action.QUICKBOOT_POWERON/ action android:namecom.htc.intent.action.QUICKBOOT_POWERON/ /intent-filter /receiver uses-permission android:nameandroid.permission.RECEIVE_BOOT_COMPLETED/注意android:name必须和 Java 文件里的package加类名完全一致大小写敏感。uses-permission放在manifest节点下、application节点外位置放错会导致权限不生效。3.4 处理 Android 10 以上的后台启动限制即便接收器被唤醒Android 10 起对后台启动 Activity 有额外限制。系统会检查应用是否处于「前台」或「最近使用」状态否则startActivity可能被忽略。绕过这个限制的常见做法是改用前台服务或通知但对于开机自启动这种场景最直接的办法是申请SYSTEM_ALERT_WINDOW权限悬浮窗拥有该权限的应用被视为「可见」后台启动 Activity 会被放行。在清单里加uses-permission android:nameandroid.permission.SYSTEM_ALERT_WINDOW/然后在 App 首次启动时引导用户去设置里开启悬浮窗权限// Delphi 侧引导用户开启悬浮窗权限 uses Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Net, Androidapi.JNI.JavaTypes, Androidapi.Helpers; procedure OpenOverlaySettings; var Intent: JIntent; Uri: Jnet_Uri; begin Intent : TJIntent.JavaClass.init( TJSettings.JavaClass.ACTION_MANAGE_OVERLAY_PERMISSION); Uri : TJnet_Uri.JavaClass.parse( StringToJString(package: JStringToString( TAndroidHelper.Context.getPackageName))); Intent.setData(Uri); Intent.addFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK); TAndroidHelper.Context.startActivity(Intent); end;逻辑说明这段 Delphi 代码构造一个跳转到系统悬浮窗权限设置页的 IntentACTION_MANAGE_OVERLAY_PERMISSION是系统定义的动作package:后面跟包名直接定位到本应用的权限页。FLAG_ACTIVITY_NEW_TASK同样必须加。参数说明TAndroidHelper.Context是 Delphi EX10 提供的获取当前上下文的辅助类getPackageName返回包名。这段代码要在主线程调用且最好在用户首次进入 App 时触发而不是开机时触发。4. 避坑开机自启动收不到广播的五种典型情况4.1 现象日志里完全没有 onReceive 输出原因接收器没被系统注册成功。最常见的是AndroidManifest.template.xml改了但没生效因为 Delphi 编译时用的是缓存或生成的清单。另一个可能是android:exported没设或设成 falseAndroid 12 起直接编译失败或运行时忽略。解决编译后解压 APK检查AndroidManifest.xml里是否真的有你的 receiver 节点。用aapt dump xmltree your.apk AndroidManifest.xml可以看。如果模板改了没生效清理工程重新编译或者检查Project Options - Application - Manifest里是否勾选了自定义清单。4.2 现象onReceive 有日志但 App 没起来原因startActivity被系统拦截。Android 10 以上的后台启动限制或者厂商 ROM 的启动管理。解决先确认悬浮窗权限是否开启这是最有效的放行手段。其次检查 Intent 是否加了FLAG_ACTIVITY_NEW_TASK。如果还不行改用前台服务加通知的方式让用户点击通知进入 App虽然体验差一点但最稳。4.3 现象部分手机能起部分手机不能原因厂商 ROM 的自启动白名单机制。国内主流 ROM 都有独立的「自启动管理」默认禁止第三方应用自启动。解决在 App 里引导用户把应用加入自启动白名单。不同厂商的设置页 Intent 不同常见做法是跳转到应用详情页让用户手动设置procedure OpenAppDetailSettings; var Intent: JIntent; Uri: Jnet_Uri; begin Intent : TJIntent.JavaClass.init( TJSettings.JavaClass.ACTION_APPLICATION_DETAILS_SETTINGS); Uri : TJnet_Uri.JavaClass.parse( StringToJString(package: JStringToString( TAndroidHelper.Context.getPackageName))); Intent.setData(Uri); Intent.addFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK); TAndroidHelper.Context.startActivity(Intent); end;这段代码跳到应用详情页用户可以在那里找到自启动或权限管理入口。注意这不是万能的部分 ROM 需要用户去系统设置的自启动管理里单独操作。4.4 现象第一次开机不启动第二次才启动原因Android 系统在首次开机后需要完成一次完整的用户解锁和初始化部分设备在首次开机时BOOT_COMPLETED的派发时机较晚或者应用还没被系统「认识」。解决这是系统行为无法完全避免。可以在 App 首次手动启动后用SharedPreferences记录一个标记后续开机时如果检测到标记存在再执行启动逻辑。但这只是缓解不能根治。4.5 现象Delphi 编译报错提示找不到接收器类原因Java 类没被正确编译进 APK或者包名和清单里的android:name不一致。解决检查 jar 是否加进了Project Options - Building - Android - Libraries检查 Java 文件的package声明和清单里的类名是否逐字符一致。用unzip -l your.apk | grep classes确认 jar 是否被打进 APK 的classes.dex里。如果 jar 没进去Delphi 会在运行时抛ClassNotFoundException。5. 进阶让自启动更可靠的两个技巧与验证方法5.1 用 JobScheduler 做延迟启动兜底BOOT_COMPLETED收到后立刻启动 Activity 有时太急系统可能还没准备好。我一般会在接收器里不直接启动 Activity而是调度一个 JobScheduler 任务延迟几秒再启动。这样能避开开机瞬间的系统繁忙期成功率更高。// 在 BootReceiver.onReceive 里改为调度 Job ComponentName component new ComponentName(context, BootJobService.class); JobInfo job new JobInfo.Builder(1001, component) .setMinimumLatency(5000) // 延迟 5 秒 .setOverrideDeadline(10000) // 最晚 10 秒 .build(); JobScheduler scheduler (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); scheduler.schedule(job);逻辑说明setMinimumLatency设置最小延迟setOverrideDeadline设置最晚执行时间两者配合让任务在 5 到 10 秒之间执行。BootJobService是你自己实现的JobService在onStartJob里执行启动 Activity 的逻辑。参数说明1001是 job ID自定义且唯一即可。延迟时间按设备性能调整低端机可以设长一点比如 8 到 15 秒。5.2 验证自启动是否真的生效验证不能只看代码要用日志和系统工具。我常用的验证流程是第一步用adb logcat过滤你的 TAG重启设备后观察是否有onReceive输出adb logcat -c adb reboot # 设备重启后 adb logcat -s BootReceiver:V第二步检查应用进程是否被拉起adb shell ps | grep com.example.bootdemo第三步如果进程起来了但 Activity 没显示检查dumpsys activity activities里的栈信息adb shell dumpsys activity activities | grep bootdemo第四步用adb shell dumpsys deviceidle查看设备是否处于 Doze 模式如果是说明电池优化在起作用需要引导用户关闭电池优化。5.3 一个我踩过的坑清单合并冲突Delphi EX10 编译时会把AndroidManifest.template.xml和 FireMonkey 自带的清单做合并。如果你在模板里重复声明了RECEIVE_BOOT_COMPLETED权限或者 receiver 的android:name和 FireMonkey 内置的冲突编译可能不报错但运行时行为异常。我的习惯是模板里只加自己需要的东西不要重复声明 Delphi 已经声明的权限。编译后一定用aapt dump xmltree检查最终清单确认 receiver 节点和权限都在且没有重复。另外android:exported在 Android 12 上是强制的漏了会直接编译失败。如果你的 Delphi EX10 版本较老编译目标 API 较低可能不会报错但装到新设备上会出问题。所以不管目标 API 是多少exported都显式写上。这套方案我在几个设备上验证过AOSP 类设备成功率最高厂商 ROM 需要额外引导用户设置。开机自启动这件事代码只占三成剩下七成是对系统策略的理解和用户引导。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →