Android指纹识别,兼容6.0以上所有版本,包括9.0适配

本资源库已上传至github,可直接用build.gradle添加依赖,链接如下:
https://github.com/JasonPearySamuel/BiometricUtil

前言

在网上搜索了不少关于Android指纹识别的开发教程,由于安卓的碎片化严重,大多文章都比较单一,有的只介绍了6.0~9.0的案例,有的只有9.0+的案例;虽然也有大牛自己封装了指纹识别类出来,我试着接入开源库放在自己的Android X项目中,但使用还是会报出异常,所以整理了以下代码。

这是大牛封装的开源库,但是我用起来会有异常,有兴趣的同学可以尝试一下。
https://github.com/ZuoHailong/BiometricPrompt

以下的代码均在Android X下进行开发,使用Kotlin编写,兼容了Android 6.0以上所有安卓版本,并都尽量整合在了同一个Util工具类中。但由于6.0与9.0的差异性,即9.0以下的安卓系统不提供专门的页面,所以9.0以下设备必须自定义指纹识别页,FingerprintDialogFragment来专门供9.0以下的机型显示指纹识别页面,Android 9.0的指纹识别开启代码较为简单,所以直接整理在了Util类里面。

关于9.0+的人脸解锁

Android系统提供的BiometricPrompt并没有支持2D/3D人脸解锁,市面上所见的具备人脸解锁的手机都是厂家对Android ROM做一定的适配而形成的,并非谷歌提供的人脸解锁接口。

BiometricPromptUtil

这是指纹识别的工具类,具备了打开9.0+设备的指纹识别监听与回调实现9.0以下指纹识别秘钥生成9.0以下指纹Dialog开启判断当前设备是否支持指纹识别的功能。使用观察者模式设置监听器,将识别数据传递给外层的Activity或Fragment:

/**
 * Android的生物识别
 */
class BiometricPromptUtil(val mContext: Context) {

    private lateinit var keyStore: KeyStore         // 6.0~9.0的指纹识别
    private lateinit var mOnFingerResultListener: OnFingerResultListener

    /**
     * 判断是否支持指纹
     * @return
     */
    fun supportFingerprint(): Boolean {
        if (Build.VERSION.SDK_INT < 23) {
            //ToastUtil.showToast(mContext, "您的系统版本过低,不支持指纹功能")
            return false
        } else {
            //键盘锁管理者
            val keyguardManager = mContext.getSystemService(KeyguardManager::class.java)
            //指纹管理者
            val fingerprintManager = mContext.getSystemService(FingerprintManager::class.java)
            if (!fingerprintManager.isHardwareDetected()) { //判断硬件支不支持指纹
                //ToastUtil.showToast(mContext, "您的手机不支持指纹功能")
                return false
            } else if (!keyguardManager.isKeyguardSecure()) { //还未设置锁屏
                ToastUtil.showToast(mContext, "您还未设置锁屏,请先设置锁屏并添加一个指纹")
                return false
            } else if (!fingerprintManager.hasEnrolledFingerprints()) { //指纹未登记
                ToastUtil.showToast(mContext, "您至少需要在系统设置中添加一个指纹")
                return false
            }
        }
        return true
    }

    /**
     * 判断是否支持人脸
     * 注:目前安卓源生BiometricPrompt暂不支持调用系统的人脸识别,
     *     现阶段人脸识别均为厂家在系统中自己定制实现。
     * @return
     */
    fun supportFace(): Boolean {
        return false
    }

    fun addFingerResultListener(onFingerResult: OnFingerResultListener) {
        mOnFingerResultListener = onFingerResult
    }

    /**
     * 安卓9.0及以上的指纹识别
     */
    @RequiresApi(Build.VERSION_CODES.P)
    fun startBiometricPromptIn28() {
        val mBiometricPrompt = BiometricPrompt.Builder(mContext)
                .setTitle("指纹验证")
                .setDescription("扫描指纹,登录系统")
                .setNegativeButton("取消", mContext.getMainExecutor(), DialogInterface.OnClickListener { dialogInterface, I ->
                    // ToastUtil.showToast(mContext, "Cancel")
                })
                .build()

        val mCancellationSignal = CancellationSignal()
        mCancellationSignal.setOnCancelListener {
            ToastUtil.showToast(mContext, "cancel")
        }

        val mAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                ToastUtil.showToast(mContext, "$errString")
                if (::mOnFingerResultListener.isInitialized) {
                    mOnFingerResultListener.fingerResult(false)
                }
            }

            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                super.onAuthenticationSucceeded(result)
                // ToastUtil.showToast(mContext, "Succeeded")
                if (::mOnFingerResultListener.isInitialized) {
                    mOnFingerResultListener.fingerResult(true)
                }
            }

            override fun onAuthenticationFailed() {
                super.onAuthenticationFailed()
                ToastUtil.showToast(mContext, "Failed")
                if (::mOnFingerResultListener.isInitialized) {
                    mOnFingerResultListener.fingerResult(false)
                }
            }
        }
        mBiometricPrompt.authenticate(mCancellationSignal, mContext.getMainExecutor(), mAuthenticationCallback)
    }

    /**
     * 安卓6.0以上,9.0以下的指纹识别
     */
    @RequiresApi(Build.VERSION_CODES.M)
    fun startBiometricPromptIn23() {
        initKey()
        initCipher()
    }

    @RequiresApi(Build.VERSION_CODES.M)
    private fun initKey() {
        try {
            keyStore = KeyStore.getInstance("AndroidKeyStore")
            keyStore.load(null)
            //秘钥生成器
            val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
            val builder = KeyGenParameterSpec.Builder("DEFAULT_KEY_NAME",
                    KeyProperties.PURPOSE_ENCRYPT or
                            KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(false)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            keyGenerator.init(builder.build())
            keyGenerator.generateKey()
        } catch (e: java.lang.Exception) {
            throw java.lang.RuntimeException(e)
        }
    }

    @RequiresApi(Build.VERSION_CODES.M)
    private fun initCipher() {
        try {
            val key = keyStore.getKey("DEFAULT_KEY_NAME", null)
            val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                    + KeyProperties.BLOCK_MODE_CBC + "/"
                    + KeyProperties.ENCRYPTION_PADDING_PKCS7)
            cipher.init(Cipher.ENCRYPT_MODE, key)
            val fragment23 = FingerprintDialogFragment()
            fragment23.setCipher(cipher)
            fragment23.show((mContext as AppCompatActivity).supportFragmentManager, "fingerprint")
            fragment23.setOnFingerResultListener { result ->
                mOnFingerResultListener.fingerResult(result)
            }
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }

    interface OnFingerResultListener {
        fun fingerResult(result: Boolean)
    }
}

FingerprintDialogFragment

这是9.0以下设备所使用的自定义Dialog类,其中包含了通过工具类穿来的秘钥启动指纹识别监听指纹识别监听回调实现的功能。使用观察者模式设置监听器,将识别数据传递给BiometricPromptUtil:

@RequiresApi(Build.VERSION_CODES.M)
public class FingerprintDialogFragment extends DialogFragment {

    private FingerprintManager fingerprintManager;
    private CancellationSignal mCancellationSignal;
    private Cipher mCipher;
    private Activity mActivity;
    private TextView errorMsg;

    private OnFingerResultListener mOnFingerResultListener;

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

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

    public void setOnFingerResultListener(OnFingerResultListener onFingerResultListener) {
        mOnFingerResultListener = onFingerResultListener;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mActivity = 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.dialog_fingerprint, 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();
                        ToastUtil.showToast(mActivity, errString + "");
                        if (mOnFingerResultListener != null) {
                            mOnFingerResultListener.fingerResult(false);
                        }
                        dismiss();
                    }
                }
            }

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

            @Override
            public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
                // ToastUtil.showToast(mActivity, "指纹认证成功");
                if (mOnFingerResultListener != null) {
                    mOnFingerResultListener.fingerResult(true);
                }
                dismiss();
                //mActivity.onAuthenticated();
            }

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

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

    public interface OnFingerResultListener {
        void fingerResult(Boolean result);
    }
}

dialog_fingerprint.xml

这是与FingerprintDialogFragment相应的布局文件,其中ic_fingerprint是指纹的一个图标,请自行添加:

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

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:src="@mipmap/ic_fingerprint" />

    <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:textColor="#f45"
        android:textSize="12sp" />

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

如何使用

以上三个文件均复制到项目中后,在需要开启指纹识别的Activity或Fragment中使用如下代码:

    /**
     *  启动指纹识别
     */
    private fun startFinger() {
        val biometricPromptUtil = BiometricPromptUtil(this)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            biometricPromptUtil.startBiometricPromptIn28()
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
            biometricPromptUtil.startBiometricPromptIn23()
        }
    }

最后

本着面向百度开发的精神就直接贴出了源码,复制到项目中即可食用,如果稍有遗漏请多指正,谢谢。

本文引用了关于安卓6.0~9.0指纹识别的代码进行参考
https://blog.csdn.net/qq_34983989/article/details/82798043

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350