春节前最后几天了,工作上几乎没有什么要做。大致整理下之前工作中写的文档,PPT,手册. 由于去年一年完全转到Android4.0+平台上,Android2.3平台已经不再做什么项目,利用这几天先把Android2.3平台相关的文档整理下,算是对android平台一个总结。尽量都发布到这里,供大家参加吧。这些文档写的时候有参照网络上的,有的是从源代码里整理出来的,不过每一部分也都是通过分析源代码,一步步分析验证过的。
Android锁屏机制原理分析
本文主要内容:
1、分析锁屏界面的组成
2、源码定制锁屏主要工作
3、锁屏布局界面分析
4、锁屏界面实现
本文分析版本具体是Android2.3版本
文件路径主要有三个部分:
锁屏框架: frameworks\base\policy\src\com\android\internal\policy\impl\
自定义View:frameworks\base\core\java\com\android\internal\widget\
资源:frameworks\base\core\res\
通常 Android手机上大家常见的界面只有一种,成功后即可解锁进入界面了。其实在Android手机中,正常的锁屏界面由两种不同性质的界面组成:
锁屏界面是LockScreen;
解锁界面是UnLockScreen,包括:
SIM卡解锁SimUnlockScreen
图案解锁PatternUnlockScreen
密码解锁PasswordUnlockScreen
帐号解锁AccountUnlockScreen
第一种界面称之为LockScreen界面(为了叙述方便,我们称为“锁屏界面),即我们通常所见到的界面,手机厂商一般定制该界面。界面如下所示:
该界面对应自定义View的是LockScreen.java类路径位于:
frameworks\base\policy\src\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)
界面显示为:(图片省略)
③、密码开锁界面 ---- PasswordUnlockScreen.java类 (自定义LinearLayout)
路径位于:frameworks\base\policy\src\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界面。
一般自定义锁屏主要也是修改默认的锁屏LockScreen.java,故主要介绍一下默认的锁屏。
源码定制锁屏我们需要修改的主要文件包括:
1. 锁屏主页面
frameworks\policies\base\phone\com\android\internal\policy\impl\LockScreen.java
2. 滑动开锁控件
frameworks\base\core\java\com\android\internal\widget\SlidingTab.java
3. 时间显示
frameworks\base\core\java\com\android\internal\widget\DigitalClock.java
4. 竖屏布局文件
frameworks\base\core\res\res\layout\keyguard_screen_tab_unlock.xml
5. 横屏布局文件
frameworks\base\core\res\res\layout\keyguard_screen_tab_unlock_land.xml
接下来分析整个界面的布局。锁屏界面(竖屏)如下图:
分析文件(以竖屏为例):frameworks\base\core\res\res\layout\keyguard_screen_tab_unlock.xml
<?xml version="1.0" encoding="utf-8"?> <!-- ** ** Copyright 2009, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License") ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ --> <!-- This is the general lock screen which shows information about the state of the device, as well as instructions on how to get past it depending on the state of the device. It is the same for landscape and portrait.--> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tabunlock="http://schemas.android.com/apk/res/com.android.tabunlock" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#70000000" android:gravity="center_horizontal" android:id="@+id/root"> <TextView android:id="@+id/carrier" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:layout_marginTop="10dip" android:layout_marginRight="8dip" android:singleLine="true" android:ellipsize="marquee" android:gravity="right|bottom" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="22sp" android:visibility="gone" /> <!-- Carrier info for second subscription--> <TextView android:id="@+id/carrier_sub2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/carrier" android:layout_alignParentRight="true" android:layout_marginTop="10dip" android:layout_marginRight="8dip" android:singleLine="true" android:ellipsize="marquee" android:gravity="right|bottom" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="22sp" android:visibility="gone" /> <!-- "emergency calls only" shown when sim is missing or PUKd --> <TextView android:id="@+id/emergencyCallText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/carrier_sub2" android:layout_alignParentRight="true" android:layout_marginTop="0dip" android:layout_marginRight="8dip" android:text="@string/emergency_calls_only" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@color/white" /> <!-- time and date --> <com.android.internal.widget.DigitalClock android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/carrier_sub2" android:layout_marginTop="52dip" android:layout_marginLeft="20dip" android:paddingBottom="8dip" > <TextView android:id="@+id/timeDisplay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" android:textSize="72sp" android:textAppearance="?android:attr/textAppearanceMedium" android:shadowColor="#C0000000" android:shadowDx="0" android:shadowDy="0" android:shadowRadius="3.0" android:layout_marginBottom="10dip" /> <TextView android:id="@+id/am_pm" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/timeDisplay" android:layout_alignBaseline="@id/timeDisplay" android:singleLine="true" android:ellipsize="none" android:textSize="22sp" android:layout_marginLeft="8dip" android:textAppearance="?android:attr/textAppearanceMedium" android:shadowColor="#C0000000" android:shadowDx="0" android:shadowDy="0" android:shadowRadius="3.0" /> </com.android.internal.widget.DigitalClock> <TextView android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/time" android:layout_marginLeft="24dip" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/status1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/date" android:layout_marginTop="4dip" android:layout_marginLeft="24dip" android:textAppearance="?android:attr/textAppearanceMedium" android:drawablePadding="4dip" /> <TextView android:id="@+id/status2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/status1" android:layout_marginTop="4dip" android:layout_marginLeft="24dip" android:textAppearance="?android:attr/textAppearanceMedium" android:drawablePadding="4dip" /> <TextView android:id="@+id/screenLocked" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/status2" android:layout_marginLeft="24dip" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_marginTop="12dip" android:drawablePadding="4dip" /> <com.android.internal.widget.SlidingTab android:id="@+id/tab_selector" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginBottom="80dip" /> <!-- emergency call button shown when sim is PUKd and tab_selector is hidden --> <!--PUK Input Add start--> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_alignParentBottom="true" android:layout_marginBottom="10dip" android:drawablePadding="8dip" > <Button android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_emergency" style="@style/Widget.Button.Transparent" android:visibility="gone" /> <Button android:id="@+id/pukCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/Widget.Button.Transparent" android:visibility="gone" /> </LinearLayout> <!--PUK Input Add End-->
上面是android2.3源代码。
@+id/carrier:显示运营商信息,没有插入SIM卡时显示“没有SIM卡”,没有信号时会显示“(无服务)”
@+id/emergencyCallText:固定字符串。“只能使用紧急呼叫”,在没有插入SIM卡时显示。
@+id/time:显示时间信息,包括12和24小时制。这里是单独写了一个ViewGroup,后面会专门介绍。
@+id/date:显示日期信息,包括星期。如果需要修改格式,请在对应语言的donottranslate-cldr.xml文件中找到需要的格式
@+id/status1、@+id/status2:分别显示充电、闹钟信息。
@+id/tab_selector:滑动解锁
@+id/emergencyCallButton:紧急拨号按钮。当没有插入SIM卡时显示。
这里可以对布局进行任意修改,但建议不要修改ID名称,因为上面所显示的都是常用的信息,一般手机都会包括这些。如果确实要删除,请先确定别的类中是否有包含这些控件。比如打电话和接电话界面。
注意问题:这里如果删除或者新增控件,需要全部编译,不能只编译framework层代码。具体什么原因暂时未找到。
任何一种界面都是由各种View/ViewGroup(当然包括自定义的)组成的,然后根据系统对应的状态值的改变去更新这些View的显示状态,锁屏界面自然也是如此。锁屏界面的实现最顶层是采用了FrameLayout去控制的,当然内部也嵌套了很多层,内嵌层数的增多的一点好处就是我们可以分开而治,具体针对每层去做相应的更新。难处就是看代码看的很郁闷。
当界面复杂时,可以借用工具---Hierarchy Viewer ,通过它,我们很清晰的弄明白整个View树的继承层次,一个布局结构,当然,看源代码也是必须的。
整个锁屏界面的继承层次如下(部分以及设置了图案开锁界面),更加完整的图大家可以使用Hierarchy Viewer 工具查看。
上图中比较重要的几个视图说明如下:
LockPatternKeyguardView 继承自FrameLayout :作为LockScreen和UnLockScreen的载体,用来控制显示LockScreen还是UnLockScreen界面。
LockScreen 继承自FrameLayout
PatterUnlockScreen ViewGroup类型 : 图案解锁界面
KeyguardViewHost 继承自FrameLayout, 该ViewGroup作为顶层View,作为WindowManager的装饰对象添加至窗口。
它和LockPatternKeyguardView关系相当于DecorView和我们Activity内设置的资源布局一样。为了在亮屏时,达到取消显示界面的效果,我们还需要知道 一下两个广播:
屏幕变暗以及屏幕点亮的广播
android.intent.action.SCREEN_ON ----- 屏幕变亮
android.intent.action.SCREEN_OFF ----- 屏幕点暗
功能:该接口的主要功能是为每个需要显示的界面LockScreen或者UnLockScreen定义了四个方法,使其在不同的状态能够得到相应处理。
路径:\frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardScreen.java
其源代码释义如下:
/** * Common interface of each {@link android.view.View} that is a screen of * {@link LockPatternKeyguardView}. */ public interface KeyguardScreen { /** Return true if your view needs input, so should allow the soft * keyboard to be displayed. */ boolean needsInput(); //View是否需要输入数值,即该界面需要键盘输入数值 /** This screen is no longer in front of the user.*/ void onPause();//当该界面不处于前台界面时调用,包括处于GONE或者该界面即将被remove掉 /** This screen is going to be in front of the user. */ void onResume();//相对于onPause()方法,当该界面不处于前台界面时调用,处于VISIBLE状态时调用 /** This view is going away; a hook to do cleanup. */ void cleanUp();//该界面即将被remove掉 ,即不在需要 }
功能:每个需要显示的界面:LockScreen或者UnLockScreen都保存了该对象的唯一实例,用来向控制界面汇报情况。
路径:frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardScreenCallback.java
其源代码释义如下:
/** Within a keyguard, there may be several screens that need a callback * to the host keyguard view. */ public interface KeyguardScreenCallback extends KeyguardViewCallback { /** Transition to the lock screen*/ void goToLockScreen(); //当前界面跳转为LockScreen ,而不是UnLockScreen /** Transition to the unlock screen.*/ void goToUnlockScreen();//LockScreen成功开锁 ,是否需要显示UnLockScreen,否则,直接开锁成功。 //忘记了开锁图案,即我们需要跳转到Google 账户去开锁。 void forgotPattern(boolean isForgotten); boolean isSecure();//当前机器是否安全,例如:设置了图案、密码开锁等 boolean isVerifyUnlockOnly(); /**Stay on me, but recreate me (so I can use a different layout).*/ void recreateMe(Configuration config); //重新根据手机当前状态,显示对应的Screen. /** Take action to send an emergency call. */ void takeEmergencyCallAction(); //紧急呼叫时的处理行为. /** Report that the user had a failed attempt to unlock with password or pattern.*/ void reportFailedUnlockAttempt(); //在UnLockScreen界面登陆失败时处理 /** Report that the user successfully entered their password or pattern.*/ void reportSuccessfulUnlockAttempt();//在UnLockScreen界面登陆成功时处理 /** Report whether we there's another way to unlock the device. * @return true */ boolean doesFallbackUnlockScreenExist(); }
功能: 提供了一些接口用来接受用户操作Screen的结果。
路径:frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardViewCallback.java
其源代码释义如下:
/** * The callback used by the keyguard view to tell the {@link KeyguardViewMediator} * various things. */ public interface KeyguardViewCallback { /** Request the wakelock to be poked for the default amount of time. */ void pokeWakelock(); //保存屏幕在一定时间内处于亮屏状况 , 默认时间为5s或者10s /** Request the wakelock to be poked for a specific amount of time. */ void pokeWakelock(int millis);//根据给定时间值,使屏幕在该事件段内保持亮屏状况 /** Report that the keyguard is done. * @param authenticated Whether the user securely got past the keyguard. * the only reason for this to be false is if the keyguard was instructed * to appear temporarily to verify the user is supposed to get past the * keyguard, and the user fails to do so. */ //成功的完成开锁,可以进入手机界面了。参数为ture表示是否正大光明的开锁,例如:图案正确,密码输入正确。 void keyguardDone(boolean authenticated); /**Report that the keyguard is done drawing. */ void keyguardDoneDrawing(); //整个锁屏界面draw()过程绘制完成时,回调该方法. }
说明:
Android平台锁屏机制相差并不是很大,高版本平台的锁屏机制都会处于各种原因对之前版本的锁屏模块进行改进和完善,但是只要分析明白其中一个版本的锁屏原理和流程,那么对于其他版本的锁屏模块都会很快搞明白。