很久沒寫文章了,最近也比較偷懶,今天繼續討論我實際開發中遇到的需求,那就是關於APP解鎖,你們都知道。如今愈來愈多的APP在填入帳號密碼後,第二次登陸後,基本不會再次重複輸入帳號密碼了。而是快捷登陸,而經常使用的就是 指紋解鎖 和 手勢解鎖 二種.javascript
這邊我只是展現個人需求的邏輯,不一樣項目可能邏輯不一樣,不影響本文主要內容。html
主要步驟就分三步:java
咱們一步步來看。android
當用帳號密碼登陸成功後,咱們就在登陸界面直接彈出一個彈框,而後讓用戶選擇想要的快捷登陸方式,固然若是用戶二種都不想要,那就直接按取消,而後登陸到主頁,而後下次再打開應用就會又要從新輸入帳號密碼。
git
由於Android手機有不少種類,有些有指紋,有些沒有指紋, 那咱們須要在有指紋的時候,跳出這個有二種選擇的彈框,若是沒有指紋解鎖,就直接跳到手勢解鎖的界面。github
個人判斷可能比較籠統,固然還有更好的:算法
在網上看到有人用反射,就是在Application中,用反射獲取FingerprintManager這個類的對象,看是否能成功獲取,若是能,就存一個boolean變量爲ture,說明這個手機裏面有指紋相關的。若是獲取失敗,就說明沒有指紋。api
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();
}
}複製代碼
我之前作彈出框都是使用Dialog系列,後來無心間看到谷歌推薦你們使用DialogFragment來作彈框,取代原來的Dialog,因此正好藉着此次機會,本身寫了這個DialogFragment。我下面只給出重要部分。具體的你們去百度下DialogFragment便可。安全
public class LockChooseFragment extends DialogFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
//設置DialogFragment 的主題及彈框的Style。
setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Material_Light_Dialog);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_lock_choose, container, false);
unbinder = ButterKnife.bind(this, view);
return view;
}
@Override
public void onResume() {
super.onResume();
aty = (LoginActivity) getActivity();
//讓咱們的彈框沒法點擊外面區域消失
getDialog().setCanceledOnTouchOutside(false);
getDialog().setCancelable(false);
}
}複製代碼
好了。接下去彈框出來了要點擊一種解鎖,而後進行下一個界面。咱們先從簡單的手勢解鎖來講好了。app
我用的是Github的開源手勢解鎖:PatternLockView
哈哈,是否是太簡單了。。。莫怪我偷懶啊。由於github中的API寫的很清楚了。我就不重複介紹怎麼使用。我使用了以爲的確還不錯。推薦哈。
首先咱們知道谷歌提供了fingerprint包。包下面的類具體有下面這些:
在開始以前,咱們須要知道使用指紋識別硬件的基本步驟:
在AndroidManifest.xml中申明以下權限:<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
得到FingerprintManager的對象引用
下面咱們詳細說一下上面的2和3 步驟:
這是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可以正常運行,有一些條件是必須知足的。
使用 fingerprintManager.isHardwareDetected()
來判斷是否有該硬件支持,fingerprintManager.hasEnrolledFingerprints()
判斷是否手機中錄有指紋。
這裏我在使用個人手機作開發時候就遇到了一個大坑,上面提到了。谷歌推薦使用FingerprintManagerCompat,可是我在用FingerprintManagerCompat 來調用isHardwareDetected()和hasEnrolledFingerprints()時候,返回的都是false,可是用FingerprintManager來調用isHardwareDetected()和hasEnrolledFingerprints()時候,倒是返回true,而實際上我用的是小米5,Android 6 ,API23 的手機,也的確是有指紋功能的,因此我不知道爲何反而FingerprintManagerCompat這個兼容類返回是有問題的,應該跟國內廠商的底層源碼修改有關。我在Google Issue Tracker中也有不少人遇到了這個問題。但基本都什麼華爲,小米,三星等,都不是谷歌親兒子。因此後來我用的是FingerprintManager這個類,這個類的使用要求在API23及以上,由於畢竟谷歌的指紋是API23纔出來的,而我上面又正好直接判斷API23才顯示指紋解鎖的選項。不謀而合。。哈哈。可能這裏有點偷懶了。
判斷了是否有硬件支持,和手機是否有指紋以後,要注意,谷歌還須要判斷當前設備必須是處於安全保護中的,即:你的設備必須是使用屏幕鎖保護的,這個屏幕鎖能夠是password,PIN或者圖案都行。爲何是這樣呢?由於google原生的邏輯就是:想要使用指紋識別的話,必須首先使能屏幕鎖才行,這個和android 5.0中的smart lock邏輯是同樣的,這是由於google認爲目前的指紋識別技術仍是有不足之處,安全性仍是不能和傳統的方式比較的。
KeyguardManager keyguardManager =(KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);
if (keyguardManager.isKeyguardSecure()) {
// this device is secure.
}複製代碼
好了,這些前戲都作好了,咱們就要開始指紋的驗證了。
要開始掃描用戶按下的指紋是很簡單的,只要調用FingerprintManager的authenticate方法便可,那麼如今咱們來看一下這個接口:
上圖是google的api文檔中的描述,如今咱們挨個解釋一下這些參數都是什麼:上面咱們分析FingerprintManager的authenticate方法的時候,看到這個方法的第一個參數就是CryptoObject類的對象,如今咱們看一下這個對象怎麼去實例化。
咱們知道,指紋識別的結果可靠性是很是重要的,咱們確定不但願認證的過程被一個第三方以某種形式攻擊,由於咱們引入指紋認證的目的就是要提升安全性。可是,從理論角度來講,指紋認證的過程是可能被第三方的中間件惡意攻擊的,常見的攻擊的手段就是攔截和篡改指紋識別器提供的結果。這裏咱們能夠提供CryptoObject對象給authenticate方法來避免這種形式的攻擊。
FingerprintManager.CryptoObject是基於Java加密API的一個包裝類,而且被FingerprintManager用來保證認證結果的完整性。一般來說,用來加密指紋掃描結果的機制就是一個Javax.Crypto.Cipher對象。Cipher對象自己會使用由應用調用Android keystore的API產生一個key來實現上面說道的保護功能。
爲了理解這些類之間是怎麼協同工做的,這裏我給出一個用於實例化CryptoObject對象的包裝類代碼,咱們先看下這個代碼是怎麼實現的,而後再解釋一下爲何是這樣。
public class CryptoObjectHelper {
// This can be key name you want. Should be unique for the app.
static final String KEY_NAME = "com.createchance.android.sample.fingerprint_authentication_key";
// We always use this keystore on Android.
static final String KEYSTORE_NAME = "AndroidKeyStore";
// Should be no need to change these values.
static final String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
static final String TRANSFORMATION = KEY_ALGORITHM + "/" +
BLOCK_MODE + "/" +
ENCRYPTION_PADDING;
final KeyStore _keystore;
public CryptoObjectHelper() throws Exception
{
_keystore = KeyStore.getInstance(KEYSTORE_NAME);
_keystore.load(null);
}
public FingerprintManagerCompat.CryptoObject buildCryptoObject() throws Exception
{
Cipher cipher = createCipher(true);
return new FingerprintManagerCompat.CryptoObject(cipher);
}
Cipher createCipher(boolean retry) throws Exception
{
Key key = GetKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
try
{
cipher.init(Cipher.ENCRYPT_MODE | Cipher.DECRYPT_MODE, key);
} catch(KeyPermanentlyInvalidatedException e)
{
_keystore.deleteEntry(KEY_NAME);
if(retry)
{
createCipher(false);
} else
{
throw new Exception("Could not create the cipher for fingerprint authentication.", e);
}
}
return cipher;
}
Key GetKey() throws Exception
{
Key secretKey;
if(!_keystore.isKeyEntry(KEY_NAME))
{
CreateKey();
}
secretKey = _keystore.getKey(KEY_NAME, null);
return secretKey;
}
void CreateKey() throws Exception
{
KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, KEYSTORE_NAME);
KeyGenParameterSpec keyGenSpec =
new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(BLOCK_MODE)
.setEncryptionPaddings(ENCRYPTION_PADDING)
.setUserAuthenticationRequired(true)
.build();
keyGen.init(keyGenSpec);
keyGen.generateKey();
}
}複製代碼
上面的類會針對每一個CryptoObject對象都會新建一個Cipher對象,而且會使用由應用生成的key。這個key的名字是使用KEY_NAME變量定義的,這個名字應該是保證惟一的,建議使用域名區別。GetKey方法會嘗試使用Android Keystore的API來解析一個key(名字就是上面咱們定義的),若是key不存在的話,那就調用CreateKey方法新建一個key。
cipher變量的實例化是經過調用Cipher.getInstance方法得到的,這個方法接受一個transformation參數,這個參數制定了數據怎麼加密和解密。而後調用Cipher.init方法就會使用應用的key來完成cipher對象的實例化工做。
這裏須要強調一點,在如下狀況下,android會認爲當前key是無效的:
怎麼使用CryptoObjectHelper呢?
下面咱們看一下怎麼使用CryptoObjectHelper這個類,咱們直接看代碼就知道了:
CryptoObjectHelper cryptoObjectHelper = new CryptoObjectHelper();
fingerprintManager.authenticate(cryptoObjectHelper.buildCryptoObject(), 0,cancellationSignal, myAuthCallback, null);複製代碼
使用是比較簡單的,首先new一個CryptoObjectHelper對象,而後調用buildCryptoObject方法就能獲得CryptoObject對象了。
上面咱們提到了取消指紋掃描的操做,這個操做是很常見的。這個時候可使用CancellationSignal這個類的cancel方法實現:
前面咱們分析authenticate接口的時候說道,調用這個接口的時候必須提供FingerprintManager.AuthenticationCallback類的對象,這個對象會在指紋認證結束以後系統回調以通知app認證的結果的。在android 6.0中,指紋的掃描和認證都是在另一個進程中完成(指紋系統服務)的,所以底層何時可以完成認證咱們app是不能假設的。所以,咱們只能採起異步的操做方式,也就是當系統底層完成的時候主動通知咱們,通知的方式就是經過回調咱們本身實現的FingerprintManager.AuthenticationCallback類,這個類中定義了一些回調方法以供咱們進行必要的處理:
這裏寫圖片描述好比這是我寫的自定義的AuthenticationCallback類
class FingerAuthCallback extends FingerprintManagerCompat.AuthenticationCallback {
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
super.onAuthenticationError(errMsgId, errString);
showError(errString);
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
super.onAuthenticationHelp(helpMsgId, helpString);
showError(helpString);
}
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
mIcon.setImageResource(R.drawable.ic_fingerprint_success);
mErrorTextView.setTextColor(
ContextCompat.getColor(context, R.color.success_color));
mErrorTextView.setText(
context.getResources().getString(R.string.fingerprint_success));
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
showError(context.getResources().getString(R.string.fingerprint_not_recognized));
}
}複製代碼
指紋解鎖能夠用這個Github上的開源的庫:FingerprintAuthHelper
我使用了。起碼我測試沒問題。
谷歌的指紋解鎖的Demo:FingerprintDialog (進入後點擊右上角的download按鈕,下載demo)
參考文章:
感謝createchance的 Android 6.0指紋識別App開發demo