9

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

 3 years ago
source link: https://guolin.blog.csdn.net/article/details/81450114
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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

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

20180816194810181

事实上,Android从6.0系统开始就支持指纹认证功能了,但是指纹功能还需要有硬件支持才行,而Android手机的硬件都是由各厂商生产的,手机档次也参差不齐,因此不能像iPhone那样保证所有的手机都是支持指纹认证功能的。所以,可能很多开发者就觉得,即使做了指纹认证功能,也无法兼容所有的手机,还是要配合图案解锁或密码等功能一起使用才行,那么索性就只用图案和密码好了,一劳永逸。

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

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

虽然有了应用场景,还有很多开发者可能会担心,指纹认证功能实现起来会不会很复杂?因为毕竟支持的设备有限,还要配合图案和密码来使用才行,如果实现起来非常复杂,又只能支持部分设备的话,那投入产出比就太低了,或许这也是很多APP不肯去实现指纹认证功能的原因。这里我不得不说,Android官方提供的指纹认证Demo的确是挺复杂的,看着让人望而却步。但是大家不用担心,本篇文章中我会带着大家一起去实现一个最简版的指纹认证Demo,直接复制粘贴本文中的代码到大家各自的项目中,即可一步集成指纹认证功能。

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

<?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>

这是一个非常简易的指纹认证界面,相信没什么需要解释的地方。界面大致样式如下图所示。

20180820213937501

注意,通常为了让用户清楚的知道现在需要进行指纹认证,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,整个指纹认证过程就此结束。

总共就这些代码了,总体来说还是相当简单的,现在我们来运行一下看看实际的效果吧。打开应用之后会立刻弹出指纹认证对话框,此时先使用错误的手指来进行认证:

20180821211721675

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

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

2018082121174151

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

这样一个最简版的指纹认证Demo就此完成,大家如果想要在自己的APP中集成指纹认证功能,只需要复制粘贴本文中的代码就可以轻松实现了。如果想要下载完整的Demo源码,点击这里 即可下载。

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

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

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

关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

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

20160507110203928         20161011100137978


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK