Android Framework------之Keyguard 简单分析
前面对于MediaPlayer的系统研究,刚刚开始,由于其他原因现在要先暂停一下。这次要看的模块是android 4.2 系统中的Keyguard模块。在接触之后才发现,android4.2的keyguard模块与之前相比,变化挺大的,最起码名字上变化挺大的。由于对于Android系统了解不是很深入,而且知识和经验都比较弱,在文中肯定有不恰当或者错误的地方,请各位路过的大神不吝指正。
在Android 4.2中,keyguard的模块存放的目录是在[/frameworks/base/policy/src/com/android/internal/policy/impl/keyguard/], 我也可以看到在同级目录下有个文件夹叫做keyguard_obsolete,这里的应该是在之前使用的版本,不过我们今天的重点不是在它,而在keyguard目录。在Android系统中,说到解锁,最容易让人想起的是开机之后的滑动解锁画面,这个也是Android系统的默认的画面。我们可以在Android手机的Settings应用中改变解锁方式,比如面部解锁,图形解锁,PIN码解锁,Password解锁等方式。在Android4.2中的解锁画面,又添加了新的内容,比如可以左右滑动,并且可以在解锁的画面上添加Calendar, Email,Clock,Message等不同的widget。下面我们就说在Android 4.2上的锁屏机制。
先从解锁的画面说起。
Keyguard的窗口从哪里来?
Android系统中的画面及用户的交互,通常都是在Activity中进行的,不过这个Keyguard这块有点不同。如果你在Keyguard模块的目录中进行搜索的话,是找不到Activity这样的关键字的。从这个搜索结果可以看出,Android系统中默认的keyguard最起码没有使用Activity。那么,keyguard的画面是如何呈现出来的呢?带着这个问题,开始Keyguard的探索之旅。第一次接触到Keyguard的时候,我不知道如何入手去看这块代码,我记得当时是当Power按下后,屏幕点亮时会显示出Keyguard。上次是从这个角度去了解Keyguard的,多少有点收获,也为再次看Keyguard模块打下点基础吧。之所以说起这个事情,是想告诫自己,求知不要心急,慢慢来不见得是坏事,市场回头看一眼,会有不一样的收获。让子弹飞一会,不要着急。有了上次的些微了解之后,这次是从起源开始了解Keyguard是如何绘制出来的。我们知道,Keyguard是系统启动之后就出现的(注:Android系统在标准情况下,刷机或格式化后的首次启动,是开始基本设置,即setupWizard),可以从这点入手。还有一点是,在Android系统中和Keyguard相关的类大多是Keyguard开头。Android系统中的大管家SystemServer启动后,在一切准备妥当之后,会根据需要通知不同的service.systemReady。Keyguard的启动就是从WindowManagerService的systemReady开始的,在WindowManagerService.systemReady()中会调用PhoneWindowManager的systemReady,因为PhoneWindowManager是WindowManagerPolicy的子类。在PhoneWindowManager中会判断KeyguarViewMediator是否已经初始化完成,其实在PhoneWindowManager的init的时候,这个对象就已经创建完毕。 这部分从SystemServer准备就绪,到WindowManagerService的systemReady,一直到PhoneWindowManager的systemReady都比较简单,这里就不再列出代码。我们知道在Keyguard的中已经存在了KeyguardViewManager了,为什么还要KeyguardViewMediator呢? 我们可以从KeyguardViewMediator的功能的角度看看这个原因, 先看Android的设计者对这个类的描述:
这是一个调解和keyguard相关请求的了类。它包括了查询keyguard的状态,电源管理相关的事件,因为电源管理事件会影响keyguard的设置或重置,回调PhoneWindowManager通知它说keyguard正在显示,或者解锁成功等状态。注:keyguard画面是在屏幕关闭的时候显示的,所以当屏幕亮起来的时候,keyguard画面能够直接准备好了。比如,查询keyguard的例子:某个事件能够唤醒Keyguard吗?keyguard正在显示吗?某个事件会被锁屏的状态约束而不起作用吗?回调PhoneWindowManager的情况:锁屏正在显示。 导致锁屏状态变化的样例:屏幕关闭,重置锁屏,并且显示出来以便于下次屏幕亮起是能够直接显示。键盘打开,如果keyguard是不安全的,就隐藏它。从解锁画面发生的事件:用户成功解锁,隐藏解锁画面,不再约束用户的输入事件。注:除了电源管理能够影响keyguard的状态外,其他的一些app或者service可能会通过方法setKeyguardEnable去关闭keyguard。比如接到电话时。这个类是在WindowManagerPolicy初始化的时候创建的,并且运行在WindowMangerPolicy所在的线程,keyguard的画面从这个线程中创建的当keyguardViewMediator构建时。但是Keyguard相关的api可能会被其他的线程调用,比如InputManagerService和windowManagerService。因此在keyguardViewMediator的方法是同步的,并且任何一个和Keyguard画面相关的事件都投掷到Handler中以确保在UI线程中处理。
对KeyguardViewMediator有了大概的了解之后,我们下面就直接从KeyguardViewMediator的onSystemReady开始分析Keyguard画面显示的过程,这个方法的代码如下:
1 /** 2 * Let us know that the system is ready after startup. 3 */ 4 public void onSystemReady() { 5 mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); 6 synchronized (this) { 7 if (DEBUG) Log.d(TAG, "onSystemReady"); 8 mSystemReady = true;//注册一个回调函数,用来监视系统中的用户切换变化,手机状态变化,sim卡状态变化等等 9 mUpdateMonitor.registerCallback(mUpdateCallback); 10 11 // Suppress biometric unlock right after boot until things have settled if it is the 12 // selected security method, otherwise unsuppress it. It must be unsuppressed if it is 13 // not the selected security method for the following reason: if the user starts 14 // without a screen lock selected, the biometric unlock would be suppressed the first 15 // time they try to use it. 16 // 17 // Note that the biometric unlock will still not show if it is not the selected method. 18 // Calling setAlternateUnlockEnabled(true) simply says don't suppress it if it is the 19 // selected method.使用生物技术解锁,比如声纹解锁解锁技术等。不常见 20 if (mLockPatternUtils.usingBiometricWeak() 21 && mLockPatternUtils.isBiometricWeakInstalled()) { 22 if (DEBUG) Log.d(TAG, "suppressing biometric unlock during boot"); 23 mUpdateMonitor.setAlternateUnlockEnabled(false); 24 } else { 25 mUpdateMonitor.setAlternateUnlockEnabled(true); 26 } 27 28 doKeyguardLocked();//比较重要的一个方法 29 } 30 // Most services aren't available until the system reaches the ready state, so we 31 // send it here when the device first boots. 32 maybeSendUserPresentBroadcast(); 33 }
这个方法主要就是注册了一个监听系统中某些属性发生变化时的回调函数,再有就是开始锁屏。方法doKeyguardLocked的代码如下:
1 private void doKeyguardLocked() { 2 doKeyguardLocked(null); 3 } 4 5 /** 6 * Enable the keyguard if the settings are appropriate. 7 */ 8 private void doKeyguardLocked(Bundle options) { 9 // 如果有其他的应用关闭了keyguard的话,不用显示 10 if (!mExternallyEnabled) { 11 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); 12 13 // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes 14 // for an occasional ugly flicker in this situation: 15 // 1) receive a call with the screen on (no keyguard) or make a call 16 // 2) screen times out 17 // 3) user hits key to turn screen back on 18 // instead, we reenable the keyguard when we know the screen is off and the call 19 // ends (see the broadcast receiver below) 20 // TODO: clean this up when we have better support at the window manager level 21 // for apps that wish to be on top of the keyguard 22 return; 23 } 24 25 // 如果keyguard正在显示的话,就不用再向下执行了。 26 if (mKeyguardViewManager.isShowing()) { 27 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); 28 return; 29 } 30 31 // if the setup wizard hasn't run yet, don't show 32 final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", 33 false); 34 final boolean provisioned = mUpdateMonitor.isDeviceProvisioned(); 35 final IccCardConstants.State state = mUpdateMonitor.getSimState(); 36 final boolean lockedOrMissing = state.isPinLocked() 37 || ((state == IccCardConstants.State.ABSENT 38 || state == IccCardConstants.State.PERM_DISABLED) 39 && requireSim); 40 41 if (!lockedOrMissing && !provisioned) { 42 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned" 43 + " and the sim is not locked or missing"); 44 return; 45 } 46 47 if (mUserManager.getUsers(true).size() < 2 48 && mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) { 49 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); 50 return; 51 } 52 53 if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen"); 54 showLocked(options);//开始显示keyguard画面,注意这里的参数是null 55 }
通过这个来决定keyguard是否应该显示,在正常情况下(非来电,非多用户,非首次启动等等),决定显示keyguard画面,然后把这个发送消息到mHandler,不过都是在WindowManagerPolicy的线程中,使用的是同一个线程进行的loop。在显示画面的过程中,要一直保持设备处于亮着状态。处理SHOW消息是由方法handleShow完成的,代码如下:
1 private void handleShow(Bundle options) { 2 synchronized (KeyguardViewMediator.this) { 3 if (DEBUG) Log.d(TAG, "handleShow"); 4 if (!mSystemReady) return; 5 //调用KeyguardviewManager进行画面的显示 6 mKeyguardViewManager.show(options); 7 mShowing = true; 8 mKeyguardDonePending = false; 9 updateActivityLockScreenState();//和ActivityManagerService交互 10 adjustStatusBarLocked();//调整StatusBar中显示的一些内容,disable一些在statusBar中进行的操作 11 userActivity(); //通知PowerManagerService有用户事件发生,及时更新屏幕超时时间为10s 12 try { 13 ActivityManagerNative.getDefault().closeSystemDialogs("lock"); 14 } catch (RemoteException e) { 15 } 16 17 // 播放解锁的完成的声音。 18 playSounds(true); 19 //在发送SHOW消息的时候,申请过这个wakelock,在这里释放 20 mShowKeyguardWakeLock.release(); 21 } 22 }
在这个方法中主要就是调用KeyguardViewManager,让其去显示我们所需要的UI.在完成现实之后,处理一些必要事情,不如更新KeyguardViewMediator的一些属性,和ActivityManagerService,StatusBarService,PowerManagerService等的交互。和播放解锁声。看来画面的显示还在下一步,我们接着往下看:
[KeyguardViewManager.java]
1 public synchronized void show(Bundle options) {//此时参数为null 2 if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView); 3 //在手机上返回值应该是false 4 boolean enableScreenRotation = shouldEnableScreenRotation(); 5 //这里就是创建Keyguard UI的地方。 6 maybeCreateKeyguardLocked(enableScreenRotation, false, options); 7 maybeEnableScreenRotation(enableScreenRotation); 8 9 // Disable common aspects of the system/status/navigation bars that are not appropriate or 10 // useful on any keyguard screen but can be re-shown by dialogs or SHOW_WHEN_LOCKED 11 // activities. Other disabled bits are handled by the KeyguardViewMediator talking 12 // directly to the status bar service. 13 final int visFlags = View.STATUS_BAR_DISABLE_HOME; 14 if (DEBUG) Log.v(TAG, "show:setSystemUiVisibility(" + Integer.toHexString(visFlags)+")"); 15 mKeyguardHost.setSystemUiVisibility(visFlags); 16 17 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 18 mKeyguardHost.setVisibility(View.VISIBLE); 19 mKeyguardView.show(); 20 mKeyguardView.requestFocus(); 21 }
这个方法的主要功能就是创建一个能够承载KeyguardView的地方,然后把这个View给显示出来。我们先看看创建能够承载KeyguardView的方法maybeCreateKeyguardLocked,代码如下:
1 private void maybeCreateKeyguardLocked(boolean enableScreenRotation, boolean force, 2 Bundle options) { 3 final boolean isActivity = (mContext instanceof Activity); // 这里的mContext是来自SystemServer的Context,因此返回值应该是false 4 5 if (mKeyguardHost != null) { 6 mKeyguardHost.saveHierarchyState(mStateContainer); 7 } 8 9 if (mKeyguardHost == null) {//这里分析的是开机之后的Keyguard的第一次显示,mKeyguardHost应该是null。因此会进入这个判断分支 10 if (DEBUG) Log.d(TAG, "keyguard host is null, creating it..."); 11 12 mKeyguardHost = new ViewManagerHost(mContext); 13 //创建承载Keyguard的窗口的属性 14 int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 15 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR 16 | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN 17 | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 18 19 if (!mNeedsInput) { 20 flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 21 } 22 if (ActivityManager.isHighEndGfx()) { 23 flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 24 } 25 26 final int stretch = ViewGroup.LayoutParams.MATCH_PARENT; 27 final int type = isActivity ? WindowManager.LayoutParams.TYPE_APPLICATION 28 : WindowManager.LayoutParams.TYPE_KEYGUARD;//Type应该是WindowManager.LayoutParams.TYPE_KEYGUARD 29 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 30 stretch, stretch, type, flags, PixelFormat.TRANSLUCENT); 31 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 32 lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen; 33 lp.screenOrientation = enableScreenRotation ? 34 ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; 35 36 if (ActivityManager.isHighEndGfx()) { 37 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 38 lp.privateFlags |= 39 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; 40 } 41 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY; 42 if (isActivity) { 43 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; 44 } 45 lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; 46 lp.setTitle(isActivity ? "KeyguardMock" : "Keyguard"); 47 mWindowLayoutParams = lp; 48 mViewManager.addView(mKeyguardHost, lp);//注意这里,如果阅读过Activity启动相关的代码的话,这个地方会很熟悉 49 } 50 51 if (force || mKeyguardView == null) { 52 inflateKeyguardView(options); 53 mKeyguardView.requestFocus(); 54 } 55 updateUserActivityTimeoutInWindowLayoutParams(); 56 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 57 58 mKeyguardHost.restoreHierarchyState(mStateContainer); 59 }
在这个方法中,首先判断目前使用的解锁方式是以应用的方式提供的还是系统默认的,如果是以应用程序方式提供的解锁,那么它的Context应该是是一个Activity。我们这里讨论的是系统自带的解锁方式,所以isActivity的值应该是false的。这是系统启动之后的首次开始解锁,所以KeyguardViewManager中的属性除了在构造函数中初始化过的,其他均应为null或未初始化。接下来因为mKeyguardHost还没有创建,所以,接下来就是创建ViewManagerHost的实例,注意,这里是的mKeyguardHost是ViewManagerHost的实例,它是KeyguardViewManager的一个内部类,继承自FrameLayout类。然后定义Keyguard窗口对应的一些特定的属性,这些属性中尤其注意type类型是WindowManager.LayoutParams.TYPE_KEYGUARD,这是一个系统级别的窗口,关于这个属性的使用,在Activity启动过程中图像的描绘时会仔细讨论的。把所需要的参数都定义完成之后,就开始把ViewManagerHost最终通过WindowManagerImpl添加到窗口中。添加过程不是本次的重点,这里忽略。后面的代码不是很复杂,很容易明白,就不再一一介绍。到这里我们的目的已经达到了,回头想一下,我们是为了寻找Keyguard的画面是在没有Activity的情况下如何呈现出来的,这里已经给出了答案,把ViewManagerHost添加到窗口中,这里的ViewManagerHost是继承自FrameLayout的,相当于一个view的容器,只要把我们所需要的View布局到这个容器中,系统会帮助我们完成绘制的过程。关于View的绘制过程,网上有很多文章分析Activity的启动过程,或者分析WindowManageService的时候都会说到这个问题,这些内容不是本次的重点,这里略过。
KeyguardView是如何布局到窗口?
我们已经拥有了View的容器了,接下来我们可以分析Keyguard的是如何把不同的解锁方式的画面放进这个容器的,也就是我们要开始分析Android系统各种解锁画面是如何布局到我们得到的容器中的。下面我们接着上面的addView之后分析。我们一直分析道这儿,在上面代码中51line,mKeyguardView还没有见到,在这里判断条件是成立的,接着执行inflateKeyguardView。从这个方法的名字可以看出,这就是把view布局到窗口的函数,而他的参数是我们之前已经说明的为null。让我们一起看看是如何布局keyguardview的,其代码如下:
1 private void inflateKeyguardView(Bundle options) { 2 View v = mKeyguardHost.findViewById(R.id.keyguard_host_view); 3 if (v != null) {//如果已经把keyguardhost布局到ViewManagerHostview中的话,这里再把它清除掉 4 mKeyguardHost.removeView(v); 5 } 6 // TODO: Remove once b/7094175 is fixed 7 if (false) Slog.d(TAG, "inflateKeyguardView: b/7094175 mContext.config=" 8 + mContext.getResources().getConfiguration()); 9 final LayoutInflater inflater = LayoutInflater.from(mContext); 10 View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true);//把KeyguardHostView布局到ViewManagerHost中,当把View布局完成后,会调用子view的onfinishInflate方法 11 mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view); 12 mKeyguardView.setLockPatternUtils(mLockPatternUtils); 13 mKeyguardView.setViewMediatorCallback(mViewMediatorCallback); 14 mKeyguardView.initializeSwitchingUserState(options != null && 15 options.getBoolean(IS_SWITCHING_USER)); 16 17 // HACK 18 // The keyguard view will have set up window flags in onFinishInflate before we set 19 // the view mediator callback. Make sure it knows the correct IME state. 20 if (mViewMediatorCallback != null) {//如果这个判断条件成立,仅仅是找到某个View而已,还没有开始show 21 KeyguardPasswordView kpv = (KeyguardPasswordView) mKeyguardView.findViewById( 22 R.id.keyguard_password_view); 23 24 if (kpv != null) { 25 mViewMediatorCallback.setNeedsInput(kpv.needsInput()); 26 } 27 } 28 29 if (options != null) { 30 int widgetToShow = options.getInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET, 31 AppWidgetManager.INVALID_APPWIDGET_ID); 32 if (widgetToShow != AppWidgetManager.INVALID_APPWIDGET_ID) { 33 mKeyguardView.goToWidget(widgetToShow); 34 } 35 } 36 }
首先判断在ViewManagerHost是不是已经存在KeyguardHostView了,如果已经存在了,先把它移除了,然后重新把它布局到ViewManagerHost中。在布局完成后调用KeyguardHostView的哦你FinishInflate方法。
接着为KeyguardHostView设置毁掉函数和属性。既然要把KeyguardHostView布局到我们得到的容器中,我们就看看KeyguardHostView的结构式怎样的。keyguardHostView对应的布局文件在
[/frameworks/base/core/res/res/layout-port/keyguard_host_view.xml],这个布局文件对应的ViewTree如下:
这个就是我们Android系统中KeyguardHostView的view树。我们知道在Android4.2的keyguard系统中,在锁屏界面是可以左右滑动的。作用滑动式通过ViewFlipper实现的,这里的keyguard_widget_remove_drop_target是用来添加widget的容器,对应的画面是带有加号,能够添加widget的画面。 而在keyguard_widget_paper中可以盛放各种widget,比如时钟,邮件等。而我们经常使用的解锁方式是放在keyguardSecurityContainer中的。到这里,我们貌似得到还是一个View容器。只不过是把KeyguardHostView这个容器布局到ViewManagerHost这个容器中,到现在还是没有见到我们的解锁方式是如何呈现的。先别急,接下来我们就分析,各种view是如何取得的。之前我们是为了寻找keyguard的窗口是如何获得的,然后我们得到了这个窗口,并且还得到了一个view容器,然后在这个容器中又布局了一个容器KeyguardHostView,现在我们一步一步的return到前面的调用的函数,一直回到keyguardViewManager.show()方法,这个方法在取得能够盛放view的容器KeyguardHostView后开始调用KeyguardHostView的show函数,及mKeyguardView.show(),代码如下:
1 public void show() {//为了缩减文章的长度,把代码中一些log,注释和一些不必要的代码给删除了,并不影响对keyguard的理解 2 showPrimarySecurityScreen(false); 3 } 4 void showPrimarySecurityScreen(boolean turningOff) { 5 SecurityMode securityMode = mSecurityModel.getSecurityMode();//获取当前的锁屏的方式,通过查找数据库,等会重点分析此函数 6 showSecurityScreen(securityMode); 7 } 8 9 private void showSecurityScreen(SecurityMode securityMode) { 10 if (securityMode == mCurrentSecuritySelection) return; 11 //注意,这里的变量的类型,是KeyguardSecrityView,根据securityMode去获取相应的View 12 KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); 13 KeyguardSecurityView newView = getSecurityView(securityMode); 14 15 // Enter full screen mode if we're in SIM or Account screen 16 boolean fullScreenEnabled = getResources().getBoolean( 17 com.android.internal.R.bool.kg_sim_puk_account_full_screen); 18 boolean isSimOrAccount = securityMode == SecurityMode.SimPin 19 || securityMode == SecurityMode.SimPuk 20 || securityMode == SecurityMode.Account; 21 mAppWidgetContainer.setVisibility( 22 isSimOrAccount && fullScreenEnabled ? View.GONE : View.VISIBLE); 23 24 if (mSlidingChallengeLayout != null) {//mSlidingChallengeLayout对应上面ViewTree中的SlidingChallengeLayout 25 mSlidingChallengeLayout.setChallengeInteractive(!fullScreenEnabled); 26 } 27 28 if (oldView != null) { 29 oldView.onPause();//模拟声明周期的方式来管理各个解锁画面 30 oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view 31 } 32 newView.onResume(KeyguardSecurityView.VIEW_REVEALED); 33 newView.setKeyguardCallback(mCallback); 34 35 final boolean needsInput = newView.needsInput(); 36 if (mViewMediatorCallback != null) { 37 mViewMediatorCallback.setNeedsInput(needsInput); 38 } 39 40 // Find and show this child. 41 final int childCount = mSecurityViewContainer.getChildCount(); 42 //切换时的动画,新的view进入的动画方式,前一个view退出的动画方式 43 mSecurityViewContainer.setInAnimation( 44 AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_fade_in)); 45 mSecurityViewContainer.setOutAnimation( 46 AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_fade_out)); 47 final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); 48 for (int i = 0; i < childCount; i++) { 49 if (mSecurityViewContainer.getChildAt(i).getId() == securityViewIdForMode) { 50 mSecurityViewContainer.setDisplayedChild(i);//这个方法会把选择的view设置上,并且会调用requestLayout把画面更新出来 51 break; 52 } 53 } 54 55 if (securityMode == SecurityMode.None) { 56 // Discard current runnable if we're switching back to the selector view 57 setOnDismissAction(null); 58 } 59 if (securityMode == SecurityMode.Account && !mLockPatternUtils.isPermanentlyLocked()) { 60 // we're showing account as a backup, provide a way to get back to primary 61 setBackButtonEnabled(true); 62 } 63 mCurrentSecuritySelection = securityMode; 64 }
在选择画面之前,要先确定我们锁屏的方式,即SecurityMode,这个mode就是对应各种各样的解锁方式。如果不做修改的话,设置之后,无论是手机是否重启,都会保持这个模式不变,粗费自己手动在Settings应用中的锁屏方式中做出修改。因此,我们可以确定锁屏的方式一定是以某种方式存储到磁盘上了,通过SecurityModel.getSecurityMode()可以读取之前存储的锁屏模式,其代码如下:
1 [SecurityMode.java] 2 SecurityMode getSecurityMode() { 3 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 4 final IccCardConstants.State simState = updateMonitor.getSimState();//获取SIM卡的状态,查看SIM是否被锁住 5 SecurityMode mode = SecurityMode.None;//这里的每一个SecurityMode都对应一个view, 6 if (simState == IccCardConstants.State.PIN_REQUIRED) { 7 mode = SecurityMode.SimPin; 8 } else if (simState == IccCardConstants.State.PUK_REQUIRED 9 && mLockPatternUtils.isPukUnlockScreenEnable()) { 10 mode = SecurityMode.SimPuk; 11 } else {//通过LockPatternUtils和LockSettingsService通信,打开locksettings.db数据库,读取保存的解锁方式 12 final int security = mLockPatternUtils.getKeyguardStoredPasswordQuality(); 13 switch (security) { 14 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: 15 mode = mLockPatternUtils.isLockPasswordEnabled() ? 16 SecurityMode.PIN : SecurityMode.None; 17 break; 18 case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: 19 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: 20 case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: 21 mode = mLockPatternUtils.isLockPasswordEnabled() ? 22 SecurityMode.Password : SecurityMode.None; 23 break; 24 25 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: 26 case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED: 27 if (mLockPatternUtils.isLockPatternEnabled()) { 28 mode = mLockPatternUtils.isPermanentlyLocked() ? 29 SecurityMode.Account : SecurityMode.Pattern; 30 } 31 break; 32 33 default: 34 throw new IllegalStateException("Unknown unlock mode:" + mode); 35 } 36 } 37 return mode; 38 } 39 [KeyguardHostView.java] 40 private int getSecurityViewIdForMode(SecurityMode securityMode) { 41 switch (securityMode) { 42 case None: return R.id.keyguard_selector_view;//这个对应的是默认的解锁方式,即滑动的方式解锁 43 case Pattern: return R.id.keyguard_pattern_view; 44 case PIN: return R.id.keyguard_pin_view; 45 case Password: return R.id.keyguard_password_view; 46 case Biometric: return R.id.keyguard_face_unlock_view; 47 case Account: return R.id.keyguard_account_view; 48 case SimPin: return R.id.keyguard_sim_pin_view; 49 case SimPuk: return R.id.keyguard_sim_puk_view; 50 } 51 return 0; 52 }
在通过LockPatternUtils去查找数据库的时候,需要和LockSettingsService通信,这里就体现了锁屏的一个重要的保护措施,对调用这个方法的UserId进行严格的检查。通过这种方式获得解锁方式,然后找到这个解锁方式对应的画面,即KeyguardSecurityView。在getSecurityViewForMode中仅仅是获取到这些view的id,在getSecurityMode方法中还要把这些view布局到KeyguardSecurityViewContainer中,实现的代码如下:
1 private KeyguardSecurityView getSecurityView(SecurityMode securityMode) { 2 final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);//根据SecurityMode获取对应的view的Id 3 KeyguardSecurityView view = null; 4 final int children = mSecurityViewContainer.getChildCount();//在开始的时候,mSecurityViewContainer中应该是没有任何view,这里的初始值应该为0 5 for (int child = 0; child < children; child++) { 6 if (mSecurityViewContainer.getChildAt(child).getId() == securityViewIdForMode) { 7 view = ((KeyguardSecurityView)mSecurityViewContainer.getChildAt(child)); 8 break; 9 } 10 } 11 int layoutId = getLayoutIdFor(securityMode);//根据SecurityMode获取对应的布局文件 12 if (view == null && layoutId != 0) { 13 final LayoutInflater inflater = LayoutInflater.from(mContext); 14 if (DEBUG) Log.v(TAG, "inflating id = " + layoutId); 15 View v = inflater.inflate(layoutId, mSecurityViewContainer, false);//把布局文件布局到KeyguardSecurityViewContainer中 16 mSecurityViewContainer.addView(v);//把找到的view添加到KeyguardSecurityViewContainer中 17 updateSecurityView(v); 18 view = (KeyguardSecurityView)v; 19 } 20 21 if (view instanceof KeyguardSelectorView) { 22 KeyguardSelectorView selectorView = (KeyguardSelectorView) view; 23 View carrierText = selectorView.findViewById(R.id.keyguard_selector_fade_container); 24 selectorView.setCarrierArea(carrierText); 25 } 26 27 return view;//注意,返回值的类型是keyguardSecurityView 28
在继续分析view是如何显示的之前,我们还要再补充一点,关于各种keyguard的画面和KeyguardSecurityView之间的关系。如下几种典型的解锁方式,及其结构关系图:
上面这幅图仅仅是Keyguard解锁方式和View相关的类图。不过图中的类KeyguardSelector这个类从名字上不容易和我们一直的解锁方式的画面对应起来,其实这类中包含的是GlowPadView,就是在Android4.2开机之后默认的以滑动方式解锁。不过呢,这样我们从这幅图中还是可以看到,解锁方式一共有五大类,分别是KeyguardFaceLockedView,KeyguardAccountView,KeyguardPatternView,KeyguardAbsInputView,GlowPadView。其中所有的需要输入数字或者字符的解锁方式都归为KeyguardAbsInputView。从这幅图中,我们还可以发现特点就是,这些解锁 画面有两个共同的基类,一个是KeyguardSecurityView,一个是LinearLayout。这两个基类中,keyguardSecurityView是为了让keyguard系统便于管理这些画面而存在;LinearLayout是为了让这些View能够正常地使用View系统描绘而存在的。所以只要KeyguardViewManager持有KeyguardSecurityView这个基类,就可以指向其任一子类的实例,即不同的解锁画面。这是面向对象的多态性。至于要使用的那个解锁方式,是通过LockPatternUtils和LockSettingsService通信,读取数据库来获得的。
如何解锁?
可以分为密码类型的解锁方式,图案类型的解锁方式。这部分内容,通过代码很容易能看明白,就不再叙述