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;
         }
     }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章