Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析

系列导航一:全景概览与架构设计 | 二:启动流程深度解析 | 三:StatusBar 状态栏 | 四:NavigationBar 导航栏 | 五:通知系统 | 六:QuickSettings | 七:Keyguard 锁屏(本文) | 八:Recents 与其他子系统

一、概述

Keyguard(锁屏)是 Android 设备安全的第一道防线,也是用户开机后看到的第一个可见界面。它负责在设备锁定时阻止未授权访问,同时提供通知预览、紧急呼叫、相机快捷入口等功能。

在 AOSP7 中,Keyguard 不仅是一个独立的锁屏界面,还需要与 StatusBar、通知面板、导航栏紧密协作,涉及复杂的调度逻辑。

Keyguard 模块的代码分散在两个 package 里:packages/SystemUI 放调度器和视图,packages/Keyguard(包名 com.android.keyguard)放状态监听和安全模型。

复制代码
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/
└── KeyguardViewMediator.java          # ★ 锁屏调度核心(约 3000+ 行)

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/
├── StatusBarKeyguardViewManager.java  # 锁屏与状态栏协调
├── KeyguardBouncer.java               # 安全锁屏认证视图
└── KeyguardBottomAreaView.java        # 锁屏底部区域(相机、紧急呼叫)

frameworks/base/packages/Keyguard/src/com/android/keyguard/   # 包名 com.android.keyguard
├── KeyguardUpdateMonitor.java         # ★ 锁屏状态监听(SIM 卡、屏幕等)
└── KeyguardSecurityModel.java         # 锁屏安全类型(SecurityMode)

关键设计KeyguardUpdateMonitorKeyguardSecurityModel 都在 packages/Keyguard 下、包名是 com.android.keyguard,不在 SystemUI 里------它们是共享给 Keyguard 独立进程用的状态/模型层。

二、Keyguard 整体架构

复制代码
┌─────────────────────────────────────────────────────────┐
│                   KeyguardViewMediator                  │
│                      (调度核心)                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │         负责协调所有锁屏相关组件                    │   │
│  │  - 处理屏幕 on/off 事件                           │   │
│  │  - 管理锁屏显示/隐藏状态                           │   │
│  │  - 协调安全认证流程                                │   │
│  │  - 与 PhoneWindowManager 通信                     │   │
│  └─────────────────────────────────────────────────┘   │
│           │                      │                      │
│           ▼                      ▼                      │
│  ┌────────────────┐    ┌──────────────────────┐        │
│  │ KeyguardUpdate │    │ StatusBarKeyguard     │        │
│  │    Monitor     │    │   ViewManager         │        │
│  │ (状态监听)      │    │ (锁屏视图管理)         │        │
│  └────────────────┘    └──────────────────────┘        │
│                                    │                     │
│                                    ▼                     │
│                          ┌──────────────────┐          │
│                          │  KeyguardBouncer │          │
│                          │  (PIN/图案/密码)  │          │
│                          └──────────────────┘          │
└─────────────────────────────────────────────────────────┘

三、KeyguardViewMediator------锁屏调度核心

3.1 启动流程

回顾系列第二篇,KeyguardViewMediatorconfig_systemUIServiceComponents 中注册,并在 SystemUIApplication 中通过反射加载。start() 里做的第一件事是在锁内调用 setupLocked() 把所有依赖准备好,然后把自身注册进组件表:

源码路径frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

java 复制代码
public class KeyguardViewMediator extends SystemUI {
    private PowerManager mPM;                        // 电源管理(用于 wake lock)
    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
    private KeyguardUpdateMonitor mUpdateMonitor;    // 锁屏状态监听
    private boolean mShowing;                        // 是否正在显示锁屏
    private LockPatternUtils mLockPatternUtils;      // 密码/图案校验

    @Override
    public void start() {
        synchronized (this) {
            setupLocked();
        }
        putComponent(KeyguardViewMediator.class, this);
    }

    private void setupLocked() {
        mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
        mLockPatternUtils = new LockPatternUtils(mContext);
        // ... 注册广播、KeyguardDisplayManager、加载锁屏音效 ...
        mStatusBarKeyguardViewManager =
                SystemUIFactory.getInstance().createStatusBarKeyguardViewManager(mContext,
                        mViewMediatorCallback, mLockPatternUtils);
    }
}

关键设计mPMPowerManager(拿 wake lock 用的),不是 PhoneWindowManager。视图管理器通过 SystemUIFactory.createStatusBarKeyguardViewManager() 工厂创建,便于定制替换。

3.2 与 PhoneWindowManager 的衔接

7.1.2 里 PhoneWindowManager 持有的是一个 binder KeyguardServiceDelegate mKeyguardDelegate,SystemUI 侧的调度器并不直接塞给它。KeyguardViewMediator 把自己注册进组件表(上面 start() 里的 putComponent(KeyguardViewMediator.class, this)),WindowManager 那边通过服务接口回调进来,锁屏显示/隐藏的语义由 KeyguardViewMediator 内部状态驱动。

关键设计 :调度器与 WMS 之间是 binder 委托 + 组件注册,而不是早期版本那种 mPM.setKeyguardDelegate(this) 的直接注入。

3.3 锁屏显示流程

显示锁屏走 handleShow(Bundle):先置 mShowing 状态,再交给 StatusBarKeyguardViewManager 真正去 show 视图:

源码路径frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

java 复制代码
public class KeyguardViewMediator extends SystemUI {
    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
    private boolean mShowing;
    private final ArrayList<IKeyguardStateCallback> mKeyguardStateCallbacks = new ArrayList<>();

    private void handleShow(Bundle options) {
        synchronized (KeyguardViewMediator.this) {
            setShowingLocked(true);
            mStatusBarKeyguardViewManager.show(options);   // 真正显示锁屏视图
            resetKeyguardDonePendingLocked();
        }
    }

    private void setShowingLocked(boolean showing) {
        if (showing != mShowing) {
            mShowing = showing;
            // 通知所有 IKeyguardStateCallback.onShowingStateChanged(showing)
            updateInputRestrictedLocked();
        }
    }
}

关键设计 :显示与否的语义状态(mShowing)由 setShowingLocked 统一维护,并同步广播给 mKeyguardStateCallbacks;真正画视图的是 mStatusBarKeyguardViewManager.show(options)

3.4 锁屏隐藏流程

解锁完成时 Keyguard 视图回调 keyguardDone(boolean),它把事件 post 到 mHandler,最终落到 handleKeyguardDone

源码路径frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

java 复制代码
public class KeyguardViewMediator extends SystemUI {
    private KeyguardUpdateMonitor mUpdateMonitor;
    private Handler mHandler;

    public void keyguardDone(boolean authenticated) {
        Message msg = mHandler.obtainMessage(KEYGUARD_DONE, authenticated ? 1 : 0);
        mHandler.sendMessage(msg);
    }

    private void handleKeyguardDone(boolean authenticated) {
        if (authenticated) {
            mUpdateMonitor.clearFailedUnlockAttempts();   // 认证成功,清零失败计数
        }
        handleHide();                                     // 走隐藏流程
    }
}

关键设计keyguardDone 只是投递消息,真正的状态翻转和 handleHide() 都在 handler 的 UI 线程执行,保证锁屏状态变更线程安全。

四、锁屏类型

AOSP7 支持多种锁屏方式:

4.1 是否安全(isSecure)

判断"当前用户到底有没有设锁"是决定滑动解锁还是安全锁屏的关键。isSecure() 综合了三种来源:

源码路径frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

java 复制代码
public class KeyguardViewMediator extends SystemUI {
    private LockPatternUtils mLockPatternUtils;
    private boolean mIsSubsidyOrDeviceLocked;   // 运营商补贴锁/设备锁

    public boolean isSecure() {
        return mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())
            || KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure()
            || mIsSubsidyOrDeviceLocked;
    }
}

关键设计:安全与否不只是"用户设了密码",还包括 SIM PIN 未解锁和设备补贴锁(subsidy lock)三种情况,任一成立即视为安全锁屏。

4.2 安全锁屏(PIN / 图案 / 密码)

安全类型由 KeyguardSecurityModel.SecurityMode 枚举统一描述,getSecurityMode() 根据当前 SIM 状态和设备策略算出应该用哪种解锁界面:

源码路径frameworks/base/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java

java 复制代码
public class KeyguardSecurityModel {
    public enum SecurityMode {
        Invalid,      // 空状态
        None,         // 未启用安全
        Pattern,      // 图案
        Password,     // 字母数字密码
        PIN,          // 纯数字 PIN
        SimPin,       // SIM PIN
        SimPuk,       // SIM PUK
        DeviceSubsidy // 补贴锁
    }

    private LockPatternUtils mLockPatternUtils;

    SecurityMode getSecurityMode() {
        // 依次判断 SimPuk → SimPin → DeviceSubsidy
        final int security = mLockPatternUtils.getActivePasswordQuality(
                KeyguardUpdateMonitor.getCurrentUser());
        switch (security) {
            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
                return SecurityMode.PIN;
            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
            case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
            case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
                return SecurityMode.Password;
            case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
                return SecurityMode.Pattern;
            case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
                return SecurityMode.None;
            default:
                throw new IllegalStateException("Unknown security quality:" + security);
        }
    }
}

KeyguardBouncer 本身是个普通类(不是接口、也不是抽象工厂),它负责认证视图的宿主。show(boolean resetSecuritySelection) 只保证宿主视图存在并展示主安全屏,具体的 PIN/图案/密码输入控件是由 KeyguardSecurityModelSecurityMode)+ KeyguardHostView 驱动的,而不是 KeyguardBouncer 上的字段:

源码路径frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java

java 复制代码
public class KeyguardBouncer {
    protected KeyguardHostView mKeyguardView;   // 宿主视图(内部承载 PIN/图案/密码)
    protected ViewGroup mRoot;
    protected ViewGroup mContainer;

    public KeyguardBouncer(Context context, ViewMediatorCallback callback,
            LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager,
            ViewGroup container) {
        mLockPatternUtils = lockPatternUtils;
        mContainer = container;
    }

    public void show(boolean resetSecuritySelection) {
        mFalsingManager.onBouncerShown();
        ensureView();
        if (resetSecuritySelection) {
            mKeyguardView.showPrimarySecurityScreen();   // 刷新主安全屏
        }
        // ... 触发上滑 dismiss 或展开 bouncer 动画 ...
    }
}

关键设计getSecurityMode() 属于 KeyguardSecurityModel,不在 KeyguardUpdateMonitor 上;KeyguardBouncer 只是宿主管道,具体输入界面由 SecurityMode + 宿主视图决定。

4.3 认证流程

复制代码
用户唤醒设备(电源键)
        │
        ▼
KeyguardViewMediator.handleNotifyScreenTurnedOn()
        │
        ▼
handleShow(options) → StatusBarKeyguardViewManager.show(options)
  显示锁屏界面(含通知预览)
        │
        ▼
用户上滑或输入 PIN/图案/密码
        │
        ├── 无安全锁(SecurityMode.None)→ 滑动直接解锁
        │
        └── 安全锁(PIN/Pattern/Password)
                │
                ▼
          KeyguardBouncer.show(resetSecuritySelection)
                │
                ▼
          KeyguardSecurityModel.getSecurityMode() 决定输入界面
                │
          LockPatternUtils 校验
                │
           ┌────┴────┐
         正确        错误
           │         │
           ▼         ▼
  keyguardDone(true)  显示错误提示
           │
           ▼
  handleKeyguardDone → handleHide(),显示桌面

五、StatusBarKeyguardViewManager------锁屏视图管理

KeyguardViewMediator 只管调度语义,真正管锁屏视图的是 StatusBarKeyguardViewManager。它在状态栏视图创建时被 registerStatusBar() 注册进来:

源码路径frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java

java 复制代码
public class StatusBarKeyguardViewManager implements RemoteInputController.Callback {
    protected PhoneStatusBar mPhoneStatusBar;
    private ViewGroup mContainer;                     // 宿主容器(NotificationPanelView 的一部分)
    private StatusBarWindowManager mStatusBarWindowManager;
    protected KeyguardBouncer mBouncer;
    protected boolean mShowing;

    public void registerStatusBar(PhoneStatusBar phoneStatusBar,
            ViewGroup container, StatusBarWindowManager statusBarWindowManager,
            ScrimController scrimController,
            FingerprintUnlockController fingerprintUnlockController) {
        mPhoneStatusBar = phoneStatusBar;
        mContainer = container;
        mStatusBarWindowManager = statusBarWindowManager;
        mScrimController = scrimController;
        mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                mViewMediatorCallback, mLockPatternUtils, mStatusBarWindowManager, container);
    }

    public void show(Bundle options) {
        mShowing = true;
        mStatusBarWindowManager.setKeyguardShowing(true);   // 通知窗口层切到锁屏
        mScrimController.abortKeyguardFadingOut();
        reset();                                            // 复位并展示 bouncer/通知锁屏
    }
}

关键设计 :锁屏不是独立窗口------bouncer 通过 createKeyguardBouncer() 生成后 inflate 进 mContainerNotificationPanelView 的子区域),KeyguardBottomAreaView 也是 NotificationPanelView 的子视图。show(Bundle) 先置 mShowing,再经 StatusBarWindowManager 通知窗口层切换锁屏态。

六、KeyguardUpdateMonitor------状态监听

KeyguardUpdateMonitor 是全局单例,聚合了锁屏关心的各类系统状态:设备是否完成开机引导、失败解锁次数、SIM 卡数据、锁屏可见性、屏幕是否点亮等。注意它的源码路径在 packages/Keyguard(包名 com.android.keyguard),不在 SystemUI 里。

源码路径frameworks/base/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java

java 复制代码
public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
    private static KeyguardUpdateMonitor sInstance;

    private boolean mDeviceProvisioned;                    // 设备是否完成开机引导
    private SparseIntArray mFailedAttempts = new SparseIntArray(); // 各用户失败解锁次数
    private HashMap<Integer, SimData> mSimDatas;           // 每个 subId 的 SIM 数据
    private boolean mKeyguardIsVisible;                    // 锁屏是否可见
    private boolean mScreenOn;                             // 屏幕是否点亮

    public static KeyguardUpdateMonitor getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new KeyguardUpdateMonitor(context);
        }
        return sInstance;
    }

    public void registerCallback(KeyguardUpdateMonitorCallback callback) {
        // 以弱引用方式加入 mCallbacks,状态变化时回调通知
    }

    public boolean isSimPinSecure() {
        // 依据 mSimDatas 判断是否要求 SIM PIN 解锁
    }

    public int getFailedUnlockAttempts(int userId) {
        return mFailedAttempts.get(userId, 0);
    }

    public void clearFailedUnlockAttempts() {
        mFailedAttempts.delete(sCurrentUser);
    }
}

关键设计KeyguardUpdateMonitor 只负责状态聚合与回调分发,getInstance() 是全局单例;它没有 isSecure() / getSecurityMode()(安全类型判断在 KeyguardSecurityModel,见 4.2),状态变更通过 registerCallback(KeyguardUpdateMonitorCallback) 弱引用订阅。

七、锁屏通知

锁屏状态下,通知可以显示在锁屏界面上,但每条通知按自身的 visibility 字段决定显示程度。这个字段是 Notification 上的真实常量:

通知敏感度 锁屏行为
VISIBILITY_PUBLIC 完整显示通知内容
VISIBILITY_PRIVATE 仅显示"内容已隐藏"提示
VISIBILITY_SECRET 完全不显示

关键设计 :锁屏可见性由 Notification.visibility 决定,VISIBILITY_PUBLIC 才能展示完整内容,PRIVATE 只露一条遮罩提示,SECRET 则在锁屏上彻底不出现------这条策略与是否锁屏无关,取决于应用声明的可见级别。

八、锁屏底部区域

锁屏底部(KeyguardBottomAreaView)提供紧急呼叫和相机快捷入口。它的初始化在 onFinishInflate() 里完成(不是 onCreate),两个按钮分别是 EmergencyButtonKeyguardAffordanceView,点击统一走 onClick

源码路径frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java

java 复制代码
public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
        UnlockMethodCache.OnUnlockMethodChangedListener,
        AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {

    private static final Intent SECURE_CAMERA_INTENT =
            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
                    .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    public static final Intent INSECURE_CAMERA_INTENT =
            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
    private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);

    private EmergencyButton mEmergencyButton;      // 紧急呼叫
    private KeyguardAffordanceView mCameraImageView; // 相机快捷入口

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mEmergencyButton = (EmergencyButton) findViewById(R.id.emergency_call_button);
        mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
        // ...
        mCameraImageView.setOnClickListener(this);
    }
}

相机用 INSECURE_CAMERA_INTENTMediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA),锁定时走 SECURE_CAMERA_INTENT;紧急呼叫则由 EmergencyButton 内部处理,其拨号 action 是 com.android.phone.EmergencyDialer.DIAL,而不是 Intent.ACTION_DIAL_EMERGENCY

关键设计 :底部区域用 onFinishInflate() 而非 onCreate 初始化;紧急呼叫按钮是 EmergencyButton(自带 com.android.phone.EmergencyDialer.DIAL 拨号逻辑),相机按钮是 KeyguardAffordanceView,二者都实现 View.OnClickListener 统一分发点击。

九、定制开发提示

9.1 移除锁屏

KeyguardViewMediator.setShowingLocked(boolean) 中把 mShowing 恒置 false,可让调度层认为锁屏永远不显示(注意这只是关闭锁屏显示,安全策略仍由 LockPatternUtils 管理)。

9.2 自定义锁屏壁纸

NotificationPanelView 的锁屏布局中添加自定义 ImageView。

9.3 修改锁屏通知显示策略

锁屏可见性由 Notification.visibility 决定(VISIBILITY_PUBLIC/PRIVATE/SECRET,见第七节);要强制显示需改通知过滤逻辑,而非简单覆盖一个 shouldShowOnKeyguard

9.4 禁用相机快捷入口

KeyguardBottomAreaView 中把相机视图 mCameraImageViewKeyguardAffordanceView)设为 GONE 即可隐藏快捷入口。

9.5 常见踩坑点

  • 锁屏无法显示 :检查 KeyguardViewMediator.start() 是否正确 putComponent 注册
  • 安全锁屏界面空白 :确认 KeyguardBouncer inflate 的宿主视图(R.layout.keyguard_bouncer)存在
  • 解锁动画卡顿keyguardDone() 经 handler 切窗口,确保在 UI 线程执行
  • 锁屏通知不显示 :检查通知的 visibility 属性

十、小结

本文深入分析了 Keyguard 锁屏模块的完整实现:

  1. KeyguardViewMediator 是锁屏调度核心,管理锁屏显示/隐藏的完整生命周期
  2. KeyguardUpdateMonitor (在 packages/Keyguard)聚合 SIM 卡、屏幕、失败解锁次数等系统状态
  3. StatusBarKeyguardViewManager 管理锁屏视图------锁屏不是独立窗口,而是嵌入在 NotificationPanelView
  4. KeyguardBouncer + KeyguardSecurityModelSecurityMode)处理安全锁屏认证(PIN/图案/密码)
  5. KeyguardBottomAreaView 提供紧急呼叫和相机快捷入口

锁屏与状态栏的紧密协作是 SystemUI 中最复杂的交互场景之一。下一篇(本系列最后一篇)将分析 Recents 最近任务、VolumeUI、PowerUI 等子系统,并给出综合定制实战建议。


下一篇:AOSP7 SystemUI 源码解析(八):Recents 最近任务与其他子系统 + 定制实战

相关推荐
致远ccc3 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼6 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone7 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen7 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone7 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui
2501_916007477 小时前
苹果商店iOS应用上架费用全面解析:开发者计划与审核费用计算
android·ios·小程序·https·uni-app·iphone·webview
Android打工仔7 小时前
Kotlin 协程源码解析:协程是如何切换线程的?
android·kotlin