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,实际上就是整个解锁界面的父容器,里面包括了时钟,通知栏,导航栏等所有需要在界面上显示的部件,为了方便大家理解:
这里写图片描述