Framework之锁屏分析与禁用锁屏
来源:互联网 发布:2017百度春运大数据 编辑:程序博客网 时间:2024/05/19 00:43
产品开发过程有一个需求:
1. 按下电源键不锁屏,只关闭屏幕;
2. 用户仍然可以设置熄屏时间(到了设定的时间屏幕熄灭,按下电源键唤醒而没有锁屏);
网上找了好几种禁用锁屏的方法,都不管用,为了方便广大找答案的童鞋,我先把我试验成功的办法贴出来,然后把我找答案过程中的错误方法也贴出来,然后如果大家有兴趣,再看下我对整个锁屏启动的分析(其实知道原理很重要,不过如果没有这个心不想了解也没关系,毕竟能干实事才是硬道理)。
实现方式
步骤1
在SystemUI应用启动时调用禁用锁屏的方法;我写在了SystemUI的SystemUIApplication里面;
@Override public void onCreate() { super.onCreate(); ... //larosn added new android.os.Handler().postDelayed(new Runnable() { @Override public void run() { android.app.KeyguardManager km = (android.app.KeyguardManager) SystemUIApplication.this.getSystemService(Context.KEYGUARD_SERVICE); /* 获取KeyguardLock对象 */ android.app.KeyguardManager.KeyguardLock kl = km.newKeyguardLock(TAG); /* 关闭系统锁屏服务 */ kl.disableKeyguard(); /*如果要重新开启,使用kl.reenableKeyguard()*/ } },6000); }
大家发现我在SystemUI的Application的onCreate方法里面加了一个延迟来禁用锁屏服务,为什么要加入一个延时呢?因为如果不加入延时,在开机后屏幕还是锁着的。这样不是我们想要的,我们需要开机后就解锁了,而且之后再也不会自动锁屏或者因为按下电源键就锁屏;
但是加了这个锁屏会出现一个很奇怪的现象(我们是知道为啥这样出现的);那就是先显示了锁屏界面,然后自己解锁了,解锁了,锁了,了。。。
步骤2:
显然,这体验不好,有什么办法不用延时达到效果?有的!要弄清为啥不加延时就没效果请看后面的锁屏分析;我先把方法告诉大家,那就是修改系统源码,把开机启动锁屏这行代码注释掉;
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
/** * Let us know that the system is ready after startup. */ public void onSystemReady() { mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); synchronized (this) { if (DEBUG) Log.d(TAG, "onSystemReady"); mSystemReady = true; mUpdateMonitor.registerCallback(mUpdateCallback); if (mLockPatternUtils.usingBiometricWeak() && mLockPatternUtils.isBiometricWeakInstalled()) { if (DEBUG) Log.d(TAG, "suppressing biometric unlock during boot"); mUpdateMonitor.setAlternateUnlockEnabled(false); } else { mUpdateMonitor.setAlternateUnlockEnabled(true); } //doKeyguardLocked(null);//larsonzhong@163.com 修改这里 } ... }
然后大家把systemUI编译好推送到设备看看效果,是不是开机没有锁屏而且按下电源键也不锁屏了?嗯~~大功告成!
如果你有点意犹未尽,欢迎继续往下看;
KeyGuard锁屏原理分析
如果有兴趣,推荐先去看一下Android系统启动原理分析;
我们这里从KeyguardViewMediator的onSystemReady开始讲起;
Android系统中的大管家SystemServer启动后,在一切准备妥当之后,会根据需要通知不同的service.systemReady。Keyguard的启动就是从WindowManagerService的systemReady开始的;
在WindowManagerService.systemReady()中会调用PhoneWindowManager的systemReady,因为PhoneWindowManager是WindowManagerPolicy的子类。在PhoneWindowManager中会判断KeyguarViewMediator是否已经初始化完成,其实在PhoneWindowManager的init的时候,这个对象就已经创建完毕。
我们下面就直接从KeyguardViewMediator的onSystemReady开始分析Keyguard画面显示的过程,这个方法的代码如下:
/** * Let us know that the system is ready after startup. */ public void onSystemReady() { mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); synchronized (this) { if (DEBUG) Log.d(TAG, "onSystemReady"); mSystemReady = true; //注册系统状态变化监听,用来监视系统中的用户切换变化,手机状态变化,sim卡状态变化等等 mUpdateMonitor.registerCallback(mUpdateCallback); //以下一堆注释大概是说如果使用生物解锁(人脸识别,指纹解锁之类),在开机的时候先禁用生物解锁setAlternateUnlockEnabled设置为false,否则设置为true;这个跟我们研究的无关,不用管 // Suppress biometric unlock right after boot until things have settled if it is the // selected security method, otherwise unsuppress it. It must be unsuppressed if it is // not the selected security method for the following reason: if the user starts // without a screen lock selected, the biometric unlock would be suppressed the first // time they try to use it. // // Note that the biometric unlock will still not show if it is not the selected method. // Calling setAlternateUnlockEnabled(true) simply says don't suppress it if it is the // selected method. if (mLockPatternUtils.usingBiometricWeak() && mLockPatternUtils.isBiometricWeakInstalled()) { if (DEBUG) Log.d(TAG, "suppressing biometric unlock during boot"); mUpdateMonitor.setAlternateUnlockEnabled(false); } else { mUpdateMonitor.setAlternateUnlockEnabled(true); } doKeyguardLocked(null);//重点是这个,开始锁屏 } // Most services aren't available until the system reaches the ready state, so we // send it here when the device first boots. maybeSendUserPresentBroadcast(); }
那么这个doKeyguardLocked是怎么实现锁屏的呢??
private void doKeyguardLocked() { doKeyguardLocked(null); }
这是穿了个空参数调用了个doKeyguardLocked,继续往下看;
/** * Enable the keyguard if the settings are appropriate. */ private void doKeyguardLocked(Bundle options) { /*如果由其他应用禁用了锁屏,那么就不显示??大概这个mExternallyEnabled是可以用来控制锁屏显示*/ // if another app is disabling us, don't show if (!mExternallyEnabled) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes // for an occasional ugly flicker in this situation: // 1) receive a call with the screen on (no keyguard) or make a call // 2) screen times out // 3) user hits key to turn screen back on // instead, we reenable the keyguard when we know the screen is off and the call // ends (see the broadcast receiver below) // TODO: clean this up when we have better support at the window manager level // for apps that wish to be on top of the keyguard return; } // 如果锁屏正在显示,就让他继续显示,不往下执行了。 // if the keyguard is already showing, don't bother if (mStatusBarKeyguardViewManager.isShowing()) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); return; } // 下面的一大段就是检查是不是锁屏初始化了,是否配置正确,没有的话就不继续执行了 // if the setup wizard hasn't run yet, don't show final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false); final boolean provisioned = mUpdateMonitor.isDeviceProvisioned(); final IccCardConstants.State state = mUpdateMonitor.getSimState(); final boolean lockedOrMissing = state.isPinLocked() || ((state == IccCardConstants.State.ABSENT || state == IccCardConstants.State.PERM_DISABLED) && requireSim); if (!lockedOrMissing && !provisioned) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned" + " and the sim is not locked or missing"); return; } if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); return; } if (mLockPatternUtils.checkVoldPassword()) { if (DEBUG) Log.d(TAG, "Not showing lock screen since just decrypted"); // Without this, settings is not enabled until the lock screen first appears mShowing = false; hideLocked(); return; } //执行到这里说明上面的配置没问题了,开始把锁屏显示出来了 if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen"); showLocked(options); }
大家记得showLocked的options参数是null的,后面要用的上的;
/** * Send message to keyguard telling it to show itself * @see #handleShow */ private void showLocked(Bundle options) { if (DEBUG) Log.d(TAG, "showLocked"); // ensure we stay awake until we are finished displaying the keyguard mShowKeyguardWakeLock.acquire(); Message msg = mHandler.obtainMessage(SHOW, options); mHandler.sendMessage(msg); }
发个消息到mHandler,不过都是在WindowManagerPolicy的线程中,使用的是同一个线程进行的loop。在显示画面的过程中,要一直保持设备处于亮着状态。处理SHOW消息是由方法handleShow完成的,代码如下:
/** * This handler will be associated with the policy thread, which will also * be the UI thread of the keyguard. Since the apis of the policy, and therefore * this class, can be called by other threads, any action that directly * interacts with the keyguard ui should be posted to this handler, rather * than called directly. */ 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);//就是这个地方负责处理,这个obj还是null break; case HIDE: handleHide(); break; case RESET: ... } }}
我们来看下这个处理的方法:
/** * Handle message sent by {@link #showLocked}. * @see #SHOW */ private void handleShow(Bundle options) { synchronized (KeyguardViewMediator.this) { if (!mSystemReady) { if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready."); return; } else { if (DEBUG) Log.d(TAG, "handleShow"); } //调用KeyguardviewManager进行画面的显示 mStatusBarKeyguardViewManager.show(options); mHiding = false; mShowing = true; mKeyguardDonePending = false; mHideAnimationRun = false; //和ActivityManagerService交互 updateActivityLockScreenState(); //调整StatusBar中显示的一些内容,disable一些在statusBar中进行的操作 adjustStatusBarLocked(); //通知PowerManagerService有用户事件发生,及时更新屏幕超时时间为10s userActivity(); // Do this at the end to not slow down display of the keyguard. //播放锁屏声音,true表示 locked = true playSounds(true); //在发送SHOW消息的时候,申请过这个wakelock,在这里释放 mShowKeyguardWakeLock.release(); } //这个是作为中间类去控制keyguard的show与hide, mKeyguardDisplayManager.show(); }
在这个方法中主要就是调用KeyguardViewManager,让其去显示我们所需要的UI.在完成现实之后,处理一些必要事情,不如更新KeyguardViewMediator的一些属性,和ActivityManagerService,StatusBarService,PowerManagerService等的交互。和播放解锁声。
那么这个界面是怎么填充上去的呢?还没讲清楚, mStatusBarKeyguardViewManager.show(options);具体怎么执行的,我们来看下com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
/** * Show the keyguard. Will handle creating and attaching to the view manager * lazily. */ public void show(Bundle options) { mShowing = true; mStatusBarWindowManager.setKeyguardShowing(true); reset();//这个才是显示绘制锁屏的地方 } /** * Reset the state of the view. */ public void reset() { if (mShowing) { if (mOccluded) {//是否有遮挡物,如果有就隐藏锁屏,没有则判断显示锁屏还是密码解锁界面 mPhoneStatusBar.hideKeyguard(); mBouncer.hide(false /* destroyView */); } else { showBouncerOrKeyguard();//判断显示锁屏还是密码解锁界面 } updateStates(); } } /** * Shows the notification keyguard or the bouncer depending on * {@link KeyguardBouncer#needsFullscreenBouncer()}. */ private void showBouncerOrKeyguard() { if (mBouncer.needsFullscreenBouncer()) {//是否需要显示密码锁屏界面 // The keyguard might be showing (already). So we need to hide it. mPhoneStatusBar.hideKeyguard();//隐藏锁屏,显示密码解锁界面 mBouncer.show();//判断是否准备好再调用绘制 } else { mPhoneStatusBar.showKeyguard();//显示锁屏,隐藏密码解锁界面 mBouncer.hide(false /* destroyView */); mBouncer.prepare();//直接调用ensureView()绘制锁屏 } }
我们看Bouncer里面是怎么做的呢?
base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
我们知道在上面,显示判断是那个锁屏模式,如果需要显示密码锁屏则显示密码锁屏,否则显示锁屏,那我们先来分析一下他是怎么判断的;
/** * @return True if and only if the current security method should be shown before showing * the notifications on Keyguard, like SIM PIN/PUK. */ public boolean needsFullscreenBouncer() { if (mKeyguardView != null) { //getSecurityMode调用了KeyguardSecurityContainer的getgetSecurityMode SecurityMode mode = mKeyguardView.getSecurityMode(); return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk; } return false; }
继续分析,怎么判断锁屏模式的呢?KeyguardSecurityContainer调用了KeyguardSecurityModel的getSecurityMode
base/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java
SecurityMode getSecurityMode() { KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); final IccCardConstants.State simState = updateMonitor.getSimState(); SecurityMode mode = SecurityMode.None; if (simState == IccCardConstants.State.PIN_REQUIRED) { mode = SecurityMode.SimPin;//pin密码模式 } else if (simState == IccCardConstants.State.PUK_REQUIRED && mLockPatternUtils.isPukUnlockScreenEnable()) { mode = SecurityMode.SimPuk;//puk码模式 } else {//非Sim卡模式,说明不是输入sim密码 final int security = mLockPatternUtils.getKeyguardStoredPasswordQuality(); switch (security) { case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC://简单数字 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: mode = mLockPatternUtils.isLockPasswordEnabled() ? SecurityMode.PIN : SecurityMode.None; break; case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX://数字字母模式 mode = mLockPatternUtils.isLockPasswordEnabled() ? SecurityMode.Password : SecurityMode.None; break; case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED: if (mLockPatternUtils.isLockPatternEnabled()) {//图案模式 mode = mLockPatternUtils.isPermanentlyLocked() ? SecurityMode.Account : SecurityMode.Pattern; } break; default: throw new IllegalStateException("Unknown security quality:" + security); } } return mode; }
以上这段就把锁屏所有用到的模式判断了个遍,并根据判断返回结果。
那么我们现在拿到了锁屏模式,回到上面来,继续探究锁屏界面的展示;
密码锁屏是怎么显示的呢?
public void show() { ensureView();//这里负责图形绘制 if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) { // show() updates the current security method. This is needed in case we are already // showing and the current security method changed. mKeyguardView.show(); return; } // Try to dismiss the Keyguard. If no security pattern is set, this will dismiss the whole // Keyguard. If we need to authenticate, show the bouncer. if (!mKeyguardView.dismiss()) { mShowingSoon = true; // Split up the work over multiple frames. mChoreographer.postCallbackDelayed(Choreographer.CALLBACK_ANIMATION, mShowRunnable, null, 48); } } private void ensureView() { if (mRoot == null) { inflateView(); } } private void inflateView() { removeView(); mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null); mKeyguardView = (KeyguardViewBase) mRoot.findViewById(R.id.keyguard_host_view); mKeyguardView.setLockPatternUtils(mLockPatternUtils); mKeyguardView.setViewMediatorCallback(mCallback); mContainer.addView(mRoot, mContainer.getChildCount()); mRoot.setVisibility(View.INVISIBLE); mRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME); } private void removeView() { if (mRoot != null && mRoot.getParent() == mContainer) { mContainer.removeView(mRoot); mRoot = null; } }
上面这段的意思就是说,还没显示的就告诉他我要显示了,已经准备好了就直接显示出来;
调用的ensureView方法,表示确定在这里显示锁屏界面;
而ensureView则直接用一个打气筒把布局填充起来,填充的这个布局就是keyguard_bouncer,然后让这个布局显示出来,并禁用home键;
keyguard_bouncer,它不是直接在layout布局里加入的,只有用户设置锁屏保护后才可见。
KeyguardBouncer是锁屏解锁界面,根据用户设置的解锁方式不同,展示不同的解锁模式。
先看看KeyguardBouncer长什么样子:
锁屏界面:
上滑锁屏后:
需要注意的是KeyguardBouncer有多种形式,上图中展示的是图案解锁,若为密码解锁KeyguardBouncer将会以数字键盘的形式展示。但无论哪种形式,都是在KeyguardBouncer中加载进来的。
public class KeyguardBouncer { private ViewGroup mRoot; private ViewGroup mContainer; private KeyguardHostView mKeyguardView;private void inflateView() { mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null); mKeyguardView =(KeyguardHostView)mRoot.findViewById(R.id.keyguard_host_view); mKeyguardView.setLockPatternUtils(mLockPatternUtils); mKeyguardView.setViewMediatorCallback(mCallback); mContainer.addView(mRoot,mContainer.getChildCount());}
KeyguardBouncer的View树如下:
那么这个keyguard_host_view布局倒是长啥样呢?
<com.android.keyguard.KeyguardSimpleHostView 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"> <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: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_margin" android:gravity="center"> </com.android.keyguard.KeyguardSecurityViewFlipper> </com.android.keyguard.KeyguardSecurityContainer></com.android.keyguard.KeyguardSimpleHostView>
惊喜不惊喜,意外不意外?啥都没有,那是怎么显示的呢?
请注意这句:
mContainer.addView(mRoot, mContainer.getChildCount());
顺藤摸瓜:找mContainer
public KeyguardBouncer(Context context, ViewMediatorCallback callback, LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager, ViewGroup container) { mContext = context; mCallback = callback; mLockPatternUtils = lockPatternUtils; mContainer = container; mWindowManager = windowManager; }zls@compiler:~/zls/nexus_source/frameworks$ grep -rn "new KeyguardBouncer("base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java:88: mBouncer = new KeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, vim base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.javapublic void registerStatusBar(PhoneStatusBar phoneStatusBar, ViewGroup container, StatusBarWindowManager statusBarWindowManager, ScrimController scrimController) { mPhoneStatusBar = phoneStatusBar; mContainer = container; mStatusBarWindowManager = statusBarWindowManager; mScrimController = scrimController; mBouncer = new KeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, mStatusBarWindowManager, container); }zls@compiler:~/zls/nexus_source/frameworks$ grep -rn ".registerStatusBar("base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java:1016: mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,vim base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.javaprivate void startKeyguard() { KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class); mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this, mStatusBarWindow, mStatusBarWindowManager, mScrimController); mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback(); }
mStatusBarWindow就是我们要找的mContainer,我们看mStatusBarWindow,实际上就是整个解锁界面的父容器,里面包括了时钟,通知栏,导航栏等所有需要在界面上显示的部件,为了方便大家理解:
- Framework之锁屏分析与禁用锁屏
- Framework层Android4.4锁屏流程分析
- android 禁用锁屏 到修改方式
- iOS 禁用休闲时钟锁屏
- 锁屏密码输入界面禁用menu
- app-framework学习--nav的Scroller禁用与启用
- android framework分析与调试
- Entity Framework之深入分析
- Entity Framework之深入分析
- Android 4.0 加密后禁用锁屏的方法
- 深入Spring MVC framework之总体分析
- 深入Spring MVC framework之总体分析
- 深入Spring MVC framework之总体分析
- Android Framework 之PackageManagerService详细分析
- android_wifi读书笔记之2-wifi framework分析
- Android Framework------之Keyguard 简单分析
- Android Framework------之Keyguard 简单分析
- Android源码分析之Framework的MediaPlayer
- java变量排序
- px,dp,sp
- 数据结构之快速排序
- 浅谈js函数继承模式之三:共享原型模式
- 虚继承的偏移量问题
- Framework之锁屏分析与禁用锁屏
- 基于大数据技术的全国高速公路通行数据 动态监测平台建设
- JAVA8 lambda 微解
- 操作CSV文件例子,并且加上文字处理:统计相同功能的问题单(报告画图用)
- tensorflow和caffe共存问题
- 16进制转为10进制
- 报错localhost is not allowed to connect
- faster-rcnn在win10+cuda8.0+1080ti+vs2013+matlab2015b下的配置 疑难问题解答
- mysql中jdbcType的匹配