尽管检查 API 级别,但 AuthenticationCallback 和 KeygenParameterSpec 仍导致应用程序崩溃

2024-01-06

编辑:请参阅下面我的答案以获取解决方案

我有一个带有指纹登录功能的登录活动。我已经实施了BiometricPrompt对于 API 28,我使用FingerprintManagerCompat对于 API 23-27,使用任何这些版本的 Android 时一切都运行良好。但是,我正在 API 19(我的应用程序的最小Sdk)上测试我的应用程序,尽管对相关区域进行了显式 API 检查,但应用程序仍然因错误而崩溃
Unable to resolve superclass of Lcom/example/myapp/LoginActivity$BiometricCallback
and
Could not find class 'android.security.keystore.KeyGenParameterSpec$Builder', referenced from method com.example.myapp.LoginActivity.generateKey
有没有办法让我的登录活动能够使用低于 23 的 API?我究竟做错了什么?这是我的代码...

登录活动.java

public class LoginActivity extends AppCompatActivity {

    //Fingerprint authorization
    private final String KEY_NAME = "FingerPrintKey";
    FingerprintManagerCompat fingerprintManager;

    KeyStore keyStore;
    KeyGenerator keyGenerator;
    Cipher cipher;
    FingerprintManagerCompat.CryptoObject cryptoObject;
    SecretKey key;

    @RequiresApi(api = 23)
    private BiometricDialog mBioDialog;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        // Checking shared preferences to see if fingerprint sign in is enabled           

        if (bioEnabled) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                showBioMetricDialog();
            }
        }
    }


    // Method to show the dialog prompting for fingerprint
    @RequiresApi(api = 23)
    private void showBioMetricDialog() {

        //Android P uses Biometric Prompt
        if (Build.VERSION.SDK_INT >= 28) {
            BiometricCallback biometricCallback = new BiometricCallback();
            displayBiometricPrompt(biometricCallback);

        //Older versions use Android's Keystore
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            fingerprintManager = FingerprintManagerCompat.from(this);

            if (fingerprintManager.isHardwareDetected()
                    && fingerprintManager.hasEnrolledFingerprints()) {
                generateKey();

                if (initCipher()) {
                    cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
                    FingerPrintCallback callback = new FingerPrintCallback();
                    fingerprintManager.authenticate(cryptoObject, 0,
                            new android.support.v4.os.CancellationSignal(),
                            callback, null);

                    mBioDialog = new BiometricDialog(this, callback);
                    mBioDialog.setTitle(getString(R.string.bio_dialog_title));
                    mBioDialog.setSubtitle(getString(R.string.bio_dialog_subtitle));
                    mBioDialog.setDescription(getString(R.string.bio_dialog_finger_desc));
                    mBioDialog.setNegativeButtonText(getString(R.string.bio_dialog_negative));
                    mBioDialog.show();
                }
            }
        }
    }


    @RequiresApi(api = Build.VERSION_CODES.M)
    private void generateKey() {
        try {

            keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);

            keyGenerator = KeyGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
            keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(true)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                    .build());
            keyGenerator.generateKey();

        } catch (KeyStoreException
                | NoSuchAlgorithmException
                | NoSuchProviderException
                | InvalidAlgorithmParameterException
                | CertificateException
                | IOException e) {
            e.printStackTrace();
        }
    }


    @RequiresApi(api = Build.VERSION_CODES.M)
    private boolean initCipher() {
        try {
            cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                    + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
        } catch (NoSuchAlgorithmException
                | NoSuchPaddingException e) {
            throw new RuntimeException("Failed to get Cipher", e);
        }

        try {
            keyStore.load(null);
            key = (SecretKey) keyStore.getKey(KEY_NAME, null);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return true;
        } catch (KeyPermanentlyInvalidatedException e) {
            return false;
        } catch (KeyStoreException | CertificateException
                | UnrecoverableKeyException | IOException
                | NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException("Failed to init Cipher", e);
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (Build.VERSION.SDK_INT >= 23) {
            if (mBioDialog != null && mBioDialog.isShowing())
                mBioDialog.cancel();
        }
    }


    @RequiresApi(api = 28)
    private void displayBiometricPrompt(final BiometricCallback callback) {

        CancellationSignal cancellationSignal = new CancellationSignal();
        cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
            @Override
            public void onCancel() {
                Toast.makeText(LoginActivity.this, "Cancelled", Toast.LENGTH_SHORT).show();
            }
        });

        new BiometricPrompt.Builder(this)
                .setTitle(getString(R.string.bio_dialog_title))
                .setSubtitle(getString(R.string.bio_dialog_subtitle))
                .setDescription(getString(R.string.bio_dialog_desc))
                .setNegativeButton(getString(R.string.bio_dialog_negative), this.getMainExecutor(), new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        callback.onAuthenticationCancelled();
                    }
                }).build().authenticate(cancellationSignal, this.getMainExecutor(), callback);
    }


    private void bioEnabledLogin() {
        // Fetch login credentials from shared prefs and do a loginTask
    }


    // Inner class for Biometric Authentication callbacks
    @RequiresApi(api = Build.VERSION_CODES.P)
    public class BiometricCallback extends android.hardware.biometrics.BiometricPrompt.AuthenticationCallback {

        @Override
        public void onAuthenticationSucceeded(android.hardware.biometrics.BiometricPrompt.AuthenticationResult result) {
            super.onAuthenticationSucceeded(result);
            bioEnabledLogin();
        } 

        // The rest of the callback methods here (they don't currently do anything)

    }


    // Inner class for FingerPrintManager Authentication callbacks
    @RequiresApi(api = Build.VERSION_CODES.M)
    public class FingerPrintCallback extends FingerprintManagerCompat.AuthenticationCallback {
        FingerPrintCallback() {
            super();
        }

        @Override
        public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
            super.onAuthenticationSucceeded(result);
            mBioDialog.dismiss();
            bioEnabledLogin();
        }
    }
}

这是 logcat 的相关部分:

09:48:01.480 15048 15048 W dalvikvm: Unable to resolve superclass of Lcom/example/myapp/LoginActivity$BiometricCallback; (300)
10-19 09:48:01.480 15048 15048 W dalvikvm: Link of class 'Lcom/example/myapp/LoginActivity$BiometricCallback;' failed
10-19 09:48:01.480   825  1043 V SmartFaceService - 3rd party pause: onReceive [android.intent.action.ACTIVITY_STATE/com.example.myapp/create]
10-19 09:48:01.490 15048 15048 E dalvikvm: Could not find class 'android.hardware.biometrics.BiometricPrompt$Builder', referenced from method com.example.myapp.LoginActivity.displayBiometricPrompt
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY: unable to resolve new-instance 302 (Landroid/hardware/biometrics/BiometricPrompt$Builder;) in Lcom/example/myapp/LoginActivity;
10-19 09:48:01.490 15048 15048 D dalvikvm: VFY: replacing opcode 0x22 at 0x000d
10-19 09:48:01.490 15048 15048 E dalvikvm: Could not find class 'android.security.keystore.KeyGenParameterSpec$Builder', referenced from method com.example.myapp.LoginActivity.generateKey
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY: unable to resolve new-instance 430 (Landroid/security/keystore/KeyGenParameterSpec$Builder;) in Lcom/example/myapp/LoginActivity;
10-19 09:48:01.490 15048 15048 D dalvikvm: VFY: replacing opcode 0x22 at 0x001a
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY: unable to resolve exception class 432 (Landroid/security/keystore/KeyPermanentlyInvalidatedException;)
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY: unable to find exception handler at addr 0x2d
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY:  rejected Lcom/example/myapp/LoginActivity;.initCipher ()Z
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY:  rejecting opcode 0x0d at 0x002d
10-19 09:48:01.490 15048 15048 W dalvikvm: VFY:  rejected Lcom/example/myapp/LoginActivity;.initCipher ()Z
10-19 09:48:01.490 15048 15048 W dalvikvm: Verifier rejected class Lcom/example/myapp/LoginActivity;
10-19 09:48:01.490 15048 15048 W dalvikvm: Class init failed in newInstance call (Lcom/example/myapp/LoginActivity;)
10-19 09:48:01.490 15048 15048 D AndroidRuntime: Shutting down VM
10-19 09:48:01.490 15048 15048 W dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x417d3da0)
10-19 09:48:01.490 15048 15048 E AndroidRuntime: FATAL EXCEPTION: main
10-19 09:48:01.490 15048 15048 E AndroidRuntime: Process: com.example.myapp, PID: 15048
10-19 09:48:01.490 15048 15048 E AndroidRuntime: java.lang.VerifyError: com/example/myapp/LoginActivity
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at java.lang.Class.newInstanceImpl(Native Method)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at java.lang.Class.newInstance(Class.java:1208)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.Instrumentation.newActivity(Instrumentation.java:1079)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2199)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2340)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread.access$800(ActivityThread.java:157)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1247)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.os.Handler.dispatchMessage(Handler.java:102)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.os.Looper.loop(Looper.java:157)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at android.app.ActivityThread.main(ActivityThread.java:5293)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at java.lang.reflect.Method.invokeNative(Native Method)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at java.lang.reflect.Method.invoke(Method.java:515)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
10-19 09:48:01.490 15048 15048 E AndroidRuntime:    at dalvik.system.NativeStart.main(Native Method)  

我尝试对流程中使用的每个变量的每个声明进行显式 API 检查,但这也不起作用。有任何想法吗?


我能够通过创建一个名为的单独的类来解决这个问题BiometricLogin。 (显然,无论你喜欢怎么称呼它)。我将所有指纹/生物识别逻辑从我的LoginActivity到新类,并实现了一个接口,其中包括onLogin()我从之前定义的回调类中调用的方法LoginActivity。在里面onCreate() of LoginActivity就做这样的事情:

if (bioEnabled) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        mBiometricLogin = new BiometricLogin(this); // Pass in context to manipulate dialog view
        mBiometricLogin.setBiometricLoginCallback(this);
        mBiometricLogin.showDialog();
    }
}

处理所有 API 检查BiometricLogin类,它只需要与LoginActivity当它完成时。要在对话框打开时简单地处理生命周期更改,请将其添加到onDestroy()

@Override
protected void onDestroy() {
    super.onDestroy();
    if (Build.VERSION.SDK_INT >= 23) {
        if (mBiometricLogin != null && mBiometricLogin.isShowing())
            mBiometricLogin.cancel();
    }
}

希望这可以帮助任何可能遇到此问题的人!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

尽管检查 API 级别,但 AuthenticationCallback 和 KeygenParameterSpec 仍导致应用程序崩溃 的相关文章

随机推荐