很久没写文章了,最近也比较偷懒,今天继续讨论我实际开发中遇到的需求,那就是关于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方法便可,那么如今咱们来看一下这个接口:
上面咱们分析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