欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

史上最全,Android P指纹应用及源码解析

程序员文章站 2022-07-03 18:21:01
简单使用首先需要了解以下几点指纹识别相关api是在Android23版本添加的,所以过早版本的设备是无法使用的;android.os.Build.VERSION.SDK_INT >= 23 // 不会真有人还在用23以前的手机吧?在Android28版本,使用BiometricPrompt替代了FingerprintManager,FingerprintManager被标记为@Deprecated,但依然可以使用它,而且BiometricPrompt与FingerprintManage...

简单使用


首先需要了解以下几点

  • 指纹识别相关api是在Android23版本添加的,所以过早版本的设备是无法使用的;
android.os.Build.VERSION.SDK_INT >= 23 // 不会真有人还在用23以前的手机吧?
  • 在Android28版本,使用BiometricPrompt替代了FingerprintManager,FingerprintManager被标记为@Deprecated,但依然可以使用它,而且BiometricPrompt与FingerprintManager并没有多大区别(Api 29 30中BiometricPrompt新增了一些方法,不在此文涉及),内部实现依然是调用的FingerprintService的相关方法;
  1. 使用BiometricPrompt(Api>=28)
    首先添加权限:
<uses-permission android:name="android.permission.USE_BIOMETRIC" />

BiometricPrompt是用于显示一个系统提供的生物识别对话框,所以我们需要新建一个构造器:

BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(mContext)
                .setTitle("指纹验证")
                .setSubtitle("小标题")
                .setDescription("描述")
                .setNegativeButton("取消", mContext.getMainExecutor(), (dialog, which) -> { Log.i(TAG, "cancel fingerprint authenticate"); })
                .build();

setTitle、setSubtitle、setDescription方法就不多说了,大伙可以在下面的效果图中直接看到。这里说一下setNegativeButton方法,这个设置项是必须有的,而且里面的三个参数都不能为null,否则会抛出异常。setNegativeButton方法用于设置指纹识别对话框的取消按钮和点击事件,第一个参数为要在提示的否定按钮上显示的文本;第二个参数为该onClick事件要执行的执行器,这里可以选择主程序执行器;第三个参数为按钮点击时间,这里打印了一条log。大家可以在这里对取消按钮做一些操作,比如将其改为“密码验证”按钮等。
与setNegativeButton类似的还有一个setPositiveButton方法,该方法的使用与setNegativeButton相同,不过它是@hide的,第三方应用无法调用,大家手上有源码的可以试试看。
接下来就是指纹验证了:

biometricPrompt.authenticate(new CancellationSignal(), mContext.getMainExecutor(), authenticationCallback);

以上三个参数都是@NonNull的。
第一个参数用于取消当前指纹验证操作,可以为它设置监听器CancellationSignal#setOnCancelListener,如果没有取消,指纹感应设备会一直等待指纹输入,直到超时;
第二个参数用于分发指纹识别结果的回调事件,这里可以设为主程序执行器;
第三个参数是最重要的一个参数,用于处理指纹识别结果,它是一个内部类:

BiometricPrompt.AuthenticationCallback authenticationCallback = new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode, CharSequence errString) {
                super.onAuthenticationError(errorCode, errString);
                Log.i(TAG, "onAuthenticationError errorCode: " + errorCode + " errString: " + errString);
            }

            @Override
            public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
                super.onAuthenticationHelp(helpCode, helpString);
                Log.i(TAG, "onAuthenticationHelp helpCode:" + helpCode + "helpString: " + helpString);
            }

            @Override
            public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                Log.i(TAG, "onAuthenticationSucceeded");
            }

            @Override
            public void onAuthenticationFailed() {
                super.onAuthenticationFailed();
                Log.i(TAG, "onAuthenticationFailed");
            }
        };

BiometricPrompt#authenticate为我们发起了一个指纹识别请求,识别结果则可以在这个callback里处理。
onAuthenticationError会在识别出现错误时调用,它通常表示一个预期之外的,不可修复的错误,比如设备不支持指纹识别功能、指纹传感器损毁、设备中不存在已录入的指纹、失败次数过多等。errorCode与errorString都是在系统内部定义好的,可以打印出来;
onAuthenticationFailed会在认证失败时调用,它与onAuthenticationError不同,这个失败通常是可预期的,可以修复的,比如输入的指纹与设备指纹库指纹不匹配,这个时候可以再次放上手指进行验证,直到超过验证次数;
onAuthenticationHelp用于指纹认证过程中给出一些帮助信息,比如手指移动过快、指纹传感器有脏污、手指移动过慢等,这些帮助信息都可以在helpString获取到;
onAuthenticationSucceeded则在输入指纹与指纹库指纹相匹配时调用,当验证成功时,将会立即结束此次指纹验证过程,再放上手指在传感器上不会有响应。
BiometricPrompt#authenticate还有一个重载方法:

biometricPrompt.authenticate(CryptoObject crypto, CancellationSignal cancel, Executor executor, BiometricAuthenticator.AuthenticationCallback callback);

多了一个参数CryptoObject,这是一个密码对象的包装类,关于这个参数,我们会在下文进行介绍。
有关BiometricPrompt内容就是这么多了,下面给出几张效果图:
史上最全,Android P指纹应用及源码解析
史上最全,Android P指纹应用及源码解析
史上最全,Android P指纹应用及源码解析

  1. 使用FingerprintManager(Api>=23)
    首先添加权限
<uses-permission android:name="android.permission.USE_FINGERPRINT" />

判断是否设备是否支持指纹功能,下面三个判断都需要为true(BiometricPrompt不需要此判断,如果不支持,BiometricPrompt会调用onAuthenticationError方法)

// 是否存在指纹功能模块
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
// 设备是否有硬件支持
fingerprintManager.isHardwareDetected();
// 设备中是否存在已录入指纹
fingerprintManager.hasEnrolledFingerprints();

新建一个指纹认证结果回调:

FingerprintManager.AuthenticationCallback authenticationCallback = new FingerprintManager.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode, CharSequence errString) {
                Log.i(TAG, "onAuthenticationError errorCode: " + errorCode + " errString: " + errString);
                super.onAuthenticationError(errorCode, errString);
            }

            @Override
            public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
                Log.i(TAG, "onAuthenticationError helpCode: " + helpCode + " helpString: " + helpString);
                super.onAuthenticationHelp(helpCode, helpString);
            }

            @Override
            public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
                Log.i(TAG, "onAuthenticationError");
                super.onAuthenticationSucceeded(result);
            }

            @Override
            public void onAuthenticationFailed() {
            	Log.i(TAG, "onAuthenticationFailed");
                super.onAuthenticationFailed();
            }
        };

作用与上文BiometricPrompt.AuthenticationCallback相同,这里就不赘述了。
接下来就是发起指纹验证:

FingerprintManager fingerprintManager = (FingerprintManager)mContext.getSystemService(Context.FINGERPRINT_SERVICE);
fingerprintManager.authenticate(null, new CancellationSignal(), 0, authenticationCallback, null);

下面看一下该方法的参数:

public void authenticate(FingerprintManager.CryptoObject crypto, CancellationSignal cancel, int flags, FingerprintManager.AuthenticationCallback callback, Handler handler)

一共有五个参数:
crypto:与BiometricPrompt相同,用于加密对象的关联,如果不需要,可以为空,不过这样会有指纹被篡改的风险;
cancel:与BiometricPrompt相同,用于取消指纹操作;
flags:此flags暂时没有用到,需要输入0;
callback:与BiometricPrompt相同,用于指纹识别结果处理;
handler:用来处理callback回调事件,类似于BiometricPrompt的Executor参数,若为空,则会使用主线程来处理;

调用fingerprintManager.authenticate后不会像BiometricPrompt那样弹出一个底部框,你可以自定义界面,然后调用fingerprintManager.authenticate方法,比如将该方法放到onCreate里,这样一进入该页面就会触发指纹认证,或者自定义一个Dialog,在Dialog弹出的时候调用指纹认证方法,认证成功或认证失败达到上限次数就会结束指纹认证过程。

  1. 指纹录制
    FingerprintManager还有一个指纹录制的方法,不过它是@hide的,第三方应用无法使用,但在系统设置里,此功能是非常重要的。
public void enroll(byte [] token, CancellationSignal cancel, int flags, int userId, EnrollmentCallback callback);

首先添加权限

<uses-permission android:name="android.permission.MANAGE_FINGERPRINT" />

然后像前面一样,新建一个录制结果回调,用来处理录制结果:

private FingerprintManager.EnrollmentCallback mEnrollmentCallback
            = new FingerprintManager.EnrollmentCallback() {

        @Override
        public void onEnrollmentProgress(int remaining) {
            // 11 to 0
            Log.i(TAG, "onEnrollmentProgress: " + remaining);
        }

        @Override
        public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
            // 指纹已存在
            Log.i(TAG, "onEnrollmentHelp: " + helpMsgId + " " + helpString);
        }

        @Override
        public void onEnrollmentError(int errMsgId, CharSequence errString) {
            // 指纹操作已取消
            Log.i(TAG, "onEnrollmentError: " + errMsgId + " " + errString);
        }
    };

和前面指纹识别的两个callback相识,这里有三个方法可以重写:

  • onEnrollmentProgress 因为指纹录制是一个持续的过程,需要多次放上手指到感应器上,每次成功识别到手指都会调用此方法,参数remaining是一个剩余步数,表示还要将手指放上感应器几次,这里是一共12步,也就是从11到0,可以在这里做动画更新的效果;
  • onEnrollmentHelp则是录制过程中要显示的提示信息,比如指纹已存在,这些信息是可以定制的;
  • onEnrollmentError是录制过程出现的错误信息,不会中断整个指纹录制过程,如某一步指纹感应过程中,指纹操作已取消。
    接下来就是开启指纹录制过程:
mFingerprintManager.enroll(mToken, new CancellationSignal(), 0 /* flags */, mUserId, mEnrollmentCallback);

下面是效果图:
史上最全,Android P指纹应用及源码解析

源码分析

  1. BiometricPrompt#authenticate
    前面我们已经知道,BiometricPrompt#authenticate有两个重载方法:
// frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java
public void authenticate(@NonNull android.hardware.biometrics.CryptoObject crypto,
            @NonNull CancellationSignal cancel,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull BiometricAuthenticator.AuthenticationCallback callback)
        if (!(callback instanceof BiometricPrompt.AuthenticationCallback))
public void authenticate(@NonNull CancellationSignal cancel,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull BiometricAuthenticator.AuthenticationCallback callback)

先来看看简单点的三参数方法:

    // frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java
    public void authenticate(@NonNull CancellationSignal cancel,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull AuthenticationCallback callback) {
        if (handlePreAuthenticationErrors(callback, executor)) {
            return;
        }
        mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback);
    }
    private boolean handlePreAuthenticationErrors(AuthenticationCallback callback,
            Executor executor) {
        // 是否存在指纹功能模块
        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
            sendError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT, callback,
                      executor);
            return true;
          // 设备是否有硬件支持
        } else if (!mFingerprintManager.isHardwareDetected()) {
            sendError(BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE, callback,
                      executor);
            return true;
          // 设备内是否存在至少一个已录制好的指纹
        } else if (!mFingerprintManager.hasEnrolledFingerprints()) {
            sendError(BiometricPrompt.BIOMETRIC_ERROR_NO_BIOMETRICS, callback,
                      executor);
            return true;
        }
        return false;
    }
    private void sendError(int error, AuthenticationCallback callback, Executor executor) {
        executor.execute(() -> {
            callback.onAuthenticationError(error, mFingerprintManager.getErrorString(
                    error, 0 /* vendorCode */));
        });
    }

大家可以看到,在调用了BiometricPrompt#authenticate方法后,首先会进行一个判断,是否满足三个条件,这三个条件我们在前面使用FingerprintManager#authenticate时自己写了判断,而在这里就不用我们写了。如果有一个条件不满足,就会调用AuthenticationCallback的onAuthenticationError方法,交给开发者处理,返回的错误码和错误信息分别在frameworks/base/core/java/android/hardware/biometrics/BiometricConstants.java和FingerprintManager.java中定义。

// /frameworks/base/core/java/android/hardware/fingerprint/FingerprintManager.java
public String getErrorString(int errMsg, int vendorCode) {
        switch (errMsg) {
            case FINGERPRINT_ERROR_UNABLE_TO_PROCESS:
                return mContext.getString(
                    com.android.internal.R.string.fingerprint_error_unable_to_process);
            case FINGERPRINT_ERROR_HW_UNAVAILABLE:
                return mContext.getString(
                    com.android.internal.R.string.fingerprint_error_hw_not_available);
            case FINGERPRINT_ERROR_NO_SPACE:
                return mContext.getString(
                    com.android.internal.R.string.fingerprint_error_no_space);
            case FINGERPRINT_ERROR_TIMEOUT:
                return mContext.getString(com.android.internal.R.string.fingerprint_error_timeout);
            case FINGERPRINT_ERROR_CANCELED:
                return mContext.getString(com.android.internal.R.string.fingerprint_error_canceled);
            case FINGERPRINT_ERROR_LOCKOUT:
                return mContext.getString(com.android.internal.R.string.fingerprint_error_lockout);
            case FINGERPRINT_ERROR_LOCKOUT_PERMANENT:
                return mContext.getString(
                        com.android.internal.R.string.fingerprint_error_lockout_permanent);
            case FINGERPRINT_ERROR_USER_CANCELED:
                return mContext.getString(
                        com.android.internal.R.string.fingerprint_error_user_canceled);
            case FINGERPRINT_ERROR_NO_FINGERPRINTS:
                return mContext.getString(
                        com.android.internal.R.string.fingerprint_error_no_fingerprints);
            case FINGERPRINT_ERROR_HW_NOT_PRESENT:
                return mContext.getString(
                        com.android.internal.R.string.fingerprint_error_hw_not_present);
            case FINGERPRINT_ERROR_VENDOR: {
                    String[] msgArray = mContext.getResources().getStringArray(
                            com.android.internal.R.array.fingerprint_error_vendor);
                    if (vendorCode < msgArray.length) {
                        return msgArray[vendorCode];
                    }
                }
        }
        Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
        return null;
    }

最后一个case为客户自定义错误信息数组,所有文本资源文件定义于/frameworks/base/core/res/res/values/strings.xml
如果条件均满足,接下来就进入到了mFingerprintManager.authenticate(这里可以发现,即使在Api28版本用BiometricPrompt代替了FingerprintManager,但其内部还是使用的FingerprintManager方法):

mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback);

这里还有两个参数mBundle和mDialogReceiver是我们没有涉及到的:

  • mBundle携带了前文所说的Title,subTitle,Description以及按钮文本positive_text和negative_text
  • mDialogReceiver
    // frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java
    IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
        @Override
        public void onDialogDismissed(int reason) {
            // Check the reason and invoke OnClickListener(s) if necessary
            if (reason == DISMISSED_REASON_POSITIVE) {
                mPositiveButtonInfo.executor.execute(() -> {
                    mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE);
                });
            } else if (reason == DISMISSED_REASON_NEGATIVE) {
                mNegativeButtonInfo.executor.execute(() -> {
                    mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE);
                });
            }
        }
    };

   // 实例化mNegativeButtonInfo对象
   public Builder setNegativeButton(@NonNull CharSequence text,
                @NonNull @CallbackExecutor Executor executor,
                @NonNull DialogInterface.OnClickListener listener) {
            mNegativeButtonInfo = new ButtonInfo(executor, listener);
            return this;
        }

可以看到,mDialogReceiver包含了指纹识别对话框dismiss时可能所要执行的操作,这个所要操作的onClick方法是在我们Builder对话框时,通过调用setNegativeButton方法自己定义的。
了解了各个参数的含义,我们正式进入指纹认证过程:

// /frameworks/base/core/java/android/hardware/fingerprint/FingerprintManager.java
public void authenticate(
            @NonNull CancellationSignal cancel,
            @NonNull Bundle bundle,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull IBiometricPromptReceiver receiver,
            @NonNull BiometricAuthenticator.AuthenticationCallback callback) {
        if (cancel == null) {
            throw new IllegalArgumentException("Must supply a cancellation signal");
        }
        if (bundle == null) {
            throw new IllegalArgumentException("Must supply a bundle");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must supply an executor");
        }
        if (receiver == null) {
            throw new IllegalArgumentException("Must supply a receiver");
        }
        if (callback == null) {
            throw new IllegalArgumentException("Must supply a calback");
        }
        // null是crypto
        authenticate(mContext.getUserId(), null, cancel, bundle, executor, receiver, callback);
    }

前面是各个参数的非空判断,看到最后一行的null参数大伙应该明白了吧,BiometricPrompt#authenticate的四参数方法正是用crypto替代了null,所以这两个重载方法其实最后都是调用了FingerprintManager内部的authenticate方法,接下来我们一起看一下这个方法:

private void authenticate(int userId,
            @Nullable android.hardware.biometrics.CryptoObject crypto,
            @NonNull CancellationSignal cancel,
            @NonNull Bundle bundle,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull IBiometricPromptReceiver receiver,
            @NonNull BiometricAuthenticator.AuthenticationCallback callback) {
        mCryptoObject = crypto;
        if (cancel.isCanceled()) {
            Slog.w(TAG, "authentication already canceled");
            return;
        } else {
            cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
        }

        if (mService != null) {
            try {
                mExecutor = executor;
                mAuthenticationCallback = callback;
                final long sessionId = crypto != null ? crypto.getOpId() : 0;
                // 主要看这里
                mService.authenticate(mToken, sessionId, userId, mServiceReceiver,
                        0 /* flags */, mContext.getOpPackageName(), bundle, receiver);
            } catch (RemoteException e) {
                Slog.w(TAG, "Remote exception while authenticating", e);
                mExecutor.execute(() -> {
                    callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                            getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
                });
            }
        }
    }

本文地址:https://blog.csdn.net/qq_40772749/article/details/107321260

相关标签: android