一、前言
前面几篇文章大致介绍了SystemUI的两个模块,StatusBar和QuickSetting,这篇文章开始分析Keyguard模块。
对于锁屏呢,需要有个基本认知,它分为两类,一是滑动锁屏,一是安全锁屏。滑动锁屏是指通过手指滑动即可解锁的锁屏,安全锁屏是指密码锁,图案锁,PIN码锁等等。这两种锁屏是在不同的地方创建的,不可一概而论,而本文只分析滑动锁屏。
https://blog.csdn.net/omnispace/article/details/78566838
https://juejin.cn/user/2805609401693943/posts
二、滑动锁屏视图
1、根据SystemUI之StatusBar创建可知,整个SystemUI视图是由super_status_bar.xml创建的
<!--根布局继承自FrameLayout的StatusBarWindowView-->
<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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
sysui:ignoreRightInset="true"
>
<ImageView android:id="@+id/backdrop_back"
android:layout_width="match_parent"
android:scaleType="centerCrop"
android:layout_height="match_parent" />
<ImageView android:id="@+id/backdrop_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:visibility="invisible" />
</com.android.systemui.statusbar.BackDropView>
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"/>
状态栏容器
<FrameLayout
android:id="@+id/status_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
车载模式的布局
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
android:layout="@layout/car_fullscreen_user_switcher"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
整个下拉通知面版,包括滑动锁屏界面
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
这里亮度调节bar
<include layout="@layout/brightness_mirror" />
状态栏下拉后,背景,半透明灰色
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_in_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"/>
</com.android.systemui.statusbar.phone.StatusBarWindowView>
2、在这个布局中,include了一个status_bar_expanded.xml布局,这是整个下拉通知面版,包括滑动锁屏的各种控件,来看看这个布局:
<com.android.systemui.statusbar.phone.NotificationPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/notification_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent" >
滑动锁屏界面状态视图: 时间,日期
<include
layout="@layout/keyguard_status_view"
android:visibility="gone" />
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:id="@+id/notification_container_parent"
android:clipToPadding="false"
android:clipChildren="false">
QS界面
<FrameLayout
android:id="@+id/qs_frame"
android:layout="@layout/qs_panel"
android:layout_width="@dimen/qs_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:clipToPadding="false"
android:clipChildren="false"
systemui:viewType="com.android.systemui.plugins.qs.QS" />
显示通知的容器
<com.android.systemui.statusbar.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
android:layout_marginTop="@dimen/notification_panel_margin_top"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:layout_marginBottom="@dimen/close_handle_underlap" />
<include layout="@layout/ambient_indication"
android:id="@+id/ambient_indication_container" />
<ViewStub
android:id="@+id/keyguard_user_switcher"
android:layout="@layout/keyguard_user_switcher"
android:layout_height="match_parent"
android:layout_width="match_parent" />
滑动锁屏状态栏
<include
layout="@layout/keyguard_status_bar"
android:visibility="invisible" />
<Button
android:id="@+id/report_rejected_touch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
android:text="@string/report_rejected_touch"
android:visibility="gone" />
</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"
android:layout_height="96dp"
android:layout_width="match_parent"
android:layout_gravity="bottom"
android:visibility="invisible"
android:background="@drawable/qs_navbar_scrim" />
</com.android.systemui.statusbar.phone.NotificationPanelView>
可以看到,滑动锁屏的各个部分比较分散,并不是在同一容器中集中创建的。
三、滑动锁屏的显示
1、一般我们通过电源键的开关来锁屏的,本文来分析下,从开机到滑动锁屏显示的过程。
在开机的过程中,当ActivityManagerService启动完毕后,会创建SystemUI
frameworks/base/services/java/com/android/server/SystemServer.java
static final void startSystemUi(Context context, WindowManagerService windowManager) {
//启动SystemUIService
Intent intent = new Intent();
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();
}
2、前面的文章已经分析了SystemUI的启动,这个过程创建了整个SystemUI的视图,包括滑动锁屏的视图。现在来看看WindowManagerService在SystemUI启动后,做了什么
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public void onSystemUiStarted() {
mPolicy.onSystemUiStarted();
}
3、Window Manager 通知了 PhoneWindowManager , SystemUI 已经启动。
public class PhoneWindowManager implements WindowManagerPolicy {
...
private void bindKeyguard() {
synchronized (mLock) {
if (mKeyguardBound) {
return;
}
mKeyguardBound = true;
}
mKeyguardDelegate.bindService(mContext);
}
@Override
public void onSystemUiStarted() {
//绑定锁屏服务KeyguardService
bindKeyguard();
}
...
}
4、策略类 PhoneWindowManager 通过一个代理类 KeyguardServiceDelegate 来绑定了 KeyguardService。
KeyguardService 是一个标准的 Service,在绑定它的时候会返回一个 IBinder 对象,也就是服务端接口。我们在后面直接称 KeyguardService 为锁屏服务端。
锁屏组件所对应的服务名称:
<string name="config_keyguardComponent" translatable="false">com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
public class KeyguardServiceDelegate {
...
public void bindService(Context context) {
Intent intent = new Intent();
final Resources resources = context.getApplicationContext().getResources();
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)) {
Log.v(TAG, "*** Keyguard: can't bind to " + keyguardComponent);
mKeyguardState.showing = false;
mKeyguardState.showingAndNotOccluded = false;
mKeyguardState.secure = false;
synchronized (mKeyguardState) {
mKeyguardState.deviceHasKeyguard = false;
}
} else {
if (DEBUG) Log.v(TAG, "*** Keyguard started");
}
}
...
}
5、这就是一个标准的绑定 Service 流程,通过绑定时传入的参数 mKeyguardConnection 可以查看成功绑定后的操作,而ServiceConnection 是服务绑定成功后返回的对象。
public class KeyguardServiceDelegate {
...
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) {
// If the system is ready, it means keyguard crashed and restarted.
mKeyguardService.onSystemReady();
}
...
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) Log.v(TAG, "*** Keyguard disconnected (boo!)");
mKeyguardService = null;
mKeyguardState.reset();
mHandler.post(() -> {
try {
// There are no longer any keyguard windows on secondary displays, so pass
// INVALID_DISPLAY. All that means is that showWhenLocked activities on
// secondary displays now get to show.
ActivityManager.getService().setLockScreenShown(true /* keyguardShowing */,
false /* aodShowing */, INVALID_DISPLAY);
} catch (RemoteException e) {
// Local call.
}
});
}
};
...
}
6、通过查询各种状态,然后按顺序向锁屏服务端发送指令。这里我们只分析服务端的onSystemReady()的实现
代理类 KeyguardServiceDelegate 内部通过 KeyguardState 对象保存锁屏的状态,从而控制 KeyguardService 的行为。
private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {
...
@Override // Binder interface
public void onSystemReady() {
// SYSTEM 用户或者有 android.Manifest.permission.CONTROL_KEYGUARD 权限
checkPermission();
mKeyguardViewMediator.onSystemReady();
}
...
};
7、锁屏服务端又辗转通知了 KeyguardViewMediator,KeyguardViewMediator 向 Handler 发送了一个 SYSTEM_READY 事件来处理,最终会调用 handleSystemReady()
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
public class KeyguardViewMediator extends SystemUI {
...
public void onSystemReady() {
mHandler.obtainMessage(SYSTEM_READY).sendToTarget();
}
...
private Handler mHandler = new Handler(Looper.myLooper(), null, true) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case SYSTEM_READY:
handleSystemReady();
break;
...
}
}
}
- KeyguardViewMediator是典型的中介者模式的应用,它综合了各方面的信息来控制锁屏。
- KeyguardViewMediator有一个与主线程 Looper 关联的 Handlder,事件和处理都是通过这个 Handler,因此保证了事件处理的有序性,这样就不会导致界面刷新混乱。
private void handleSystemReady() {
synchronized (this) {
mSystemReady = true;
// 开启锁屏, 这里参数为 null
doKeyguardLocked(null);
// KeyguardUpdateMonitor 通过监听数据库Uri, 注册广播接收器,向各种服务注册监听,从而获取到与锁屏有关的更新
// KeyguardViewMediator 这个中介者关心的回调如下
// 1. onClockVisibilityChanged
// 2. onDeviceProvisioned
// 3. onSimStateChanged
// 4. onBiometricAuthFailed, onBiometricAuthenticated
// 5. onHasLockscreenWallpaperChanged
mUpdateMonitor.registerCallback(mUpdateCallback);
}
maybeSendUserPresentBroadcast();
}
8、doKeyguardLocked方法又会调用showLocked方法,showLocked方法通过Handler发送SHOW类型的消息,最终调用handleShow方法,handleShow方法会继续调用KeyguardDisplayManager的show方法。
private void doKeyguardLocked(Bundle options) {
...
showLocked(options);
}
...
//显示锁屏
private void showLocked(Bundle options) {
...
mShowKeyguardWakeLock.acquire();
Message msg = mHandler.obtainMessage(SHOW, options);
mHandler.sendMessage(msg);
}
...
//处理消息的Handler
private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW:
//处理显示锁屏的消息
handleShow((Bundle) msg.obj);
break;
...
}
}
private void handleShow(Bundle options) {
...
//继续调用KeyguardDisplayManager的show方法
mKeyguardDisplayManager.show();
}
9、KeyguardDisplayManager的show方法又会继续调用updateDisplays方法,
public class KeyguardDisplayManager {
...
public void show() {
if (!mShowing) {
if (DEBUG) Slog.v(TAG, "show");
mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
updateDisplays(true);
}
mShowing = true;
}
...
protected void updateDisplays(boolean showing) {
Presentation originalPresentation = mPresentation;
if (showing) {
MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(
MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
boolean useDisplay = route != null
&& route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
Display presentationDisplay = useDisplay ? route.getPresentationDisplay() : null;
if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
if (DEBUG) Slog.v(TAG, "Display gone: " + mPresentation.getDisplay());
mPresentation.dismiss();
mPresentation = null;
}
if (mPresentation == null && presentationDisplay != null) {
if (DEBUG) Slog.i(TAG, "Keyguard enabled on display: " + presentationDisplay);
mPresentation = new KeyguardPresentation(mContext, presentationDisplay,
R.style.keyguard_presentation_theme);
mPresentation.setOnDismissListener(mOnDismissListener);
try {
mPresentation.show();
} catch (WindowManager.InvalidDisplayException ex) {
Slog.w(TAG, "Invalid display:", ex);
mPresentation = null;
}
}
} else {
if (mPresentation != null) {
mPresentation.dismiss();
mPresentation = null;
}
}
// mPresentation is only updated when the display changes
if (mPresentation != originalPresentation) {
final int displayId = mPresentation != null
? mPresentation.getDisplay().getDisplayId() : INVALID_DISPLAY;
mCallback.onSecondaryDisplayShowingChanged(displayId);
}
}
...
}