前面幾篇文章大體介紹了SystemUI的兩個模塊,StatusBar和QuickSetting,這篇文章開始分析Keyguard模塊。java
對於鎖屏呢,須要有個基本認知,它分爲兩類,一是滑動鎖屏,一是安全鎖屏。滑動鎖屏是指經過手指滑動便可解鎖的鎖屏,安全鎖屏是指密碼鎖,圖案鎖,PIN碼鎖等等。這兩種鎖屏是在不一樣的地方建立的,不可一律而論,而本文只分析滑動鎖屏。android
根據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>
複製代碼
能夠看到,滑動鎖屏的各個部分比較分散,並非在同一容器中集中建立的。app
通常咱們經過電源鍵的開關來鎖屏的,本文來分析下,從開機到滑動鎖屏顯示的過程。ide
在開機的過程當中,當ActivityManagerService啓動完畢後,會建立SystemUIoop
// 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 已經啓動post
// 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()
這個方法的名字起的不是很直觀,它作了不少事,主要有
加載安全鎖視圖的過程就不分析了,很簡單,就是加載佈局,而後添加到 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 窒息。