SystemUI之滑動鎖屏的建立

前面幾篇文章大體介紹了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()

  1. KeyguardViewMediator 是典型的中介者模式的應用,它綜合了各方面的信息來控制鎖屏。
  2. 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 介紹

  1. 它是一個單例。
  2. 它接收註冊回調。
  3. 它利用了 KeyguardUpdateMonitor 只監聽了 onTrustedChanged() 回調,而且在事件發生時,會調用各類已經註冊的回調。
  4. 它暴露了一些 public 方法,經過這些方法設置鎖屏狀態,而後經過已經註冊的回調。
  5. 它的做用就是供 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() 這個方法的名字起的不是很直觀,它作了不少事,主要有

  1. 經過 KeyguardBouncer#needsFullScreenBouncer() 加載了安全鎖視圖到 SystemUI 根視圖中。
  2. 決定滑動鎖,安全鎖的顯示或隱藏。
  3. 通知各方,鎖屏狀態改變了。

加載安全鎖視圖的過程就不分析了,很簡單,就是加載佈局,而後添加到 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表示不須要動畫&emsp;
        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 窒息。

相關文章
相關標籤/搜索