桌面上的"计算器"其实是应用锁:安卓图标伪装 + 假崩溃实现拆解
这是 PhoneGuard 开源系列第三篇(前两篇:六件套总览、ADB 取证原理)。这篇讲一个好玩但实用的设计:怎么让应用锁在桌面上"消失"。
先说场景
你手机设了应用锁,桌上图标明晃晃写着"应用锁"——窥屏者一眼就知道了,甚至可能直接卸载它。
更好的策略是让保护本身隐身:把应用锁伪装成一台"计算器"或"备忘录"。窥屏者扫一眼桌面,根本不知道有锁的存在;就算手贱点开计算器,看到的也就是个能正常算数的计算器。入口只有你自己知道——输对密码才能进入真正的锁管理界面。
这个功能在 AppLock v1.1 里叫「图标伪装」,下面拆实现。
核心:activity-alias,一个 Manifest 里的"分身术"
安卓桌面上显示的图标,其实来自带 MAIN + LAUNCHER intent-filter 的 Activity。而 activity-alias 可以为同一个 Activity 声明多个"替身入口",每个替身可以有自己的图标和名字。
我的做法:Manifest 里注册三个互斥的 launcher alias——
xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!– 真实入口 alias(默认启用) –>
<activity-alias
android:name=".MainAlias"
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<!– 伪装入口 alias:计算器(默认禁用) –>
<activity-alias
android:name=".DisguiseCalcAlias"
android:targetActivity=".ui.DisguiseActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/ic_disguise_calculator"
android:label="@string/disguise_label_calculator">
<intent-filter>…</intent-filter>
</activity-alias>
三个 alias 共用一套 launcher intent-filter,但 enabled 状态互斥——任意时刻桌面只显示一个图标。
切换的原子性:先启后禁
切换图标的代码不长,但顺序有讲究:
kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// DisguiseManager.kt(节选)
fun applyDisguise(context: Context, mode: Int, config: ConfigManager) {
val targetAlias = aliasForMode(mode)
// 1. 先启用目标 alias(保证桌面始终有图标)
setAliasEnabled(context, targetAlias,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
// 2. 再禁用其余 alias
ALL_ALIASES.filter { it != targetAlias }.forEach { alias ->
setAliasEnabled(context, alias,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
}
// 3. 持久化模式
config.disguiseMode = mode
}
为什么必须先启用再禁用?如果把顺序反过来,中间会出现一小段时间桌面没有任何入口——用户会以为 App"消失"了。先启后禁保证切换过程桌面上始终有图标,体验无损。
切换后系统会自动重启桌面图标(Launcher 收到 PACKAGE_CHANGED 广播),无需重启手机。
实战踩坑:ComponentName 的两个"名字"
这是全篇最大的坑,写出来给大家避雷。禁用/启用 alias 要构造 ComponentName(packageName, className),但这两个参数的取值规则不一样:
kotlin
1
2
3
4
5
6
7
8
9
10
11
// 错误写法:两个都用 packageName 拼
ComponentName(context.packageName, ".MainAlias") // ❌ 找不到组件
// 正确写法:
// packageName 用运行时包名(debug 构建带 .debug 后缀)
// className 必须用 namespace 展开后的完整类名
private const val ALIAS_MAIN = "com.phonGuard.applock.MainAlias"
private const val ALIAS_CALCULATOR = "com.phonGuard.applock.DisguiseCalcAlias"
ComponentName(context.packageName, ALIAS_MAIN) // ✅
原因:applicationId(运行时包名)和 namespace(代码包名)在 Gradle 里是两个独立配置。debug 构建的 applicationId 是 com.phonGuard.applock.debug,但 alias 类名永远挂在 namespace 下。两个混用,运行时就是一脸懵的 IllegalArgumentException。
假崩溃:伪装的"第二道门"
图标换了还不够——万一有人知道 AppLock 存在,通过应用列表找到并点开呢?
这就是「假崩溃」的用武之地:未授权状态下打开 App,界面不是锁屏,而是表演一个 "应用已停止运行"的系统弹窗,配一个"关闭应用"按钮。窥屏者的心理活动是"哦,这 App 崩了",点关闭退出——殊不知这次访问已经被后台记录。
设计上有个细节:真崩溃弹窗是系统按应用主题渲染的,所以假弹窗的文字、按钮布局要跟你手机 ROM 的风格一致,不然有经验的用户一眼就能看穿。这也是为什么我把假崩溃做成可选模式而不是默认——有人喜欢吓退,有人喜欢纯静默。
顺手提一句:保险箱
伪装成计算器后,"计算器"里还藏了个加密相册(v1.1 的隐私保险箱):正确的密码序列打开管理界面,错误的序列打开真计算器。照片用 AES 加密落盘在应用私有目录,不root读不出来。
小结
这套"隐身"方案的全部家当:
表格
组件 作用
activity-alias ×3 互斥桌面入口,图标秒切
先启后禁 切换原子性,桌面永不为空
伪装壳 Activity 假计算器/假备忘录,功能真实可用
假崩溃弹窗 二道门,吓退 + 留痕
加密保险箱 计算器肚子里最后的底牌
安全工具的最高境界:让人意识不到保护的存在。
开源仓库(Star 是更新的动力 ⭐):
https://github.com/hnpdsyn/PhoneGuard1
下篇预告:接系统设备管理器 API,实现「密码输错自动锁机 + 指数退避」——把暴力破解者的时间成本拉到无穷大。关注不迷路。
网硕互联帮助中心






评论前必须登录!
注册