前面几篇文章大致介绍了SystemUI的两个模块,StatusBar和QuickSetting,这篇文章开始分析Keyguard模块。
对于锁屏呢,需要有个基本认知,它分为两类,一是滑动锁屏,一是安全锁屏。滑动锁屏是指通过手指滑动即可解锁的锁屏,安全锁屏是指密码锁,图案锁,PIN码锁等等。这两种锁屏是在不同的地方创建的,不可一概而论,而本文只分析滑动锁屏。
滑动锁屏视图
根据SystemUI之StatusBar创建可知,整个SystemUI视图是由super_status_bar.xml创建的
<!--根布局FrameLayout-->
<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.android.systemui.statusbar.BackDropView
android:id="@+id/backdrop">
<ImageView android:id="@+id/backdrop_back" />
<ImageView android:id="@+id/backdrop_front"/>
</com.android.systemui.statusbar.BackDropView>
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_behind"
/>
<!--状态栏容器-->
<FrameLayout
android:id="@+id/status_bar_container" />
<!--整个下拉通知面版,包括滑动锁屏界面-->
<include layout="@layout/status_bar_expanded" />
<!--这里亮度调节bar-->
<include layout="@layout/brightness_mirror" />
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_in_front"/>
<!--在锁屏界面,显示在状态栏之下,显示在时间之上-->
<LinearLayout
android:id="@+id/lock_icon_container">
<com.android.systemui.statusbar.phone.LockIcon
android:id="@+id/lock_icon" />
<com.android.keyguard.KeyguardMessageArea
android:id="@+id/keyguard_message_area" />
</LinearLayout>
</com.android.systemui.statusbar.phone.StatusBarWindowView>
在这个布局中,ID为lock_icon_container的布局是在锁屏界面上显示锁图标和一些信息的。这个布局include了一个status_bar_expanded.xml布局,这是整个下拉通知面版,包括滑动锁屏的各种控件,来看看这个布局
<com.android.systemui.statusbar.phone.NotificationPanelView
android:id="@+id/notification_panel" >
<FrameLayout
android:id="@+id/big_clock_container" />
<!--滑动锁屏界面状态视图: 时间,日期-->
<include
layout="@layout/keyguard_status_view"
android:visibility="gone" />
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
android:id="@+id/notification_container_parent">
<include layout="@layout/dock_info_overlay" />
<!--QS界面-->
<FrameLayout
android:id="@+id/qs_frame"
android:layout="@layout/qs_panel" />
<!--显示通知的容器-->
<com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"/>
<include layout="@layout/ambient_indication"
android:id="@+id/ambient_indication_container" />
<ViewStub
android:id="@+id/keyguard_user_switcher" />
<!--滑动锁屏状态栏-->
<include
layout="@layout/keyguard_status_bar"
android:visibility="invisible" />
<Button
android:id="@+id/report_rejected_touch" />
</com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
<!--这里锁屏底部图标, 填充整个父布局,例如左下角的图标,右下角的图标Camera-->
<include
layout="@layout/keyguard_bottom_area"
android:visibility="gone" />
<com.android.systemui.statusbar.AlphaOptimizedView
android:id="@+id/qs_navbar_scrim"/>
</com.android.systemui.statusbar.phone.NotificationPanelView>
可以看到,滑动锁屏的各个部分比较分散,并不是在同一容器中集中创建的。
滑动锁屏的显示
一般我们通过电源键的开关来锁屏的,本文来分析下,从开机到滑动锁屏显示的过程。
在开机的过程中,当ActivityManagerService启动完毕后,会创建SystemUI
// frameworks/base/services/java/com/android/server/SystemServer.java
private static void startSystemUi(Context context, WindowManagerService windowManager) {
Intent intent = new Intent();
// 1. 启动 SystemUIService
intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
// 2. 通知WMS,SystemUI已经启动
windowManager.onSystemUiStarted();
}
前面的文章已经分析了SystemUI的启动,这个过程创建了整个SystemUI的视图,包括滑动锁屏的视图。现在来看看WindowManagerService在SystemUI启动后,做了什么
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public void onSystemUiStarted() {
mPolicy.onSystemUiStarted();
}
Window Manager 通知了 PhoneWindowManager , SystemUI 已经启动
// frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
public void onSystemUiStarted() {
// 绑定 KeyguardService
bindKeyguard();
}
private void bindKeyguard() {
synchronized (mLock) {
if (mKeyguardBound) {
return;
}
mKeyguardBound = true;
}
// 绑定 KeyguardService 的操作交给了代理类 KeyguardServiceDelegate
mKeyguardDelegate.bindService(mContext);
}
策略类 PhoneWindowManager 通过一个代理类 KeyguardServiceDelegate 来绑定了 KeyguardService。
KeyguardService 是一个标准的 Service,在绑定它的时候会返回一个 IBinder 对象,也就是服务端接口。我们在后面直接称 KeyguardService 为锁屏服务端。
// frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
public void bindService(Context context) {
Intent intent = new Intent();
final Resources resources = context.getApplicationContext().getResources();
// KeyguardService
final ComponentName keyguardComponent = ComponentName.unflattenFromString(
resources.getString(com.android.internal.R.string.config_keyguardComponent));
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
intent.setComponent(keyguardComponent);
if (!context.bindServiceAsUser(intent, mKeyguardConnection,
Context.BIND_AUTO_CREATE, mHandler, UserHandle.SYSTEM)) {
// ...
}
}
这就是一个标准的绑定 Service 流程,通过绑定时传入的参数 mKeyguardConnection 可以查看成功绑定后的操作
// frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mKeyguardService = new KeyguardServiceWrapper(mContext,
IKeyguardService.Stub.asInterface(service), mCallback);
if (mKeyguardState.systemIsReady) {
mKeyguardService.onSystemReady();
if (mKeyguardState.currentUser != UserHandle.USER_NULL) {
mKeyguardService.setCurrentUser(mKeyguardState.currentUser);
}
if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE
|| mKeyguardState.interactiveState == INTERACTIVE_STATE_WAKING) {
mKeyguardService.onStartedWakingUp();
}
if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) {
mKeyguardService.onFinishedWakingUp();
}
if (mKeyguardState.screenState == SCREEN_STATE_ON
|| mKeyguardState.screenState == SCREEN_STATE_TURNING_ON) {
mKeyguardService.onScreenTurningOn(
new KeyguardShowDelegate(mDrawnListenerWhenConnect));
}
if (mKeyguardState.screenState == SCREEN_STATE_ON) {
mKeyguardService.onScreenTurnedOn();
}
mDrawnListenerWhenConnect = null;
}
if (mKeyguardState.bootCompleted) {
mKeyguardService.onBootCompleted();
}
if (mKeyguardState.occluded) {
mKeyguardService.setOccluded(mKeyguardState.occluded, false /* animate */);
}
if (!mKeyguardState.enabled) {
mKeyguardService.setKeyguardEnabled(mKeyguardState.enabled);
}
}
}
通过查询各种状态,然后按顺序向锁屏服务端发送指令。而我只分析服务端的onSystemReady()的实现
代理类 KeyguardServiceDelegate 内部通过 KeyguardState 对象保存锁屏的状态,从而控制 KeyguardService 的行为。
// frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {
public void onSystemReady() {
// SYSTEM 用户或者有 android.Manifest.permission.CONTROL_KEYGUARD 权限
checkPermission();
mKeyguardViewMediator.onSystemReady();
}
}
锁屏服务端又辗转通知了 KeyguardViewMediator,KeyguardViewMediator 向 Handler 发送了一个 SYSTEM_READY 事件来处理,最终会调用 handleSystemReady()
- KeyguardViewMediator 是典型的中介者模式的应用,它综合了各方面的信息来控制锁屏。
- KeyguardViewMediator 有一个与主线程 Looper 关联的 Handlder,事件和处理都是通过这个 Handler,因此保证了事件处理的有序性,这样就不会导致界面刷新混乱。
// frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
private void handleSystemReady() {
synchronized (this) {
if (DEBUG) Log.d(TAG, "onSystemReady");
mSystemReady = true;
// 开启锁屏, 这里参数为 null
doKeyguardLocked(null);
// KeyguardUpdateMonitor 通过监听数据库Uri, 注册广播接收器,向各种服务注册监听,从而获取到与锁屏有关的更新
// KeyguardViewMediator 这个中介者关心的回调如下
// 1. onClockVisibilityChanged
// 2. onDeviceProvisioned
// 3. onSimStateChanged
// 4. onBiometricAuthFailed, onBiometricAuthenticated
// 5. onHasLockscreenWallpaperChanged
mUpdateMonitor.registerCallback(mUpdateCallback);
}
// Most services aren't available until the system reaches the ready state, so we
// send it here when the device first boots.
maybeSendUserPresentBroadcast();
}
KeyguardMediator 通过 doKeyguardLocked() 来开启锁屏,这里也是通过主线程的 Handler 来处理的,最终调用 handleShow()
// frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
private void handleShow(Bundle options) {
// ...
synchronized (KeyguardViewMediator.this) {
if (!mSystemReady) {
return;
}
// 通知监听都,锁屏状态改变了
setShowingLocked(true, mAodShowing);
// 显示锁屏屏界面
mStatusBarKeyguardViewManager.show(options);
mHiding = false;
mWakeAndUnlocking = false;
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
// 通过StatusBarManager调整状态栏
adjustStatusBarLocked();
userActivity();
// KeyguardUpdateMonitor 保存锁屏状态
mUpdateMonitor.setKeyguardGoingAway(false /* away */);
mShowKeyguardWakeLock.release();
}
// 更新显示什么
mKeyguardDisplayManager.show();
}
锁屏的中介者 KeyguardViewMediator 最终把显示锁屏这个任务交给了 StatusBarKeyguardViewManager。
StatusBarKeyguardViewManager 管理锁屏的创建,显示,隐藏,重置。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
public void show(Bundle options) {
mShowing = true;
// 更新锁屏状态
mStatusBarWindowController.setKeyguardShowing(true);
// 通知各种关心锁屏状态的组件,锁屏状态改变了
mKeyguardMonitor.notifyKeyguardState(
mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());
// 重置锁屏界面。参数为true,就表示当显示滑动锁屏时,隐藏安全锁界面
reset(true /* hideBouncerWhenShowing */);
}
KeyguardMonitorImpl 介绍
- 它是一个单例。
- 它接收注册回调。
- 它利用了 KeyguardUpdateMonitor 只监听了 onTrustedChanged() 回调,并且在事件发生时,会调用各种已经注册的回调。
- 它暴露了一些 public 方法,通过这些方法设置锁屏状态,然后通过已经注册的回调。
- 它的作用就是供 SystemUI 其它组件获取最新的锁屏状态。
锁屏管理者 StatusBarKeyguardViewManager 先通知各路人马,锁屏状态改变了,然后重置了锁屏界面
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
public void reset(boolean hideBouncerWhenShowing) {
if (mShowing) {
if (mOccluded && !mDozing) {
// ...
} else {
// 参数为true, 表示隐藏安全锁界面,显示滑动锁界面
showBouncerOrKeyguard(hideBouncerWhenShowing);
}
// 调用 KeyguardUpdateMonitor#handleKeyguardReset()
// 更新指纹解锁,面部解锁状态
KeyguardUpdateMonitor.getInstance(mContext).sendKeyguardReset();
updateStates();
}
}
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
/**
* 注意: needsFullscreenBouncer() 会加载安全锁视图,并添加到SystemUI的根视图中
* 但是只有显示SIM PIN/PUK锁时,这个方法才会返回true, 表示需要全屏显示
*/
if (mBouncer.needsFullscreenBouncer() && !mDozing) {
// ...
} else {
// 显示滑动锁屏
mStatusBar.showKeyguard();
// 隐藏安全锁
if (hideBouncerWhenShowing) {
hideBouncer(shouldDestroyViewOnReset() /* destroyView */);
mBouncer.prepare();
}
}
// 更新各方状态
updateStates();
}
reset() 这个方法的名字起的不是很直观,它做了很多事,主要有
- 通过 KeyguardBouncer#needsFullScreenBouncer() 加载了安全锁视图到 SystemUI 根视图中。
- 决定滑动锁,安全锁的显示或隐藏。
- 通知各方,锁屏状态改变了。
加载安全锁视图的过程就不分析了,很简单,就是加载布局,然后添加到 SystemUI 根视图中。
本该只关心滑动锁,从代码可以看到,锁屏管理者 StatusBarKeyguardViewManager 把显示滑动锁的任务交给了 StatusBar。
从SystemUI之StatusBar创建可知,StatusBar 创建并管理了整个 SystemUI 视图,因此最终由它来显示滑动锁是顺理成章的。
StatusBar#showKeyguard() 的调用链很长,这里就不展示代码了,直接给出调用链。
showKeyguard() -> updateIsKeyguard() -> showKeyguardImpl() -> updatePanelExpansionForKeyguard() -> instantExpandNotificationsPanel()
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
public void showKeyguard() {
// 保存状态
mStatusBarStateController.setKeyguardRequested(true);
mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
mPendingRemoteInputView = null;
// 更新滑动锁屏,最终调用了 instantExpandNotificationsPanel()
updateIsKeyguard();
mAssistManager.onLockscreenShown();
}
@Override
public void instantExpandNotificationsPanel() {
// Make our window larger and the panel expanded.
// 设置通知面版可见
makeExpandedVisible(true);
// 展开,参数为false表示不需要动画 
mNotificationPanel.expand(false /* animate */);
mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
}
NotificationPanelView mNotificationPanel是整个下拉通知面版的父容器,在文章开头,通过查看布局可以发现,滑动锁屏的大部分控件都是在下拉通知面版里,因此这里的操作就不奇怪了。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@Override
public void expand(boolean animate) {
// 通过父类PanelView实现展开操作,并最终调用 requestLayout() 重新布局
super.expand(animate);
// 让各种控件监听它感兴趣的事情,例如 KeyguardStatusBarView 监听电池事件
setListening(true);
}
这里就属于Android控件的各种操作了,这里就不展开了,最终它会调用 requestLayout() 重新布局,因此它会调用 onLayout() 来更新布局
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// ...
// 显示 Clock 和 Notification
positionClockAndNotifications();
// ...
// 这里更新了锁屏大的时钟,以及底部图标区域
updateExpandedHeight(getExpandedHeight());
// 显示时间,日期的状态视图
updateHeader();
// ...
}
可以看到各种锁屏的控件显示出来了。
结束
从系统启动到显示滑动锁屏,这个过程其实并不复杂,有了这个基础,就可以分析安全锁界面的显示过程了。
另外,我们应该对电源键控制锁屏的显示与隐藏很感兴趣,其实本文也给出了实现,例如点亮屏幕显示锁屏的过程是
DisplayManagerService -> DisplayPowerController -> PhoneWindowManager#screenTurnedOff() -> KeyguardServiceDelegate#onScreenTurnedOff() -> KeyguardService#onScreenTurnedOff()。
SystemUI 还有很多模块,下篇挑个简单点的吧,就分析 VolumeUI 吧,让我们继续为 SystemUI 窒息。