Android 设备管控开发实战:普通三方 APK 如何实现“设备锁定”并限制用户退出?

做第三方平板管控时,管理员说的“锁定设备”,通常不是把屏幕熄灭,而是让设备立刻进入一个不可继续使用的页面:屏幕显示锁定原因,返回键无效;用户按 Home、打开最近任务或切到其他应用后,设备会重新回到锁定页;管理员解除后才恢复正常。

我在项目里处理这个功能时,最先踩到的坑是只做了一张全屏 Activity。它看起来像锁住了,但按一次 Home 就能离开,通知栏也还能下拉。真正能用的方案不是一个页面,而是下面这条闭环:

实现层 解决的问题
持久化锁定状态 Activity 或进程重建后,仍然知道设备处于锁定中
全屏锁定页 展示锁定原因,消费返回键,并从最近任务中隐藏
无障碍前台监听 用户通过 Home、最近任务或其他入口离开后,重新拉起锁定页
顶部辅助功能窗口 在支持并验证过的设备上,限制从屏幕顶部下拉状态栏
有序解除 先清除状态,再移除窗口和关闭页面,避免旧事件把锁定页再次拉起

普通三方 APK 的设备锁定闭环

普通三方 APK 的设备锁定闭环

这里实现的是普通三方 APK 的业务锁定,不是 Android Keyguard,也不是 Device Owner 的 Lock Task/Kiosk。这个边界要先说清楚,但正文不展开权限体系,直接看功能怎么落地。

完整锁定还有一个硬前提:用户已经明确开启无障碍服务,而且服务当前处于连接状态。没有这个条件时只能展示一张页面,无法保证离开拉回和顶部限制,控制端应返回“能力未就绪”,不能返回锁定成功。

一、锁定状态必须独立于 Activity

锁定页是否还在屏幕上,只是界面状态;设备是否仍被锁定,才是业务事实。不能把一个静态的 isShowing 当成唯一依据,因为按 Home、旋转屏幕、系统回收 Activity 或进程重启,都可能让界面实例消失。

实际项目里,我会缓存当前设备控制状态,并在进程恢复后重新执行。下面把它缩成一个最小实现:只保存是否锁定和展示文案。

LockStateStore.kt

data class LockSnapshot(
    val active: Boolean,
    val reason: String
)

class LockStateStore(context: Context) {
    private val prefs = context.applicationContext
        .getSharedPreferences("managed_lock", Context.MODE_PRIVATE)

    fun activate(reason: String): Boolean {
        return prefs.edit()
            .putBoolean("active", true)
            .putString("reason", reason)
            .commit()
    }

    fun deactivate(): Boolean {
        return prefs.edit()
            .putBoolean("active", false)
            .remove("reason")
            .commit()
    }

    fun current(): LockSnapshot {
        return LockSnapshot(
            active = prefs.getBoolean("active", false),
            reason = prefs.getString("reason", null)
                ?: "设备已被管理员锁定"
        )
    }
}

这里刻意使用同步提交,是为了保证后续拉起界面前,磁盘中的锁定状态已经更新。正式项目也可以换成 DataStore,但顺序不能反:先写入锁定状态,再展示页面;解除时先清状态,再关闭页面。

二、全屏页只负责展示和拦截返回

锁定页本身不承担前台识别,也不决定设备是否仍被锁定。它只做三件事:读取锁定原因、隐藏系统栏、消费返回键。Manifest 中把它设为单任务,并从最近任务列表排除:

AndroidManifest.xml

<activity
    android:name=".lock.ManagedLockActivity"
    android:exported="false"
    android:excludeFromRecents="true"
    android:launchMode="singleTask"
    android:theme="@style/Theme.App.Lock" />

ManagedLockActivity.kt

class ManagedLockActivity : AppCompatActivity() {
    private val store by lazy { LockStateStore(this) }

    companion object {
        private val mainHandler = Handler(Looper.getMainLooper())
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安卓与 AI 研习社

你的支持,让技术分享走得更远

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值