
简介面向安卓开发者的应用锁程序锁完整源码包用于在应用启动时提供密码、图案或指纹解锁等安全防护适合需要学习系统权限管理、后台监控与敏感数据存储的初中级开发者也可支撑防盗保护、家长控制或企业设备管理等场景。压缩包共 1444 个文件大小 15.36MB以 PNG 图片、XML 布局与配置、class 编译文件、Java 源文件及 jar 依赖库为主并含可安装运行的 APK便于对照源码理解编译与打包流程。当前已有 392 人学习下载。源码实现主界面解锁逻辑、后台锁服务、图案解锁控件和系统广播监听同时提供 SharedPreferences/SQLite 方式保存密码并预留指纹识别所需权限与界面配套资源覆盖布局、图标、字符串及多语言配置。从解锁界面绘制到服务拦截应用启动项目结构完整、注释清晰可直接导入编译适合作为二次开发或深入分析安卓安全机制的起点。1. 应用锁源码拆解从 AppLocker.apk 到 LockService 的锁机实现手机借给别人用三五分钟相册、银行 App 总让人不踏实。Android 系统本身没有“锁定单个应用”的官方 API市面上的应用锁本质都是在前台应用变化时弹出自己的解锁界面以此阻断对受保护应用的访问。这份 android 应用程序锁源码包里AppLocker.apk 是编译产物class 文件里的 LockService、AppLockService、PatternView 三个类基本串起了一条“监控前台应用拦截跳转图案校验”的完整链路。它适合想搞懂安卓锁机原理的开发者也适合要做家长控制、企业设备管控的人直接二次开发。下面从 class 反推架构再给出可复现的改造路径最后聊几个真正耗时间的兼容性坑。2. LockService 与 AppLockService 的分工逻辑从 class 反推应用锁架构2.1 两个 Service 类各自的职责在 Android 应用锁项目里类名往往会直接暴露设计意图。AppLockService 带着 App 前缀职责通常是“应用级锁定”监听哪些应用被打开、判断这些应用是否在加锁名单里、必要时拉起解锁界面。LockService 不带前缀更可能承担系统级状态的兜底比如屏幕关闭后的状态同步、开机后的首次锁定以及两个 Service 之间的互相拉起。这种按层拆分的做法在早期应用锁里很常见业务逻辑和状态维护分开便于单独调试。从编译产物看LockService.class 和 AppLockService.class 是相互独立的类文件说明两者不是内部类关系而是注册在 AndroidManifest.xml 中的两个 Service 组件。拆成两个组件有一个实际好处即使其中一个被系统回收另一个仍能靠 START_STICKY 机制重建并重新拉活对方。我在反编译这类 APK 时第一件事就是看 manifest 里 service 的 exported 和 process 属性判断它是否常驻、是否把接口暴露给了外部应用调用。如果 exportedtrue 又没有权限保护其他应用可以直接 bindService 拉起或者控制它这在做安全评估时是要扣分的点。2.2 jarlist.cache 能反映出哪些依赖信息jarlist.cache 不是源码而是构建系统为加速增量编译生成的依赖 jar 列表缓存。它能告诉我们两件事项目构建时引入了哪些第三方库以及这些库的编译顺序。比如缓存里出现 android-support-v4 或 appcompat 的路径说明它用了旧版兼容库如果只有纯 SDK 框架 API则说明图案校验和锁屏逻辑都是手写的没有依赖第三方安全组件。这个判断直接关系到改造工作量。另外要提醒一个权限认知的更新。早期的应用锁源码里常出现 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE用来扫描已安装应用信息但在 Android 11 之后读取应用列表靠的是 QUERY_ALL_PACKAGES 或queries标签外部存储权限已经大面积收紧。如果你拿到一份老源码看到这两个权限不要直接照抄先确认 targetSdk 和实际还要不要读写外部文件否则上架审核会以“权限与功能无关”为由被驳回。2.3 为什么锁机逻辑要放在 Service 里应用锁必须持续感知当前前台是谁而 Activity 生命周期做不到这一点用户打开任意应用前台就是那个应用自己的 Activity锁应用拿不到任何回调。常规解法是 Service 配合 UsageStatsManager 或无障碍服务做前台检测。LockService 在 onStartCommand 中启动前台通知以降低被回收概率借助 Handler 定时轮询同时注册屏幕点亮、应用安装等广播来触发检查。public class LockService extends Service { private static final long CHECK_INTERVAL 800L; private Handler checkHandler new Handler(Looper.getMainLooper()); private Runnable checkTask new Runnable() { Override public void run() { // 获取当前最可能在前台的应用包名 String topPackage getTopPackageName(); if (isLockedApp(topPackage)) { startUnlockActivity(topPackage); } // 无论是否命中调度下一轮检查 checkHandler.postDelayed(this, CHECK_INTERVAL); } }; Override public int onStartCommand(Intent intent, int flags, int startId) { // 重启后清掉旧任务再重新调度避免任务叠加 checkHandler.removeCallbacksAndMessages(null); checkHandler.postDelayed(checkTask, CHECK_INTERVAL); return START_STICKY; } Override public void onDestroy() { checkHandler.removeCallbacksAndMessages(null); super.onDestroy(); } }这段代码的逻辑是Service 启动后每 800ms 检查一次当前前台应用包名命中加锁名单就启动解锁界面。CHECK_INTERVAL 是轮询频率800ms 是平衡响应速度和耗电的常用取值太短会频繁唤醒 CPU太长用户会明显感觉锁得慢。START_STICKY 的含义是Service 被系统杀死后会被重建但重建时传入的 Intent 为 null所以 onStartCommand 里必须对 intent 做空判断否则进程重建后会直接空指针。调用 removeCallbacksAndMessages 是防止 onStartCommand 被多次触发时多个 Runnable 叠加导致轮询频率翻倍。提示targetSdk 30 以上的设备Doze 模式会让后台 Handler 延迟执行屏幕关闭时轮询基本停摆。常见做法是同时注册 SCREEN_ON 广播亮屏瞬间立即补一次检查。3. PatternView 图案锁触摸事件解析、校验状态机与安全存储3.1 九宫格的触摸分发与路径绘制PatternView.class 表明这份源码用的是原生风格的九宫格图案锁。自绘 View 的核心在 onTouchEvent按下时记录起点滑动过程中不断判断手指落在哪个圆点附近命中就加入选中序列抬起时对完整序列做校验并重绘连线。相比直接用 ImageView 拼九个点自绘的好处是理顺了触摸事件和绘制状态动画和数学判断都在同一个 View 里完成。Override public boolean onTouchEvent(MotionEvent event) { float x event.getX(); float y event.getY(); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: clearPattern(); checkPoint(x, y); return true; case MotionEvent.ACTION_MOVE: checkPoint(x, y); invalidate(); return true; case MotionEvent.ACTION_UP: if (selectedPoints.size() MIN_PATTERN_LENGTH) { onPatternComplete(selectedPoints); } return true; } return super.onTouchEvent(event); }checkPoint 会遍历九个圆心坐标计算手指位置与每个圆心的欧氏距离落在触摸半径内的点如果还没被选中就加入序列。MIN_PATTERN_LENGTH 通常设为 4这是 Android 原生图案锁的最短长度要求。invalidate() 请求系统重绘 onDraw把新连的线段画出来onTouchEvent 必须返回 true否则后续的 MOVE 和 UP 事件不会继续派发给这个 View。容易忽略的细节是 UP 事件里校验完成后要清空当前路径并 invalidate 一次否则下一轮绘制会带着上一次的残留线段视觉上像重影。3.2 校验逻辑与错误次数限制图案校验不是直接比对字符串而是把点的 id 序列编码成一个整数或字符串再与存储值比对。但“存明文图案”是不合格的任何拿到 root 的人都能直接读出来。合格方案是加盐哈希public static String hashPattern(ListInteger pattern, byte[] salt) { try { MessageDigest digest MessageDigest.getInstance(SHA-256); digest.update(salt); for (int point : pattern) { digest.update((byte) point); } byte[] hash digest.digest(); StringBuilder sb new StringBuilder(); for (byte b : hash) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }MessageDigest 是 Android 标准库的摘要工具getInstance(SHA-256) 指定摘要算法。salt 建议用 SecureRandom 生成 16 字节存储到 KeyStore 或 Preferences 中校验时用同一个 salt 重新计算哈希并与存储值比较。错误次数限制一般用 SharedPreferences 记录失败次数连续 5 次失败进入 30 秒锁定。这个功能有两个容易翻车的点一是失败计数放在内存变量里进程被杀计数就清零等于用户可以无限试错二是锁定状态没有持久化应用重启后锁定窗口消失暴力破解窗口又重新打开。3.3 存储选型SharedPreferences、SQLite 还是 KeyStore应用锁需要保存三类数据解锁信息、加锁应用名单、安全策略错误次数、锁定时间等。下表是我做这类项目时的选型参考数据类别推荐方案理由图案哈希与 saltKeyStore SharedPreferences密钥不导出salt 可明文存储加锁应用名单SharedPreferences/JSON数量小、读写频率低错误次数与锁定时间SharedPreferences覆盖写不需要事务按时间/场景锁配置SQLite条件查询多时才需要数据库选型上最容易犯的错误是把加锁名单放在静态变量里进程被杀就丢了下次冷启动时所有应用都处于未锁状态。数据必须落盘内存缓存只用来加速读取。如果你要在名单上叠加“按时间段锁”“连接特定 WiFi 才锁”这类逻辑SharedPreferences 会迅速变成读写泥潭届时再迁移 SQLite 也不晚接口层提前做好抽象即可。4. 前台应用识别与解锁跳转无障碍服务和 UsageStatsManager 的取舍4.1 无障碍服务方案的两个门槛拿到 AppLockService 之后第一个问题必然是怎么知道某个应用被打开了。最直接的方式是注册无障碍服务系统在窗口状态变化时回调 onAccessibilityEvent实时性最强锁机响应最快。但它有两个门槛第一用户必须在系统设置里手动开启无障碍权限这一步会劝退大量普通用户第二部分定制 ROM 对无障碍服务有激进的清理策略长时间挂后台的掉线率明显偏高需要配合前台服务和厂商白名单引导才能维持。另外无障碍服务的事件流非常密集每次界面切换都会触发回调。如果把加锁判断写在回调里必须加节流否则会阻塞系统事件线程。早期不少应用锁卡顿就是因为在 onAccessibilityEvent 里做了耗时的包名反射或跨进程查询。对于这份源码我建议把无障碍服务定位成可选增强项而不是主依赖主路径用下面的轮询方案兼容性更可控。4.2 UsageStatsManager 轮询适配更友好的常用路径替代方案是用 UsageStatsManager 查询前台应用。它不需要无障碍权限但要申请 PACKAGE_USAGE_STATS 特殊权限用户得在安全中心或开发者选项里打开“使用情况访问”。LockService 既然已经有 Handler 轮询框架把前台查询逻辑替换成下面这段即可private String getTopPackageName() { UsageStatsManager usm (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); long now System.currentTimeMillis(); ListUsageStats stats usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, now - 1000, now); String topPackage null; long lastTime 0; if (stats ! null) { for (UsageStats usageStats : stats) { if (usageStats.getLastTimeUsed() lastTime) { lastTime usageStats.getLastTimeUsed(); topPackage usageStats.getPackageName(); } } } return topPackage; }queryUsageStats 的三个参数分别是聚合区间、起始时间、结束时间。INTERVAL_DAILY 表示按天聚合这里取最近 1 秒目的是拿到当前活跃记录getLastTimeUsed 最大的包名就是最可能在前台的应用。这个方案的问题在于部分系统对短时间内的统计有合并策略所以轮询间隔不要小于 500ms否则会反复拿到同一个旧值导致漏锁或延迟。如果用 INTERVAL_BEST系统会自适应粒度但各版本行为差异大不建议在这种场景使用。拿到包名之后还要过白名单这是几乎所有应用锁都会翻车的地方private boolean isLockedApp(String topPackage) { if (topPackage null) { return false; } if (skipLockPackages.contains(topPackage)) { return false; } return blockedPackages.contains(topPackage); }skipLockPackages 至少要包含本应用包名、桌面 launcher、系统设置和输入法不然会出现“打开设置改锁屏密码却被锁住”的死循环。blockedPackages 是真正要锁的名单。判断顺序不能反先判空、再豁免、最后查名单因为 launcher 包名在各种 ROM 上完全不同把它放进豁免名单能明显减少误锁。这里还有一个细节系统自带的包安装器也要豁免否则用户正在安装 APK 时会触发锁屏体验非常差。4.3 解锁界面的启动模式与返回键处理轮询命中加锁名单后用 Service 启动解锁界面有两个硬性要求一是必须加 FLAG_ACTIVITY_NEW_TASK因为 Service 上下文不在 Activity 任务栈中二是解锁界面的 launchMode 要设成 singleInstance否则每次轮询命中都会新建实例用户还没输入完就被新实例覆盖出现闪烁和重复跳转。manifest 里的配置一般是activity android:name.UnlockActivity android:launchModesingleInstance android:excludeFromRecentstrue android:taskAffinitycom.example.applock.unlock android:exportedfalse /singleInstance 保证整个系统里只有一个解锁界面实例已存在就直接复用taskAffinity 把解锁任务隔离在主任务之外excludeFromRecents 让它在多任务列表里不可见exportedfalse 禁止外部应用强行拉起解锁界面。这里最容易被忽略的是返回键用户在锁屏页按返回如果不拦截等于直接绕过锁。onBackPressed 里要么 moveTaskToBack要么直接不响应具体策略取决于产品定义偏安全的应用会选择忽略返回偏易用的应用会允许退到桌面但锁仍然生效。5. 二次开发落地重打包签名、后台启动限制与界面替换的实战坑5.1 重打包与签名一致性这套资源没有完整 Gradle 工程直接用 Android Studio 打开会缺配置。常见的做法是新建空工程把反编译得到的 java 和 res 目录搬进去再补依赖。第一个坑是签名新包签名与已装版本不一致时系统只能先卸载旧包用户数据全丢所以从最初版本就要固定一套签名配置signingConfigs { release { storeFile file(applock.jks) storePassword store-pass keyAlias applock keyPassword key-pass } }storeFile 指向签名文件keyAlias 与 keyPassword 对应别名和口令三者必须与首次发布完全一致。开启 minify 后要为 LockService、AppLockService 添加 keep 规则避免组件类名被混淆。5.2 Android 10 以后的后台启动限制Android 10API 29开始系统限制后台应用启动 ActivityService 里直接 startActivity 跳解锁页会被拦截日志出现 Background activity start from ... blocked。这条限制对应用锁是致命的轮询发现目标应用在前台时Service 处于后台解锁页根本拉不起来。主流做法是引导用户授权 SYSTEM_ALERT_WINDOW 悬浮窗权限用 TYPE_APPLICATION_OVERLAY 悬浮窗承载解锁界面绕过后台启动限制。5.3 将图案锁替换成数字密码锁如果你想把九宫格改成 4 位数字密码不需要动两个 Service只需替换解锁界面的输入控件。常见做法是自定义 PasscodeView内部维护 char[]每输入一位自动聚焦到下一格满 4 位触发校验校验逻辑复用 SHA-256 加盐哈希。输入框要设 inputTypenumberPassword让输入法进入密码模式同时禁止复制粘贴。改完按三个场景验证杀进程后冷启动进被锁应用看是否仍能触发锁定在解锁界面连续按返回看能否绕过锁屏待机后再点亮看 Handler 轮询是否被 Doze 挂起。三个场景的排查点分别是 Service 重建、返回键事件分发、前台服务与唤醒锁搭配日志里逐一核对即可。本文还有配套的精品资源点击获取