Android指纹识别API讲解,一种更快更好的用户体验

转载请注明出处:https://blog.csdn.net/guolin_blog/article/details/81450114java

本文同步发表于个人微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 便可关注,每一个工做日都有文章更新。android

我发现了一个比较怪的现象。在iPhone上使用十分广泛的指纹认证功能,在Android手机上却鲜有APP使用,我简单观察了一下,发现Android手机上基本上只有支付宝、微信和极少APP支持指纹认证功能,就连银行和金融类的应用都基本不支持,甚至不少开发者都不知道Android系统是有指纹认证的官方API的。git

事实上,Android从6.0系统开始就支持指纹认证功能了,可是指纹功能还须要有硬件支持才行,而Android手机的硬件都是由各厂商生产的,手机档次也良莠不齐,所以不能像iPhone那样保证全部的手机都是支持指纹认证功能的。因此,可能不少开发者就以为,即便作了指纹认证功能,也没法兼容全部的手机,仍是要配合图案解锁或密码等功能一块儿使用才行,那么索性就只用图案和密码好了,一劳永逸。github

看似这样解释好像也合情合理,但其实受伤的是数以亿计的Android手机用户。明明有更轻松更快捷的使用方式,却由于APP不予支持,最终只能使用更加原始和笨拙的方式。在国内,绝大多数Android手机的指纹认证功能都仅仅只局限于用来解锁手机而已,不多有使用到APP的功能逻辑当中。web

其实将指纹认证功能使用到APP的功能逻辑当中是有不少功能场景的,好比说金融银行类APP可使用指纹认证来快速登陆,应用商店类APP可使用指纹认证来下载安装软件,股票证券类APP可使用指纹认证来操做和交易等等。微信

虽然有了应用场景,还有不少开发者可能会担忧,指纹认证功能实现起来会不会很复杂?由于毕竟支持的设备有限,还要配合图案和密码来使用才行,若是实现起来很是复杂,又只能支持部分设备的话,那投入产出比就过低了,或许这也是不少APP不愿去实现指纹认证功能的缘由。这里我不得不说,Android官方提供的指纹认证Demo的确是挺复杂的,看着让人望而却步。可是你们不用担忧,本篇文章中我会带着你们一块儿去实现一个最简版的指纹认证Demo,直接复制粘贴本文中的代码到你们各自的项目中,便可一步集成指纹认证功能。ide

那么话很少说,首先新建一个FingerprintTest项目,并选择添加一个Empty Activity。而后修改activity_main.xml中的代码,以下所示:svg

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">

    <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="已进入App主界面" android:textSize="18sp" android:layout_gravity="center" />

</FrameLayout>

这里咱们修改了MainActivity中的布局文件,在界面上添加了一个 已进入App主界面 的TextView,待会在指纹认证经过以后,就会让APP跳转到此界面。布局

接下来咱们开始编写指纹认证界面,新建fingerprint_dialog.xml,代码以下所示:学习

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">

    <ImageView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:src="@drawable/ic_fp_40px" />

    <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="20dp" android:text="请验证指纹解锁" android:textColor="#000" android:textSize="16sp" />

    <TextView  android:id="@+id/error_msg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="5dp" android:maxLines="1" android:textSize="12sp" android:textColor="#f45" />

    <View  android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_marginTop="10dp" android:background="#ccc" />

    <TextView  android:id="@+id/cancel" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:text="取消" android:textColor="#5d7883" android:textSize="16sp" />

</LinearLayout>

这是一个很是简易的指纹认证界面,相信没什么须要解释的地方。界面大体样式以下图所示。

注意,一般为了让用户清楚的知道如今须要进行指纹认证,Google官方建议最好使用一个通用的指纹图标,而不该该由各APP制做本身的指纹图标。为此,Google也特地提供了一套指纹认证的组图,能够 点击这里 查看和下载。

接着咱们建立一个FingerprintDialogFragment类,并让它继承自DialogFragment,用于做为提示用户进行指纹认证的对话框,代码以下所示:

@TargetApi(23)
public class FingerprintDialogFragment extends DialogFragment {

    private FingerprintManager fingerprintManager;

    private CancellationSignal mCancellationSignal;

    private Cipher mCipher;

    private LoginActivity mActivity;

    private TextView errorMsg;

    /** * 标识是不是用户主动取消的认证。 */
    private boolean isSelfCancelled;

    public void setCipher(Cipher cipher) {
        mCipher = cipher;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mActivity = (LoginActivity) getActivity();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        fingerprintManager = getContext().getSystemService(FingerprintManager.class);
        setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fingerprint_dialog, container, false);
        errorMsg = v.findViewById(R.id.error_msg);
        TextView cancel = v.findViewById(R.id.cancel);
        cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                stopListening();
            }
        });
        return v;
    }

    @Override
    public void onResume() {
        super.onResume();
        // 开始指纹认证监听
        startListening(mCipher);
    }

    @Override
    public void onPause() {
        super.onPause();
        // 中止指纹认证监听
        stopListening();
    }

    private void startListening(Cipher cipher) {
        isSelfCancelled = false;
        mCancellationSignal = new CancellationSignal();
        fingerprintManager.authenticate(new FingerprintManager.CryptoObject(cipher), mCancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode, CharSequence errString) {
                if (!isSelfCancelled) {
                    errorMsg.setText(errString);
                    if (errorCode == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
                        Toast.makeText(mActivity, errString, Toast.LENGTH_SHORT).show();
                        dismiss();
                    }
                }
            }

            @Override
            public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
                errorMsg.setText(helpString);
            }

            @Override
            public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
                Toast.makeText(mActivity, "指纹认证成功", Toast.LENGTH_SHORT).show();
                mActivity.onAuthenticated();
            }

            @Override
            public void onAuthenticationFailed() {
                errorMsg.setText("指纹认证失败,请再试一次");
            }
        }, null);
    }

    private void stopListening() {
        if (mCancellationSignal != null) {
            mCancellationSignal.cancel();
            mCancellationSignal = null;
            isSelfCancelled = true;
        }
    }

}

说了是实现一个最简版的指纹认证Demo,所以这里的代码也都是很是简单的,基本上就是一个Fragment类的最普通实现,下面我带你们简单解析一下。

首先setCipher()方法用于接受一个Cipher对象,这个参数在待会进行指纹认证的时候会用到。

接下来几个生命周期方法都很简单,在onAttach()方法中获取了Activity的实例,在onCreate()方法获取了FingerprintManager的实例,在onCreateView()方法中加载了咱们刚刚建立的fingerprint_dialog.xml布局,都是一些常规操做。

紧接着重点的要来了,在onResume()方法中调用了startListening()方法开始指纹认证监听,在onPause()方法中调用了stopListening()方法中止指纹认证监听。为何要这么作呢?由于指纹传感器和摄像头相似,是不能多个程序同时使用的,所以任何一个程序都不该该在非前台时刻占用着指纹传感器的资源,因此须要在onPause()方法中及时释放资源。

那么,如今咱们只须要把全部的目光都放在startListening()和stopListening()这两个方法上就能够了。在startListening()方法中,调用了FingerprintManager的authenticate()方法来开启指纹指纹监听。authenticate()方法接收五个参数,第一个参数是CryptoObject对象,这里咱们只须要将刚才传入的Cipher对象包装成CryptoObject对象就能够了。第二个参数是CancellationSignal对象,可使用它来取消指纹认证操做。第三个参数是可选参数,官方的建议是直接传0就能够了。第四个参数用于接收指纹认证的回调,上述代码中我将全部的回调可能都进行了界面提示,方便你们观察。第五个参数用于指定处理回调的Handler,这里直接传null表示回调到主线程便可。

而在stopListening()方法中的逻辑则简单得多了,咱们只须要调用CancellationSignal的cancel()方法将指纹认证操做取消就能够了。

这样咱们就将FingerprintDialogFragment中的代码所有完成了,这段代码能够直接复制到任意项目当中来做为指纹认证提醒对话框。

最后,咱们再来编写一个简单的登陆界面,整个指纹认证过程就完整了。建立LoginActivity,代码以下所示:

public class LoginActivity extends AppCompatActivity {

    private static final String DEFAULT_KEY_NAME = "default_key";

    KeyStore keyStore;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        if (supportFingerprint()) {
            initKey();
            initCipher();
        }
    }

    public boolean supportFingerprint() {
        if (Build.VERSION.SDK_INT < 23) {
            Toast.makeText(this, "您的系统版本太低,不支持指纹功能", Toast.LENGTH_SHORT).show();
            return false;
        } else {
            KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
            FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
            if (!fingerprintManager.isHardwareDetected()) {
                Toast.makeText(this, "您的手机不支持指纹功能", Toast.LENGTH_SHORT).show();
                return false;
            } else if (!keyguardManager.isKeyguardSecure()) {
                Toast.makeText(this, "您还未设置锁屏,请先设置锁屏并添加一个指纹", Toast.LENGTH_SHORT).show();
                return false;
            } else if (!fingerprintManager.hasEnrolledFingerprints()) {
                Toast.makeText(this, "您至少须要在系统设置中添加一个指纹", Toast.LENGTH_SHORT).show();
                return false;
            }
        }
        return true;
    }

    @TargetApi(23)
    private void initKey() {
        try {
            keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);
            KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(DEFAULT_KEY_NAME,
                    KeyProperties.PURPOSE_ENCRYPT |
                            KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(true)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
            keyGenerator.init(builder.build());
            keyGenerator.generateKey();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @TargetApi(23)
    private void initCipher() {
        try {
            SecretKey key = (SecretKey) keyStore.getKey(DEFAULT_KEY_NAME, null);
            Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                    + KeyProperties.BLOCK_MODE_CBC + "/"
                    + KeyProperties.ENCRYPTION_PADDING_PKCS7);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            showFingerPrintDialog(cipher);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void showFingerPrintDialog(Cipher cipher) {
        FingerprintDialogFragment fragment = new FingerprintDialogFragment();
        fragment.setCipher(cipher);
        fragment.show(getFragmentManager(), "fingerprint");
    }

    public void onAuthenticated() {
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
        finish();
    }

}

首先在onCreate()方法中,调用了supportFingerprint()方法来判断当前设备是否支持指纹认证功能。这一点是很是重要的,由于当设备不支持指纹认证的时候,还须要及时切换到如图案、密码等其余的认证方式。

当设备支持指纹认证的时候,再分为两步,第一步生成一个对称加密的Key,第二步生成一个Cipher对象,这都是Android指纹认证API要求的标准用法。获得了Cipher对象以后,咱们建立FingerprintDialogFragment的实例,并将Cipher对象传入,再将FingerprintDialogFragment显示出来就能够了。

最后的最后,当指纹认证成功以后,会在FingerprintDialogFragment的回调当中调用LoginActivity的onAuthenticated()方法,而后界面会跳转到MainActivity,整个指纹认证过程就此结束。

总共就这些代码了,整体来讲仍是至关简单的,如今咱们来运行一下看看实际的效果吧。打开应用以后会马上弹出指纹认证对话框,此时先使用错误的手指来进行认证:

能够看到,当指纹验证失败的时候,会在界面上显示相应的错误提示信息。

接下来使用正确的手指来进行认证:

OK,指纹验证成功,并自动跳转到了MainActivity界面。

这样一个最简版的指纹认证Demo就此完成,你们若是想要在本身的APP中集成指纹认证功能,只须要复制粘贴本文中的代码就能够轻松实现了。若是想要下载完整的Demo源码,点击这里 便可下载。

在文章的结尾我还想再补充几句,虽然本文中的指纹认证Demo实现过程很简单,可是切记它是不能单独使用的,必需要配合着图案或其余认证方式一块儿来使用,由于必定要提供一个在设备不支持指纹状况下的其余认证方式。

另外,比较遗憾的是,虽然是刚刚写出来的文章,可是FingerprintManager在最新的Android 9.0系统上已经被废弃了。由于Android 9.0系统提供了更增强大的生物识别认证功能,包括指纹识别、面部识别、甚至是虹膜识别等等,所以仅仅只能用于指纹识别的FingerprintManager已经不能知足新系统的强大需求了。

不过你们也不用担忧,虽然被标为废弃,可是至少在较长一段时间内,FingerprintManager仍是能够正常使用的。而我过段时间也会针对Android 9.0的生物识别功能专门再写一篇文章,敬请期待吧。


关注个人技术公众号,天天都有优质技术文章推送。关注个人娱乐公众号,工做、学习累了的时候放松一下本身。

微信扫一扫下方二维码便可关注: