背景  
 
   先说说背景吧,这是本人从WinCE系统转到Android之后,接到的第一个任务就是修改Android原生的解锁界面,之前看了两个星期的书和网络博客,Java的也有、Android应用开发的也有、Linux开发的也有、Android框架介绍的也有。然后写了几个APK试了了一下,觉得自己有能力了,便充满自信地找到组长接任务。组长没有说什么,拿出一个竞争对手公司的样机,玩了几下拿个我,说道:这是竞争对手公司的方案,他们的解锁效果不错,很方便,你看看能不能也做出来。
   说实话接到这个任务当时真的有点失望,心里面一直想,当初做WinCE做的几乎全是驱动,整天和寄存器、指针、协议打交道,现在转到Android,做这些应用的东西真的不太习惯。不过转头一想,不管什么东西,要做就要做好,慢慢来嘛,于是拿走样机回到工位上,安心开始研究。
   样机解锁界面效果类似如下
  其实这也是我后面做出的效果,样机因为没有ROOT不好调试截图,功能是将解锁的图标添加了最近运行的运用的图标,这样的话更方便用户去使用
1.分析 
1.1.Android锁屏功能分析
Android锁屏相关的代码在以下几个路径:
锁屏的具体实现:
|  | \frameworks\base\policy\src\com\android\internal\policy\impl | 
其中的主要代码如下:

锁屏控件的View类
| \frameworks\base\core\java\com\android\internal\widget\multiwaveview | 

锁屏控件使用到的资源
| \frameworks\base\core\res\res\values-sw600dp-land\arrays.xml \frameworks\base\core\res\res\drawableXXX | 
Android上常用的锁屏方法有以下几种:默认锁屏方式(LockScreen)、SIM卡解锁方式(SimUnlockScreen)、图案解锁方式(PatternUnlockScreen)、密码解锁方式(PasswordUnlockScreen)、账号解锁方式(AccountUnlockScreen),这些解锁方式都有对应的源码实现,我们这里讨论的是最常用的默认解锁方式,在Android4.0之后,解锁控件变为“波纹解锁”,即如概述介绍的那样,通过控制中心的圆圈来实现解锁,这种解锁方式,实际上可以进一步增强。
 
1.1.1.  Android启动后从窗口管理器运行到解锁界面的动作:
1.开机启动后执行到PhoneWindowManager.systemReady()。
2.调用KeyguardViewMediator.onSystemReady()进行待机锁屏及解锁逻辑。
3.KeyguardViewMediator是整个待机解锁屏业务的调度器,负责调度锁屏界面的相关动作及查询解锁屏状态。
1.1.2.  KeyguardViewMediator的作用
1.查询锁屏状态,及当前处于锁屏状态还是已解锁状态,PhoneWindowManager持有KeyguardViewMediator的引用,当用户触摸屏幕或者按下某个键是,PhoneWindowManager会通过KeyguardViewMediator查询锁屏状态(锁定/解锁),进行不同的响应处理。如果处于锁定状态,系统输入事件会受到限制。
2.响应电源事件(黑/亮屏)。判断锁屏界面应该处于什么状态(显示或者重置)。手机黑屏后,锁屏界面马上就会显示出来,以便下一次亮屏后,马上就能显示锁屏界面,而不会出现闪烁或延时。
3.其他应用程序或者服务也可以请求禁止锁屏(通过调用KeyguardViewMediator的setKeyguardEnabled(boolean)方法)。例如接听来电界面。
KeyguardViewMediator类在WindowManagerPolicy(在手机系统中是PhoneWindowManager实例)初始化时被创建,并运行在它的线程上,锁屏的UI界面也是在这个线程上创建及显示的。KeyguardViewMediator类提供的状态查询api可以被诸如android.view.WindowManager、com.android.server.InputManager等其它线程调用,所以,KeyguardViewMediator类上的这些api方法都是线程同步的(synchronized)。
1.1.3.  KeyguardViewMediator可以进行的调度操作
1) 点亮屏幕pokeWakelock();
2) 报告锁屏权限验证是否成功keyguardDone(boolean);
3) 响应SIM卡状态变化并对锁屏界面做相应的调整onSimStateChanged()。
4) 调度待机锁屏UI界面的管理,包括:
1.显示handleShow ()、
2.隐藏handleHide ()、
3.重置handleReset ()、
4.点亮屏幕handleWakeWhenReady()等。
KeyguardViewMediator实现这部分调度是通过持有一个KeyguardViewManager来实现的。总之KeyguardUpdateMonitor是所有会影响整个待机解/锁屏业务的事件的监控器。(除了作为监控器,它还发挥着类似上下文的作用,也许我们应该把这个类命名为(KeyguardContext)。它监控诸如时间改变、电池状态改变、时区改变、SIM卡状态变化、电话状态变化、电话信号变化等事件。它是一个观察者模式的被观察对象。观察者通过调用KeyguardUpdateMonitor的以下方法进行注册,观察自己感兴趣的变化。
| registerInfoCallback(InfoCallback)registerSimStateCallback(SimStateCallback) | 
 
    KeyguardUpdateMonitor的观察者包括KeyguardViewMediator、LockScreen、PatternUnlockScreen、AccountUnlockScreen、PasswordUnlockScreen、SimUnlockScreen等。观察者通过调用KeyguardUpdateMonitor的removeCallback(Object)取消观察。
KeyguardViewManager负责管理待机屏UI界面的创建、显示、隐藏、重置以及通过一个回调KeyguardViewCallback通知调度器KeyguardViewMediator进行相关的调度。
LockPatternKeyguardView(KeyguardViewBase)是所有锁屏和解锁UI界面的宿主。它有2个模式Mode. LockScreen和Mode. UnlockScreen。它负责根据当前上下文环境切换当前应该显示的待机屏。
它提供一个回调给当前显示的待机屏并处理其回调,如果回调动作是自己处理不了的,则继续报告给KeyguardViewMediator进行处理。
锁屏界面就是LockScreen;解锁界面包括SIM卡解锁SimUnlockScreen、图案解锁PatternUnlockScreen、密码解锁PasswordUnlockScreen、帐号解锁AccountUnlockScreen
解锁成功后,锁屏流程转到KeyguardViewMediator的keyguardDone(boolean, boolean) 进行后续的流程(如转到Launcher桌面)。
1.2. 解锁界面布局
解锁界面布局在LockScreen类的构造函数中进行,LockScreen构造函数内容如下:
- LockScreen(Context context, Configuration configuration, LockPatternUtils lockPatternUtils,
-             KeyguardUpdateMonitor updateMonitor,
-             KeyguardScreenCallback callback) {
-         super(context);
-         mLockPatternUtils = lockPatternUtils;
-         mUpdateMonitor = updateMonitor;
-         mCallback = callback;
-         mEnableMenuKeyInLockScreen = shouldEnableMenuKey();
-         mCreationOrientation = configuration.orientation;
-         mKeyboardHidden = configuration.hardKeyboardHidden;
-         if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
-             Log.v(TAG, "***** CREATING LOCK SCREEN", new RuntimeException());
-             Log.v(TAG, "Cur orient=" + mCreationOrientation
-                     + " res orient=" + context.getResources().getConfiguration().orientation);
-         }
-         final LayoutInflater inflater = LayoutInflater.from(context);
-         if (DBG) Log.v(TAG, "Creation orientation = " + mCreationOrientation);
-         if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) {
-             inflater.inflate(R.layout.keyguard_screen_tab_unlock, this, true);
-         } else {
-             inflater.inflate(R.layout.keyguard_screen_tab_unlock_land, this, true);
-         }
-         mStatusViewManager = new KeyguardStatusViewManager(this, mUpdateMonitor, mLockPatternUtils,
-                 mCallback, false);
-         setFocusable(true);
-         setFocusableInTouchMode(true);
-         setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-         mSilentMode = isSilentMode();
-         mUnlockWidget = findViewById(R.id.unlock_widget);
-         if (mUnlockWidget instanceof SlidingTab) {
-             SlidingTab slidingTabView = (SlidingTab) mUnlockWidget;
-             slidingTabView.setHoldAfterTrigger(true, false);
-             slidingTabView.setLeftHintText(R.string.lockscreen_unlock_label);
-             slidingTabView.setLeftTabResources(
-                     R.drawable.ic_jog_dial_unlock,
-                     R.drawable.jog_tab_target_green,
-                     R.drawable.jog_tab_bar_left_unlock,
-                     R.drawable.jog_tab_left_unlock);
-             SlidingTabMethods slidingTabMethods = new SlidingTabMethods(slidingTabView);
-             slidingTabView.setOnTriggerListener(slidingTabMethods);
-             mUnlockWidgetMethods = slidingTabMethods;
-         } else if (mUnlockWidget instanceof WaveView) {
-             WaveView waveView = (WaveView) mUnlockWidget;
-             WaveViewMethods waveViewMethods = new WaveViewMethods(waveView);
-             waveView.setOnTriggerListener(waveViewMethods);
-             mUnlockWidgetMethods = waveViewMethods;
-         } else if (mUnlockWidget instanceof MultiWaveView) {
-             MultiWaveView multiWaveView = (MultiWaveView) mUnlockWidget;
-             MultiWaveViewMethods multiWaveViewMethods = new MultiWaveViewMethods(multiWaveView);
-             multiWaveView.setOnTriggerListener(multiWaveViewMethods);
-             mUnlockWidgetMethods = multiWaveViewMethods;
-         } else {
-             throw new IllegalStateException("Unrecognized unlock widget: " + mUnlockWidget);
-         }
-         // Update widget with initial ring state
-         mUnlockWidgetMethods.updateResources(context);
-         if (DBG) Log.v(TAG, "*** LockScreen accel is "
-                 + (mUnlockWidget.isHardwareAccelerated() ? "on":"off"));
-     }
 
通过添加打印发现在480分辨率下采用的Layout文件为\layout-sw480dp\keyguard_screen_tab_unlock_land.xml,此文件的内容如下:
 
   从文件中可以看出,解锁界面的数字时钟、充电状态、波纹解锁等控件均在其中布局,对解锁控件的大小修改也是通过修改该文件进行的。
 
1.3. MultiWaveView控件分析
对于Android4.0默认的LockScreen,采用的是MultiWaveView控件,LockScreen中创建该控件的代码如下:
 
- mUnlockWidget = findViewById(R.id.unlock_widget);
-         if (mUnlockWidget instanceof SlidingTab) {
-             SlidingTab slidingTabView = (SlidingTab) mUnlockWidget;
-             slidingTabView.setHoldAfterTrigger(true, false);
-             slidingTabView.setLeftHintText(R.string.lockscreen_unlock_label);
-             slidingTabView.setLeftTabResources(
-                     R.drawable.ic_jog_dial_unlock,
-                     R.drawable.jog_tab_target_green,
-                     R.drawable.jog_tab_bar_left_unlock,
-                     R.drawable.jog_tab_left_unlock);
-             SlidingTabMethods slidingTabMethods = new SlidingTabMethods(slidingTabView);
-             slidingTabView.setOnTriggerListener(slidingTabMethods);
-             mUnlockWidgetMethods = slidingTabMethods;
-         } else if (mUnlockWidget instanceof WaveView) {
-             WaveView waveView = (WaveView) mUnlockWidget;
-             WaveViewMethods waveViewMethods = new WaveViewMethods(waveView);
-             waveView.setOnTriggerListener(waveViewMethods);
-             mUnlockWidgetMethods = waveViewMethods;
-         } else if (mUnlockWidget instanceof MultiWaveView) {
-             MultiWaveView multiWaveView = (MultiWaveView) mUnlockWidget;
-             MultiWaveViewMethods multiWaveViewMethods = new MultiWaveViewMethods(multiWaveView);
-             multiWaveView.setOnTriggerListener(multiWaveViewMethods);
-             mUnlockWidgetMethods = multiWaveViewMethods;
-         } else {
-             throw new IllegalStateException("Unrecognized unlock widget: " + mUnlockWidget);
-         }
 
这是LockScreen的构造函数中的代码,函数根据R.id.unlock_widget定义的类型选择不同的控件类,其中第一种是Android2.3的滑动解锁类,第二种的简单的波纹解锁类,第三种才是我们使用的MultiWaveView类,函数并创建了一个MultiWaveViewMethods类,这个类实际上是为了更好地使用而进行的封装,它的代码如下:
- class MultiWaveViewMethods implements MultiWaveView.OnTriggerListener,
-             UnlockWidgetCommonMethods {
-         private final MultiWaveView mMultiWaveView;
-         private boolean mCameraDisabled;
-         MultiWaveViewMethods(MultiWaveView multiWaveView) {
-             mMultiWaveView = multiWaveView;
-             final boolean cameraDisabled = mLockPatternUtils.getDevicePolicyManager()
-                     .getCameraDisabled(null);
-             if (cameraDisabled) {
-                 Log.v(TAG, "Camera disabled by Device Policy");
-                 mCameraDisabled = true;
-             } else {
-                 // Camera is enabled if resource is initially defined for MultiWaveView
-                 // in the lockscreen layout file
-                 mCameraDisabled = mMultiWaveView.getTargetResourceId()
-                         != R.array.lockscreen_targets_with_camera;
-             }
-         }
-         public void updateResources() {
-             int resId;
-             if (mCameraDisabled) {
-                 // Fall back to showing ring/silence if camera is disabled by DPM...
-                 resId = mSilentMode ? R.array.lockscreen_targets_when_silent
-                     : R.array.lockscreen_targets_when_soundon;
-             } else {
-                 resId = R.array.lockscreen_targets_with_camera;
-             }
-             mMultiWaveView.setTargetResources(resId);
-         }
-         public void onGrabbed(View v, int handle) {
-         }
-         public void onReleased(View v, int handle) {
-         }
-         public void onTrigger(View v, int target) {
-             if (target == 0 || target == 1) { // 0 = unlock/portrait, 1 = unlock/landscape
-                 mCallback.goToUnlockScreen();
-             } else if (target == 2 || target == 3) { // 2 = alt/portrait, 3 = alt/landscape
-                 if (!mCameraDisabled) {
-                     // Start the Camera
-                     Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
-                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                     mContext.startActivity(intent);
-                     mCallback.goToUnlockScreen();
-                 } else {
-                     toggleRingMode();
-                     mUnlockWidgetMethods.updateResources();
-                     mCallback.pokeWakelock();
-                 }
-             }
-         }
-         public void onGrabbedStateChange(View v, int handle) {
-             // Don‘t poke the wake lock when returning to a state where the handle is
-             // not grabbed since that can happen when the system (instead of the user)
-             // cancels the grab.
-             if (handle != MultiWaveView.OnTriggerListener.NO_HANDLE) {
-                 mCallback.pokeWakelock();
-             }
-         }
 
这个类有两个方法特别重要,一个是更新MultiWaveView的资源,即public void updateResources(),另一个是对解锁后的响应,即public void onTrigger(View v, int target),这方法传入的第二个参数为解锁选择的图标编号,图标是从右向左逆时针编号的,即最右边的图标编号为0,在此函数中即可进行解锁的处理,选择是进入主界面还是启动其他的Activity。Android4.0默认target = 0对应的是进入主界面,
target = 2或者target = 3启动Camera,其解锁界面的图标是每次都是使用固定的资源,在完成解锁后按下Power键或者系统再次进入锁定状态,LockScreen都会再构造一次,会重新布局并加载资源,因此可以使每次解锁界面都不一样。
2. 实现2.1. 获取最近运行程序
在Android中可以通过ActivityManager获取到最近运行的Activity,详细的用法如下:

输入参数是需要查询的最大最近运行任务个数,查询的方式(默认采用ActivityManager.RECENT_IGNOR_UNAVAILABLE),返回ActivityManager.RecentTaskInfo对象,其定义如下:

其中的baseIntent为启动最近运行任务的Intent,通过它我们可以很方便地获取Activity的图标并启动最近运行的Activity。
相关的代码如下:
| final ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);            List<ActivityManager.RecentTaskInfo> recentLs = am.getRecentTasks(7, ActivityManager.RECENT_IGNORE_UNAVAILABLE); | 
 
2.1.1. 获取Activity图标的方法
通过PackManager我们可以很方便地获取到Activity的图标,对应的代码如下:
| final PackageManager pm = context.getPackageManager();  pm.getActivityIcon(recentLs.get(i).baseIntent) | 
 
2.2.          修改/system/build.prop文件
这个文件中包含了大量Android中的配置信息,其中也有和显示分辨率相关的,修改的方法如下:
1.通过ADB将文件获取到主机任意文件夹。
| adb pull /system/build.prop | 
2.用编辑软件打开build.prop,找到如下行

2.将ro.sf.lcd_density=120修改为=160或者直接删除掉这一行(这样就使用默认160)。
3.执行
| adb push build.prop /system/ | 
 
 
2.3. 修改MultiWaveView控件
修改以下位置的源文件:
| Z:\exdroid\android4.0.1\frameworks\base\core\java\com\android\internal\widget\multiwaveview\MultiWaveView.java | 
重载setTargetResources方法,重载后的方法如下:
 
- public void setTargetResources(ArrayList<Drawable> drawables)
-      {
-        Resources res = getContext().getResources();
-        int count = drawables.size();
-        ArrayList<TargetDrawable> targetDrawables = new ArrayList<TargetDrawable>(count);
-         for (int i = 0; i < count; i++) {
-             Drawable drawable = drawables.get(i);
-             targetDrawables.add(new TargetDrawable(res, drawable));
-               Log.v(TAG,"Add a Drawable");
-         }
-         mTargetDrawables = targetDrawables;
-         updateTargetPositions();
-      }
 
重载后的方法支持直接传入图片链表。这个方法是提供给LockScreen调用的
2.4.修改LockScreen.java文件
重载其中的MultiWaveViewMethods类的updateResources方法,重载后的方法如下:
 
- public void updateResources(Context context) 
-          { 
-               ArrayList<Drawable> drawableAl = new ArrayList();
-               final ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); 
-               final PackageManager pm = context.getPackageManager(); 
-               drawableAl.add(getView().getResources().getDrawable(R.drawable.ic_lockscreen_unlock));
-               List<ActivityManager.RecentTaskInfo> recentLs = am.getRecentTasks(7, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
-               for(int i=0;i<recentLs.size();i++)
-               {
-                    try
-                    {
-                        drawableAl.add(pm.getActivityIcon(recentLs.get(i).baseIntent));
-                        mIntentList.add(recentLs.get(i).baseIntent);
-                    }
-                    catch(Exception e)
-                    {
-                        Log.v(TAG,"Catch Exception");
-                    }
-               }
-               mMultiWaveView.setTargetResources(drawableAl);
-          }
 
重载后的updateResources函数实际上先获取最近运行的任务列表,再获取任务图标,最后用获取的图标设置MultiWaveView控件。
修改解锁响应onTigger:
- public void onTrigger(View v, int target) {
-               Log.v(TAG,"onTrigger = " + target);
-               if(target ==0)
-               {
-                    mCallback.goToUnlockScreen();
-               }
-               else
-               {
-                    if(mIntentList.get(target-1)!=null)
-                    {
-                        Intent intent = mIntentList.get(target-1);
-                        mContext.startActivity(intent);
-                        mCallback.goToUnlockScreen();
-                    }
-               }
-         }
 
这样解锁事件的响应被修改成0号图标解锁,其余启动对应的Activity。
2.5. 调整解锁圆圈大小
启动应用程序的功能实现了,但是解锁圆圈的大小还是太大,特别是在更改dpi之后,圆圈几乎占据了半个显示屏,显得并不雅观。
实际上,解锁圆圈的资源就是/framework/cors/res/res/drawable-xxx中的unlock_ring.png,对于不同分辨率的设备,会采用不同的大小。我们可以通过修改位图的大小来进行更改,但在实际测试中修改了所有的unlock_ring.png,也未见解锁图标变小。
    决定采用代码配合更改图案大小的功能,如图:

 
mOuterRing为外圆圈的资源,mOuterRadius为解锁圆圈的活动半径,修改代码后直接从图片获取,这样将采用drawable-sw480dp-mdpi下的图片,通过PhotoShop等软件将圆圈图片缩小,最终显示出的圆圈就变小了。
3. 关于Android屏幕的知识(摘自网上) 
3.1. density
density表示每英寸有多少个显示点(逻辑值),它的单位是dpi:dot per inch,通常屏幕大时,density就大,屏幕小时,density就小,通常:
屏幕实际分辨率为240px*400px时,density=120
屏幕实际分辨率为320px*533px,density=160
屏幕实际分辨率为480px*800px,density=240
3.2. 分辨率
是整个屏是多少点,比如800x480,它是软件的显示单位,实际上会因为不同的显示屏的像素大小不同,造成density不同。
3.3. 资源目录名称
res/xxx-hdpi          当density为240时,使用此目录下的资源
res/xxx-mdpi          当density为160时,使用此目录下的资源
res/xxx-ldpi          当density为120时,使用此目录下的资源
res/xxx                不常后缀,为默认设置,同xxx-mdpi
3.4. 资源单位(xml文件中定义大小的单位)
a)dp=dip=dx (Density independent pixel)
基于屏幕密度的抽象单位,设备无关的点,用于说明与密度无关的尺寸和位置。这些单位是相对于一个160dpi的屏幕,所有一个dp是160dpi屏幕上的一个点。
b)px (Pixel)
px指软件的单位点,设备相关的点
3.5. 获取屏幕信息的相关代码片段
- public static String getDisplayMetrics(Context cx) {
-   String str = "";
-   DisplayMetrics dm = new DisplayMetrics();
-   dm = cx.getApplicationContext().getResources().getDisplayMetrics();
-   int screenWidth = dm.widthPixels;
-   int screenHeight = dm.heightPixels;
-   float density = dm.density;
-   float xdpi = dm.xdpi;
-   float ydpi = dm.ydpi;
-   str += "The absolute width:" + String.valueOf(screenWidth) + "pixels\n";
-   str += "The absolute heightin:" + String.valueOf(screenHeight)
-       + "pixels\n";
-   str += "The logical density of the display.:" + String.valueOf(density)
-       + "\n";
-   str += "X dimension :" + String.valueOf(xdpi) + "pixels per inch\n";
-   str += "Y dimension :" + String.valueOf(ydpi) + "pixels per inch\n";
-   return str;
- }
- TextView tv1;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
-   super.onCreate(savedInstanceState);
-   setContentView(R.layout.layout_test2);
-   tv1 = (TextView) findViewById(R.id.TextView01);
-   tv1.post(new Runnable(){
-     public void run()
-     {
-       processLayout();
-     }
-   });
- }
- private void processLayout(){
-   Rect rect= new Rect();
-   Window window= getWindow();
-   tv1.getWindowVisibleDisplayFrame(rect);
-   //状态栏高度
-   int statusBarHeight= rect.top;
-   int contentViewTop= window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
-   //标题栏高度
-   int titleBarHeight= contentViewTop - statusBarHeight;
-   //测试结果:ok之后 100多 ms 才运行了
-   Log.v("test", "=-init-= statusBarHeight="+statusBarHeight+
-   " contentViewTop="+contentViewTop+
-   " titleBarHeight="+titleBarHeight); 
- }
 
 
4.总结 
    也算是费了一些功夫,总算是把First Task完成得差不多了,毕业一年以来都是在做WinCE驱动,虽然在大学里面学过一点Linux的知识,但是初次转到Android还是有点不适应,好在有网络这个好东西,Android学习才不是那么难,但是感觉也不能太依靠网络。从长期来看,在消费电子这一块,智能系统的进化将会越来越快,这对我等的考验会越来越大。但是当今世界就是一个不断学习的世界,今后要多把自己的学习总结出来,这样进步才会更快。
【转】修改Android解锁界面
原文:http://www.cnblogs.com/cslunatic/p/6870052.html