在Android6.0(Api23)的时候,Android系统加入了指纹识别的api接口,即
FingerprintManager
,定义了最基础的指纹识别接口。不过,在AndroidP(Api28)的时候,官方不再推荐使用,做了@Deprecated处理。
后来,在support v4库中添加了FingerprintManagerCompat
类,我看了他的源码,其实就是对FingerprintManager
做了一定的封装,比如做了对SDK版本的判断、对于加密部分的处理等等,其本质还是在用FingerprintManager
来实现指纹识别功能。
到了AndroidP,FingerprintManager
就正式退役了,系统新增了BiometricPrompt
接口,从接口名字'生物识别'也能看出来,今后的安全验证功能,将不会局限于指纹了,应该还会加入面部识别等等。
下面就通过我写的一个demo,展开来介绍一下FingerprintManager
以及BiometricPrompt
。
一、公共部分:
1、总的来说,我们写一个Manager类,类的内部通过Api版本的判断,来分别实现Api23和Api28的适配
2、其中,判断版本号的办法是:
3、其次,我们声明了一个接口IBiometricPromptImpl,Api28和Api23的实例都要继承他
3、对于系统是否支持指纹识别的判断:
分别说明一下判断的细节:
①isAboveApi23()
:上面已经说过了;
②isHardwareDetected()
: 这是用来判断系统硬件是否支持指纹识别,这里也是分情况判断,但是AndroidP还不知道用什么确切的办法来判断,所以暂时用与AndroidM一样的方式。Api23的具体实现在实现类中,后续你会看到
③hasEnrolledFingerprints()
:这个方法是用来判断你的设备在系统设置里面是否设置了指纹。
如果用户没有设置,这时候你可以引导他去设置。不过,我查了一下,各个厂家的设置指纹的页面Activity名都不是统一的,所以这里要一一做适配能累成狗。所以如果要引导的话,引导到安全设置页面就可以了,安全设置页面系统有统一的Intent,是【Settings.ACTION_SECURITY_SETTINGS
】。
④isKeyguardSecure()
:这个方法是判断系统有没有设置锁屏。
这个方法我认为是个鸡肋,因为现在如果你设置了指纹的话,肯定要让你先设置一种密码(PIN/Password/Pattern),那么锁屏肯定也就随之设置了,不理解为啥还要判断一下这个。。。
二、BiometricPromptApi23: 针对Api23~Api27的部分
1、authenticate()
在看BiometricPromptApi23.java
里面的内容之前,我们先需要了解一下指纹识别的关键方法:authenticate()
。
上图是google的api文档中的描述,现在我们挨个解释一下这些参数都是什么:
①.
crypto
这是一个加密类的对象,指纹扫描器会使用这个对象来判断认证结果的合法性。这个对象可以是null,但是这样的话,就意味这app无条件信任认证的结果,虽然从理论上这个过程可能被攻击,数据可以被篡改,这是app在这种情况下必须承担的风险。因此,建议这个参数不要置为null。这个类的实例化有点麻烦,主要使用javax的security接口实现,后面我的demo程序中会给出一个helper类(CryptoObjectHelper.java
),这个类封装内部实现的逻辑,开发者可以直接使用我的类简化实例化的过程。②.
cancel
这个是CancellationSignal
类的一个对象,这个对象用来在指纹识别器扫描用户指纹的是时候取消当前的扫描操作,如果不取消的话,那么指纹扫描器会移植扫描直到超时(一般为30s,取决于具体的厂商实现),这样的话就会比较耗电。建议这个参数不要置为null。③.
flags
标识位,根据上图的文档描述,这个位暂时应该为0,这个标志位应该是保留将来使用的。④.
callback
这个是FingerprintManager.AuthenticationCallback
类的对象,这个是这个接口中除了第一个参数之外最重要的参数了,稍后我们详细来介绍。这个参数不能为NULL。⑤.
handler
这是Handler类的对象,如果这个参数不为null的话,那么FingerprintManager
将会使用这个handler中的looper来处理来自指纹识别硬件的消息。通常来讲,开发这不用提供这个参数,可以直接置为null,因为FingerprintManager
会默认使用app的main looper来处理。
2、指纹认证之后的回调方法
这里就要介绍的是上面提到的FingerprintManager.AuthenticationCallback
了,因为扫描指纹和认证的过程都是在另外一个进程中完成的,所以我们需要采取异步的方式,等操作完成之后,让系统回调给我们,回调方法就是AuthenticationCallback
类中的4个方法了
下面我们简要介绍一下这些接口的含义:
①.
OnAuthenticationError(int errorCode, ICharSequence errString)
这个接口会再系统指纹认证出现不可恢复的错误的时候才会调用,并且参数errorCode就给出了错误码,标识了错误的原因。在AndroidP以前,这个方法回调回来之后,指纹识别sensor将会被关闭,也就是说,你再把手指放在指纹硬件上,将不会有反应了。这时候你需要提示用户关闭指纹识别弹窗,或改用密码支付等等
什么情况下会回调error错误呢?比如,连续识别错误5次指纹、指纹硬件不可用等等。
②.
OnAuthenticationFailed()
这个接口会在系统指纹认证失败的情况的下才会回调。注意这里的认证失败和上面的认证错误是不一样的,虽然结果都是不能认证。认证失败是指所有的信息都采集完整,并且没有任何异常,但是这个指纹和之前注册的指纹是不相符的;但是认证错误是指在采集或者认证的过程中出现了错误,比如指纹传感器工作异常等。也就是说认证失败是一个可以预期的正常情况,而认证错误是不可预期的异常情况。③.
OnAuthenticationHelp(int helpMsgId, ICharSequence helpString)
上面的认证失败是认证过程中的一个异常情况,我们说那种情况是因为出现了不可恢复的错误,而我们这里的OnAuthenticationHelp方法是出现了可以回复的异常才会调用的。什么是可以恢复的异常呢?一个常见的例子就是:手指移动太快,当我们把手指放到传感器上的时候,如果我们很快地将手指移走的话,那么指纹传感器可能只采集了部分的信息,因此认证会失败。但是这个错误是可以恢复的,因此只要提示用户再次按下指纹,并且不要太快移走就可以解决。④.
OnAuthenticationSucceeded(FingerprintManagerCompati.AuthenticationResult result)
这个接口会在认证成功之后回调。我们可以在这个方法中提示用户认证成功。这里需要说明一下,如果我们上面在调用authenticate的时候,我们的CryptoObject不是null的话,那么我们在这个方法中可以通过AuthenticationResult来获得Cypher对象然后调用它的doFinal方法。doFinal方法会检查结果是不是会拦截或者篡改过,如果是的话会抛出一个异常。当我们发现这些异常的时候都应该将认证当做是失败来来处理,为了安全建议大家都这么做。好了,下面来看看我的demo里的实现
这个authenticate方法是重写了IBiometricPromptImpl接口中的方法,重要的部分我已经加了注释,剩下的应该能看懂了吧,不懂的可以在评论中问~~【手动笑脸☺】
下面是两个判断方法的实现
三、BiometricPromptApi28: 针对Api28及以后的平台
在AndroidP中,原来的fingerprintManager
将被BiometricPrompt
类替换,Google旨在统一生物识别的方式(虽然目前api中还没有看到虹膜、面部识别等),包括UI,UI也不允许自定义了,必须使用BiometricPrompt.Builder
来创建对话框,其中可以自定义title、subtitle、description和一个NegativeButton(也就是cancel键)。
只有一个NegativeButton,这个很尴尬,意思是只能有button存在界面上,如果我想加个UsePassword的button,只能把这个cancel键给改掉。。。(不过,大家放心,虽然AndroidP的source还没有放出来,不过,我让老同事帮忙找了一分BiometricPrompt的源码,里面还是有一个PositiveButton的,只不过api应该还没有放出来)
下面来看看实现代码:
构造方法,创建signature对象(对于加密这块理解的不好,哪位大神可以给普及普及)
跟Api23很像,实现authenticate方法
回调
附上源码:有问题可以探讨:https://github.com/gaoyangcr7/BiometricPromptDemo
常见问题:
1,报错 java.io.IOException: Failed to find byte code for android/hardware/biometrics/BiometricPrompt$AuthenticationCallback
去设置里把InstantRun关掉就好了
2,报错 java.lang.RuntimeException: java.security.InvalidAlgorithmParameterException: java.lang.IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
- 这个异常不是模拟器上才出现的,真机也会,和设备无关,怀疑是谷歌 API 的坑
- 我的做法是catch住异常,友好提示用户暂不支持指纹,引导用户使用其他的验证方式
- 备用做法是:直接使用无密钥验证,但是有一定的安全风险,目前在观察线上用户出现频率,再考虑是否用备用方案。
1,小米6、6X手机上点击“Turn On Identification”的时候会先走一遍onAuthenticationHelp,helpCode=1021,helpString为空
应该是MIUI自行修改了底层时间,可以尝试晚一点调用authenticate方法(没试验过,主要手边没小米手机)