设置应用开机前台自启
本文将通过修改 Android 系统源代码和配置文件,实现应用在设备开机后以前台形式自启的功能(即用户可直接看到应用界面)。具体实现涉及多个文件的修改和联动,以下内容包括详细代码块、代码解释,以及最终实现该功能的总结。
关联文章《使用fake launcher设置应用前台开机自启后,如何返回桌面》。
1 开机后台自启的基础实现
如果仅需要开机后台自启(用户看不到界面),可以通过编辑 init.rc 脚本实现:
修改文件:device/rockchip/rk356x/rk3568_HW/init.rk356x.rc
在 on property:sys.boot_completed=1 节点下添加启动命令:
on property:sys.boot_completed=1
在此处添加
1.1 解释:
on property:sys.boot_completed=1:此处监听系统属性sys.boot_completed,该属性在系统启动完成后被设置为1。- 在该节点下添加脚本、服务或应用启动命令,即可在系统完成启动后触发对应服务或应用的后台运行。
- 限制:从 Android 9 开始,Google 引入了更严格的安全限制。应用如果未通过用户交互触发启动,其 Activity 将无法显示在前台。此时,应用即使后台自启,也可能因为未在前台运行而被系统杀掉。
2 前台自启的解决方案:使用 Fake Launcher
要实现前台自启,需要修改 Android 系统源码,通过一个伪造的 Launcher (Fake Launcher) 来绕过系统限制。以下是修改的具体步骤和代码解释。
特点:
- 替换机制:
homeIntent被替换为应用的launchIntent,而不是系统默认的桌面launcher。
- 返回桌面的限制:
- 在应用启动后,用户尝试返回到系统桌面将无法实现。因为此时的“桌面”已经由该应用所取代。
- 退出应用的表现:
- 当用户试图退出这个被替换的“桌面”时,Android 系统会在“桌面”退出后自动重新启动这个“桌面”。这导致用户无法真正退出该应用的界面。
- 如何设置以退回真正的桌面:
- 参考另一篇文章《使用fake launcher设置应用前台开机自启后,如何返回桌面》。
- 做法就是修改fake launcher的设置代码,在完成fake launcher启动后,重新设置
homeIntent为真正的系统桌面launcher。
2.1 修改 ActivityRecord.java
修改文件:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
新增代码:
diff --git a/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java b/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
index 08ea33af80..e19cecda23 100755
--- a/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5409,6 +5409,26 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (task != null) {
task.setHasBeenVisible(true);
}
+ int startFakeLauncher = Settings.System.getInt(mAtmService.mContext.getContentResolver(), "start_fake_launcher", 0);
+ Slog.i(TAG, "nodka onWindowsDrawn shortComponentName = " + shortComponentName + " , start_fake_launcher = " + startFakeLauncher);
+ if ((isHomeIntent(intent) && shortComponentName != null && !shortComponentName.contains("FallbackHome")) ||
+ ((1==startFakeLauncher) && shortComponentName != null && !shortComponentName.contains("FallbackHome"))) {
+ SystemProperties.set("service.bootanim.exit", "1");
+ try {
+ IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
+ if (surfaceFlinger != null) {
+ Slog.i(TAG_WM, "nodka ******* TELLING SURFACE FLINGER WE ARE BOOTED!");
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
+ data, null, 0);
+ data.recycle();
+ }
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "nodka Boot completed: SurfaceFlinger is dead!");
+ }
+ Settings.System.putInt(mAtmService.mContext.getContentResolver(), "start_fake_launcher", 0);
+ }
}
代码解释:
-
读取
start_fake_launcher标志位:int startFakeLauncher = Settings.System.getInt(mAtmService.mContext.getContentResolver(), "start_fake_launcher", 0);- 从系统设置中读取
start_fake_launcher标志位,判断是否需要启动伪造的 Launcher(Fake Launcher)。 - 通过
RootWindowContainer中的逻辑(稍后解释),此标志位由系统在启动过程中设置为1,表示应用需要以前台形式启动。
- 从系统设置中读取
-
判断是否为 HomeIntent 或伪造的 Launcher:
if ((isHomeIntent(intent) && shortComponentName != null && !shortComponentName.contains("FallbackHome")) || ((1==startFakeLauncher) && shortComponentName != null && !shortComponentName.contains("FallbackHome"))) {- 检查当前 Intent 是否为 Home 类型(即 Launcher)。
- 同时判断短组件名称(
shortComponentName)是否为正常的 Launcher(排除FallbackHome,即系统备用的 Launcher)。 - 如果
start_fake_launcher为1,也会触发启动逻辑。
-
关闭开机动画:
SystemProperties.set("service.bootanim.exit", "1");- 通过设置系统属性
service.bootanim.exit=1,通知系统退出开机动画。 - 这一步骤确保应用界面能够尽快显示,而不会被开机动画遮挡。
- 通过设置系统属性
-
通知 SurfaceFlinger 开机完成:
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger"); if (surfaceFlinger != null) { Parcel data = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0); data.recycle(); }- 使用
SurfaceFlinger通知系统图形服务,设备已经完成启动。 - 这一操作能够让开机流程顺利结束,并切换到 Fake Launcher 的界面。
- 使用
-
重置标志位:
Settings.System.putInt(mAtmService.mContext.getContentResolver(), "start_fake_launcher", 0);- 将
start_fake_launcher标志位重置为0,防止重复触发伪造的 Launcher 启动。
- 将
2.2 修改 RootWindowContainer.java
修改文件:frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
新增代码:
diff --git a/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java b/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
index 197e2188c3..ede69e379e 100644
--- a/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1519,6 +1520,15 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
ActivityInfo aInfo = null;
if (taskDisplayArea == getDefaultTaskDisplayArea()) {
homeIntent = mService.getHomeIntent();
+ String pkgName = SystemProperties.get("persist.fake.launcher.name", "");
+ if (!"".equals(pkgName)) {
+ Slog.i(TAG, "nodka start persist.fake.launcher.name " + pkgName);
+ Intent launchIntent = mService.mContext.getPackageManager().getLaunchIntentForPackage(pkgName);
+ if (launchIntent != null) {
+ Settings.System.putInt(mService.mContext.getContentResolver(), "start_fake_launcher", 1);
+ homeIntent = launchIntent;
+ }
+ }
aInfo = resolveHomeActivity(userId, homeIntent);
代码解释:
-
读取 Fake Launcher 的包名:
String pkgName = SystemProperties.get("persist.fake.launcher.name", "");- 从系统属性
persist.fake.launcher.name中读取需要启动的应用包名。 - 该属性在设备配置文件(
device.mk)中配置,例如:persist.fake.launcher.name=com.example.app。
- 从系统属性
-
获取目标应用的启动 Intent:
Intent launchIntent = mService.mContext.getPackageManager().getLaunchIntentForPackage(pkgName);- 通过包管理器查询目标应用的启动 Intent。
- 如果目标应用存在,则返回其主入口 Activity 的 Intent。
-
设置
start_fake_launcher标志位:Settings.System.putInt(mService.mContext.getContentResolver(), "start_fake_launcher", 1);- 设置标志位
start_fake_launcher=1,通知后续流程(如ActivityRecord)需要启动伪造的 Launcher。
- 设置标志位
-
替换默认 HomeIntent:
homeIntent = launchIntent;- 将系统默认的 HomeIntent 替换为目标应用的启动 Intent。
- 这一步确保目标应用能够在开机后以 Launcher 的形式启动。
2.3 修改 WindowManagerService.java
修改文件:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
新增代码:
diff --git a/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java b/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
index 6ee6027326..c68c91aedc 100644
--- a/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3450,6 +3451,10 @@ public class WindowManagerService extends IWindowManager.Stub
//box:skip FallbackHome to fix 2-3s black screen before launcher.
if (!"box".equals(SystemProperties.get("ro.target.product"))) {
+ KeyguardManager mKeyguardManager = (KeyguardManager) mAtmService.mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ boolean mFlag = mKeyguardManager.inKeyguardRestrictedInputMode();
+ Slog.e(TAG, "nodka performEnableScreen : " + mFlag);
+ if(mFlag) {
if (!mBootAnimationStopped) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
// stop boot animation
@@ -3477,6 +3482,7 @@ public class WindowManagerService extends IWindowManager.Stub
} catch (RemoteException ex) {
ProtoLog.e(WM_ERROR, "Boot completed: SurfaceFlinger is dead!");
}
+ }
}
代码解释:
-
判断设备是否处于锁屏状态:
KeyguardManager mKeyguardManager = (KeyguardManager) mAtmService.mContext.getSystemService(Context.KEYGUARD_SERVICE); boolean mFlag = mKeyguardManager.inKeyguardRestrictedInputMode();- 使用
KeyguardManager检查设备是否处于锁屏状态。 - 如果设备处于锁屏状态,则可能不触发前台显示。
- 使用
2.4 修改设备配置文件 device.mk
修改文件:device/rockchip/rk356x/device.mk
新增配置:
diff --git a/device/rockchip/rk356x/device.mk b/device/rockchip/rk356x/device.mk
index 6d31c46d87..4f7480dcab 100644
--- a/device/rockchip/rk356x/device.mk
+++ b/device/rockchip/rk356x/device.mk
@@ -77,6 +77,7 @@ PRODUCT_PROPERTY_OVERRIDES += \
ro.opengles.version=196610 \
wifi.interface=wlan0 \
ro.audio.monitorOrientation=true \
+ persist.fake.launcher.name = com.kgbs.gatecheck \
debug.nfc.fw_download=false \
debug.nfc.se=false \
vendor.hwc.compose_policy=0 \
代码解释:
persist.fake.launcher.name:定义需要启动的应用包名,例如com.kgbs.gatecheck。- 核心功能:
- 通过设备配置文件指定启动应用,灵活性更高。
总体总结
通过以上步骤,实现了设备开机后指定应用以前台形式自启的功能。流程如下:
- Fake Launcher 替换:
- 修改
RootWindowContainer,替换默认 Launcher 为指定应用。 - 修改
ActivityRecord,控制启动行为,确保前台显示。
- 修改
- 优化流程:通过
WindowManagerService停止开机动画,避免黑屏或延迟。 - 配置灵活性:通过
device.mk配置文件定义启动的应用包名,方便修改。
该方案通过结合系统属性和 Launcher 替换机制,绕过了 Android 9+ 的安全限制,实现了应用开机自动前台启动的需求。
留下评论