本文来自于腾讯bugly开发者社区,非经做者赞成,请勿转载,原文地址:http://dev.qq.com/topic/57875330c9da73584b025873html
锁屏做为一种黑白屏时代就存在的手机功能,至今仍发挥着巨大做用,特别是触屏时代的到来,锁屏的功用被发挥到了极致。多少人曾经在无聊的时候每隔几分钟划开锁屏再关上,孜孜不倦,其酸爽程度不亚于捏气泡膜。确实,一款漂亮的锁屏能为手机增色很多,但锁屏存在的核心目的主要是三个:保护本身手机的隐私,防止误操做,在不关闭系统软件的状况下节省电量。java
当下,各个款式的手机自带的系统锁屏彻底可以知足这些需求,并且美观程度非凡,那么开发者为何仍然须要构建自定义锁屏呢?让咱们试想一个场景,一位正在使用音乐播放器听歌的美女用户,在没有播放器自定义锁屏的状况下,切换一首歌须要几步(参考自同类文章):android
点亮手机屏幕程序员
解开系统锁屏安全
打开音乐播放器微信
切歌再熄灭屏幕app
这时的她估计已经被广场舞的歌曲骚扰了有10秒,续了10次命,这是咱们程序员不肯意看到的,因此有必要依靠咱们灵活的双手构建出自定义的音乐锁屏页,将切歌过程被压缩为两步:点亮屏幕和切歌,顺即可以看看歌词。若是再加个开启和关闭自定义锁屏的开关,就能完美解决用户的痛点。ide
然而,要实现一个自定义锁屏是一件繁琐的事情,由于系统有100种方法让这个非本地的锁屏待不下去。可是,人类的智慧是无限的,程序员须要逆流而上。布局
Android系统实现自定义锁屏页的思路很简单,即在App启动时开启一个service,在Service中时刻监听系统SCREEN_OFF的广播,当屏幕熄灭时,Service监听到广播,开启一个锁屏页Activity在屏幕最上层显示,该Activity建立的同时会去掉系统锁屏(固然若是有密码是禁不掉的)。示意图以下:测试
道理很简单,咱们这里须要讨论的是细节。
Service是普通的Service,在应用启动时直接startService,与应用同一个进程便可。此外,SCREEN_OFF广播监听必须是动态注册的,若是在AndroidManifest.xml中静态注册将没法接收到SCREEN_OFF广播,这点在Android官方文档中有明确说明,即须要经过以下代码注册:
IntentFilter mScreenOffFilter = new IntentFilter(); mScreenOffFilter.addAction(Intent.ACTION_SCREEN_OFF); registerReceiver(mScreenOffReceiver, mScreenOffFilter); 对应的BroadcastReceiver定义以下: private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { @SuppressWarnings("deprecation") @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(NOTIFY_SCREEN_OFF)) { Intent mLockIntent = new Intent(context, LockScreenActivity.class); mLockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(mLockIntent); } } };
关于启动Activity时Intent的Flag问题,若是不添加FLAG_ACTIVITY_NEW_TASK的标志位,会出现“Calling startActivity() from outside of an Activity”的运行时异常,毕竟咱们是从Service启动的Activity。Activity要存在于activity的栈中,而Service在启动activity时必然不存在一个activity的栈,因此要新起一个栈,并装入启动的activity。使用该标志位时,也须要在AndroidManifest中声明taskAffinity,即新task的名称,不然锁屏Activity实质上仍是在创建在原来App的task栈中。
标志位FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,是为了不在最近使用程序列表出现Service所启动的Activity,但这个标志位不是必须的,其使用依状况而定。
锁屏的activity内部也要作相应的配置,让activity在锁屏时也可以显示,同时去掉系统锁屏。固然若是设置了系统锁屏密码,系统锁屏是没有办法去掉的,这里考虑没有设置密码的状况。
典型的去掉系统锁屏页的方法是使用KeyguardManager,具体代码以下:
KeyguardManager mKeyguardManager = (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE); KeyguardManager.KeyguardLock mKeyguardLock = mKeyguardManager.newKeyguardLock("CustomLockScreen"); mKeyguardLock.disableKeyguard();
其中,KeyguardManager是锁屏管理类,咱们经过getSystemService()的方式获取实例对象mKeyguardManager,调用该对象的newKeyguardLock()方法获取KeyguardManager的内部类KeyguardLock的实例mKeyguardLock,该方法传入的字符串参数用于标识是谁隐藏了系统锁屏,最后调用mKeyguardLock的disableKeyguard()方法能够取消系统锁屏。
上述方法已经不推荐使用,可使用更好的方法来替代。咱们在自定义锁屏Activity的onCreate()方法里设定如下标志位就能彻底实现相同的功能:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
FLAG_DISMISS_KEYGUARD用于去掉系统锁屏页,FLAG_SHOW_WHEN_LOCKED使Activity在锁屏时仍然可以显示。固然,不要忘记在Manifest中加入适当的权限:
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
当自定义锁屏页最终出如今手机上时,咱们总但愿它像系统锁屏页那样屹立不倒,全部的按键都不能触动它,只有经过划瓶或者指纹才能解锁,所以有必要对按键进行必定程度上的屏蔽。针对只有虚拟按键的手机,咱们能够经过隐藏虚拟按键的方式部分解决这个问题,具体方法在后文会介绍。可是当用户在锁屏页底部滑动,隐藏后的虚拟按键仍是会滑出,并且若是用户是物理按键的话就必须进行屏蔽了。
Back键和Menu键能够经过重写onKeyDown()方法进行屏蔽:
public boolean onKeyDown(int keyCode, KeyEvent event) { int key = event.getKeyCode(); switch (key) { case KeyEvent.KEYCODE_BACK: { return true; } case KeyEvent.KEYCODE_MENU:{ return true; } } return super.onKeyDown(keyCode, event); }
Home键与Recent键(调出最近打开应用的按键)的点击事件是在framework层进行处理的,所以onKeyDown与dispatchKeyEvent都捕获不到点击事件。关于这两个按键的屏蔽方法,网上相关的资料有不少,有的用到了反射,有的经过改变Window的标志位和Type等,总的来讲这些方法只对部分android版本有效,有的则彻底没法编译经过。其实,这么作的目的无非是为了实现一个纯粹的锁屏页,可是这种作法有些多此一举,容易形成锁屏页的异常崩溃,咱们要知足的是用户在锁屏页的快捷操做,Home键和Recent键无关痛痒,彻底能够无论,少一些套路,多一点真诚嘛。
作完以上几步,当屏幕熄灭后,再打开屏幕就可以看到咱们的自定义锁屏页了,可是这时候,就算划破手指也没法解锁。因此,接下来要实现划屏解锁。
划瓶解锁的基本思路很简单,当手指在屏幕上滑动时,拦截并处理滑动事件,使锁屏页面随着手指运动,当运动到达必定的阀值时,用户手指松开手指,锁屏页自动滑动到屏幕边界消失,若是没有达到运动阀值,就会自动滑动到起始位置,从新覆盖屏幕。
为了将划屏逻辑与页面内容隔离开来,咱们在锁屏页面布局中添加一个自定义的UnderView,这个UnderView填充整个屏幕,位于锁屏内容View(将其引用称之为mMoveView,并传入到UnderView中)的下方,全部划屏相关的事件都在这里拦截并处理。
mMoveView是锁屏页的显示内容,除了处理一些简单的点击事件,其余非点击事件序列都由底层的UnderView进行处理。只须要重写UnderView的onTouchEvent方法就可以实现:
Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); final float nx = event.getX(); switch (action) { case MotionEvent.ACTION_DOWN: mStartX = nx; onAnimationEnd(); case MotionEvent.ACTION_MOVE: handleMoveView(nx); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: doTriggerEvent(nx); break; } return true; }
其中,mStartX记录滑动操做起始的x坐标,handleMoveView方法控制mMoveView随手指的移动,doTriggerEvent处理手指离开后mMoveView的移动动画。两个方法的定义以下:
private void handleMoveView(float x) { float movex = x - mStartX; if (movex < 0) movex = 0; mMoveView.setTranslationX(movex); float mWidthFloat = (float) mWidth;//屏幕显示宽度 if(getBackground()!=null){ getBackground().setAlpha((int) ((mWidthFloat - mMoveView.getTranslationX()) / mWidthFloat * 200));//初始透明度的值为200 } }
在handleMoveView()中,首先计算当前触点x坐标与初始x坐标mStartX的差值movex,而后调用mMoveView的setTranslationX方法移动。值得注意的是,目前setTranslationX方法只能在Android 3.0以上版本使用,若是采用动画兼容库nineoldandroid中ViewHelper类提供的setTranslation方法,则没有这个问题。scrollTo与scrollBy也能够实现移动,可是只是移动View的内容,并不能移动View自己。另外就是经过修改布局参数LayoutParams实现移动,虽然没有版本的限制,用起来相对复杂。这里咱们采用setTranslationX,为了简洁,也是为了可以与后续使用的属性动画相统一。
此外,咱们能够经过getBackground()获取UnderView的背景,并根据已划开屏幕占整个屏幕的百分比调用setAlpha方法改变背景的透明度,作出抽屉拉开时的光影变化效果。
private void doTriggerEvent(float x) { float movex = x - mStartX; if (movex > (mWidth * 0.4)) { moveMoveView(mWidth-mMoveView.getLeft(),true);//自动移动到屏幕右边界以外,并finish掉 } else { moveMoveView(-mMoveView.getLeft(),false);//自动移动回初始位置,从新覆盖 } } private void moveMoveView(float to,boolean exit){ ObjectAnimator animator = ObjectAnimator.ofFloat(mMoveView, "translationX", to); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if(getBackground()!=null){ getBackground().setAlpha((int) (((float) mWidth - mMoveView.getTranslationX()) / (float) mWidth * 200)); } } });//随移动动画更新背景透明度 animator.setDuration(250).start(); if(exit){ animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mainHandler.obtainMessage(LockScreenActivity.MSG_LAUNCH_HOME).sendToTarget(); super.onAnimationEnd(animation); } }); }//监听动画结束,利用Handler通知Activity退出 }
当手指离开屏幕,doTraiggerEvent方法会对滑动的距离与阀值进行一个比较,此处的阀值为0.4*屏幕宽度,若是低于阀值,则经过ObjectAnimator在0.25s将mMoveView移动到初始位置,同时在ObjectAnimator的AnimatorUpdateListener的onAnimationUpdate方法中更新背景透明度;若是低于阀值,以一样的方式将mMoveView移出屏幕右边界,而后将Activity干掉,具体作法是为animator增长一个AnimatorListenerAdapter的监听器,在该监听器的onAnimationEnd方法中使用在Activity中定义的mHandler发送finish消息,完成解锁,效果以下图:
沉浸模式与透明栏是两个不一样的概念,因为某些缘由,国内一些开发或产品会把这两个概念混淆。不过不要紧,在接下来的内容咱们会对这两个概念进行详细的解释和区分,并应用这两种不一样的模式进一步完善已经初具模样的锁屏页。
什么是沉浸模式?从4.4开始,Android 为 “setSystemUiVisibility()”方法提供了新的标记 “SYSTEM_UI_FLAG_IMMERSIVE”以及”SYSTEM_UI_FLAG_IMMERSIVE_STIKY”,就是咱们所谈的沉浸模式,全称为 “Immersive Full-Screen Mode”,它可使你的app隐藏状态栏和导航栏,实现真正意义上的全屏体验。
以前 Android 也是有全屏模式的,主要经过”setSystemUiVisibility()”添加两个Flag,即”SYSTEM_UI_FLAG_FULLSCREEN”,”SYSTEM_UI_FLAG_HIDE_NAVIGATION”(仅适用于使用导航栏的设备,即虚拟按键)。
这两个标记都存在一些问题,例如使用第一个标记的时候,除非 App 提供暂时退出全屏模式的功能(例如部分电子书软件中点击一次屏幕中央位置),用户是一直都无法看见状态栏的。这样,若是用户想去看看通知中心有什么通知,那就必须点击一次屏幕,显示状态栏,而后才能调出通知中心。
而第二个标记的问题在于,Google 认为导航栏对于用户来讲是十分重要的,因此只会短暂隐藏导航栏。一旦用户作其余操做,例如点击一次屏幕,导航栏就会立刻被从新调出。这样的设定对于看图软件,视频软件等等没什么大问题,可是对于游戏之类用户须要常常点击屏幕的 App,那就几乎是悲剧了——这也是为何你在 Android 4.4 以前找不到什么全屏模式会自动隐藏导航栏的应用。
Android 4.4 以后加入的Immersive Full-Screen Mode 容许用户在应用全屏的状况下,经过在原有的状态栏/导航栏区域内作向内滑动的手势来实现短暂调出状态栏和导航栏的操做,且不会影响应用的正常全屏,短暂调出的状态栏和导航栏会呈半透明状态,而且在一段时间内或者用户与应用内元素进行互动的状况下自动隐藏,沉浸模式的四种状态以下图。(参考http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0616/3047.html)
状态1表明没有进入沉浸模式时页面的状态,仍然能够看到Status Bar和Navigation Bar;状态2表明用户第一次进入沉浸模式时,系统的提示弹窗,告诉用户如何在沉浸模式下呼出Status Bar和Navigation Bar;状态3表明沉浸模式,能够看到Status Bar和Navigation Bar都被隐藏;状态4表明用户在Sticky沉浸模式下呼出Status Bar和Navigation Bar,能够看到两个Bar从新出现,可是过一段时间可以自动隐藏。
通常来讲,沉浸模式的标记与其余Full Screen相关的Flag搭配起来才能达到咱们想要的效果,即经过沉浸模式标记规定状态栏status bar和导航栏navigation bar显示和隐藏的运转逻辑,经过其余标签设定状态栏和导航栏显示或隐藏,以及显示或隐藏的样子。这些常见的Flag及相应功能以下表:
如此多的标签,看起来很是乱,但用起来却很是简单和明确,感兴趣的开发者能够自由搭配来测试一下。下面,咱们经过一个例子,将这些标签应用于锁屏页,实现对Navigation Bar的自动隐藏,同时保留Status Bar。代码很是简单,在Activity的onCreate()方法中使用:
getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION );
总共用到了5个Flag:SYSTEM_UI_FLAG_LAYOUT_STABLE保持整个View稳定,使View不会由于SystemUI的变化而作layout;SYSTEM_UI_FLAG_IMMERSIVE_STIKY,可以在隐藏的bar被呼出时(好比从屏幕下边缘开始向上作滑动手势),使bar在无相关操做的状况下自动再次隐藏;对于SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,开发者容易被其中的HIDE_NAVIGATION所迷惑,其实这个Flag没有隐藏导航栏的功能,只是控制导航栏浮在屏幕上层,不占据屏幕布局空间;SYSTEM_UI_FLAG_HIDE_NAVIGATION,才是可以隐藏导航栏的Flag;SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,由上面可知,也不能隐藏状态栏,只是使状态栏浮在屏幕上层。
须要注意的是,这段代码除了须要加在Activity的OnCreate()方法中,也要加在重写的onWindowFocusChanged()方法中,在窗口获取焦点时再将Flag设置一遍,不然可能致使没法达到预想的效果。
Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if(hasFocus){ getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION ); } }
此外,有个部份要稍微留意一下,若是不但愿界面的内容被上拉到状态栏(Status bar)的话,要记得在界面(Layout)XML文件中,在最外层Layout中将fitsSystemWindows属性设置为true。以下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!-- Content --> </RelativeLayout>
设置了前文的5个Flag以后,锁屏页效果图以下:
手指在屏幕底端上划,Navigation Bar会弹出,悬浮于锁屏页底部,随后自动消失。Status Bar也按照咱们预期的那样,悬浮在上方,没有隐藏。
什么是透明栏?Google 在 Android 4.4 的 API 描述页面里提到了“Translucent system UI styling”,即半透明化的系统UI风格。这个“半透明化”包括了状态栏和通知栏,当开发者让应用支持这个新特性的时候,状态栏和导航栏能够单独/同时变为渐变的半透明样式,以下图:
在 Android 5.0 以后引入了 Material Design,状态栏和导航栏也玩出了更多花样。如今除了原有的“半透明”模式之外,还有“全透明”以及“变色”模式,一种会彻底隐藏背景,另外一种能够取色做为背景颜色,多种样式的透明栏以下图(上图为透明状态栏,下图为透明导航栏):
因此,透明栏只是可以改变状态栏和导航栏的颜色,并不像沉浸模式那样隐藏状态栏和导航栏,二者是有本质区别的。
对于Android 4.4以上5.0如下的版本,设置透明状态栏的方式以下:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); }
对于Android 5.0及以上版本,设置透明状态栏的方法以下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.getDecorView() .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(0); }
除了要清理掉4.4的FLAG_TRANSLUCENT_STATUS外,还要配合SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和SYSTEM_UI_FLAG_LAYOUT_STABLE,添加标志位FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,并调用setStatusBarColor设置状态栏的颜色为透明。
在综合运用了沉浸模式和透明栏以后,锁屏页效果以下:
到这里,咱们的锁屏页已经基本完工,彻底可以很是优雅地解决用户的痛点,可是跟当下App自定义锁屏页的区别并不明显。接下来对新型号手机广泛具有的指纹解锁功能的考虑,则可以为锁屏页增色很多。
持有指纹解锁手机的用户在使用App自定义锁屏页时会出现一种困惑,当你点亮屏幕,可以看到自定义锁屏页,在使用指纹解锁成功以后(部分机型指纹解锁操做只能在系统锁屏页进行),自定义锁屏页依然存在,你仍是须要划开自定义锁屏页,才能看到手机主界面。
解决这一问题的方案是一种取巧的方法,那就是在锁屏页的service中监听ACTION_USER_PRESENT广播。ACTION_USER_PRESENT广播是系统锁屏解锁广播,当系统锁屏页解锁时就会触发。若是在接收到这一广播时,将自定义锁屏页finish掉,就能避免在指纹解锁成功后自定义锁屏页仍然显示的问题。可是细心的读者会发现这种解法在逻辑上还存在问题,由于在用户没有设置锁屏密码的状况下,前文自定义锁屏页在onCreate()时设置的FLAG_DISMISS_KEYGUARD标志位可以轻易解锁系统的锁屏页,并触发ACTION_USER_PRESENT广播,此时自定义锁屏页的Service接收到这一广播后,发finish广播给自定义锁屏页,致使自定义锁屏页刚create就finish掉了,永远不可能出现。
所以,咱们必须对场景进行区分,只在有锁屏密码的状况下,才对接收到的ACTION_USER_PRESENT广播进行处理,finish自定义锁屏页。即在BroadcastReceiver的onReceive()方法中加入以下代码:
if(intent.getAction().equals(Intent.ACTION_USER_PRESENT)) { if (VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (km.isKeyguardSecure()) { MLog.d(TAG, "KeyguardSecure!"); Intent i = new Intent(NOTIFY_USER_PRESENT); context.sendBroadcast(i); } } }
这里KeyguardManager对象km的isKeyguardSecure()方法就是用来判断是否设置了锁屏密码。NOTIFY_USER_PRESENT是自定义广播,用来通知锁屏页Activity调用finish方法。
这种作法是合理的,由于若是没有设置锁屏密码,FLAG_DISMISS_KEYGUARD标志位解锁系统锁屏以后,到达上述代码块,isKeyguardSecure()返回为false,不会致使自定义锁屏页Activity的finish操做。而若是设置了锁屏密码,FLAG_DISMISS_KEYGUARD必然没法解锁系统锁屏,到达不了上述代码块,也不会finish。这样就避免了自定义锁屏页刚建立出来就将本身finish掉的困境。另外一方面,其余非FLAG_DISMISS_KEYGUARD方式触发的解锁,好比指纹解锁,都会使Activity消失,知足了需求。
此外,有些手机型号,好比小米,在自定义锁屏页罩在系统锁屏页之上时(设置有锁屏密码),指纹解锁是无效的,也就是必需要划开自定义锁屏页,在系统锁屏页上才能进行指纹解锁。为了改善这种体验,咱们能够在Activity中引入指纹解锁API,识别指纹并解锁,具体代码以下:
private void startFingerPrintListening() { if (!isFingerprintAuthAvailable()) { return; } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED) { mFingerprintManager.authenticate(null, mCancellationSignal, 0, new FingerprintManager.AuthenticationCallback() { @Override public void onAuthenticationError(int errorCode, CharSequence errString) { super.onAuthenticationError(errorCode, errString); } @Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { super.onAuthenticationSucceeded(result); finish(); } @Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); } }, null); return; } } } } public boolean isFingerprintAuthAvailable() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mKeyguardManager = (KeyguardManager) getSystemService(Activity.KEYGUARD_SERVICE); if(!mKeyguardManager.isKeyguardSecure()){ return false; } if (checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED) { mFingerprintManager = (FingerprintManager) getSystemService(Activity.FINGERPRINT_SERVICE); mCancellationSignal = new CancellationSignal(); return mFingerprintManager.isHardwareDetected()&&mFingerprintManager.hasEnrolledFingerprints(); }else{ return false; } }else{ return false; } }
固然,不要忘记在Manifest中加入适当的权限:
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
在调用指纹识别功能以前,咱们须要判断指纹识别功能是否可用,以及APP是否有相应的权限。这一过程体如今isFingerprintAuthAvailable()中,第一步是获取KeyguardManager对象,调用isKeyguardSecure()判断是否设置有锁屏密码,若是有,则需进一步判断。checkSelfPermission用来判断APP是否有指纹识别的权限(SDK 23要求),若是有则获取FingerprintManager对象,调用该对象的isHardwareDetected()方法判断指纹识别硬件是否可用,调用hasEnrolledFingerprints()判断是否有事先录入好的指纹,只有以上条件都知足,接下来才能调用指纹识别功能。
指纹识别的调用体如今startFingerPrintListening()方法中,主要就是调用FingerprintManager的方法
authenticate(FingerprintManager.CryptoObject crypto, CancellationSignal cancel, int flags, FingerprintManager.AuthenticationCallback callback, Handler handler)
其中,crypto参数表明Android6.0中crypto objects的wrapper class,能够经过该对象使authenticate过程更加安全,也能够不使用,这里咱们将其设为null;cancel用来取消anthenticate(),咱们new出一个对象传入就能够;flags是标志位,设置为0;callback为指纹识别回调,包含指纹识别的核心方法:onAuthenticationError()是指纹匹配连续失败后的回调(几十秒后才能继续匹配),onAuthenticationSucceeded()是指纹匹配成功的回调,onAuthenticationFailed()是指纹匹配失败时的回调。咱们在这几个方法中作相应的处理便可,在onAuthenticationSucceeded()方法中调用finish(),就可以在指纹识别成功后关闭Activity。
经过以上内容的分享,本鹅但愿可以对你们的开发有所帮助,若是内容有问题,也但愿你们指点。综上所述,在Android上实现自定义锁屏页并非一件复杂的事情,关键是对一些技术点的把握要比较清楚。Service中启动Activity的正确方法,广播静态注册与动态注册的差异,touch事件的分发传播机制,透明栏与沉浸模式的综合运用,以及指纹识别新技术的应用,都有不少值得推敲的地方。笔者当初实现自定义锁屏页时,没有太多思考,有时照搬前人的作法,有时各类flag随便添加,有时新旧API混淆,虽然实现了需求,可是代码不够简洁,可读性也差。所以,在从此的开发过程当中,除了要快速实现需求,还要在随后的维护中,多多思考和研究,使代码可以达到“少一行不行,多一行难受”的境界。
更多精彩内容欢迎关注bugly的微信公众帐号: