Android框架浅析之锁屏(Keyguard)机制原理
锁屏、状态栏、Launcher---姑且称之为“IDLE”小组,或许叫手机美容小组
1、分析锁屏界面的组成 ;
2、基于源代码分析锁屏相关类 ;
3、提出一种在框架取消锁屏的方法 。
本文分析版本具体是Android2.3版本。
源文件路径主要有两个:
frameworks\base\policy\src\com\android\internal\policy\impl\ ---->锁屏框架
frameworks\base\core\java\com\android\internal\widget\ ----> 提供了一些的自定义View.
一、锁屏界面的组成
通常 Android手机上大家常见的界面只有一种,成功后即可解锁进入界面了。其实在Android手机中,正常的锁屏界面由
两种不同性质的界面组成:
第一种界面称之为LockScreen界面(为了叙述方便,我们姑且称为“解锁界面),即我们通常所见到的界面,手机厂商一般定制
该界面。界面如下所示:
该界面对应自定义View的是LockScreen.java类
路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\LockScreen.java
第二种界面称之为UnLockScreen(为了后文叙述方便,我们姑且称为“开锁界面”),一般由Android源码提供,有如下四种:
①、图案开锁界面 ---- PatternUnlockScreen.java类 (自定义LinearLayout)
路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\PatternUnlockScreen.java
界面显示为:
②、PIN开锁界面 ---- SimUnlockScreen.java 类 (自定义LinearLayout)
路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\SimUnlockScreen.java
界面显示为: (图片省略)
③、密码开锁界面 ---- PasswordUnlockScreen.java类 (自定义LinearLayout)
路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\PasswordUnlockScreen.java
界面显示为:
④、GoogleAccount 开锁界面 ,即Google账户开锁界面。一般用于当用户输入密码错误次数超过上限值时,系统会提示
你输入Google账户去开锁。注意:开启它需要你手动设置账户与同步,否则该界面是不会出来的。
对应的源文件是: AccountUnlockScreen.java类 (自定义LinearLayout)
路径位于:frameworks\policies\base\phone\com\android\internal\policy\impl\AccountUnlockScreen.java
界面显示为:
可以按照如下办法选择开启哪一种开锁界面: 设置—>位置和安全—>设置屏幕锁定 ,具体选择那种开锁界面。
显示规则
当然,这两种界面的组合也是有很多变化的,总的规则如下:
首先显示LockScreen界面,接着判断是否开启了UnLockScreen界面,如果设置了UnLockScreen界面,则进入对应的
UnLockScreen界面去解锁,才算成功解锁。但,存在一种特殊的情况,就是假如我们选择了图案 UnLockScreen界面,是不会
显示LockScreen界面,而只会显示UnLockScreen界面。
二、锁屏界面的实现
任何一种界面都是由各种View/ViewGroup(当然包括自定义的)组成的,然后根据系统对应的状态值的改变去更新
这些View的显示状态,锁屏界面自然也是如此。锁屏界面的实现最顶层是采用了FrameLayout去控制的,当然内部也嵌套了很
多层,内嵌层数的增多的一点好处就是我们可以分开而治,具体针对每层去做相应的更新。
当界面复杂时,Google为开发人员提供的一款优秀工具---Hierarchy Viewer ,通过它,我们很清晰的弄明白整
个View树的继承层次,一个布局结构,当然,看源代码也是必须的。
Hierarchy Viewer介绍开始
在Android的SDK工具包中,有很多十分有用的工具,可以帮助程序员开发和测试Android应用程序,大大提高其工作效率。其中的一款叫Hierachy Viewer的可视化调试工具,可以很方便地在开发者设计,调试和调整界面时,提高用户的开发效率。本文将以一个实际例子讲解如何使用该款工具运用在Android的开发过程中。本文的读者对象为具备初步Android知识的用户。
步骤1 设计界面
在我们的这个例子中,有三个不同的界面,以方便我们演示使用Hierarchy Viewer。每一个界面都使用了LinearLayout和FrameLayout布局,以及文本框TextView及图片框ImageView控件。如下图,三个界面中的图案分别用了一只小猫,一个鱼缸,一条金鱼,它们各自的位置布局见下图:
在上面的三个图中,最左面的一个图,使用了一个垂直布局的LinearLayout,并且划分为两行,第一行是一个TextView文本框,里面的文字是“Safe”,第2行是一个FrameLayout帧布局,分别包含了一条鱼和一个鱼缸子。
中间的图中,使用了一个垂直布局的LinearLayout,并且划分为两行,第一行是写有“Unsafe”文本的文本框,第二行也有一个LinearLayout的水平布局,分别又包含了两个ImageView控件:一个鱼缸及鱼,还有一只小猫。
最右边的图中,使用了一个垂直布局的LinearLayout,并且划分为两行,第一行是写有“Yum” 文本的文本框,第2行是一个FrameLayout帧布局,分别包含了一条小猫和一条鱼。跟第一张图有点相象。
步骤2 启动应用程序
在设计界面后,我们直接用模拟器启动我们的应用程序,要注意的是,在debug状态下,是不能启动Hierachy Viewer的。
步骤3 启动Hierachy Viewer
目前,在eclipse的ADT Android插件中,还不能启动Hierachy Viewer,但可以从Android SDK工具包中,通过命令行的方式可以启动,具体方法为,到Android SDK下的tools目录下,在命令行方式下运行hierachyviewer即可。
在启动后,可以看到如下的界面,会显示当前正在运行中的模拟器的信息,这里我们可以鼠标点击我们已经启动了的Activity:
同时可以看到,有两个按钮,分别代表两个功能:
1)Load View Hierarchy (可以查看界面的控件层次)
2)Inspect Screenshot (进入界面精确查看模式)
下面分别介绍两者的功能。
步骤4 Inspecting Screenshots(界面精确查看模式)
先点Inspecting Screenshots按钮,进入界面精确查看模式。在这个模式下,开发者可以随意点界面的任意一部分,进行放大或缩小观察以查看界面中各控件的具体位置和情况,如下图所示:
同时,还可以将截取的界面另外保存为PNG格式的图片文件。
步骤5 Load View Hierarchy 查看界面的控件层次
接下来,我们重点学习如何在Load View Hierachy中查看界面中各个控件的层次结构关系。首先当点Load View Hierarchy按钮后,会进入如下图所示界面:
要注意的是,在屏幕的左下方,有三个按钮,当点最左边的按钮时,返回的是模拟器的列表界面(也就是刚进入Hierarchy Viwer的界面),中间按钮则是Load View Hierachy的主界面,用户可以在这两种状态中来回切换。
接下来,我们看上图,Load View Hierachy界面被划分为四个部分,分别是最左边(面积最大一块),该部分显示界面控件的层次结构,我们称之为主窗口,而右上方的一个部分,是以缩略图的方式显示整个应用中的各控件的层次关系,当如果一个界面中的控件比较多的时候,可以通过鼠标在这个显示区域进行移动,则左边的主窗口中会具体显示相关的控件信息。右边区域的中间部分,显示的每个控件的具体属性,是控件的属性面版。而右下角部分的区域,则是当用户点界面中的某个控件时,会在该部分显示区域,显示出用户所点的控件,在界面中的具体位置,会用红色部分标出,方便用户辨识。
步骤6 理解Hierachy Viewer的主窗口
最左部分的主窗口,将一个Activity中的所有控件的层次结构从左到右显示了出来,其中最右部分是最低一层的控件。用我们的例子来说,如果选择了第一个界面(即上文提到的三张图界面的最左边的一张),在主窗口的最右边,从右往左看,可以看到最右边的是两个ImageView控件:鱼缸的图片和小鱼的图片。
再往左看,会看到这两个控件实际上是被包裹在FrameLayout布局中,这里可以清楚看到指出了这个布局的id为@id/frameLayoutFishbowl。再往左边看,可以看到再上一层的控件:LinearLayout布局控件以及它包含的一个TextView控件(显示“Safe”字样)以及@id/frameLayoutFishbowl的frameLayout布局控件。
读者可以尝试,在Hierachy viewer中,查看另外两个activity界面中的控件,熟悉其中的用法。
步骤7 查看每个具体控件的情况
当在主窗口中,点击每一个控件时,将会可以看到很多关于这个控件的详细信息,会在该控件的上方弹出一个窗口,其中会显示该控件的实际的效果图外,通过view的数目显示了该控件及其子控件的数目,该控件的该节点的测量(measure)、布局(layout)以及画视图(draw)的时间,如下图:
如上图,这里1 view表明这个文本控件没再包含其他子控件了,只有1个就是它本身。而下方的带颜色的三个圆圈指示灯,分别说明了在测量(measure)、布局(layout)以及画视图(draw)三个阶段,这个控件所占用的时间百分比,如果是绿色的,表示该控件在该阶段比起其他的50%的控件的速度要快,为黄色的表示比起其他的50%的控件的速度要慢,为红色的则表示该控件在该阶段的处理速度是最慢的,如下图:
当我们按“display View”按钮后,在当我们点某个控件时,在稍等1-2秒后,会另外单独打开一个小窗口,显示该空间的单独效果图。
我们再来看下右上角的缩略显示窗口。当界面里的控件太多时,可以在这个窗口中,点选某一部分,随即会在主窗口中显示该区域控件的情况,而在右方中部的属性列表中,会显示所点击的控件的详细属性情况。右下角则显示所点击的控件在整个界面中的实际位置,如果勾选了show extra views,则还会在这个区域中,将控件的实际图片也显示出来,十分清晰。下图是我们点金鱼这个图片时,实际显示的效果:
步骤8 刷新显示
要注意的是,在Hierarchy Viewer中,当修改了界面后,需要手工点Refresh按钮,才能同步在Hierarchy Viewer中显示更新过后的界面情况。下图是分别对应用中的第2,第3个界面进行操作的示意图,可以看到,这两个界面的布局比第一个界面稍微复杂了。
对于Android的UI来说,invalidate和requestLayout是最重要的过程,Hierarchyviewer提供了帮助我们Debug特定的UI执行invalidate和requestLayout过程的途径,方法很简单,只要选择希望执行这两种操作的View点击按钮就可以。当然,我们需要在例如onMeasure()这样的方法中打上断点。这个功能对于UI组件是自定义的非常有用,可以帮助单独观察相关界面显示逻辑是否正确。
Hierarchy Viewer介绍结束
整个锁屏界面的继承层次如下(部分以及设置了图案开锁界面),更加完整的图请使用Hierarchy Viewer 工具查看。
上图中比较重要的几个视图说明如下:
LockPatternKeyguardView 继承至FrameLayout :作为LockScreen和UnLockScreen的载体,用来控制显示LockScreen
还是UnLockScreen界面。
LockScreen 继承至FrameLayout
PatterUnlockScreen ViewGroup类型 : 图案解锁界面
KeyguardViewHost继承至FrameLayout, 该ViewGroup作为顶层View,作为WindowManager的装饰对象添加至窗口。 它和LockPatternKeyguardView关系相当于DecorView和我们Activity内设置的资源布局一样。
三、锁屏机制的类结构说明
接下来我们分析下一些主要的类及其重要的函数,更多函数实现。
1、 KeyguardScreen 类 接口
功能:该接口的主要功能是为每个需要显示的界面:LockScreen或者UnLockScreen定义了四个方法,使其在不同的状态能够得到相应处理。优点就是:利用设计原则的面向接口编程,减少对具体对象的依赖。
路径:\frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardScreen.java
其源代码释义如下:
-
-
-
-
- public interface KeyguardScreen {
-
-
- boolean needsInput();
-
- void onPause();
-
- void onResume();
-
- void cleanUp();
- }
2、KeyguardScreenCallback类 接口
功能:每个需要显示的界面:LockScreen或者UnLockScreen都保存了该对象的唯一实例,用来向控制界面汇报情况。
路径:frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardScreenCallback.java
其源代码释义如下:
-
-
-
- public interface KeyguardScreenCallback extends KeyguardViewCallback {
-
- void goToLockScreen();
-
- void goToUnlockScreen();
-
- void forgotPattern(boolean isForgotten);
- boolean isSecure();
-
- boolean isVerifyUnlockOnly();
-
- void recreateMe(Configuration config);
-
- void takeEmergencyCallAction();
-
-
- void reportFailedUnlockAttempt();
-
-
- void reportSuccessfulUnlockAttempt();
-
-
-
- boolean doesFallbackUnlockScreenExist();
- }
其唯一实现类位于LockPatternKeyguardView类的内部类。
3、KeyguardViewCallback类 接口
功能: 提供了一些接口用来接受用户操作Screen的结果。
路径:frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardViewCallback.java
其源代码释义如下:
-
-
-
-
- public interface KeyguardViewCallback {
-
-
- void pokeWakelock();
-
- void pokeWakelock(int millis);
-
-
-
-
-
-
-
- void keyguardDone(boolean authenticated);
-
- void keyguardDoneDrawing();
- }
其唯一实现类是 KeyguardViewMediator类
4、 KeyguardWindowController类 接口
功能:提供通用 接口,判断该界面是否需要显示输入法窗口。
其源代码释义如下:
-
-
-
-
- public interface KeyguardWindowController {
-
-
- void setNeedsInput(boolean needsInput);
- }
其唯一实现类是KeyguardViewManager类。
5、KeyguardViewManager类
功能:包装了WindowManager功能了,提供了添加、删除锁屏界面的功能。
其源代码释义如下:
- public class KeyguardViewManager implements KeyguardWindowController {
- ...
- private WindowManager.LayoutParams mWindowLayoutParams;
- private boolean mNeedsInput = false;
-
- private FrameLayout mKeyguardHost;
- private KeyguardViewBase mKeyguardView;
-
-
- private boolean mScreenOn = false;
-
- public KeyguardViewManager(Context context, ViewManager viewManager,
- KeyguardViewCallback callback, KeyguardViewProperties keyguardViewProperties, KeyguardUpdateMonitor updateMonitor) {
- ...
- }
-
-
-
- private static class KeyguardViewHost extends FrameLayout {
- ...
- }
-
-
-
-
-
- public synchronized void show() {
- if (mKeyguardHost == null) {
- ...
- mViewManager.addView(mKeyguardHost, lp);
- }
- if (mKeyguardView == null) {
- ...
- mKeyguardHost.addView(mKeyguardView, lp);
- if (mScreenOn) {
- mKeyguardView.onScreenTurnedOn();
- }
- }
- ...
- }
- ...
-
-
- public synchronized void hide() {
- if (mKeyguardHost != null) {
- mKeyguardHost.setVisibility(View.GONE);
- ...
- }
- }
-
- public synchronized boolean isShowing() {
- return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE);
- }
- }
-
-
- }
-
6、 KeyguardUpdateMonitor.java类
功能:该类的主要功能就是根据监视系统状态值的改变(例如:时间、SIM卡状态、电池电量;使用广播监听),根据这种状态值的改变回调监听了该状态信息的对象实例。
其源代码释义如下:
- public class KeyguardUpdateMonitor {
- ...
- private int mFailedAttempts = 0;
- private ArrayList mInfoCallbacks;
- private ArrayList mSimStateCallbacks ;
- private static class SimArgs {
- ...
- }
-
-
-
- interface InfoCallback {
-
- void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel);
- void onTimeChanged();
-
- void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn);
-
- void onRingerModeChanged(int state);
-
- void onPhoneStateChanged(String newState);
- }
-
- interface SimStateCallback {
- void onSimStateChanged(IccCard.State simState);
- }
-
-
-
- public void registerInfoCallback(InfoCallback callback) {
- if (!mInfoCallbacks.contains(callback)) {
- mInfoCallbacks.add(callback);
- } ...
- }
- ...
- }
7, LockPatternKeyguardView类 (自定义ViewGroup)
功能:作为LockScreen和UnLockScreen界面的载体,控制显示哪个界面。
其源代码释义如下:
- public class LockPatternKeyguardView extends KeyguardViewBase {
- ...
- private View mLockScreen;
- private View mUnlockScreen;
-
- private boolean mScreenOn = false;
-
- enum Mode {
-
- }
- enum UnlockMode {
- ...
- }
-
- public LockPatternKeyguardView( ...) {
-
- mKeyguardScreenCallback = new KeyguardScreenCallback() {
- ...
- };
- ...
- }
- public void reset() {
- ...
- }
- private void recreateLockScreen() {
- ...
- }
- private void recreateUnlockScreen() {
- ...
- }
- private void recreateScreens() {
- ...
- }
- public void verifyUnlock() {
- ...
- }
- public void cleanUp() {
- ...
- }
- private boolean isSecure() {
- ...
- }
- private void updateScreen(final Mode mode) {
- ...
- }
- View createLockScreen() {
- ...
- }
- View createUnlockScreenFor(UnlockMode unlockMode) {
- ...
- }
- private Mode getInitialMode() {
- ...
- }
-
- private UnlockMode getUnlockMode() {
- ...
- }
- private void showTimeoutDialog() {
- ...
- }
- private void showAlmostAtAccountLoginDialog() {
- ...
- }
- }
8、KeyguardViewBase类 抽象类 (自定义ViewGroup)
功能:为LockPatternKeyguardView提供了一组通用的方法 。需要值得注意的方法就是他对某些KeyEvent的监听,当他消费监听到这些KeyEvent,App就监听不到这些KeyEvent了 。常用的有KEYEVENT_VOLUME_UP/DOWN等。
- public abstract class KeyguardViewBase extends FrameLayout {
- ...
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- ...
- if (interceptMediaKey(event)) {
- return true;
- }
- return super.dispatchKeyEvent(event);
- }
-
- private boolean interceptMediaKey(KeyEvent event) {
- final int keyCode = event.getKeyCode();
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- switch (keyCode) {
- ...
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN: {
- ...
-
- return true;
- }
- }
- }
- return false;
- }
- }
9、 KeyguardViewProperties.java 接口
功能:提供了创建界面的通用方法。
- public interface KeyguardViewProperties {
-
- KeyguardViewBase createKeyguardView(Context context,
- KeyguardUpdateMonitor updateMonitor,
- KeyguardWindowController controller);
-
- boolean isSecure();
- }
其唯一实现类是是LockPatternKeyguardViewProperties类。
10、LockPatternKeyguardViewProperties类
源代码释义如下:
- public class LockPatternKeyguardViewProperties implements KeyguardViewProperties {
- ...
-
- public KeyguardViewBase createKeyguardView(Context context,
- KeyguardUpdateMonitor updateMonitor,
- KeyguardWindowController controller) {
- return new LockPatternKeyguardView(context, updateMonitor,
- mLockPatternUtils, controller);
- }
11、KeyguardViewMediator核心类 ,该类是唯一实现了KeyguardViewCallback的类。
功能: 功能:该类提供了一些接口,由PhoneWindowManager)去访问控制Keyguard....
该类的初始化是在PolicyWindowManager的构造函数中创建的。如下:
- public class PhoneWindowManager implements WindowManagerPolicy {
- ...
-
- public void init(Context context, IWindowManager windowManager,
- LocalPowerManager powerManager) {
- ...
- mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager);
- }
- }
-
参照源代码,把一些重要的属性和方法的大意给分析下:
该类的很多方法都是由PhoneWindowManager调用访问的。
问题:如何在框架中, 解除锁屏 ?
基本思路:每次需要显示Keyguard---锁屏界面时,我们并不真正的去锁屏,而只是提供了一个空的方法去给系统调用,让系统觉得我们“锁屏”了,同样也不去真正的隐藏“锁屏”界面,提供一个空壳给系统调用。由于可能涉及到其它问题,例如:能否下拉状态栏,按下POWER键后,屏幕很快休眠等。Come on ,我们需要统一做处理。
所有步骤函数都发生在KeyguardViewMediator 类中,注释部分为我们所添加的。
Step 1、 取消 真正的去锁屏实现
-
- private void handleShow() {
- synchronized (KeyguardViewMediator.this) {
- if (DEBUG) Log.d(TAG, "handleShow");
- if (!mSystemReady) return;
-
- playSounds(true);
-
-
-
-
-
-
-
-
- try {
- ActivityManagerNative.getDefault().closeSystemDialogs("lock");
- } catch (RemoteException e) {
- }
- mShowKeyguardWakeLock.release();
- }
- }
Step 2、 取消 真正的去隐藏锁屏实现
-
- private void handleHide() {
- synchronized (KeyguardViewMediator.this) {
- if (DEBUG) Log.d(TAG, "handleHide");
- if (mWakeAndHandOff.isHeld()) {
- Log.w(TAG, "attempt to hide the keyguard while waking, ignored");
- return;
- }
-
-
- if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
- playSounds(false);
- }
-
-
-
-
-
-
-
-
- }
- }
以上两步行动后,存在一个Bug(问题),就是唤醒屏幕后,会在指定的时间内屏幕由亮变暗,我们还需要做如下修改
Step 3、按下POWER键时, 解除屏幕由亮变暗的Bug
- private void handleWakeWhenReady(int keyCode) {
- synchronized (KeyguardViewMediator.this) {
- if (DBG_WAKE) Log.d(TAG, "handleWakeWhenReady(" + keyCode + ")");
-
-
-
- if (!mKeyguardViewManager.wakeWhenReadyTq(keyCode)) {
-
- Log.w(TAG, "mKeyguardViewManager.wakeWhenReadyTq did not poke wake lock, so poke it ourselves");
-
-
-
- }
-
-
-
-
- mWakeAndHandOff.release();
-
- if (!mWakeLock.isHeld()) {
- Log.w(TAG, "mWakeLock not held in mKeyguardViewManager.wakeWhenReadyTq");
- }
- }
- }
上面Step 1、以及Step 2可以由如下方法代替:
将属性mExternallyEnabled 设置为 false, 接下来需要显示界面时都不会继续走下去,如下函数:
-
-
-
- private void doKeyguard() {
- synchronized (this) {
-
- if (!mExternallyEnabled) {
- if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
-
-
-
-
-
-
-
-
-
-
- return;
- }
- ...
- }
- }
该方法的一个缺点就是,假如存在重新调用了setKeyguardEnabled()设置该值,一切都是白搭( 但从源码看,这点不可能
出现,因为存在另一个判断依据:变量mNeedToReshowWhenReenabled , 其初始值为false,只有成功禁止锁屏之后才置为
true )。 因此,我们可以仿照这个方法,主动添加一个私有变量,禁止显示锁屏界面,即禁止doKeyguard()方法继续走下去。
显然易见,手机厂商,基于框架只需要修改LockScreen这个自定义ViewGroup即可,其他的一套Google已经为我们封装好了。
在框架层修改肯定不是最好的,对于第三方的App而言,实现不了该功能。还好,SDK为我们提供了接口类去处理隐藏锁屏接口
的方法,该类是KeyguardManager类:
我们可以通过KeyguardManager类实例获得一个KeyguardManager.KeyguardLock对象,进而调用相应方法去取消锁屏界面和显示锁屏界面。
KeyguardManager.KeyguardLock的两个方法说明如下:
public void disableKeyguard ()
功能:取消锁屏界面显示,同时禁止显示锁屏界面。除非你显示调用了reenableKeyguard()方法使能显示锁屏界面。
public void reenableKeyguard ()
功能: 使能显示锁屏界面,如果你之前调用了disableKeyguard()方法取消锁屏界面,那么会马上显示锁屏界面。
这两个方法最终都会调用到KeyguardViewMediator类的setKeyguardEnabled(boolean enable)方法。
参数说明: enable = false 对应于disableKeyguard()方法,
enable = true 对应于reenableKeyguard()方法。
该方法原型为: 位于KeyguardViewMediator类中
-
-
-
-
-
- public void setKeyguardEnabled(boolean enabled) {
- synchronized (this) {
- mExternallyEnabled = enabled;
-
- if (!enabled && mShowing) {
- if (mExitSecureCallback != null) {
- ...
- return ;
- }
- mNeedToReshowWhenReenabled = true;
- hideLocked();
- } else if (enabled && mNeedToReshowWhenReenabled) {
- mNeedToReshowWhenReenabled = false;
-
- if (mExitSecureCallback != null) {
-
- } else {
- showLocked();
- ...
- }
- }
- }
- }
使用这两个方法时,记得加上如下权限:android.permission.DISABLE_KEYGUARD
为了在亮屏时,达到取消显示界面的效果,我们还需要知道 一下两个广播:
屏幕变暗以及屏幕点亮的广播
android.intent.action.SCREEN_ON --- 屏幕变亮
android.intent.action.SCREEN_OFF ---- 屏幕点暗
于是在监听到屏幕变暗/变亮时,通过KeyguardManager 类实现即可。对与用户而言,就相当于解除了锁屏界面了。
可能代码如下:
-
- private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver(){
- @Override
- public void onReceive(Context context , Intent intent) {
- String action = intent.getAction() ;
-
- Log.i(TAG, intent.toString());
-
- if(action.equals("android.intent.action.SCREEN_OFF")
- || action.equals("android.intent.action.SCREEN_ON") ){
- mKeyguardManager = (KeyguardManager)context.getSystemService(Context.KEYGUARD_SERVICE);
- mKeyguardLock = mKeyguardManager.newKeyguardLock("zdLock 1");
- mKeyguardLock.disableKeyguard();
- startActivity(zdLockIntent);
- }
- }
-
- };
Android自定义锁屏实现----仿正点闹钟滑屏解锁
效果图:
(我们的解锁界面) (正点闹钟的解锁界面)
Hierarchy Viewer工具查看,我们重点关注下图蓝色区域,如下:
上图中的蓝色区域利用Hierarchy Viewer工具得出如下的布局文件组成:
(这三张图片采用了黑色背景,便于观看)
其中最顶层RelativeLayout采用了一个背景图片,即圆弧形的图片。
上图的编号解释如下 :
编号 1 :TextView控件,显示一个图片资源,可能采用了android:background或者android:drawableLeft类似属性显示图片。
编号 2 :TextView控件 ---- “向右滑动结束提醒”
编号 3 :ImageView控件 --- 为了显示动画效果,采用了一个对应的动画文件,作为该控件的背景图片,从其图片资源文件看验证了这点,采用了三张不同效果的图片。
编号 4 : ImageView控件,显示一张图片。
说明: 代码块中 节点实则为<RelativeLayout>节点 , 大家请注意下。
- "http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
-
- "@+id/slider_layout"
- android:layout_width="fill_parent"
- android:layout_height="63dip"
- android:layout_gravity="center"
- android:layout_marginTop="200dip"
- android:background="@drawable/step2_tip_2"
- >
-
- "@+id/getup_finish_ico"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="10dip"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:background="@drawable/slider_ico_alarm">
-
-
-
-
- "@+id/getup_arrow"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="20dip"
- android:layout_toLeftOf="@id/getup_finish_ico"
- android:layout_alignTop="@id/getup_finish_ico"
- android:background="@anim/slider_tip_anim">
-
-
-
- "wrap_content"
- android:layout_height="wrap_content"
- android:layout_toLeftOf="@id/getup_arrow"
- android:layout_alignTop="@id/getup_finish_ico"
- android:text="@string/hint_unlock">
-
-
-
- "@+id/slider_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="5dip"
- android:drawableTop="@drawable/getup_slider_ico_normal">
-
-
-
-
棘手问题: 如何实现动态效果,即绘制一个更随手指移动的东东 ?
如果参照正点闹钟显示的资源文件,即完全采用系统提供的控件,然后为这些控件提供触摸事件监听去滑动前面所说的编号为1的TextView控件----滑动可以调用scrollTo()或者scrollBy()方法,我试了几次但是都没有效果,原因是scrollTo()和scrollBy()方法并不偏移背景图片,仅仅偏移某个View的内容视图(onDraw()方法里绘制的),具体答案可以参考:
《Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解》的draw()方法流程。
自定义View/ViewGroup可以唯所欲为。因此,为了实现这种拖拽效果,实现了一个继承于RelativeLayout的自定义ViewGroup控件,并且重写了其中的onTouchEvent()方法去绘制一个跟随手指而动的Bitmap图片资源。当然,这只是第一步,还有很多细节需要处理,主要是一些坐标处理以及图片随着时间返回的问题,但这块内容大家看看代码注释加以理解吧。
下面的知识主要是如何以一个APK的形式去实现解锁界面。
知识点一 : 如何以APK形式去达到锁屏的目的以及 屏幕变暗以及屏幕点亮的广播。
android.intent.action.SCREEN_ON --- 屏幕变亮的广播
android.intent.action.SCREEN_OFF ---- 屏幕点暗的广播
请参考该博客:http://www.2cto.com/kf/201111/109815.html
知识点二:Activity里如何屏蔽Home键和Back键?
请参考该博客:http://www.cnblogs.com/domybest/archive/2011/06/13/2080036.html
知识点三:KeyguardManager简介
请参考该博客:http://hubingforever.blog.163.com/blog/static/171040579201191524550863/
具体原因可以去看看我上篇博客<
Android框架浅析之锁屏(Keyguard)机制原理
>的最后面所述。
为了帮助大家更好的理解代码,最后说明图片是如何回退的?
基本思路:我们根据当前Bitmap拖拽图片的所在位置,每隔几毫秒就让Bitmap拖拽图片回退一定距离,并且请求View会
重新绘制,直到该Bitmap拖拽图片返回初始地方,即可显示一个回退动画效果了。代码中是利用Handler来控制的。
自定义RelativeLayout代码:
- public class SliderRelativeLayout extends RelativeLayout {
-
- private static String TAG = "SliderRelativeLayout";
-
- private TextView tv_slider_icon = null;
- private Bitmap dragBitmap = null;
- private Context mContext = null;
-
- private Handler mainHandler = null;
-
-
- private int mLastMoveX = 1000;
-
- public SliderRelativeLayout(Context context) {
- super(context);
- mContext = context;
- initDragBitmap();
- }
-
- private void initDragBitmap() {
- if (dragBitmap == null)
- dragBitmap = BitmapFactory.decodeResource(mContext.getResources(),
- R.drawable.getup_slider_ico_pressed);
- }
-
- @Override
- protected void onFinishInflate() {
-
- super.onFinishInflate();
-
- tv_slider_icon = (TextView) findViewById(R.id.slider_icon);
- }
- public boolean onTouchEvent(MotionEvent event) {
- int x = (int) event.getX();
- int y = (int) event.getY();
- Log.i(TAG, "onTouchEvent" + " X is " + x + " Y is " + y);
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mLastMoveX = (int) event.getX();
-
- return handleActionDownEvenet(event);
- case MotionEvent.ACTION_MOVE:
- mLastMoveX = x;
- invalidate();
- return true;
- case MotionEvent.ACTION_UP:
-
- handleActionUpEvent(event);
- return true;
- }
- return super.onTouchEvent(event);
- }
-
-
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
-
- invalidateDragImg(canvas);
- }
-
-
- private void invalidateDragImg(Canvas canvas) {
-
-
- int drawXCor = mLastMoveX - dragBitmap.getWidth();
- int drawYCor = tv_slider_icon.getTop();
- Log.i(TAG, "invalidateDragImg" + " drawXCor "+ drawXCor + " and drawYCor" + drawYCor);
- canvas.drawBitmap(dragBitmap, drawXCor < 0 ? 5 : drawXCor , drawYCor , null);
- }
-
-
- private boolean handleActionDownEvenet(MotionEvent event) {
- Rect rect = new Rect();
- tv_slider_icon.getHitRect(rect);
- boolean isHit = rect.contains((int) event.getX(), (int) event.getY());
-
- if(isHit)
- tv_slider_icon.setVisibility(View.INVISIBLE);
-
-
-
- return isHit;
- }
-
-
- private static int BACK_DURATION = 20 ;
-
- private static float VE_HORIZONTAL = 0.7f ;
-
-
- private void handleActionUpEvent(MotionEvent event){
- int x = (int) event.getX() ;
- Log.e(TAG, "handleActionUpEvent : x -->" + x + " getRight() " + getRight() );
-
- boolean isSucess= Math.abs(x - getRight()) <= 15 ;
-
- if(isSucess){
- Toast.makeText(mContext, "解锁成功", 1000).show();
- resetViewState();
- virbate();
-
- mainHandler.obtainMessage(MainActivity.MSG_LOCK_SUCESS).sendToTarget();
- }
- else {
-
- mLastMoveX = x ;
- int distance = x - tv_slider_icon.getRight() ;
-
- Log.e(TAG, "handleActionUpEvent : mLastMoveX -->" + mLastMoveX + " distance -->" + distance );
- if(distance >= 0)
- mHandler.postDelayed(BackDragImgTask, BACK_DURATION);
- else{
- resetViewState();
- }
- }
- }
-
- private void resetViewState(){
- mLastMoveX = 1000 ;
- tv_slider_icon.setVisibility(View.VISIBLE);
- invalidate();
- }
-
-
- private Runnable BackDragImgTask = new Runnable(){
-
- public void run(){
-
- mLastMoveX = mLastMoveX - (int)(BACK_DURATION * VE_HORIZONTAL);
-
- Log.e(TAG, "BackDragImgTask ############# mLastMoveX " + mLastMoveX);
-
- invalidate();
-
- boolean shouldEnd = Math.abs(mLastMoveX - tv_slider_icon.getRight()) <= 8 ;
- if(!shouldEnd)
- mHandler.postDelayed(BackDragImgTask, BACK_DURATION);
- else {
- resetViewState();
- }
- }
- };
- private Handler mHandler =new Handler (){
- public void handleMessage(Message msg){
- Log.i(TAG, "handleMessage : #### " );
- }
- };
-
- private void virbate(){
- Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
- vibrator.vibrate(200);
- }
- public void setMainHandler(Handler handler){
- mainHandler = handler;
- }
- }
Activity采用了“singleTop”启动模式,防止启动多个Activity实例。
示例DEMO下载地址: http://download.csdn.net/detail/qinjuning/4295410