1.1 自动启动机制
功能说明
Android 系统开机完成后,自动启动具微遥控应用并进入主界面。
技术实现
方案一:BroadcastReceiver 监听开机广播(推荐)
kotlin
// AutoStartReceiver.kt
class AutoStartReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
// 启动应用主 Activity
val starterIntent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
}
context.startActivity(starterIntent)
// 启动锁定服务
val serviceIntent = Intent(context, LockTaskService::class.java)
context.startForegroundService(serviceIntent)
}
}
}
AndroidManifest.xml 配置
xml
<receiver
android:name=".receivers.AutoStartReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<!-- 华为设备 -->
<action android:name="com.huawei.android.intent.action.BOOT" />
</intent-filter>
</receiver>
<!-- 权限声明 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.START_FOREGROUND_SERVICES" />
方案二:Launcher Activity 设置(辅助)
xml
<!-- 将 MainActivity 设为 launcher,确保从桌面启动也能直接进入 -->
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
启动时序要求
| 阶段 | 时间要求 | 实现要点 |
|---|---|---|
| 系统开机 → BroadcastReceiver 触发 | < 5s | 优先级高的 BroadcastReceiver |
| BroadcastReceiver → Activity 启动 | < 2s | 前台服务辅助启动 |
| Activity 启动 → Lock Task 进入 | < 1s | Activity 创建后立即调用 startLockTask() |
| 总启动时间 | ≤ 10s | 需要实测验证 |
2.2 Kiosk 模式(Lock Task Mode)
功能说明
进入无界应用模式,禁止用户退出到系统桌面、切换应用或使用系统导航。
技术实现
LockTaskManager.kt
kotlin
class LockTaskManager(private val activity: MainActivity) {
private var isLockTaskEnabled = false
/**
* 进入 Kiosk 模式
* 使用 Activity.startLockTask() 进入无界应用模式
*/
fun enterKioskMode() {
if (!isLockTaskEnabled) {
activity.startLockTask()
isLockTaskEnabled = true
Log.d(TAG, "进入 Kiosk 模式")
}
}
/**
* 退出 Kiosk 模式(仅工程模式可用)
*/
fun exitKioskMode(authorization: Authorization): Boolean {
if (!isLockTaskEnabled) return false
if (!authorization.isValid) {
Log.w(TAG, "退出 Kiosk 模式授权失败")
return false
}
activity.stopLockTask()
isLockTaskEnabled = false
Log.d(TAG, "退出 Kiosk 模式")
return true
}
/**
* 检查是否处于锁定模式
*/
fun isLocked(): Boolean = isLockTaskEnabled
companion object {
private const val TAG = "LockTaskManager"
}
}
MainActivity.kt 集成
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var lockTaskManager: LockTaskManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1. 设置全屏模式
setupFullScreen()
// 2. 初始化锁定管理器
lockTaskManager = LockTaskManager(this)
// 3. 进入 Kiosk 模式
lockTaskManager.enterKioskMode()
// 4. 注册生命周期回调
registerLifecycleCallbacks()
}
/**
* 设置全屏模式
*/
private fun setupFullScreen() {
// 隐藏状态栏和导航栏
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.hide(WindowInsetsCompat.Type.statusBars() or
WindowInsetsCompat.Type.navigationBars())
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
// 禁止截屏(保护敏感信息)
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
/**
* 注册生命周期回调,防止应用被切换到后台
*/
private fun registerLifecycleCallbacks() {
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity) {
// 应用进入后台时,立即重新进入前台
if (activity is MainActivity && lockTaskManager.isLocked()) {
// 延迟一点重新进入,避免循环
Handler(Looper.getMainLooper()).postDelayed({
if (lockTaskManager.isLocked()) {
reenterForeground()
}
}, 500)
}
}
// 其他回调...
})
}
/**
* 重新进入前台
*/
private fun reenterForeground() {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
}
startActivity(intent)
lockTaskManager.enterKioskMode()
}
override fun onResume() {
super.onResume()
// 确保每次回到前台都重新进入锁定模式
lockTaskManager.enterKioskMode()
}
}
Lock Task 白名单配置
kotlin
// LockTaskWhitelist.kt
class LockTaskWhitelist(private val context: Context) {
// 允许在锁定模式下启动的系统组件
private val whitelist: List<String> = listOf(
// 系统设置(工程模式可用)
"com.android.settings/.Settings",
// 电话应用(紧急呼叫)
"com.android.phone/.InCallScreen",
// 电源管理
"com.android.systemui/.GlobalActions"
)
fun isAllowed(componentName: String): Boolean {
return whitelist.any { it in componentName }
}
}
2.3 防退出机制
功能说明
禁止用户通过 Home 键、任务切换键、多任务界面等退出到系统桌面。
技术实现
onBackPressedDispatcher 拦截
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 拦截返回键
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// 在 Kiosk 模式下,返回键无效
if (lockTaskManager.isLocked()) {
// 可选:提供震动反馈
vibrate(50)
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
}
})
}
/**
* 屏蔽 Home 键(Android 10+ 需要额外处理)
*/
override fun onUserLeaveHint() {
super.onUserLeaveHint()
// 用户尝试按 Home 键时触发
if (lockTaskManager.isLocked()) {
// 重新进入前台
reenterForeground()
}
}
/**
* 屏蔽最近任务键
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// 应用从外部返回时,重新进入锁定模式
if (lockTaskManager.isLocked()) {
lockTaskManager.enterKioskMode()
}
}
}
Home 键屏蔽(Android 5.0+)
kotlin
/**
* 方法1: 使用 setHomeButtonEnabled(false) - 仅适用于 ActionBar
*/
supportActionBar?.setHomeButtonEnabled(false)
/**
* 方法2: 使用 Flags(适用于所有版本)
*/
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
Android 10+ 特殊处理
kotlin
/**
* Android 10+ 需要注册 Home 键监听
*/
class HomeKeyInterceptor(private val context: Context) {
private var recentAppsListener: RecentAppsListener? = null
fun startIntercept() {
// 使用 AccessibilityService 监听任务切换
recentAppsListener = RecentAppsListener(context).apply {
startListening()
}
}
fun stopIntercept() {
recentAppsListener?.stopListening()
}
}
/**
* AccessibilityService 实现任务切换拦截
*/
class RecentAppsListener(context: Context) : AccessibilityService() {
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
when (event?.eventType) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
// 检测到应用切换,重新回到本应用
val pkg = event.packageName?.toString()
if (pkg != context.packageName) {
returnToApp()
}
}
}
}
private fun returnToApp() {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or
Intent.FLAG_ACTIVITY_NEW_TASK
}
context.startActivity(intent)
}
override fun onInterrupt() {}
companion object {
const val SERVICE_ID = "com.jvewit.p3s.remote.recentapps"
}
}
AccessibilityService 配置
xml
<!-- res/xml/accessibility_service_config.xml -->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowStateChanged"
android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_service_description" />
xml
<!-- AndroidManifest.xml -->
<service
android:name=".services.RecentAppsListener"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
2.4 工程模式保护
功能说明
工程模式需密码或数字证书才能进入,防止未经授权的系统配置修改。
技术实现
工程模式入口
kotlin
/**
* 工程模式激活方式(多种方式)
*/
class EngineeringModeManager(private val context: Context) {
companion object {
// 工程模式入口标识
const val ENGINEERING_MODE_ACTION = "com.jvewit.p3s.ENGINEERING_MODE"
// 激活码(SHA-256 存储)
private const val DEFAULT_PASSWORD_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
/**
* 方式1: 密码验证
*/
fun enterByPassword(password: String): Boolean {
val inputHash = sha256(password)
val storedHash = getStoredPasswordHash()
return inputHash == storedHash
}
/**
* 方式2: 证书验证
*/
fun enterByCertificate(cert: X509Certificate): Boolean {
return cert.verify(context.applicationInfo.publicKey) &&
isCertValid(cert)
}
/**
* 方式3: 特定按键序列(备用)
*/
fun enterByKeypadSequence(sequence: List<String>): Boolean {
return sequence == getExpectedSequence()
}
/**
* 方式4: 远程授权码(需联网)
*/
suspend fun enterByRemoteCode(code: String): Boolean {
return withContext(Dispatchers.IO) {
val response = api.verifyEngineeringCode(code)
response.isSuccess
}
}
}
密码管理
kotlin
/**
* 工程模式密码管理器
*/
class PasswordManager(private val context: Context) {
private val prefs = EncryptedSharedPreferences.create(
"engineering_prefs",
MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build(),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
/**
* 存储密码哈希
*/
fun storePasswordHash(hash: String) {
prefs.edit()
.putString(PREF_PASSWORD_HASH, hash)
.apply()
}
/**
* 获取存储的密码哈希
*/
fun getStoredPasswordHash(): String? {
return prefs.getString(PREF_PASSWORD_HASH, null)
}
/**
* 清除密码(恢复出厂设置时)
*/
fun clearPassword() {
prefs.edit()
.remove(PREF_PASSWORD_HASH)
.apply()
}
companion object {
private const val PREF_PASSWORD_HASH = "engineering_password_hash"
}
}
证书验证
kotlin
/**
* 证书验证器
*/
class CertificateValidator(private val context: Context) {
/**
* 验证客户端证书
*/
fun validateClientCertificate(certBytes: ByteArray): Boolean {
return try {
val certFactory = CertificateFactory.getInstance("X.509")
val cert = certFactory.generateCertificate(
ByteArrayInputStream(certBytes)
) as X509Certificate
// 验证证书有效期
cert.checkValidity()
// 验证证书指纹
val fingerprint = calculateFingerprint(cert)
fingerprint == getExpectedFingerprint()
} catch (e: Exception) {
Log.e(TAG, "证书验证失败: ${e.message}")
false
}
}
/**
* 计算证书指纹(SHA-256)
*/
fun calculateFingerprint(cert: X509Certificate): String {
val md = MessageDigest.getInstance("SHA-256")
val digest = md.digest(cert.encoded)
return digest.joinToString(":") { "%02x".format(it) }
}
/**
* 获取期望的证书指纹(硬编码或从服务器获取)
*/
fun getExpectedFingerprint(): String {
// 生产环境应从安全服务器获取
return BuildConfig.ENGINEERING_CERT_FINGERPRINT
}
companion object {
private const val TAG = "CertificateValidator"
}
}
工程模式 Activity
kotlin
/**
* 工程模式入口 Activity
*/
class EngineeringModeActivity : AppCompatActivity() {
private lateinit var passwordManager: PasswordManager
private lateinit var certValidator: CertificateValidator
private lateinit var lockTaskManager: LockTaskManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
passwordManager = PasswordManager(this)
certValidator = CertificateValidator(this)
lockTaskManager = LockTaskManager(this)
// 显示验证界面
setContentView(R.layout.activity_engineering_mode)
setupVerificationMethods()
}
/**
* 设置验证方式
*/
private fun setupVerificationMethods() {
// 密码验证
findViewById<Button>(R.id.btn_password).setOnClickListener {
val password = findViewById<EditText>(R.id.et_password).text.toString()
if (passwordManager.enterByPassword(password)) {
onVerificationSuccess()
} else {
onVerificationFailed()
}
}
// 证书验证
findViewById<Button>(R.id.btn_certificate).setOnClickListener {
val certBytes = readCertificateFromIntent()
if (certValidator.validateClientCertificate(certBytes)) {
onVerificationSuccess()
} else {
onVerificationFailed()
}
}
// 按键序列验证(备用)
findViewById<Button>(R.id.btn_keypad).setOnClickListener {
showKeypadDialog()
}
}
/**
* 验证成功
*/
private fun onVerificationSuccess() {
// 退出 Kiosk 模式
lockTaskManager.exitKioskMode(Authorization.SUCCESS)
// 启动工程模式主界面
val intent = Intent(this, EngineeringDashboardActivity::class.java)
startActivity(intent)
finish()
}
/**
* 验证失败
*/
private fun onVerificationFailed() {
// 记录失败日志
logFailedAttempt()
// 显示错误提示
Snackbar.make(findViewById(R.id.root_layout),
"验证失败,请重试", Snackbar.LENGTH_SHORT).show()
// 尝试次数过多时锁定
if (getFailedAttempts() >= MAX_FAILED_ATTEMPTS) {
lockoutForDuration()
}
}
}
安全策略
kotlin
/**
* 工程模式安全策略
*/
class EngineeringSecurityPolicy {
// 最大失败尝试次数
const val MAX_FAILED_ATTEMPTS = 5
// 锁定持续时间(毫秒)
const val LOCKOUT_DURATION_MS = 30_000L // 30秒
// 密码最小长度
const val MIN_PASSWORD_LENGTH = 6
// 密码最大长度
const val MAX_PASSWORD_LENGTH = 32
/**
* 验证密码强度
*/
fun validatePasswordStrength(password: String): PasswordStrength {
return when {
password.length < MIN_PASSWORD_LENGTH -> PasswordStrength.WEAK
password.length >= MAX_PASSWORD_LENGTH -> PasswordStrength.STRONG
password.any { it.isDigit() } && password.any { it.isLetter() } -> PasswordStrength.MEDIUM
else -> PasswordStrength.WEAK
}
}
/**
* 计算失败尝试次数
*/
fun getFailedAttempts(context: Context): Int {
val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
return prefs.getInt("failed_attempts", 0)
}
/**
* 增加失败尝试次数
*/
fun incrementFailedAttempts(context: Context) {
val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
prefs.edit()
.putInt("failed_attempts", getFailedAttempts(context) + 1)
.apply()
}
/**
* 重置失败尝试次数
*/
fun resetFailedAttempts(context: Context) {
val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
prefs.edit()
.remove("failed_attempts")
.remove("lockout_until")
.apply()
}
/**
* 检查是否处于锁定状态
*/
fun isLockedOut(context: Context): Boolean {
val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
val lockoutUntil = prefs.getLong("lockout_until", 0L)
return lockoutUntil > System.currentTimeMillis()
}
/**
* 设置锁定状态
*/
fun setLockedOut(context: Context, durationMs: Long = LOCKOUT_DURATION_MS) {
val prefs = context.getSharedPreferences("security_prefs", Context.MODE_PRIVATE)
prefs.edit()
.putLong("lockout_until", System.currentTimeMillis() + durationMs)
.apply()
}
}
3. 架构图
lua
+---------------------------------------------------------------------+
│ 启动与权限管控架构 │
+---------------------------------------------------------------------+
│ │
│ +------------------+ +------------------+ +----------------+ │
│ │ 系统开机广播 │───>│ AutoStartReceiver│───>│ MainActivity│ │
│ │ BOOT_COMPLETED │ │ │ │ │ │
│ +------------------+ +------------------+ +-------+--------+ │
│ │ │
│ v │
│ +----------------------------------------------------------------+ │
│ │ LockTaskManager │ │
│ │ +-------------+ +-------------+ +----------------------+ │ │
│ │ │ enterKiosk │ │ isLocked │ │ exitKiosk │ │ │
│ │ │ (进入锁定) │ │ (检查状态) │ │ (需授权) │ │ │
│ │ +-------------+ +-------------+ +----------------------+ │ │
│ +----------------------------------------------------------------+ │
│ │ │
│ +-------------+-------------+ │
│ v v v │
│ +--------------+ +--------------+ +--------------+ │
│ │ 防退出机制 │ │ 工程模式保护 │ │ 安全策略 │ │
│ │ │ │ │ │ │ │
│ │ + Home键拦截 │ │ + 密码验证 │ │ + 失败计数 │ │
│ │ + 返回键拦截 │ │ + 证书验证 │ │ + 锁定策略 │ │
│ │ + 任务切换拦截│ │ + 按键序列 │ │ + 密码强度 │ │
│ │ + 后台恢复 │ │ + 远程授权 │ │ + 审计日志 │ │
│ +--------------+ +--------------+ +--------------+ │
│ │
+---------------------------------------------------------------------+
4. 安全设计要点
4.1 多层防护
| 层级 | 防护措施 | 实现方式 |
|---|---|---|
| 系统层 | Lock Task Mode | Android 原生 API |
| 应用层 | Activity 生命周期管理 | registerActivityLifecycleCallbacks |
| 服务层 | Foreground Service | 保持应用前台运行 |
| 辅助功能 | Accessibility Service | 拦截任务切换 |
| 安全层 | 密码/证书验证 | EncryptedSharedPreferences + KeyStore |
4.2 防御策略
arduino
攻击场景 防御措施
─────────────────────────────────────────────────────
用户按 Home 键退出 Lock Task + 后台恢复
用户切换应用 Accessibility Service 拦截
用户尝试退出 Kiosk 需要工程模式授权
暴力破解密码 失败锁定 + 指数退避
篡改应用配置 EncryptedSharedPreferences
窃取敏感数据 FLAG_SECURE 防截屏
5. 测试用例
5.1 功能测试
| 用例ID | 测试项 | 测试步骤 | 预期结果 |
|---|---|---|---|
| T-APP-001 | 开机自动启动 | 重启设备,等待系统启动完成 | 10s 内进入具微遥控应用 |
| T-APP-002 | Home键拦截 | 应用运行中按 Home 键 | 应用立即回到前台,无法进入桌面 |
| T-APP-003 | 返回键拦截 | 应用运行中按返回键 | 无响应,应用保持前台 |
| T-APP-004 | 任务切换拦截 | 打开最近任务列表 | 无法切换到其他应用 |
| T-APP-005 | 工程模式-密码 | 输入正确密码 | 成功进入工程模式 |
| T-APP-006 | 工程模式-密码错误 | 输入错误密码 | 提示失败,记录尝试次数 |
| T-APP-007 | 工程模式-证书 | 导入有效证书 | 成功进入工程模式 |
| T-APP-008 | 工程模式-锁定 | 连续5次密码错误 | 锁定30秒 |
| T-APP-009 | 后台恢复 | 强制杀死应用 | 5s 内自动重启并恢复锁定 |
| T-APP-010 | 截屏保护 | 尝试截屏 | 截屏失败或显示黑屏 |
5.2 性能测试
| 用例ID | 测试项 | 测试条件 | 通过标准 |
|---|---|---|---|
| T-APP-P01 | 启动时间 | 冷启动 | ≤10s 进入操控界面 |
| T-APP-P02 | 内存占用 | 应用运行中 | ≤200MB |
| T-APP-P03 | CPU占用 | 待机状态 | ≤5% |
| T-APP-P04 | 启动频率 | 连续重启10次 | 100% 成功启动 |
6. 附录:关键代码片段
6.1 完整 MainActivity
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var lockTaskManager: LockTaskManager
private lateinit var securityPolicy: EngineeringSecurityPolicy
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1. 设置窗口属性
setupWindow()
// 2. 初始化组件
lockTaskManager = LockTaskManager(this)
securityPolicy = EngineeringSecurityPolicy()
// 3. 检查是否需要进入工程模式
if (intent.action == EngineeringModeManager.ENGINEERING_MODE_ACTION) {
startActivity(Intent(this, EngineeringModeActivity::class.java))
finish()
return
}
// 4. 设置布局
setContentView(R.layout.activity_main)
// 5. 进入 Kiosk 模式
lockTaskManager.enterKioskMode()
// 6. 注册生命周期回调
registerLifecycleCallbacks()
// 7. 启动必要服务
startRequiredServices()
}
private fun setupWindow() {
// 全屏模式
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.hide(WindowInsetsCompat.Type.statusBars() or
WindowInsetsCompat.Type.navigationBars())
}
// 禁止截屏
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
private fun registerLifecycleCallbacks() {
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity) {
if (activity is MainActivity && lockTaskManager.isLocked()) {
Handler(Looper.getMainLooper()).postDelayed({
if (lockTaskManager.isLocked()) {
reenterForeground()
}
}, 500)
}
}
override fun onActivityResumed(activity: Activity) {
if (activity is MainActivity) {
lockTaskManager.enterKioskMode()
}
}
// 其他回调...
})
}
private fun reenterForeground() {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or
Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
}
override fun onBackPressed() {
// 在 Kiosk 模式下禁用返回键
if (!lockTaskManager.isLocked()) {
super.onBackPressed()
}
}
override fun onUserLeaveHint() {
// Home 键被按下
if (lockTaskManager.isLocked()) {
reenterForeground()
}
}
private fun startRequiredServices() {
// 启动前台服务保持应用活跃
val serviceIntent = Intent(this, KeepAliveService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
}
}
6.2 KeepAliveService
kotlin
class KeepAliveService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 创建通知(前台服务必需)
val notification = createNotification()
startForeground(NOTIFICATION_ID, notification)
// 定期检查应用状态
scheduleHealthCheck()
return START_STICKY
}
private fun scheduleHealthCheck() {
val handler = Handler(Looper.getMainLooper())
handler.postDelayed(object : Runnable {
override fun run() {
// 检查应用是否在前台
if (!isAppInForeground()) {
bringAppToForeground()
}
// 定时检查
handler.postDelayed(this, CHECK_INTERVAL_MS)
}
}, CHECK_INTERVAL_MS)
}
private fun isAppInForeground(): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val appProcesses = activityManager.runningAppProcesses
for (proc in appProcesses) {
if (proc.importance == ImportanceForeground &&
proc.processName == packageName) {
return true
}
}
return false
}
private fun bringAppToForeground() {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or
Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
}
override fun onBind(intent: Intent?): IBinder? = null
companion object {
private const val NOTIFICATION_ID = 1001
private const val CHECK_INTERVAL_MS = 5000L
}
}
7. 总结
| 需求 | 实现方案 | 关键API/技术 |
|---|---|---|
| 自动启动 | BroadcastReceiver + 前台服务 | BOOT_COMPLETED |
| Kiosk模式 | Lock Task Mode | startLockTask()/stopLockTask() |
| 防退出 | Activity生命周期管理 | registerActivityLifecycleCallbacks |
| 工程模式 | 多因素认证 | EncryptedSharedPreferences + KeyStore |
| 安全防护 | 多层防御 | FLAG_SECURE + AccessibilityService |
本方案满足 PRD-APP-001 所有要求:
- ✅ 开机自动进入应用
- ✅ 无授权不得退出到系统桌面
- ✅ 工程模式需密码或证书