Android—指纹识别系统的原理与使用

指纹识别是什么?

提到指纹识别我们就要先弄清楚什么事指纹,指纹为何能够做到区别性。

指纹,由于其具有终身不变性、唯一性和方便性,已几乎成为生物特征识别的代名词。指纹是指人的手指末端正面皮肤上凸凹不平产生的纹线。纹线有规律的排列形成不同的纹型。纹线的起点、终点、结合点和分叉点,称为指纹的细节特征点(minutiae)。

指纹识别即指通过比较不同指纹的细节特征点来进行鉴别。指纹识别技术涉及图像处理、模式识别、计算机视觉、数学形态学、小波分析等众多学科。由于每个人的指纹不同,就是同一人的十指之间,指纹也有明显区别,因此指纹可用于身份鉴定。由于每次捺印的方位不完全一样,着力点不同会带来不同程度的变形,又存在大量模糊指纹,如何正确提取特征和实现正确匹配,是指纹识别技术的关键。

指纹识别的易用性

指纹识别功能提高了系统的易用性,不仅是解锁屏幕,它还可以用来设置一些快捷操作如:拍照、快捷打开程序等等,同时鉴于指纹识别的高区别性质,指纹识别不仅仅是对于功能的简化,更深层次的是安全级别的一个提高。

指纹识别的安全性

随着时代的发展网络支付等安全问题面临越来越多的问题,这传统的基于密码、加密算法和验证码的安全机制在安全性和方便性方面已经无法满足现有需求,甚至已经受到挑战。为了能够更好地确保系统的安全性和方便性,迫切需要寻找其他的技术。于是人们将目光转移到了生物特征识别技术上,因为人体某些生物特征各不相同并且不会发生变化以及很难遗失和仿制。目前被使用的生物识别技术主要有指纹、虹膜、视网膜、语音、面部、DNA以及签名,它们各自的性能以及优缺点如表所示:

每个人的指纹独一无二并且很难发生变化,此外,它不需要像密码那样需要记忆,真正做到了随时随地使用。目前已经有很多品种的低成本的指纹采集传感器供选择。指纹在采集的过程中对硬件系统的要求不高,指纹采集设备实现比较容易。目前已经有标准的指纹库供开发者使用,识别系统开发相对比较容易,实用性强。随着现代电子集成制造技术的提高,可以制造体积较小并且精度更高的指纹图像传感器。另外,快速可靠的指纹图像处理、识别算法也得到迅速发展,同时现代计算机运算速度越来越快,已经完全具备在微机上进行两个指纹的快速比对运算。可以说,目前指纹技术已经是非常成熟的生物识别技术,具有很大可靠性和实用性。

综上所述,当前指纹识别技术因其低成本识别率高而具有最为广阔的应用前景,已经达到实用化、产业化的程度。也正是因为指纹识别有如此多的好处,Google在2015年Android6.0发布会上指出Android6.0会在系统级别支持指纹识别功能,虽然相对Apple晚了一些但是对于广大的Android用户来说这是一个福音。

指纹识别功能实现简介

指纹识别通过指纹传感器采集信息,进行指纹图像的预处理,然后进行特征点提取,最后进行特征匹配如下图所示:

通过指纹图像传感器采集到指纹图像经常会受到传感器本身误差、手指压力不同以及手指存在尘埃等众多因素影响,使得采集到的指纹图像的质量不够高。因此首先需要对指纹图像进行预处理,以便获得较为清晰的指纹图像并为后期的匹配做好准备工作。经过预处理后的指纹纹线被处理成单一象素点,接下来就可以对指纹图像进行特征提取以及特征装配。最后将获得的特征信息与指纹特征数据库中的指纹特征模板做一一对比,如果有匹配的指纹模板则提取出与之一一映射的身份信息,这样就可以进行身份论证。

指纹识别用途

大概列举几个指纹识别的用途

系统解锁
应用锁
支付认证
普通的登录认证

指纹识别Google官方文档

分析(原理探索)

  • Android程序硬件访问机制简介

在 Android 系统中,最上层的面向界面的应用程序使用 Java 语言编写,Java 编写的应用程序都运行在 Android 特有的虚拟机中。Android 系统是基于 Linux 内核构建,Linux 设备驱动程序程序使用 C 语言编写,且运行在 Linux 内核空间。用户空间访问硬件的方法是通过基于C库的系统调用来调用工作于内核空间的设备驱动程序,从而访问到硬件。显然使用 Java 语言开发的应用程序显然无法直接访问硬件。为了解决这个问题,在Android 系统中提供了硬件抽象层(HAL)来解决这个问题,硬件抽象层运行在用户空间并且使用 C/C++语言编写,它向下屏蔽了硬件驱动模块的实现细节,向上提供了硬件访问服务。

  • 指纹识别两种场景

指纹识别是在Android 6.0之后新增的功能,因此在使用的时候需要先判断用户手机的系统版本是否支持指纹识别。另外,实际开发场景中,使用指纹的主要场景有两种:

纯本地使用。即用户在本地完成指纹识别后,不需要将指纹的相关信息给后台。
与后台交互。用户在本地完成指纹识别后,需要将指纹相关的信息传给后台。

由于使用指纹识别功能需要一个加密对象(CryptoObject)该对象一般是由对称加密或者非对称加密获得。
上述两种开发场景的实现大同小异,主要区别在于加密过程中密钥的创建和使用,一般来说:
纯本地的使用指纹识别功能,只需要对称加密即可;
后台交互则需要使用非对称加密:将私钥用于本地指纹识别,识别成功后将加密信息传给后台,后台开发人员用公钥解密,以获得用户信息。

  • 对称加密、非对称加密和签名

在正式使用指纹识别功能之前,有必要先了解一下对称加密和非对称加密的相关内容。


对称加密:

所谓对称,就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。密钥是控制加密及解密过程的指令。算法是一组规则,规定如何进行加密和解密。因此加密的安全性不仅取决于加密算法本身,密钥管理的安全性更是重要。因为加密和解密都使用同一个密钥,如何把密钥安全地传递到解密者手上就成了必须要解决的问题。

非对称加密:

非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。 非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。

签名:
在信息的后面再加上一段内容,可以证明信息没有被修改过。一般是对信息做一个hash计算得到一个hash值,注意,这个过程是不可逆的,也就是说无法通过hash值得出原来的信息内容。在把信息发送出去时,把这个hash值加密后做为一个签名和信息一起发出去。

对称加密和非对称加密的特点:

对称加密的优点:

速度快,适合于本地数据和本地数据库的加密,安全性不如非对称加密。常见的对称加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、RC6。

非对称加密的特点:

安全性比较高,适合对需要网络传输的数据进行加密,速度不如对称加密。非对称加密应用于SSH, HTTPS, TLS,电子证书,电子签名,电子身份证等等

  • 指纹识别的对称加密实现

Generator [ˈdʒenəreɪtə(r)] 发电机
Authentication [ɔ:ˌθentɪ’keɪʃn] 认证

使用指纹识别的对称加密功能的主要流程如下:
1、使用 KeyGenerator 创建一个对称密钥,存放在 KeyStore 里。
2、设置 KeyGenParameterSpec.Builder.setUserAuthenticationRequired() 为true,
3、使用创建好的对称密钥初始化一个Cipher对象,并用该对象调用 FingerprintManager.authenticate() 方法启动指纹传感器并开始监听。
4、重写FingerprintManager.AuthenticationCallback 的几个回调方法,以处理指纹识别成功(onAuthenticationSucceeded())、失败(onAuthenticationFailed() 和 onAuthenticationError())等情况。

  • 创建密钥

创建密钥要涉及到两个类:KeyStore(俗称密钥商店) 和 KeyGenerator(密钥发电机)
(关于KeyGenerator和KeyStore,可以研究下这篇文章)
(关于加密算法,请看 这篇文章)

 三个类:
        KeyGenerator产生密钥
        KeyStore存放获取密钥
        Cipher,是一个按照一定的加密规则,将数据进行加密后的一个对象


产生密钥

 

public void createKey() {

        try {
            // 创建KeyGenerator对象
            mKeyGenerator = KeyGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
            mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
                    KeyProperties.PURPOSE_ENCRYPT |
                            KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                            // 设置需要用户验证
                    .setUserAuthenticationRequired(true)                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                    .build());
            // 生成key
            mKeyGenerator.generateKey();
        } catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        }
    }
}


KeyStore存放获取密钥

 

if (mKeyStore == null) {
          mKeyStore = KeyStore.getInstance("AndroidKeyStore");
      }
          mKeyStore.load(null);  


创建并初始化 Cipher 对象

 

SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
            mCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            mCipher.init(Cipher.ENCRYPT_MODE, key);


上面几个参数对象就已经创建完成,使用指纹识别时只需要FingerprintManager.authenticate(),系统就会启动指纹传感器,将结果反馈给回调方法.
在完成指纹验证之后,关闭指纹验证:

if (mCancellationSignal != null) {
        mCancellationSignal.cancel();
        mCancellationSignal = null;
  }


非对称加密的实现

非对称加密的步骤和对称加密算法基本上是一样的:

1.KeyPairGenerator产生非对称密钥
2.Cipher是通过创建好的私钥进行签名来产生的


创建 KeyPairGenerator 对象

try {
    mKeyPairGenerator =  KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
    throw new RuntimeException("Fail", e);
}


产生密钥对

try {
    mKeyPairGenerator.initialize(
            new KeyGenParameterSpec.Builder(KEY_NAME,
                    KeyProperties.PURPOSE_SIGN)
                  .setDigests(KeyProperties.DIGEST_SHA256)
                  .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
                    .setUserAuthenticationRequired(true)
                    .build());
    mKeyPairGenerator.generateKeyPair();
} catch (InvalidAlgorithmParameterException e) {
    throw new RuntimeException(e);
}


使用私钥签名产生Cipher

try {
    mKeyStore.load(null);
    PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_NAME, null);
    mSignature.initSign(key);
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
        | NoSuchAlgorithmException | InvalidKeyException e) {
    throw new RuntimeException("Failed to init Cipher", e);
}


其他的流程基本上是一样的!

使用指纹识别功能

真正到了使用指纹识别功能的时候,你会发现其实很简单,只是调用 FingerprintManager 类的的方法authenticate()而已,然后系统会有相应的回调反馈给我们,该方法如下:
使用指纹识别功能

真正到了使用指纹识别功能的时候,你会发现其实很简单,只是调用 FingerprintManager 类的的方法authenticate()而已,然后系统会有相应的回调反馈给我们,该方法如下:
public void authenticate(CryptoObject crypto, CancellationSignal cancel, int flags, AuthenticationCallback callback, Handler handler)

完成指纹识别后,还要记得将 AuthenticationCallback 关闭掉:

public void stopListening() {
  if (cancellationSignal != null) {
    selfCancelled = true;
    cancellationSignal.cancel();
    cancellationSignal = null;
  }
}


重写回调方法
调用了 authenticate() 方法后,系统就会启动指纹传感器,并开始扫描。这时候根据扫描结果,会通过
FingerprintManager.AuthenticationCallback类返回几个回调方法:
// 成功
onAuthenticationSucceeded()
// 失败
onAuthenticationFaile()
// 错误
onAuthenticationError()

一般我们需要重写这几个方法,以实现我们的功能。

注意事项
在使用指纹验证的时候需要几个条件:
1.设备支持指纹验证

Android6.0后才支持指纹功能,现在很多5.0的手机也有指纹功能,但是开发者拿不到FingerprintManager的使用权,可以初步通过api判定


2.用户开启指纹验证

if (!keyguardManager.isKeyguardSecure()) {
   // 如果没有开启密码锁屏,则不能使用指纹识别 
}


3.用户录入了指纹

if (!fingerprintManager.hasEnrolledFingerprints()) {
    //没有录入指纹,不能使用该功能
}


解读FingerprintManager的API

指纹识别Google官方文档 点击查看

上面的图中,我们看到这个包中总共有4个类,下面我们简要介绍一下他们:
1.FingerprintManager:主要用来协调管理和访问指纹识别硬件设备
2.FingerprintManager.AuthenticationCallback这个一个callback接口,当指纹认证后系统会回调这个接口通知app认证的结果是什么
3.FingerprintManager.AuthenticationResult这是一个表示认证结果的类,会在回调接口中以参数给出
4.FingerprintManager.CryptoObject这是一个加密的对象类,用来保证认证的安全性,这是一个重点,下面我们会分析。

Google提供的与指纹识别相关的核心类不多,主类是FingerprintManager,主类依赖三个内部类,如下图所示:

FingerprintManager主要提供三个方法如下:

 

    public void authenticate(CryptoObject, CancellationSignal, flags, AuthenticationCallback,Handler) 
第一个参数是一个加密对象。还记得之前我们大费周章地创建和初始化的Cipher对象吗?这里的 CryptoObject 对象就是使用 Cipher 对象创建创建出来的:new FingerprintManager.CryptoObject(cipher)。

第二个参数是一个 CancellationSignal 对象,该对象提供了取消操作的能力。创建该对象也很简单,使用 new CancellationSignal() 就可以了。

第三个参数是一个标志,默认为0。

第四个参数是 AuthenticationCallback 对象,它本身是 FingerprintManager 类里面的一个抽象类。该类提供了指纹识别的几个回调方法,包括指纹识别成功、失败等。需要我们重写。
最后一个 Handler,可以用于处理回调事件,可以传null。


FingerprintManager.AuthenticationCallback类提供的回调接口如下,重点区分红色下划线标注的部分

 

 public void onAuthenticationError(int errorCode, CharSequence errString) {
           // 验证出错回调 指纹传感器会关闭一段时间,在下次调用authenticate时,会出现禁用期(时间依厂商不同30,1分都有)
           //Called if the authentication threw an error.
        }

        public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
          // 验证帮助回调
          // Called if the user asked for help.
        }

        public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
        // 验证通过
        // Called if the authentication succeeded.
        }

        public void onAuthenticationFailed() {
        // 验证失败  指纹验证失败后,指纹传感器不会立即关闭指纹验证,系统会提供5次重试的机会,即调用5次onAuthenticationFailed后,才会调用onAuthenticationError
        //Called if the authentication failed (bad finger etc...)
        }


因为指纹识别是6.0系统才有的(当然不排除有些厂商定制系统的时候加入这些API,这里暂不考虑)。
有两种方式:
//这种使用的是v4的兼容包,推荐使用这种
FingerprintManagerCompat managerCompat = FingerprintManagerCompat.from(this);
//这种是使用系统服务,但是必须要在sdk为23以上版本才行
FingerprintManager fingerprintManager = (FingerprintManager)getSystemService(Context.FINGERPRINT_SERVICE);

v4包中的类使用与上面标准库中的一致,就是名字不一样而已

指纹识别适配

  指纹识别适配会有很多问题,这些问题可以从下面三种情况中看出。

1、Google官方支持指纹识别的标准接口是在Android6.0开始的,如果各个厂商都升级到6.0并且硬件上都给予支持,那么我们按照标准的指纹识别接口使用就可以了。
2、如果在android6.0发布以后,手机厂商来不及升级,但是工程师们参考了官方指纹识别的代码,把代码移植到他们的6.0版本以下的系统,或者参照Google提供的接口自己实现了一套指纹识别机制,只是对开发者暴露的接口一样,这样就可以像使用标准接口一样使用,但是这种情况就难说了,实现不好的可能本身就有很多bug,适配起也比较麻烦,不过起码还是能用的。
如果厂商在Google之前就已经做了指纹识别,那这种情况肯定不能使用官方标准接口,如果要适配这种设备,只能使用厂商提供的第三方指纹识别SDK。
3、 一般情况下只需要跟着Google官方走就行,6.0以下系统直接不支持,这样也省去很多适配问题。但是如果一个app拥有大量第三方厂商6.0以下的设备,非要支持指纹识别功能,那么只能去做支持了。对于上面提到的三种情况,前面两种情况代码写法是一致的,只需要按照Google官方文档写就行了,只是不再需要api>=23的逻辑判断,代码会有警告,还必须使用try catch进程异常捕获,因为鬼都不知道厂商系统内部会发生什么崩溃出来(红米note3,系统5.0或者5.1的,调用mFingerprintManager.hasEnrolledFingerprints()方法时,内部抛出空指针异常)。第三种情况如果要做支持,只能通过公司合作的方式去找厂商提供SDK了。

  适配建议:

  一般来讲可以弄一个测试渠道发一个基础版本出去收集一下指纹识别相关的数据,以下是我司经过数据统计与验证得到的一些结论。

  1. 6.0及以上系统选择性屏蔽一些机型(有些厂商支持不好)

  2. 6.0以下支持标准接口的设备选择性支持(水很深,只能根据数据收集决定哪些设备可以放开)

  3. 6.0以下不支持标准接口但有指纹识别的设备根据提供的SDK进行适配

Demo栗子

这里写图片描述


这种是使用系统服务,但是必须要在sdk为23以上版本才行,使用案例:
FingerprintManager fingerprintManager = (FingerprintManager)getSystemService(Context.FINGERPRINT_SERVICE)

  • 条件

指纹识别的必要条件:仅支持API23及以上的系统,设备必须带有指纹识别功能

  • 思路
  1. 验证锁屏是否是安全的,
  2. 确认在智能手机上已经有一个指纹是注册的
  3. 访问 Android keystore 存储将对象加密/解密的密钥 生成一个加密密钥和密码
  4. 启动认证过程 实现一个回调类来处理身份认证事件

1. 开启触摸传感器与身份认证的权限,在清单文件 Manifest.xml 中添加

<uses-permission android:name="android.permission.USE_FINGERPRINT" />


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout 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:gravity="center"  
    android:orientation="vertical"  
    android:paddingBottom="@dimen/activity_vertical_margin"  
    android:paddingLeft="@dimen/activity_horizontal_margin"  
    android:paddingRight="@dimen/activity_horizontal_margin"  
    android:paddingTop="@dimen/activity_vertical_margin"  
    tools:context="com.liu.finger.MainActivity">  

    <TextView  
        android:id="@+id/textView"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Hello World!"  
        android:textSize="18sp" />  

    <Button  
        android:id="@+id/btn_activity_main_finger"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_alignParentStart="true"  
        android:layout_below="@+id/textView"  
        android:layout_marginTop="54dp"  
        android:text="指纹识别" />  
</LinearLayout>  


MainActivity.java

public class MainActivity extends FragmentActivity {  
    FingerprintManager manager;  
    KeyguardManager mKeyManager;  
    private final static int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 0;  
    private final static String TAG = "finger_log";  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        manager = (FingerprintManager) this.getSystemService(Context.FINGERPRINT_SERVICE);  

        //获取钥匙管理者
        mKeyManager = (KeyguardManager) this.getSystemService(Context.KEYGUARD_SERVICE);  

       //找到按钮控件
      Button btn_finger = (Button) findViewById(R.id.btn_activity_main_finger);  
      //为按钮设置点击事件
      btn_finger.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                if (isFinger()) {  
       Toast.makeText(MainActivity.this, "请进行指纹识别", Toast.LENGTH_LONG).show();  

                    startListening(null);  
                } else{
                 textView.setText("您的手机暂不支持指纹识别");
                    return;
} 
            }  
        });  

    }  



/**  设备条件判断
 - 设备是否支持指纹识别
 - 设备是否处于安全保护中(有指纹识别的手机,在使用指纹识别的时候,还需要强制设置密码或者图案解锁,如果未设置的话是不许使用指纹识别的)
 - 设备是否已经注册过指纹(如果用户未使用过这个指纹技术,那么只能提示用户到系统设置里面去设置)指纹识别API调用**/
    public boolean isFinger() {  
        //判断当前手机版本
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {  
            Toast.makeText(this, "没有指纹识别权限", Toast.LENGTH_SHORT).show();  
            return false;  
        }  

        Log(TAG, "有指纹权限");  

        //判断硬件是否支持指纹识别  
        if (!manager.isHardwareDetected()) {  
            Toast.makeText(this, "没有指纹识别模块", Toast.LENGTH_SHORT).show();  
            return false;  
        }  

         Log(TAG, "有指纹模块");  

        //判断 是否开启锁屏密码  

        if (!mKeyManager.isKeyguardSecure()) {  
            Toast.makeText(this, "没有开启锁屏密码", Toast.LENGTH_SHORT).show();  
            return false;  
        }  

        Log(TAG, "已开启锁屏密码");  

        //判断是否有指纹录入  
        if (!manager.hasEnrolledFingerprints()) {  
            Toast.makeText(this, "没有录入指纹", Toast.LENGTH_SHORT).show();  
            return false;  
        }  

         Log(TAG, "已录入指纹");  

        return true;  
    }else{
        return false;
   }



/**该对象提供了取消操作的能力。创建该对象也很简单,使用 new CancellationSignal() 就可以了。**/
    CancellationSignal mCancellationSignal = new CancellationSignal();  


/**回调方法**/ 
    FingerprintManager.AuthenticationCallback mSelfCancelled = new FingerprintManager.AuthenticationCallback() {  
        @Override  
        public void onAuthenticationError(int errorCode, CharSequence errString) { 
        // 验证出错回调 指纹传感器会关闭一段时间,在下次调用authenticate时,会出现禁用期(时间依厂商不同30,1分都有) 

            Toast.makeText(MainActivity.this, errString, Toast.LENGTH_SHORT).show();  
            showAuthenticationScreen();  
        }  

        @Override  
        public void onAuthenticationHelp(int helpCode, CharSequence helpString) {  
               // 验证帮助回调
            Toast.makeText(MainActivity.this, helpString, Toast.LENGTH_SHORT).show();  
        }  

        @Override  
        public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {  //验证成功

   Toast.makeText(MainActivity.this, "指纹识别成功", Toast.LENGTH_SHORT).show();  
        }  

        @Override  
        public void onAuthenticationFailed() {  
        // 验证失败  指纹验证失败后,指纹传感器不会立即关闭指纹验证,系统会提供5次重试的机会,即调用5次onAuthenticationFailed后,才会调用onAuthenticationError

          Toast.makeText(MainActivity.this, "指纹识别失败", Toast.LENGTH_SHORT).show();  
        }  
    };  


  /**如果支持一系列的条件,可以认证回调,参数是加密对象**/
      public void startListening(FingerprintManager.CryptoObject cryptoObject) {  
        //判断是否添加指纹识别权限  
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {  

Toast.makeText(this, "没有指纹识别权限", Toast.LENGTH_SHORT).show();  
            return;  
        }  
/**参数分别是:防止第三方恶意攻击的包装类,CancellationSignal对象,flags,回调对象,handle**/
        manager.authenticate(cryptoObject, mCancellationSignal, 0, mSelfCancelled, null);  
    }  


    /**  
     * 如果识别失败次数过多,则转入输入解锁密码界面,
     */  
    private void showAuthenticationScreen() {  

        Intent intent = mKeyManager.createConfirmDeviceCredentialIntent("finger", "测试指纹识别");  
        if (intent != null) {  
            startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);  
        }  
    }  


    @Override  
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
        if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {  
            // Challenge completed, proceed with using cipher  
            if (resultCode == RESULT_OK) {  
                Toast.makeText(this, "识别成功", Toast.LENGTH_SHORT).show();  
            } else {  
                Toast.makeText(this, "识别失败", Toast.LENGTH_SHORT).show();  
            }  
        }  
    }  
    private void Log(String tag, String msg) {  
        Log.d(tag, msg);  
    }  
}  


这种使用的是v4的兼容包,推荐使用这种的使用案例
FingerprintManagerCompat managerCompat = FingerprintManagerCompat.from(this);

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";
    private Button check;
    private FingerprintManagerCompat manager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        check = (Button) findViewById(R.id.btn_check);

        check.setOnClickListener(this);

        // 获取一个FingerPrintManagerCompat的实例
        manager = FingerprintManagerCompat.from(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_check:
                /**
                 * 开始验证,什么时候停止由系统来确定,如果验证成功,那么系统会关系sensor,如果失败,则允许
                 * 多次尝试,如果依旧失败,则会拒绝一段时间,然后关闭sensor,过一段时候之后再重新允许尝试
                 * 
                 * 第四个参数为重点,需要传入一个FingerprintManagerCompat.AuthenticationCallback的子类
                 * 并重写一些方法,不同的情况回调不同的函数
                 */
                manager.authenticate(null, 0, null, new MyCallBack(), null);
                break;
        }
    }

    public class MyCallBack extends FingerprintManagerCompat.AuthenticationCallback {
        private static final String TAG = "MyCallBack";

        // 当出现错误的时候回调此函数,比如多次尝试都失败了的时候,errString是错误信息
        @Override
        public void onAuthenticationError(int errMsgId, CharSequence errString) {
            Log.d(TAG, "onAuthenticationError: " + errString);
        }

        // 当指纹验证失败的时候会回调此函数,失败之后允许多次尝试,失败次数过多会停止响应一段时间然后再停止sensor的工作
        @Override
        public void onAuthenticationFailed() {
            Log.d(TAG, "onAuthenticationFailed: " + "验证失败");
        }

        @Override
        public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
            Log.d(TAG, "onAuthenticationHelp: " + helpString);
        }

        // 当验证的指纹成功时会回调此函数,然后不再监听指纹sensor
        @Override
        public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult
                                                      result) {
            Log.d(TAG, "onAuthenticationSucceeded: " + "验证成功");
        }
    }

}


1.如何让失败或者成功之后Sensor继续保持监听新的指纹?

答:因为API较新的缘故,这个兼容的Manager类还不能做到自动重启的功能,但是我们可以自己写一个。因为Api中规定了如果回调了Error或者Succeed方法之后,sensor会被关闭,直到下一次重新调用authenticate方法授权,但是我们不能在Error或Succeed直接调用这个方法,因为处于安全性的考虑,不允许开发者短时间内连续授权,经过粗略的测试,android允许我们在30s之后重新打开Sensor授权监听,所以我们要做的,就是通过Handler的sendMessageDelayed方法发送一个延迟的消息,再在Handler中重新调用authenticate方法,具体的代码如下:

private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.d(TAG, "handleMessage: 重启指纹模块");
        manager.authenticate(null, 0, null, new MyCallBack(), handler);
    }
};

 

@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
    handler.sendMessageDelayed(new Message(), 1000 * 30);
    Log.d(TAG, "onAuthenticationError: " + errString);
}

@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
    handler.sendMessageDelayed(new Message(), 1000 * 30);
    Log.d(TAG, "onAuthenticationSucceeded: " + "验证成功");
}


低版本适配问题

为什么6.0以下的一些带指纹手机可以用FingerprintManager来操作指纹,而没有指纹的手机会崩溃。
这个估计时因为某些厂商(小米、vivo等)的指纹识别机器的Rom中添加了FingerprintManager的API,实际上这个API是在6.0才加入的。文档:


这就导出了一个问题:如果我要给6.0以下或者没有适配6.0指纹的手机进行指纹操作的时候,要怎么做?

使用FingerprintManagerCompat肯定是不行的,因为文档也说了,低于M的系统版本,FingerprintManagerCompat无论手机是否有指纹识别模块,均认为没有指纹识别。那么我们实际上是可以用FingerprintManager来做的,因为小米等厂商已经把API加进去了(这里要充分测试,毕竟不是官方的api)。

在工程中,使用下面代码来获得一个FingerprintManager:

FingerprintManager manager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);


这个地方要保证你的CompleteSdkVersion版本要大于23,否则IDE也找不到这个类。

接着,假设我们要检查手机是否支持指纹识别:

 manager.hasEnrolledFingerprints();


这个时候,如果你的App需要在6.0的及以上的平台运行,还需要进行运行时权限检查,代码如下:

 

 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) !=
                PackageManager.PERMISSION_GRANTED) {
            manager.hasEnrolledFingerprints();
            return;
        }


到了这里,Studio中还会提示,这个方法要添加注解,表明在6.0及以上的平台中才能使用:

而实际上,我们不能添加这个注解,因为我们不仅仅要给6.0及以上的平台使用,还要给6.0以下的平台使用,那么我们可以直接选中第4行的Disable inspection来忽略这个错误。
当我们把APP安装到手机中的时候,如果手机没有指纹识别模块,APP就会Crash,Log中会显示ClassNotFoundException,而在低版本带指纹识别的机器上运行却没有问题。这就佐证了我们一开始说到的,厂商的Rom中,没有指纹识别的手机并不会添加对应的API,所以自然会提示找不到该类。

那么我们究竟怎么用到这个FingerprintManager类呢?

很简单,在Application中先进行判断,通过反射来检查是否存在该类,然后把结果保存起来:

public class MyApplication extends Application {
    public static final String HAS_FINGERPRINT_API = "hasFingerPrintApi";
    public static final String SETTINGS = "settings";

    @Override
    public void onCreate() {
        super.onCreate();
        SharedPreferences sp = getSharedPreferences(SETTINGS, MODE_PRIVATE);
        if (sp.contains(HAS_FINGERPRINT_API)) { // 检查是否存在该值,不必每次都通过反射来检查
            return;
        }
        SharedPreferences.Editor editor = sp.edit();
        try {
            Class.forName("android.hardware.fingerprint.FingerprintManager"); // 通过反射判断是否存在该类
            editor.putBoolean(HAS_FINGERPRINT_API, true);
        } catch (ClassNotFoundException e) {
            editor.putBoolean(HAS_FINGERPRINT_API, false);
            e.printStackTrace();
        }
        editor.apply();
    }
}


怎么取消监听?

很简单,authenticate方法中的第二个参数是一个CancellationSignal对象,这个对象是用来维护取消操作的,这些操作包括取消监听和设定取消回调等。所以,如果要取消,这个参数就不能是null,可以把代码稍作修改:

在Activity中添加一个CancellationSignal变量:

 private CancellationSignal mCancellationSignal = new CancellationSignal();


接着,在要验证的时候传入这个对象,在要取消的时候,调用这个对象的cancel方法即可:


     @Override
     public void onClick(View v) {
       switch (v.getId()) {
            case R.id.start:
                 if (mCancellationSignal.isCanceled()) {
                    mCancellationSignal = new CancellationSignal();
                }
      mFingerprintManagerCompat.authenticate(null, 0, mCancellationSignal, new MyCallBack(), null);
                 break;
             case R.id.stop:
                mCancellationSignal.cancel();
                 break;
         }
     }

 

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值