天天看点

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

简单使用

    • 源码分析

首先需要了解以下几点

  • 指纹识别相关api是在Android23版本添加的,所以过早版本的设备是无法使用的;
  • 在Android28版本,使用BiometricPrompt替代了FingerprintManager,FingerprintManager被标记为@Deprecated,但依然可以使用它,而且BiometricPrompt与FingerprintManager并没有多大区别(Api 29 30中BiometricPrompt新增了一些方法,不在此文涉及),内部实现依然是调用的FingerprintService的相关方法;
  1. 使用BiometricPrompt(Api>=28)

    首先添加权限:

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的,第三方应用无法调用,大家手上有源码的可以试试看。

接下来就是指纹验证了:

以上三个参数都是@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还有一个重载方法:

多了一个参数CryptoObject,这是一个密码对象的包装类,关于这个参数,我们会在下文进行介绍。

有关BiometricPrompt内容就是这么多了,下面给出几张效果图:

史上最全,Android P指纹应用及源码解析
史上最全,Android P指纹应用及源码解析
史上最全,Android P指纹应用及源码解析
  1. 使用FingerprintManager(Api>=23)

    首先添加权限

判断是否设备是否支持指纹功能,下面三个判断都需要为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);
           

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

一共有五个参数:

crypto:与BiometricPrompt相同,用于加密对象的关联,如果不需要,可以为空,不过这样会有指纹被篡改的风险;

cancel:与BiometricPrompt相同,用于取消指纹操作;

flags:此flags暂时没有用到,需要输入0;

callback:与BiometricPrompt相同,用于指纹识别结果处理;

handler:用来处理callback回调事件,类似于BiometricPrompt的Executor参数,若为空,则会使用主线程来处理;

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

  1. 指纹录制

    FingerprintManager还有一个指纹录制的方法,不过它是@hide的,第三方应用无法使用,但在系统设置里,此功能是非常重要的。

首先添加权限

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

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是录制过程出现的错误信息,不会中断整个指纹录制过程,如某一步指纹感应过程中,指纹操作已取消。

    接下来就是开启指纹录制过程:

下面是效果图:

史上最全,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(() -> {
            // 给出错误信息,vendorCode是客户自定义code,当通过error找不到系统已定义的错误信息时,会通过vendorCode来查找
            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方法):

这里还有两个参数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
            // 因为点击确认按钮退出,调用确认按钮onClick方法
            if (reason == DISMISSED_REASON_POSITIVE) {
                mPositiveButtonInfo.executor.execute(() -> {
                    mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE);
                });
              //因为点击取消按钮退出,调用取消按钮onClick方法
            } 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方法,接下来我们一起看一下这个方法:

// /frameworks/base/core/java/android/hardware/fingerprint/FingerprintManager.java
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会在AuthenticationCallback#onAuthenticationSucceeded作为结果返回
        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(() -> {
                    // 调用AuthenticationCallback#onAuthenticationError发送硬件不可用错误信息
                    callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                            getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
                });
            }
        }
    }
           

大家看一下mService.authenticate注释处的mServiceReceiver,还记得我们在一开始的创建的mDialogReceiver吗,对应的就是这里的最后一个参数receiver,用来处理取消对话框点击事件的,这里需要与mServiceReceiver区分开。那么mServiceReceiver是干嘛的呢:

// /frameworks/base/core/java/android/hardware/fingerprint/FingerprintManager.java
private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {

        @Override // binder call
        public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
            mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,
                    new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget();
        }

        @Override // binder call
        public void onAcquired(long deviceId, int acquireInfo, int vendorCode) {
            if (mExecutor != null) {
                mExecutor.execute(() -> {
                    sendAcquiredResult(deviceId, acquireInfo, vendorCode);
                });
            } else {
                mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode,
                        deviceId).sendToTarget();
            }
        }

        @Override // binder call
        public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
            if (mExecutor != null) {
                mExecutor.execute(() -> {
                    sendAuthenticatedSucceeded(fp, userId);
                });
            } else {
                mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();
            }
        }

        @Override // binder call
        public void onAuthenticationFailed(long deviceId) {
            if (mExecutor != null) {
                mExecutor.execute(() -> {
                    sendAuthenticatedFailed();
                });
            } else {
                mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
            }
        }

        @Override // binder call
        public void onError(long deviceId, int error, int vendorCode) {
            if (mExecutor != null) {
                // BiometricPrompt case
                if (error == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
                        || error == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
                    // User tapped somewhere to cancel, or authentication was cancelled by the app
                    // or got kicked out. The prompt is already gone, so send the error immediately.
                    mExecutor.execute(() -> {
                        sendErrorResult(deviceId, error, vendorCode);
                    });
                } else {
                    // User got an error that needs to be displayed on the dialog, post a delayed
                    // runnable on the FingerprintManager handler that sends the error message after
                    // FingerprintDialog.HIDE_DIALOG_DELAY to send the error to the application.
                    mHandler.postDelayed(() -> {
                        mExecutor.execute(() -> {
                            sendErrorResult(deviceId, error, vendorCode);
                        });
                    }, BiometricPrompt.HIDE_DIALOG_DELAY);
                }
            } else {
                mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();
            }
        }

        @Override // binder call
        public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
            mHandler.obtainMessage(MSG_REMOVED, remaining, 0,
                    new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget();
        }

        @Override // binder call
        public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) {
            // TODO: propagate remaining
            mHandler.obtainMessage(MSG_ENUMERATED, fingerId, groupId, deviceId).sendToTarget();
        }
    };
           

这个IFingerprintServiceReceiver mServiceReceiver可是非常重要的,刚才不是说了指纹验证的结果会交给BiometricPrompt.AuthenticationCallback来处理吗?包括success、error、failed、help,那么AuthenticationCallback的这些方法是怎么被调用的呢?没错,大部分都是由IFingerprintServiceReceive来调用的(前面有些地方我们已经看到了发生错误时也调用过onAuthenticationError)。