Android 6.0指紋識別App開發demo
本文章轉載自 : http://blog.csdn.net/baniel01/article/details/51991764
不詳細的地方,請檢視原文
在Android 6.0中google終於給android系統加上了指紋識別的支援,這個功能在iPhone上早就已經實現了,並且在很多廠商的定製的ROM中也都自己內部實現這個功能了,這個功能來的有點晚啊。在google全新發布的nexus裝置:nexus
5x和nexus 6p中都攜帶了一顆指紋識別晶片在裝置的背面,如下圖(圖片來自網路):
筆者手中的裝置就是圖上的那臺黑色的nexus 5x,話說這臺機器很是好看呢!手感超棒!
廢話不多說,下面我出一個指紋識別的demo app,並且詳細說明怎麼開發一個基於google api的指紋識別app。demo的原始碼在我的github上:
Android M中的指紋識別介面
這個是首先需要關注的問題,在實際動手開始寫app之前需要知道最新的平臺為我們提供了那些指紋識別的介面。所有的指紋識別介面全部在android.hardware.fingerprint這個包下,這個包中的類不是很多,如下:
api doc連結地址:
https://developer.android.com/reference/android/hardware/fingerprint/package-summary.html
大家最好FQ自己看下。
上面的圖中,我們看到這個包中總共有4個類,下面我們簡要介紹一下他們:
1.FingerprintManager:主要用來協調管理和訪問指紋識別硬體裝置
2.FingerprintManager.AuthenticationCallback這個一個callback介面,當指紋認證後系統會回撥這個介面通知app認證的結果是什麼
3.FingerprintManager.AuthenticationResult這是一個表示認證結果的類,會在回撥介面中以引數給出
4.FingerprintManager.CryptoObject這是一個加密的物件類,用來保證認證的安全性,這是一個重點,下面我們會分析。
好了,到這裡我們簡要知道了android 6.0給出的指紋識別的介面不是很多,可以說是簡短幹練。
動手開發一個指紋識別app
現在,我們要動手寫一個利用上面介面的指紋識別app,這個app介面很簡單,就一個activity,這個activity上會啟用指紋識別,然後提示使用者按下指紋,並且會將認證的結果顯示出來。
開始
在開始之前,我們需要知道使用指紋識別硬體的基本步驟:
1.在AndroidManifest.xml中申明如下許可權:
<uses-permission android:name = "android.permission.USE_FINGERPRINT"/>
2.獲得FingerprintManager的物件引用
3.在執行是檢查裝置指紋識別的相容性,比如是否有指紋識別裝置等。下面我們詳細說一下上面的步驟:
申明許可權
這一步比較簡單,只要在AndroidManifest.xml中新增上面說到的許可權就可以了。
獲得FingerprintManager物件引用
這是app開發中獲得系統服務物件的常用方式,如下:
// Using the Android Support Library v4
fingerprintManager = FingerprintManagerCompat.from(this);
// Using API level 23:
fingerprintManager = (FingerprintManager)getSystemService(Context.FINGERPRINT_SERVICE);
上面給出兩種方式,第一種是通過V4支援包獲得相容的物件引用,這是google推行的做法;還有就是直接使用api 23 framework中的介面獲得物件引用。
檢查執行條件
要使得我們的指紋識別app能夠正常執行,有一些條件是必須滿足的。 1). API level 23 指紋識別API是在api level 23也就是android 6.0中加入的,因此我們的app必須執行在這個系統版本之上。因此google推薦使用 Android Support Library v4包來獲得FingerprintManagerCompat物件,因為在獲得的時候這個包會檢查當前系統平臺的版本。2). 硬體 指紋識別肯定要求你的裝置上有指紋識別的硬體,因此在執行時需要檢查系統當中是不是有指紋識別的硬體:
<pre name="code" class="prettyprint"><code class="hljs avrasm has-numbering">if (!fingerprintManager<span class="hljs-preprocessor">.isHardwareDetected</span>()) { AlertDialog<span class="hljs-preprocessor">.Builder</span> builder = new AlertDialog<span class="hljs-preprocessor">.Builder</span>(this)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setTitle</span>(R<span class="hljs-preprocessor">.string</span><span class="hljs-preprocessor">.no</span>_sensor_dialog_title)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setMessage</span>(R<span class="hljs-preprocessor">.string</span><span class="hljs-preprocessor">.no</span>_sensor_dialog_message)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setIcon</span>(android<span class="hljs-preprocessor">.R</span><span class="hljs-preprocessor">.drawable</span><span class="hljs-preprocessor">.stat</span>_sys_warning)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setCancelable</span>(false)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setNegativeButton</span>(R<span class="hljs-preprocessor">.string</span><span class="hljs-preprocessor">.cancel</span>_btn_dialog, new DialogInterface<span class="hljs-preprocessor">.OnClickListener</span>() { @Override public void onClick(DialogInterface dialog, int which) { finish()<span class="hljs-comment">;</span> } })<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.create</span>()<span class="hljs-preprocessor">.show</span>()<span class="hljs-comment">;</span> }</code>
呼叫上面的介面接可以知道系統中是不是有一個這樣的硬體,如果沒有的話,那就需要做一些合適的事情,比如提示使用者當前系統中沒有指紋識別硬體等。 3). 當前裝置必須是處於安全保護中的 這個條件的意思是,你的裝置必須是使用螢幕鎖保護的,這個螢幕鎖可以是password,PIN或者圖案都行。為什麼是這樣呢?因為google原生的邏輯就是:想要使用指紋識別的話,必須首先使能螢幕鎖才行,這個和android 5.0中的smart lock邏輯是一樣的,這是因為google認為目前的指紋識別技術還是有不足之處,安全性還是不能和傳統的方式比較的。我們可以使用下面的程式碼檢查當前裝置是不是處於安全保護中的:
<pre name="code" class="prettyprint"><code class="hljs coffeescript has-numbering"> KeyguardManager keyguardManager =(KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE); <span class="hljs-keyword">if</span> (keyguardManager.isKeyguardSecure()) { }</code>
我們使用KeyguardManager的isKeyguardSecure介面就能知道。 4). 系統中是不是有註冊的指紋 在android 6.0中,普通app要想使用指紋識別功能的話,使用者必須首先在setting中註冊至少一個指紋才行,否則是不能使用的。所以這裡我們需要檢查當前系統中是不是已經有註冊的指紋資訊了:
<pre name="code" class="prettyprint"><code class="hljs avrasm has-numbering">if (!fingerprintManager<span class="hljs-preprocessor">.hasEnrolledFingerprints</span>()) { AlertDialog<span class="hljs-preprocessor">.Builder</span> builder = new AlertDialog<span class="hljs-preprocessor">.Builder</span>(this)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setTitle</span>(R<span class="hljs-preprocessor">.string</span><span class="hljs-preprocessor">.no</span>_fingerprint_enrolled_dialog_title)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setMessage</span>(R<span class="hljs-preprocessor">.string</span><span class="hljs-preprocessor">.no</span>_fingerprint_enrolled_dialog_message)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setIcon</span>(android<span class="hljs-preprocessor">.R</span><span class="hljs-preprocessor">.drawable</span><span class="hljs-preprocessor">.stat</span>_sys_warning)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setCancelable</span>(false)<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.setNegativeButton</span>(R<span class="hljs-preprocessor">.string</span><span class="hljs-preprocessor">.cancel</span>_btn_dialog, new DialogInterface<span class="hljs-preprocessor">.OnClickListener</span>() { @Override public void onClick(DialogInterface dialog, int which) { finish()<span class="hljs-comment">;</span> } })<span class="hljs-comment">;</span> builder<span class="hljs-preprocessor">.create</span>()<span class="hljs-preprocessor">.show</span>()<span class="hljs-comment">;</span> }</code>
如果使用者還沒有註冊一個指紋的話,那麼我們的app可以提示使用者:如果想要使用指紋是功能,請再setting中註冊一個你的指紋。這裡需要囉嗦一句,如果你做過bluetooth或者其他裝置開發的話,那麼你知道你可以通過傳送一個intent來啟動bluetooth開啟的介面,只要是聲明瞭藍芽的管理許可權。但是,到目前位置google任然沒有開放讓普通app啟動指紋註冊介面的許可權,這一點我們可以從setting的AndroidManifest中看到:
<pre name="code" class="prettyprint"><code class="hljs xml has-numbering"><span class="hljs-tag"> <<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.FingerprintSettings"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span>/></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.FingerprintEnrollOnboard"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span>/></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.FingerprintEnrollFindSensor"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span>/></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.FingerprintEnrollEnrolling"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span>/></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.FingerprintEnrollFinish"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span>/></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.FingerprintEnrollIntroduction"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span> /></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.SetupFingerprintEnrollOnboard"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span>/></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.SetupFingerprintEnrollFindSensor"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span>/></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.SetupFingerprintEnrollEnrolling"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span>/></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.SetupFingerprintEnrollFinish"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"false"</span>/></span> <span class="hljs-tag"><<span class="hljs-title">activity</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">".fingerprint.SetupFingerprintEnrollIntroduction"</span> <span class="hljs-attribute">android:exported</span>=<span class="hljs-value">"true"</span> <span class="hljs-attribute">android:permission</span>=<span class="hljs-value">"android.permission.MANAGE_FINGERPRINT"</span> <span class="hljs-attribute">android:theme</span>=<span class="hljs-value">"@style/SetupWizardDisableAppStartingTheme"</span>></span> <span class="hljs-tag"><<span class="hljs-title">intent-filter</span>></span> <span class="hljs-tag"><<span class="hljs-title">action</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">"android.settings.FINGERPRINT_SETUP"</span> /></span> <span class="hljs-tag"><<span class="hljs-title">category</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">"android.intent.category.DEFAULT"</span> /></span> <span class="hljs-tag"></<span class="hljs-title">intent-filter</span>></span> <span class="hljs-tag"></<span class="hljs-title">activity</span>></span></code>
大部分的fingerprint設定介面都沒有exporte,只有SetupFingerprintEnrollIntroduction,但是這個介面需要android.permission.MANAGE_FINGERPRINT這個許可權,並且這個許可權只能是系統app使用,這就直接防止第三方app啟動這個介面了。(不知道日後google會不會開放這個許可權。。。。。)
一個好的app,應該在執行時都檢查一下上面的條件,防止app出現意外的錯誤。
掃描使用者按下的指紋
要開始掃描使用者按下的指紋是很簡單的,只要呼叫FingerprintManager的authenticate方法即可,那麼現在我們來看一下這個介面: 上圖是google的api文件中的描述,現在我們挨個解釋一下這些引數都是什麼: 1. crypto這是一個加密類的物件,指紋掃描器會使用這個物件來判斷認證結果的合法性。這個物件可以是null,但是這樣的話,就意味這app無條件信任認證的結果,雖然從理論上這個過程可能被攻擊,資料可以被篡改,這是app在這種情況下必須承擔的風險。因此,建議這個引數不要置為null。這個類的例項化有點麻煩,主要使用javax的security介面實現,後面我的demo程式中會給出一個helper類,這個類封裝內部實現的邏輯,開發者可以直接使用我的類簡化例項化的過程。2. cancel 這個是CancellationSignal類的一個物件,這個物件用來在指紋識別器掃描使用者指紋的是時候取消當前的掃描操作,如果不取消的話,那麼指紋掃描器會移植掃描直到超時(一般為30s,取決於具體的廠商實現),這樣的話就會比較耗電。建議這個引數不要置為null。3. flags 標識位,根據上圖的文件描述,這個位暫時應該為0,這個標誌位應該是保留將來使用的。 4. callback 這個是FingerprintManager.AuthenticationCallback類的物件,這個是這個介面中除了第一個引數之外最重要的引數了。當系統完成了指紋認證過程(失敗或者成功都會)後,會回撥這個物件中的介面,通知app認證的結果。這個引數不能為NULL。5. handler 這是Handler類的物件,如果這個引數不為null的話,那麼FingerprintManager將會使用這個handler中的looper來處理來自指紋識別硬體的訊息。通常來講,開發這不用提供這個引數,可以直接置為null,因為FingerprintManager會預設使用app的main looper來處理。
取消指紋掃描
上面我們提到了取消指紋掃描的操作,這個操作是很常見的。這個時候可以使用CancellationSignal這個類的cancel方法實現: 這個方法專門用於傳送一個取消的命令給特定的監聽器,讓其取消當前操作。 因此,app可以在需要的時候呼叫cancel方法來取消指紋掃描操作。
建立CryptoObject類物件
上面我們分析FingerprintManager的authenticate方法的時候,看到這個方法的第一個引數就是CryptoObject類的物件,現在我們看一下這個物件怎麼去例項化。我們知道,指紋識別的結果可靠性是非常重要的,我們肯定不希望認證的過程被一個第三方以某種形式攻擊,因為我們引入指紋認證的目的就是要提高安全性。但是,從理論角度來說,指紋認證的過程是可能被第三方的中介軟體惡意攻擊的,常見的攻擊的手段就是攔截和篡改指紋識別器提供的結果。這裡我們可以提供CryptoObject物件給authenticate方法來避免這種形式的攻擊。FingerprintManager.CryptoObject是基於Java加密API的一個包裝類,並且被FingerprintManager用來保證認證結果的完整性。通常來講,用來加密指紋掃描結果的機制就是一個Javax.Crypto.Cipher物件。Cipher物件本身會使用由應用呼叫Android keystore的API產生一個key來實現上面說道的保護功能。 為了理解這些類之間是怎麼協同工作的,這裡我給出一個用於例項化CryptoObject物件的包裝類程式碼,我們先看下這個程式碼是怎麼實現的,然後再解釋一下為什麼是這樣。
<pre name="code" class="prettyprint"><code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CryptoObjectHelper</span> {</span> <span class="hljs-comment"></span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String KEY_NAME = <span class="hljs-string">"com.createchance.android.sample.fingerprint_authentication_key"</span>; <span class="hljs-comment"></span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String KEYSTORE_NAME = <span class="hljs-string">"AndroidKeyStore"</span>; <span class="hljs-comment"></span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES; <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String TRANSFORMATION = KEY_ALGORITHM + <span class="hljs-string">"/"</span> + BLOCK_MODE + <span class="hljs-string">"/"</span> + ENCRYPTION_PADDING; <span class="hljs-keyword">final</span> KeyStore _keystore; <span class="hljs-keyword">public</span> <span class="hljs-title">CryptoObjectHelper</span>() <span class="hljs-keyword">throws</span> Exception { _keystore = KeyStore.getInstance(KEYSTORE_NAME); _keystore.load(<span class="hljs-keyword">null</span>); } <span class="hljs-keyword">public</span> FingerprintManagerCompat.CryptoObject <span class="hljs-title">buildCryptoObject</span>() <span class="hljs-keyword">throws</span> Exception { Cipher cipher = createCipher(<span class="hljs-keyword">true</span>); <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> FingerprintManagerCompat.CryptoObject(cipher); } Cipher createCipher(<span class="hljs-keyword">boolean</span> retry) <span class="hljs-keyword">throws</span> Exception { Key key = GetKey(); Cipher cipher = Cipher.getInstance(TRANSFORMATION); <span class="hljs-keyword">try</span> { cipher.init(Cipher.ENCRYPT_MODE | Cipher.DECRYPT_MODE, key); } <span class="hljs-keyword">catch</span>(KeyPermanentlyInvalidatedException e) { _keystore.deleteEntry(KEY_NAME); <span class="hljs-keyword">if</span>(retry) { createCipher(<span class="hljs-keyword">false</span>); } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">"Could not create the cipher for fingerprint authentication."</span>, e); } } <span class="hljs-keyword">return</span> cipher; } Key GetKey() <span class="hljs-keyword">throws</span> Exception { Key secretKey; <span class="hljs-keyword">if</span>(!_keystore.isKeyEntry(KEY_NAME)) { CreateKey(); } secretKey = _keystore.getKey(KEY_NAME, <span class="hljs-keyword">null</span>); <span class="hljs-keyword">return</span> secretKey; } <span class="hljs-keyword">void</span> CreateKey() <span class="hljs-keyword">throws</span> Exception { KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, KEYSTORE_NAME); KeyGenParameterSpec keyGenSpec = <span class="hljs-keyword">new</span> KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(BLOCK_MODE) .setEncryptionPaddings(ENCRYPTION_PADDING) .setUserAuthenticationRequired(<span class="hljs-keyword">true</span>) .build(); keyGen.init(keyGenSpec); keyGen.generateKey(); } }</code>
上面的類會針對每個CryptoObject物件都會新建一個Cipher物件,並且會使用由應用生成的key。這個key的名字是使用KEY_NAME變數定義的,這個名字應該是保證唯一的,建議使用域名區別。GetKey方法會嘗試使用Android Keystore的API來解析一個key(名字就是上面我們定義的),如果key不存在的話,那就呼叫CreateKey方法新建一個key。cipher變數的例項化是通過呼叫Cipher.getInstance方法獲得的,這個方法接受一個transformation引數,這個引數制定了資料怎麼加密和解密。然後呼叫Cipher.init方法就會使用應用的key來完成cipher物件的例項化工作。這裡需要強調一點,在以下情況下,android會認為當前key是無效的: 1. 一個新的指紋image已經註冊到系統中 2. 當前裝置中的曾經註冊過的指紋現在不存在了,可能是被全部刪除了 3. 使用者關閉了螢幕鎖功能 4. 使用者改變了螢幕鎖的方式 當上面的情況發生的時候,Cipher.init方法都會丟擲KeyPermanentlyInvalidatedException的異常,上面我的程式碼中捕獲了這個異常,並且刪除了當前無效的key,然後根據引數嘗試再次建立。上面的程式碼中使用了android的KeyGenerator來建立一個key並且把它儲存在裝置中。KeyGenerator類會建立一個key,但是需要一些原始資料才能建立key,這些原始的資訊是通過KeyGenParameterSpec類的物件來提供的。KeyGenerator類物件的例項化是使用它的工廠方法getInstance進行的,從上面的程式碼中我們可以看到這裡使用的AES(Advanced Encryption Standard )加密演算法的,AES會將資料分成幾個組,然後針對幾個組進行加密。接下來,KeyGenParameterSpec的例項化是使用它的Builder方法,KeyGenParameterSpec.Builder封裝了以下重要的資訊: 1. key的名字 2. key必須在加密和解密的時候是有效的 3. 上面程式碼中BLOCK_MODE被設定為Cipher Block Chaining也就是KeyProperties.BLOCK_MODE_CBC,這意味著每一個被AES切分的資料塊都與之前的資料塊進行了異或運算了,這樣的目的就是為了建立每個資料塊之間的依賴關係。4. CryptoObjectHelper類使用了PKSC7(Public Key Cryptography Standard #7)的方式去產生用於填充AES資料塊的位元組,這樣就是要保證每個資料塊的大小是等同的(因為需要異或計算還有方面演算法進行資料處理,詳細可以檢視AES的演算法原理)。5. setUserAuthenticationRequired(true)呼叫意味著在使用key之前使用者的身份需要被認證。 每次KeyGenParameterSpec建立的時候,他都被用來初始化KeyGenerator,這個物件會產生儲存在裝置上的key。
怎麼使用CryptoObjectHelper呢?
下面我們看一下怎麼使用CryptoObjectHelper這個類,我們直接看程式碼就知道了:
<pre name="code" class="prettyprint pad_bot"><code class="hljs cs has-numbering">CryptoObjectHelper cryptoObjectHelper = <span class="hljs-keyword">new</span> CryptoObjectHelper(); fingerprintManager.authenticate(cryptoObjectHelper.buildCryptoObject(), <span class="hljs-number">0</span>, cancellationSignal, myAuthCallback, <span class="hljs-keyword">null</span>);</code>
使用是比較簡單的,首先new一個CryptoObjectHelper物件,然後呼叫buildCryptoObject方法就能得到CryptoObject物件了。
處理使用者的指紋認證結果
前面我們分析authenticate介面的時候說道,呼叫這個介面的時候必須提供FingerprintManager.AuthenticationCallback類的物件,這個物件會在指紋認證結束之後系統回撥以通知app認證的結果的。在android 6.0中,指紋的掃描和認證都是在另外一個程序中完成(指紋系統服務)的,因此底層什麼時候能夠完成認證我們app是不能假設的。因此,我們只能採取非同步的操作方式,也就是當系統底層完成的時候主動通知我們,通知的方式就是通過回撥我們自己實現的FingerprintManager.AuthenticationCallback類,這個類中定義了一些回撥方法以供我們進行必要的處理: 下面我們簡要介紹一下這些介面的含義: 1. OnAuthenticationError(int errorCode, ICharSequence errString) 這個介面會再系統指紋認證出現不可恢復的錯誤的時候才會呼叫,並且引數errorCode就給出了錯誤碼,標識了錯誤的原因。這個時候app能做的只能是提示使用者重新嘗試一遍。2. OnAuthenticationFailed() 這個介面會在系統指紋認證失敗的情況的下才會回撥。注意這裡的認證失敗和上面的認證錯誤是不一樣的,雖然結果都是不能認證。認證失敗是指所有的資訊都採集完整,並且沒有任何異常,但是這個指紋和之前註冊的指紋是不相符的;但是認證錯誤是指在採集或者認證的過程中出現了錯誤,比如指紋感測器工作異常等。也就是說認證失敗是一個可以預期的正常情況,而認證錯誤是不可預期的異常情況。3. OnAuthenticationHelp(int helpMsgId, ICharSequence helpString) 上面的認證失敗是認證過程中的一個異常情況,我們說那種情況是因為出現了不可恢復的錯誤,而我們這裡的OnAuthenticationHelp方法是出現了可以回覆的異常才會呼叫的。什麼是可以恢復的異常呢?一個常見的例子就是:手指移動太快,當我們把手指放到感測器上的時候,如果我們很快地將手指移走的話,那麼指紋感測器可能只採集了部分的資訊,因此認證會失敗。但是這個錯誤是可以恢復的,因此只要提示使用者再次按下指紋,並且不要太快移走就可以解決。4. OnAuthenticationSucceeded(FingerprintManagerCompati.AuthenticationResult result)這個介面會在認證成功之後回撥。我們可以在這個方法中提示使用者認證成功。這裡需要說明一下,如果我們上面在呼叫authenticate的時候,我們的CryptoObject不是null的話,那麼我們在這個方法中可以通過AuthenticationResult來獲得Cypher物件然後呼叫它的doFinal方法。doFinal方法會檢查結果是不是會攔截或者篡改過,如果是的話會丟擲一個異常。當我們發現這些異常的時候都應該將認證當做是失敗來來處理,為了安全建議大家都這麼做。關於上面的介面還有2點需要補充一下: 1. 上面我們說道OnAuthenticationError 和 OnAuthenticationHelp方法中會有錯誤或者幫助碼以提示為什麼認證不成功。Android系統定義了幾個錯誤和幫助碼在FingerprintManager類中,如下: 我們的callback類實現的時候最好需要處理這些錯誤和幫助碼。 2. 當指紋掃描器正在工作的時候,如果我們取消本次操作的話,系統也會回撥OnAuthenticationError方法的,只是這個時候的錯誤碼是FingerprintManager.FINGERPRINT_ERROR_CANCELED(值為5),因此app需要區別對待。下面給出我的程式碼中實現的callback子類:
<pre name="code" class="prettyprint"><code class="hljs java has-numbering"><span class="hljs-keyword">package</span> com.createchance.fingerprintdemo; <span class="hljs-keyword">import</span> android.os.Handler; <span class="hljs-keyword">import</span> android.support.v4.hardware.fingerprint.FingerprintManagerCompat; <span class="hljs-keyword">import</span> javax.crypto.BadPaddingException; <span class="hljs-keyword">import</span> javax.crypto.IllegalBlockSizeException; <span class="hljs-javadoc">/** * Created by baniel on 7/21/16. */</span> <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyAuthCallback</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">FingerprintManagerCompat</span>.<span class="hljs-title">AuthenticationCallback</span> {</span> <span class="hljs-keyword">private</span> Handler handler = <span class="hljs-keyword">null</span>; <span class="hljs-keyword">public</span> <span class="hljs-title">MyAuthCallback</span>(Handler handler) { <span class="hljs-keyword">super</span>(); <span class="hljs-keyword">this</span>.handler = handler; } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAuthenticationError</span>(<span class="hljs-keyword">int</span> errMsgId, CharSequence errString) { <span class="hljs-keyword">super</span>.onAuthenticationError(errMsgId, errString); <span class="hljs-keyword">if</span> (handler != <span class="hljs-keyword">null</span>) { handler.obtainMessage(MainActivity.MSG_AUTH_ERROR, errMsgId, <span class="hljs-number">0</span>).sendToTarget(); } } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAuthenticationHelp</span>(<span class="hljs-keyword">int</span> helpMsgId, CharSequence helpString) { <span class="hljs-keyword">super</span>.onAuthenticationHelp(helpMsgId, helpString); <span class="hljs-keyword">if</span> (handler != <span class="hljs-keyword">null</span>) { handler.obtainMessage(MainActivity.MSG_AUTH_HELP, helpMsgId, <span class="hljs-number">0</span>).sendToTarget(); } } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAuthenticationSucceeded</span>(FingerprintManagerCompat.AuthenticationResult result) { <span class="hljs-keyword">super</span>.onAuthenticationSucceeded(result); <span class="hljs-keyword">try</span> { result.getCryptoObject().getCipher().doFinal(); <span class="hljs-keyword">if</span> (handler != <span class="hljs-keyword">null</span>) { handler.obtainMessage(MainActivity.MSG_AUTH_SUCCESS).sendToTarget(); } } <span class="hljs-keyword">catch</span> (IllegalBlockSizeException e) { e.printStackTrace(); } <span class="hljs-keyword">catch</span> (BadPaddingException e) { e.printStackTrace(); } } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAuthenticationFailed</span>() { <span class="hljs-keyword">super</span>.onAuthenticationFailed(); <span class="hljs-keyword">if</span> (handler != <span class="hljs-keyword">null</span>) { handler.obtainMessage(MainActivity.MSG_AUTH_FAILED).sendToTarget(); } } }</code>
這個子類實現很簡單,主要的實現方式就是將訊息拋給主介面的Handler來處理:
<pre name="code" class="prettyprint"><code class="hljs cs has-numbering">handler = <span class="hljs-keyword">new</span> Handler() { @Override <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span>(Message msg) { super.handleMessage(msg); Log.d(TAG, <span class="hljs-string">"msg: "</span> + msg.what + <span class="hljs-string">" ,arg1: "</span> + msg.arg1); <span class="hljs-keyword">switch</span> (msg.what) { <span class="hljs-keyword">case</span> MSG_AUTH_SUCCESS: setResultInfo(R.<span class="hljs-keyword">string</span>.fingerprint_success); mCancelBtn.setEnabled(<span class="hljs-keyword">false</span>); mStartBtn.setEnabled(<span class="hljs-keyword">true</span>); cancellationSignal = <span class="hljs-keyword">null</span>; <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> MSG_AUTH_FAILED: setResultInfo(R.<span class="hljs-keyword">string</span>.fingerprint_not_recognized); mCancelBtn.setEnabled(<span class="hljs-keyword">false</span>); mStartBtn.setEnabled(<span class="hljs-keyword">true</span>); cancellationSignal = <span class="hljs-keyword">null</span>; <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> MSG_AUTH_ERROR: handleErrorCode(msg.arg1); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> MSG_AUTH_HELP: handleHelpCode(msg.arg1); <span class="hljs-keyword">break</span>; } } };</code>
這裡分別處理四中回撥,並且針對錯誤碼呼叫handleErrorCode方法處理:
<pre name="code" class="prettyprint"><code class="hljs cs has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleErrorCode</span>(<span class="hljs-keyword">int</span> code) { <span class="hljs-keyword">switch</span> (code) { <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ERROR_CANCELED: setResultInfo(R.<span class="hljs-keyword">string</span>.ErrorCanceled_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE: setResultInfo(R.<span class="hljs-keyword">string</span>.ErrorHwUnavailable_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ERROR_LOCKOUT: setResultInfo(R.<span class="hljs-keyword">string</span>.ErrorLockout_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ERROR_NO_SPACE: setResultInfo(R.<span class="hljs-keyword">string</span>.ErrorNoSpace_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ERROR_TIMEOUT: setResultInfo(R.<span class="hljs-keyword">string</span>.ErrorTimeout_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS: setResultInfo(R.<span class="hljs-keyword">string</span>.ErrorUnableToProcess_warning); <span class="hljs-keyword">break</span>; } }</code>
很簡單,就是針對不同的錯誤碼,設定介面上不同的顯示文字,以提示使用者。這裡大家可以很據自己的需要修改邏輯。 針對幫助碼呼叫handleHelpCode方法處理:
<pre name="code" class="prettyprint"><code class="hljs cs has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleHelpCode</span>(<span class="hljs-keyword">int</span> code) { <span class="hljs-keyword">switch</span> (code) { <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ACQUIRED_GOOD: setResultInfo(R.<span class="hljs-keyword">string</span>.AcquiredGood_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY: setResultInfo(R.<span class="hljs-keyword">string</span>.AcquiredImageDirty_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ACQUIRED_INSUFFICIENT: setResultInfo(R.<span class="hljs-keyword">string</span>.AcquiredInsufficient_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL: setResultInfo(R.<span class="hljs-keyword">string</span>.AcquiredPartial_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST: setResultInfo(R.<span class="hljs-keyword">string</span>.AcquiredTooFast_warning); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> FingerprintManager.FINGERPRINT_ACQUIRED_TOO_SLOW: setResultInfo(R.<span class="hljs-keyword">string</span>.AcquiredToSlow_warning); <span class="hljs-keyword">break</span>; } }</code>
這裡的處理和handleErrorCode是一樣的。
總結
這裡我們總計一下,android 6.0上的指紋識別開發的幾個要點:
1. 建議使用Android Support Library v4 Compatibility API,不要使用直接framework中的api。
2. 在使用指紋硬體之前一定要檢查上面提到的幾個檢查條件
3. 根據google的建議最好使用google提供的指紋是被icon來標示你的指紋識別介面:
這個做的目的就是為了很明確地提示使用者這是一個指紋識別操作,就像人們看到藍芽的那個小標識就知道這是藍芽操作一樣。當然,這只是google的一個實踐性的建議,並非強制。
4. app需要及時通知使用者當前的操作以及操作的結果,比如需要明確告訴使用者當前正在掃描指紋,請把你的指紋放在感測器上等。
5. 最後需要注意的就是Android Support Library v4中的FingerprintManager類名字是FingerprintManagerCompat,並且他們的authenticate方法引數順序不一樣,flags和cancel的位置在兩個類中是不一樣的,這一點需要注意(個人覺得,這會不會是google的失誤呢???嘿嘿。。。。。)
demo執行效果截圖(運行於nexus 5x)
初始狀態
掃描狀態
掃描失敗(出現可以恢復的錯誤,這裡是手指移動太快)
認證失敗
認證成功
本文章轉載自 : http://blog.csdn.net/baniel01/article/details/51991764