2025年AndroidQ SystemUI之锁屏加载(下)密码锁屏

AndroidQ SystemUI之锁屏加载(下)密码锁屏本篇文章接着上一篇 上一篇主要分析了滑动锁屏加载流程 这篇我们分析密码锁屏也叫 Bouncer 的加载 看看系统如何加载不同密码锁屏以及如何动态添加的 我们知道密码锁屏有三种 pin password pattern 还有两种 SIM 卡的 bouncer SIM PIN SIM PUK

大家好,我是讯享网,很高兴认识大家。

本篇文章接着上一篇,上一篇主要分析了滑动锁屏加载流程,这篇我们分析密码锁屏也叫Bouncer的加载,看看系统如何加载不同密码锁屏以及如何动态添加的

我们知道密码锁屏有三种,pin/password/pattern,还有两种SIM卡的bouncer,SIM PIN/SIM PUK,要显示密码锁屏,首先需要上滑滑动锁屏,我们就以上滑滑动锁屏为入口分析密码锁屏的出现

通过上一篇文章我们知道滑动锁屏是一个id为notification_panel的自定义ViewGroup,对应类NotificationPanelView,继承PanelView,我们需要找到滑动锁屏的TouchEvent事件分发,当用户在滑动锁屏上滑的速度和距离满足一定条件之后,滑动锁屏就会解锁,这时候如果有密码锁屏就会显示,没有就直接解锁,我们首先来看看PanelView的isFalseTouch方法,这个方法定义了当前是否为误触的条件,isFalseTouch返回true代表是误触

private boolean isFalseTouch(float x, float y) { 
    if (!mStatusBar.isFalsingThresholdNeeded()) { 
    return false; } if (mFalsingManager.isClassiferEnabled()) { 
    return mFalsingManager.isFalseTouch(); } if (!mTouchAboveFalsingThreshold) { 
    return true; } if (mUpwardsWhenTresholdReached) { 
    return false; } return !isDirectionUpwards(x, y); } 

讯享网

此方法有几个判断条件:

  1. isFalsingThresholdNeeded方法,此方法定义在StatusBar中,代码很简单"mStatusBarStateController.getState() == StatusBarState.KEYGUARD",返回当前是否是锁屏状态,肯定为true的
  2. 通过一系列算法来判断的,结合了P-sersor,也就是近距离传感器,
    实际应用就是口袋模式防误触,当手机顶部sersor被遮挡了,那不管怎么滑都会被判定为误触
  3. mTouchAboveFalsingThreshold这个条件判定的就是滑动阙值,后面onTouchEvent方法中再分析
  4. mUpwardsWhenTresholdReached判定的是滑动方向是否向上且与水平方向成45度角以上,如果是则mUpwardsWhenTresholdReached为true
  5. !isDirectionUpwards(x, y),其实mUpwardsWhenTresholdReached的值就是调用isDirectionUpwards的返回值,!isDirectionUpwards == !mUpwardsWhenTresholdReached

锁屏误触的条件我们已经清楚了,我们来看PanelView的onTouchEvent方法,主要看Move事件就行了

PanelView.onTouchEvent

讯享网 @Override public boolean onTouchEvent(MotionEvent event) { 
    ... switch (event.getActionMasked()) { 
    case MotionEvent.ACTION_DOWN: ... break; case MotionEvent.ACTION_MOVE: //滑动Y轴的距离 float h = y - mInitialTouchY; if (Math.abs(h) > mTouchSlop && (Math.abs(h) > Math.abs(x - mInitialTouchX) || mIgnoreXTouchSlop)) { 
    mTouchSlopExceeded = true; ... //设置mTracking为true,代表滑动锁屏开始... onTrackingStarted(); } ... //阙值判断,getFalsingThreshold获取 //R.dimen.unlock_falsing_threshold(80dp)的值,然后 //乘上一个系数,1或者1.5 if (-h >= getFalsingThreshold()) { 
    //阙值满足之后将mTouchAboveFalsingThreshold置为true mTouchAboveFalsingThreshold = true; //并且获取滑动方向和角度 mUpwardsWhenTresholdReached = isDirectionUpwards(x, y); } if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) { 
    //这里会传递一个newHeight最终给到NotificationPanelView //根据这个值,锁屏上的时钟,通知等透明度会变化 setExpandedHeightInternal(newHeight); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: addMovement(event); endMotionEvent(event, x, y, false /* forceCancel */); break; } } 

当再次满足一个阙值getFalsingThreshold()之后会将mTouchAboveFalsingThreshold置为true,并且得到当前滑动的方向和角度是否满足要求,这两个条件就是我们前面分析isFalseTouch中误触的条件

通过setExpandedHeightInternal,调用NotificationPanelView中的onHeightUpdated方法根据传递的newHeight的值更新锁屏上时钟,通知等的透明度,newHeight这个值开始为手机屏幕高度,随着手指上滑逐渐变小,最终变为0代表滑动锁屏完全退出

Move事件结束后在UP事件中调用了endMotionEvent方法

endMotionEvent

private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { 
    ...... //expand这个值是解锁的关键,false代表成功,true代表失败 boolean expand = flingExpands(vel, vectorVel, x, y) || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel; ...... fling(vel, expand, isFalseTouch(x, y)); onTrackingStopped(expand); ... } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking && !mStatusBar.isBouncerShowing() && !mKeyguardMonitor.isKeyguardFadingAway()) { 
    ... } } else if (!mStatusBar.isBouncerShowing()) { 
    ... } } 

先看看给expand赋值方法flingExpands

flingExpands

讯享网protected boolean flingExpands(float vel, float vectorVel, float x, float y) { 
    //如果解锁被禁用则返回true,这里不太清楚如何禁用 if (mFalsingManager.isUnlockingDisabled()) { 
    return true; } //是否为误触 if (isFalseTouch(x, y)) { 
    return true; } //对滑动速度的判断 if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 
    return getExpandedFraction() > 0.5f; } else { 
    return vel > 0; } } 

flingExpands主要对是否误触和滑动速度的判断,即使通过了误触检测,还会检测滑动的速度,是否误触我们前面已经分析过,这里的速度我自己测试了下,稍微快点的速度都能满足条件,所以flingExpands的返回结果就决定了是否能够滑动解锁,expand等于true代表为误触,为false代表成功滑动解锁

到这里我们可以总结滑动解锁的检测条件了:(1)对P-senor的近距离检测,(2)对滑动的阙值检测,(3)对滑动的方向和角度检测,(4)对滑动速度的检测

fling这个方法主要是对滑动锁屏的一些动画设置和处理,就不具体看了,我们接着分析onTrackingStopped方法,此方法代表手指滑动锁屏结束

因为子类NotificationPanelView覆盖了此方法,所以会调用NotificationPanelView的onTrackingStopped方法

NotificationPanelView.onTrackingStopped

@Override protected void onTrackingStopped(boolean expand) { 
    ... super.onTrackingStopped(expand); ... } 

子类NotificationPanelView中做了一些工作之后通过super再调用了父类的此方法

onTrackingStopped

讯享网protected void onTrackingStopped(boolean expand) { 
    mTracking = false; mBar.onTrackingStopped(expand); notifyBarPanelExpansionChanged(); } 

接着调用了PanelBar的onTrackingStopped方法,又会调用到PanelBar的子类PhoneStatusBarView覆盖的此方法

PhoneStatusBarView.onTrackingStopped

 @Override public void onTrackingStopped(boolean expand) { 
    super.onTrackingStopped(expand); mBar.onTrackingStopped(expand); } 

这里先通过super调用了父类的onTrackingStopped方法,父类中就一行代码” mTracking = false“,接着调到了StatusBar的onTrackingStopped方法

StatusBar.onTrackingStopped

讯享网public void onTrackingStopped(boolean expand) { 
    //当前设备状态为KEYGUARD或者SHADE_LOCKED if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { 
    //expand就是flingExpands返回值,一直传递过来,为false //有密码锁屏时canSkipBouncer为false,没有则为true if (!expand && !mUnlockMethodCache.canSkipBouncer()) { 
    showBouncer(false /* scrimmed */); } } } 

接着调用showBouncer

showBouncer

@Override public void showBouncer(boolean scrimmed) { 
    mStatusBarKeyguardViewManager.showBouncer(scrimmed); } 

StatusBarKeyguardViewManager.showBouncer

讯享网 public void showBouncer(boolean scrimmed) { 
    //Keyguard已经已经show并且Bouncer还没有show if (mShowing && !mBouncer.isShowing()) { 
    mBouncer.show(false /* resetSecuritySelection */, scrimmed); } //更新当前锁屏的各种状态值 updateStates(); } 

KeyguardBouncer.show

 public void show(boolean resetSecuritySelection, boolean isScrimmed) { 
    ... //ensureView我们前一篇文章分析过,里面会调用inflateView加载 //id为keyguard_bouncer和keyguard_host_view的bouncer布局文件 //并通过addView添加到SystemUI顶层View中 ensureView(); ... //resetSecuritySelection传递过来为false,代表不更新当前bouncer类型 //什么情况下会更新呢?插入带PIN或者PUK的sim卡时 if (resetSecuritySelection) { 
    showPrimarySecurityScreen(); } //如果当前bouncer已经可见或者mShowingSoon为true则return //mShowingSoon是个标志位,避免重复调用,代表即将显示bouncer if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) { 
    return; } final int activeUserId = KeyguardUpdateMonitor.getCurrentUser(); final boolean isSystemUser = UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM; final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId; //allowDismissKeyguard一般为true,mKeyguardView.dismiss //决定是否显示bouncer,显示哪种bouncer,返回true代表没有bouncer if (allowDismissKeyguard && mKeyguardView.dismiss(activeUserId)) { 
    return; } ... //标志位 mShowingSoon = true; DejankUtils.removeCallbacks(mResetRunnable); if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) { 
    mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY); } else { 
    DejankUtils.postAfterTraversal(mShowRunnable); } mCallback.onBouncerVisiblityChanged(true /* shown */); mExpansionCallback.onStartingToShow(); } 

我们先看看决定是否显示bouncer,显示何种类型bouncer(pin/password/pattern)的核心方法KeyguardHostView.dismiss


讯享网

KeyguardHostView.dismiss

讯享网 public boolean dismiss(int targetUserId) { 
    ... return dismiss(false, targetUserId); } @Override public boolean dismiss(boolean authenticated, int targetUserId) { 
    return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId); } 

mSecurityContainer是一个id为keyguard_security_container,类型为KeyguardSecurityContainer的自定义FrameLayout,被包含在keyguard_host_view.xml中,它是(pin/password/pattern)主要容器

KeyguardSecurityContainer.showNextSecurityScreenOrFinish

boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId) { 
    //finish作为此方法的返回值,代表了是否显示bouncer界面 boolean finish = false; boolean strongAuth = false; int eventSubtype = -1; //是否可以信任设备,google有一个smart lock的功能,用户设置可信任 //方式则可以跳过密码锁屏 if (mUpdateMonitor.getUserHasTrust(targetUserId)) { 
    finish = true; ... //如果使用了生物识别解锁也可以跳过密码锁屏 } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) { 
    finish = true; ... //如果当前的密码锁屏方式为None } else if (SecurityMode.None == mCurrentSecuritySelection) { 
    SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); if (SecurityMode.None == securityMode) { 
    finish = true; eventSubtype = BOUNCER_DISMISS_NONE_SECURITY; } else { 
    showSecurityScreen(securityMode); } } else if (authenticated) { 
   //authenticated传递过来为false switch (mCurrentSecuritySelection) { 
    case Pattern: case Password: case PIN: strongAuth = true; finish = true; eventSubtype = BOUNCER_DISMISS_PASSWORD; break; case SimPin: case SimPuk: SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled( KeyguardUpdateMonitor.getCurrentUser())) { 
    finish = true; eventSubtype = BOUNCER_DISMISS_SIM; } else { 
    showSecurityScreen(securityMode); } break; default: Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe"); showPrimarySecurityScreen(false); break; } } ... return finish; } 

通过手指上滑锁屏显示bouncer不会走此方法里的任何分支,finish为是否显示密码锁屏,true代表不显示,false代表显示,所以当我们设置了(pin/password/pattern)则finish不会进任何分支,直接返回false

那我们的锁屏类型是哪里获取的呢?

我们再回到KeyguardBouncer中的inflateView方法,再来深入分析一下此方法相关的代码

inflateView

讯享网protected void inflateView() { 
    ... mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null); mKeyguardView = mRoot.findViewById(R.id.keyguard_host_view); ... mContainer.addView(mRoot, mContainer.getChildCount()); ... } 
<com.android.keyguard.KeyguardHostView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/res-auto" android:id="@+id/keyguard_host_view" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent from this view when bouncer is shown --> <com.android.keyguard.KeyguardSecurityContainer android:id="@+id/keyguard_security_container" android:layout_width="wrap_content" android:layout_height="wrap_content" androidprv:layout_maxHeight="@dimen/keyguard_security_max_height" android:clipChildren="false" android:clipToPadding="false" android:padding="0dp" android:fitsSystemWindows="true" android:layout_gravity="center"> <com.android.keyguard.KeyguardSecurityViewFlipper android:id="@+id/view_flipper" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" android:paddingTop="@dimen/keyguard_security_view_top_margin" android:paddingStart="@dimen/keyguard_security_view_lateral_margin" android:paddingEnd="@dimen/keyguard_security_view_lateral_margin" android:gravity="center"> </com.android.keyguard.KeyguardSecurityViewFlipper> </com.android.keyguard.KeyguardSecurityContainer> </com.android.keyguard.KeyguardHostView> 

我们可以看到keyguard_host_view是KeyguardHostView的布局文件,所以我们来看看KeyguardHostView加载完成后做的事情

KeyguardHostView.onFinishInflate

讯享网 @Override protected void onFinishInflate() { 
    mSecurityContainer = findViewById(R.id.keyguard_security_container); ... mSecurityContainer.showPrimarySecurityScreen(false); } 

showPrimarySecurityScreen

void showPrimarySecurityScreen(boolean turningOff) { 
    //获取当前的bouncer类型 SecurityMode securityMode = mSecurityModel.getSecurityMode( KeyguardUpdateMonitor.getCurrentUser()); showSecurityScreen(securityMode); } 

mSecurityModel.getSecurityMode

讯享网SecurityMode getSecurityMode(int userId) { 
    KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); //如果当前有插入SIM卡,并且SIM卡的状态是PUK_REQUIRED //则bouncer类型为SimPuk if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId( monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) { 
    return SecurityMode.SimPuk; } //如果当前有插入SIM卡,并且SIM卡的状态是PIN_REQUIRED //则bouncer类型为SimPin if (SubscriptionManager.isValidSubscriptionId( monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) { 
    return SecurityMode.SimPin; } //否则就通过mLockPatternUtils获取当前的bouncer, //当Settings中设置好了bouncer类型,就会保存到mLockPatternUtils中 final int security = mLockPatternUtils.getActivePasswordQuality(userId); 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); } } 

上面代码就是获取当前的锁屏类型,包含如下的类型

public enum SecurityMode { 
    Invalid, // NULL state None, // No security enabled Pattern, // Unlock by drawing a pattern. Password, // Unlock by entering an alphanumeric password PIN, // Strictly numeric password SimPin, // Unlock by entering a sim pin. SimPuk // Unlock by entering a sim puk } 

bouncer类型有了之后调用showSecurityScreen方法,此方法就是为了更新当前的锁屏类型,以便需要显示的时候直接显示出来

showSecurityScreen

讯享网private void showSecurityScreen(SecurityMode securityMode) { 
    //如果bouncer的类型并没有发生变化,就不需要更新 if (securityMode == mCurrentSecuritySelection) return; //getSecurityView方法会根据的bouncer类型,得到具体的bouncer自定义View KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); KeyguardSecurityView newView = getSecurityView(securityMode); //待getSecurityView分析完之后再贴出后面代码分析 ...... } 

getSecurityView

private KeyguardSecurityView getSecurityView(SecurityMode securityMode) { 
    //根据bouncer类型获取具体的bouncer的控件id final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); KeyguardSecurityView view = null; //获取mSecurityViewFlipper中的bouncer个数,如果有的话 final int children = mSecurityViewFlipper.getChildCount(); for (int child = 0; child < children; child++) { 
    if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) { 
    //找到bouncer返回 view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child)); break; } } //根据bouncer类型获取具体的bouncer的布局文件id int layoutId = getLayoutIdFor(securityMode); //如果mSecurityViewFlipper还没有bouncer并且能够获取到bouncer的layout if (view == null && layoutId != 0) { 
    final LayoutInflater inflater = LayoutInflater.from(mContext); //通过layoutId加载具体的bouncer View View v = mInjectionInflationController.injectable(inflater) .inflate(layoutId, mSecurityViewFlipper, false); //并且将bouncer View添加到mSecurityViewFlipper中 mSecurityViewFlipper.addView(v); //给bouncer设置回调和mLockPatternUtils updateSecurityView(v); view = (KeyguardSecurityView)v; view.reset(); } return view; } 

我们先看下上面代码中获取bouncer控件id(getSecurityViewIdForMode)和bouncer布局文件id(getLayoutIdFor),我们发现这五种bouncer类型各自有自己的布局文件

讯享网 private int getSecurityViewIdForMode(SecurityMode securityMode) { 
    switch (securityMode) { 
    case Pattern: return R.id.keyguard_pattern_view; case PIN: return R.id.keyguard_pin_view; case Password: return R.id.keyguard_password_view; case SimPin: return R.id.keyguard_sim_pin_view; case SimPuk: return R.id.keyguard_sim_puk_view; } return 0; } @VisibleForTesting public int getLayoutIdFor(SecurityMode securityMode) { 
    switch (securityMode) { 
    case Pattern: return R.layout.keyguard_pattern_view; case PIN: return R.layout.keyguard_pin_view; case Password: return R.layout.keyguard_password_view; case SimPin: return R.layout.keyguard_sim_pin_view; case SimPuk: return R.layout.keyguard_sim_puk_view; default: return 0; } } 

好了我们总结一下getSecurityView方法:

  1. 根据当前bouncer类型获取bouncer控件id
  2. 遍历mSecurityViewFlipper,获取已经添加的bouncer
  3. 根据当前bouncer类型获取bouncer布局文件id
  4. 如果没有在mSecurityViewFlipper获取到bouncer,则通过LayoutInflater加载布局得到具体的bouncer View
  5. 最后将bouncer添加到mSecurityViewFlipper中

我们发现其实最终bouncer的直接父容器是KeyguardSecurityViewFlipper,我们再回头看看keyguard_host_view布局:
KeyguardSecurityViewFlipper是此布局最里面的容器,各种bouncer就是这样动态添加到了keyguard_host_view中

<com.android.keyguard.KeyguardHostView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/res-auto" android:id="@+id/keyguard_host_view" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent from this view when bouncer is shown --> <com.android.keyguard.KeyguardSecurityContainer android:id="@+id/keyguard_security_container" android:layout_width="wrap_content" android:layout_height="wrap_content" androidprv:layout_maxHeight="@dimen/keyguard_security_max_height" android:clipChildren="false" android:clipToPadding="false" android:padding="0dp" android:fitsSystemWindows="true" android:layout_gravity="center"> <com.android.keyguard.KeyguardSecurityViewFlipper android:id="@+id/view_flipper" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" android:paddingTop="@dimen/keyguard_security_view_top_margin" android:paddingStart="@dimen/keyguard_security_view_lateral_margin" android:paddingEnd="@dimen/keyguard_security_view_lateral_margin" android:gravity="center"> </com.android.keyguard.KeyguardSecurityViewFlipper> </com.android.keyguard.KeyguardSecurityContainer> </com.android.keyguard.KeyguardHostView> 

另外我们需要知道所有的bouncer都实现了KeyguardSecurityView接口,到此bouncer的获取和创建以及动态添加我们就分析完毕了

接着回到showSecurityScreen后半部分继续分析:

讯享网private void showSecurityScreen(SecurityMode securityMode) { 
    //如果bouncer的类型并没有发生变化,就不需要更新 if (securityMode == mCurrentSecuritySelection) return; //getSecurityView方法会根据的bouncer类型,得到具体的bouncer自定义View //如果需要更新boucer的情况,先获取老的bouncer类型 KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); //再获取最新改变的bouncer类型 KeyguardSecurityView newView = getSecurityView(securityMode); //如果之前的bouncer不为空则调用onPause隐藏并设置空回调 if (oldView != null) { 
    oldView.onPause(); oldView.setKeyguardCallback(mNullCallback); } //如果当前设置的bouncer不为None,则调用其onResume激活,并设置回调接口 if (securityMode != SecurityMode.None) { 
    newView.onResume(KeyguardSecurityView.VIEW_REVEALED); newView.setKeyguardCallback(mCallback); } //获取bouncer的数量 final int childCount = mSecurityViewFlipper.getChildCount(); final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); //遍历mSecurityViewFlipper的bouncer,并根据当前的bouncer类型 //调用setDisplayedChild切换到此bouncer for (int i = 0; i < childCount; i++) { 
    if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) { 
    mSecurityViewFlipper.setDisplayedChild(i); break; } } //将当前的bouncer类型保存到mCurrentSecuritySelection mCurrentSecuritySelection = securityMode; //将当前的bouncer View保存到mCurrentSecurityView mCurrentSecurityView = newView; //通知bouncer类型发生改变 mSecurityCallback.onSecurityModeChanged(securityMode, securityMode != SecurityMode.None && newView.needsInput()); } 

到此showSecurityScreen方法我们已经全部分析完毕,此方法主要目的就是更新当前系统的bouncer类型并添加到mSecurityViewFlipper,此时bouncer并不可见

我们可以回到KeyguardBouncer.show方法中去了,我们之前分析到mKeyguardView.dismiss方法,此方法返回false代表需要显示bouncer,返回true代表可以跳过,继续看代码:

KeyguardBouncer.show

 public void show(boolean resetSecuritySelection, boolean isScrimmed) { 
    ... //ensureView我们前一篇文章分析过,里面会调用inflateView加载 //id为keyguard_bouncer和keyguard_host_view的bouncer布局文件 //并通过addView添加到SystemUI顶层View中 ensureView(); ... //resetSecuritySelection传递过来为false,代表不更新当前bouncer类型 //什么情况下会更新呢?插入带PIN或者PUK的sim卡时 if (resetSecuritySelection) { 
    showPrimarySecurityScreen(); } //如果当前bouncer已经可见或者mShowingSoon为true则return //mShowingSoon是个标志位,避免重复调用,代表即将显示bouncer if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) { 
    return; } final int activeUserId = KeyguardUpdateMonitor.getCurrentUser(); final boolean isSystemUser = UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM; final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId; //allowDismissKeyguard一般为true,mKeyguardView.dismiss //决定是否显示bouncer,显示哪种bouncer,返回true代表没有bouncer if (allowDismissKeyguard && mKeyguardView.dismiss(activeUserId)) { 
    return; } ... //标志位 mShowingSoon = true; if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) { 
    mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY); } else { 
    DejankUtils.postAfterTraversal(mShowRunnable); } //通知bouncer的可见性发生了变化 mCallback.onBouncerVisiblityChanged(true /* shown */); //通知bouncer开始显示,里面会更新LockIcon图标的变化 mExpansionCallback.onStartingToShow(); } 

最后来看看mShowRunnable,

讯享网private final Runnable mShowRunnable = new Runnable() { 
    @Override public void run() { 
    //首先就是让mRoot显示出来 mRoot.setVisibility(View.VISIBLE); ... if (mKeyguardView.getHeight() != 0 && mKeyguardView.getHeight() != mStatusBarHeight) { 
    mKeyguardView.startAppearAnimation(); } else { 
    mKeyguardView.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { 
    @Override public boolean onPreDraw() { 
    mKeyguardView.getViewTreeObserver().removeOnPreDrawListener(this); mKeyguardView.startAppearAnimation(); return true; } }); mKeyguardView.requestLayout(); } //标志位置为false mShowingSoon = false; if (mExpansion == EXPANSION_VISIBLE) { 
    mKeyguardView.onResume(); mKeyguardView.resetSecurityContainer(); } ... } }; 

这个Runnable做的事情其实就是将bouncer显示出来,不管哪种类型的bouncer在显示的时候都会调用startAppearAnimation方法,开始显示的动画效果

到此滑动锁屏上滑显示密码锁屏已经全部分析完毕,总结一下:

  1. 我们分析了滑动锁屏上滑的判定策略,系统提供了几种误触判定的方式,包括P-seneor近距离检测,上滑距离检测,速度检测和方向检测
  2. 我们分析了bouncer的创建,bouncer类型的获取,bouncer的添加,所有bouncer都是添加到KeyguardSecurityViewFlipper中的,以动态添加的方式
  3. bouncer的显示,不管何种类型的bouncer显示时都会调用startAppearAnimation方法

上篇和这篇全面总结了锁屏的流程,以后遇到和锁屏相关的问题,我们只需要顺着这个流程查找问题,一定会很容易的

小讯
上一篇 2025-03-12 22:57
下一篇 2025-01-12 13:16

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/72558.html