10

我有一个DialogFragment,它为我的应用程序处理登录和指纹验证。该片段使用API​​ 23,KeyGenParameterSpecKeyPermanentlyInvalidatedException独有的两个类。我一直认为,我可以使用这些类的印象,只要我检查内部版本之前,我尝试初始化类(概述here):如何使用不受支持的例外来降低平台版本

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
    ... 
} else { 
    ... 
} 

但现在看来,这种情况并非如此。如果我尝试在API 20之前的版本上运行此代码,则Dalvik VM将拒绝整个班级并抛出一个VerifyError。尽管如此,代码确实适用于API 20及更高版本。我如何在代码中使用这些方法,同时仍允许将代码用于以前的API级别?

完整的堆栈跟踪如下:

05-31 14:35:50.924 11941-11941/com.example.app E/dalvikvm: Could not find class 'android.security.keystore.KeyGenParameterSpec$Builder', referenced from method com.example.app.ui.fragment.util.LoginFragment.createKeyPair 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve new-instance 263 (Landroid/security/keystore/KeyGenParameterSpec$Builder;) in Lcom/example/app/ui/fragment/util/LoginFragment; 
05-31 14:35:50.924 11941-11941/com.example.app D/dalvikvm: VFY: replacing opcode 0x22 at 0x000c 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve exception class 265 (Landroid/security/keystore/KeyPermanentlyInvalidatedException;) 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to find exception handler at addr 0x3f 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z 
05-31 14:35:50.924 11941-11941/cp W/dalvikvm: VFY: rejecting opcode 0x0d at 0x003f 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: Verifier rejected class Lcom/example/app/ui/fragment/util/LoginFragment; 
05-31 14:35:50.924 11941-11941/com.example.app D/AndroidRuntime: Shutting down VM 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x9cca9b20) 
05-31 14:35:50.934 11941-11941/com.example.app E/AndroidRuntime: FATAL EXCEPTION: main 
     Process: com.example.app, PID: 11941 java.lang.VerifyError: com/example/app/ui/fragment/util/LoginFragment 
      at com.example.app.util.NetworkUtility.login(NetworkUtility.java:41) 
      at com.example.app.ui.activity.AbstractNavActivity.onOptionsItemSelected(AbstractNavActivity.java:68) 
      at android.app.Activity.onMenuItemSelected(Activity.java:2600) 
      at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:403) 
      at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:189) 
      at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100) 
      at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100) 
      at android.support.v7.app.ToolbarActionBar$2.onMenuItemClick(ToolbarActionBar.java:69) 
      at android.support.v7.widget.Toolbar$1.onMenuItemClick(Toolbar.java:169) 
      at android.support.v7.widget.ActionMenuView$MenuBuilderCallback.onMenuItemSelected(ActionMenuView.java:760) 
      at android.support.v7.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:811) 
      at android.support.v7.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152) 
      at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:958) 
      at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:948) 
      at android.support.v7.view.menu.MenuPopupHelper.onItemClick(MenuPopupHelper.java:191) 
      at android.widget.AdapterView.performItemClick(AdapterView.java:299) 
      at android.widget.AbsListView.performItemClick(AbsListView.java:1113) 
      at android.widget.AbsListView$PerformClick.run(AbsListView.java:2904) 
      at android.widget.AbsListView$3.run(AbsListView.java:3638) 
      at android.os.Handler.handleCallback(Handler.java:733) 
      at android.os.Handler.dispatchMessage(Handler.java:95) 
      at android.os.Looper.loop(Looper.java:136) 
      at android.app.ActivityThread.main(ActivityThread.java:5017) 
      at java.lang.reflect.Method.invokeNative(Native Method) 
      at java.lang.reflect.Method.invoke(Method.java:515) 
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) 
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) 
      at dalvik.system.NativeStart.main(Native Method) 

更新了代码

login()方法只是一个方便的方法来启动LoginFragment

public static void login(FragmentManager manager) { 
    manager.beginTransAction().add(LoginFragment.newInstance(), null).commit(); 
} 

r优先代码在LoginFragment本身。具体的createKeyPair()initializeCipher方法:

public class LoginFragment extends DialogFragment 
     implements TextView.OnEditorActionListener, FingerprintCallback.Callback { 

    ... 

    public static LoginFragment newInstance() { 
     return newInstance(null); 
    } 

    public static LoginFragment newInstance(Intent intent) { 
     LoginFragment fragment = new LoginFragment(); 

     Bundle args = new Bundle(); 
     args.putParcelable(EXTRA_INTENT, intent); 
     fragment.setArguments(args); 

     return fragment; 
    } 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     Injector.getContextComponent().inject(this); 
     setStyle(STYLE_NO_TITLE, R.style.DialogTheme); 
     setRetainInstance(true); 
     setCancelable(false); 

     mSaveUsernamePreference = mPreferences.getBoolean(getString(R.string.key_auth_username_retain)); 
     mUseFingerprintPreference = mPreferences.getBoolean(getString(R.string.key_auth_fingerprint)); 
     mUsernamePreference = mPreferences.getString(getString(R.string.key_auth_username)); 
     mPasswordPreference = mPreferences.getString(getString(R.string.key_auth_password)); 
    } 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
          Bundle savedInstanceState) { 
     View view = inflater.inflate(R.layout.dialog_login_container, container, false); 
     ButterKnife.bind(this, view); 

     mPasswordView.setOnEditorActionListener(this); 

     if(!mFingerprintManager.isHardwareDetected()) { 
      mUseFingerprintToggle.setVisibility(View.GONE); 
     } else { 
      mGenerated = initializeKeyPair(false); 
     } 

     if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
      setStage(isFingerprintAvailable() ? Stage.FINGERPRINT : Stage.CREDENTIALS); 
     } else { 
      setStage(Stage.CREDENTIALS); 
     } 

     return view; 
    } 

    @Override 
    public void onResume() { 
     super.onResume(); 

     ... 

     if(mStage == Stage.FINGERPRINT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
      startListening(initializeCipher(Cipher.DECRYPT_MODE)); 
     } 
    } 

    @Override 
    public void onPause() { 
     super.onPause(); 
     stopListening(); 
    } 

    ... 

    @Override 
    public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) { 
     Timber.i("Fingerprint succeeded"); 
     showFingerprintSuccess(); 

     mSubscriptions.add(
      mGenerated.subscribeOn(Schedulers.newThread()) 
        .observeOn(AndroidSchedulers.mainThread()) 
        .doOnCompleted(() -> { 
         try { 
          mUsername = mUsernamePreference.get(); 
          mPassword = decryptPassword(result.getCryptoObject().getCipher()); 
          initLoginAttempt(); 
         } catch (IllegalBlockSizeException | BadPaddingException exception) { 
          Timber.e(exception, "Failed to decrypt password"); 
         } 
        }).subscribe()); 
    } 

    @Override 
    public void onAuthenticationHelp(int messageId, CharSequence message) { 
     Timber.i("Fingerprint help id: " + messageId + " message: " + message); 
     showFingerprintError(message); 
    } 

    @Override 
    public void onAuthenticationError(int messageId, CharSequence message) { 
     Timber.i("Fingerprint error id: " + messageId + " message: " + message); 
     if(messageId != 5) { 
      showFingerprintError(message); 
     } 
    } 

    @Override 
    public void onAuthenticationFailed() { 
     Timber.i("Fingerprint failed"); 
     showFingerprintError(getResources().getString(R.string.msg_fingerprint_error_unknown)); 
    } 

    @OnClick(R.id.button_cancel) 
    public void onCancel() { 
     dismiss(); 
    } 

    @OnClick(R.id.button_continue) 
    public void onContinue() { 
     switch (mStage) { 
      case CREDENTIALS: 
       mUsername = mUsernameView.getText().toString(); 
       mPassword = mPasswordView.getText().toString(); 
       initLoginAttempt(); 
       break; 
      case FINGERPRINT: 
       setStage(Stage.CREDENTIALS); 
       break; 
     } 
    } 

    private void showFingerprintSuccess() { 
     int colorAccent = ThemeUtil.getColorAttribute(getContext(), android.R.attr.colorAccent); 
     mFingerprintIcon.setImageResource(R.drawable.ic_done_white_24dp); 
     mFingerprintIcon.setCircleColor(colorAccent); 
     mFingerprintStatus.setText(R.string.msg_fingerprint_success); 
     mFingerprintStatus.setTextColor(colorAccent); 
    } 

    private void showFingerprintError(CharSequence message) { 
     int colorError = ContextCompat.getColor(getContext(), R.color.material_deep_orange_600); 
     mFingerprintIcon.setImageResource(R.drawable.ic_priority_high_white_24dp); 
     mFingerprintIcon.setCircleColor(colorError); 
     mFingerprintStatus.setText(message); 
     mFingerprintStatus.setTextColor(colorError); 
     resetFingerprintStatus(); 
    } 

    private void resetFingerprintStatus() { 
     mSubscriptions.add(Observable.timer(1600, TimeUnit.MILLISECONDS) 
       .subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(finished -> { 
        mFingerprintIcon.setImageResource(R.drawable.ic_fingerprint_white_24dp); 
        mFingerprintIcon.setCircleColor(ContextCompat 
          .getColor(getContext(), R.color.material_blue_gray_500)); 
        mFingerprintStatus.setText(R.string.msg_fingerprint_input); 
        mFingerprintStatus.setTextColor(ThemeUtil 
          .getColorAttribute(getContext(), android.R.attr.textColorHint)); 
       })); 
    } 

    private void onSaveUsernameChanged(boolean checked) { 
     if(!checked) { 
      mUseFingerprintToggle.setChecked(false); 
     } 
    } 

    private void onUseFingerprintChanged(boolean checked) { 
     if(checked) { 
      mSaveUsernameToggle.setChecked(true); 

      if(!mFingerprintManager.hasEnrolledFingerprints()) { 
       displaySettingsDialog(); 
       mUseFingerprintToggle.setChecked(false); 
      } 
     } 
    } 

    public void setStage(Stage stage) { 
     switch (stage) { 
      case CREDENTIALS: 
       Timber.d("Set stage Credentials"); 
       mPositiveButton.setText(R.string.btn_login); 
       mFingerprintContent.setVisibility(View.GONE); 
       mCredentialContent.setVisibility(View.VISIBLE); 
       setForm(); 
       break; 
      case FINGERPRINT: 
       mPositiveButton.setText(R.string.btn_password); 
       mCredentialContent.setVisibility(View.GONE); 
       mFingerprintContent.setVisibility(View.VISIBLE); 
       break; 
     } mStage = stage; 
    } 

    private void startListening(boolean cipher) { 
     Timber.v("Start listening for fingerprint input"); 
     mCancellationSignal = new CancellationSignal(); 
     if(cipher) { 
      mFingerprintManager.authenticate(new FingerprintManagerCompat.CryptoObject(mCipher), 
        0, mCancellationSignal, new FingerprintCallback(this), null); 
     } else { 
      setStage(Stage.CREDENTIALS); 
     } 
    } 

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

    private void setForm() { 
     if(mSaveUsernamePreference.isSet() && mSaveUsernamePreference.get() 
       && mUsernamePreference.isSet()) { 
      mUsernameView.setText(mUsernamePreference.get()); 
      mUsernameView.setSelectAllOnFocus(true); 
      mPasswordView.requestFocus(); 
     } else { 
      mUsernameView.requestFocus(); 
     } 
    } 

    public void initLoginAttempt() { 
     mProgressBar.setVisibility(View.VISIBLE); 
     mAuthenticationService.getLoginForm().subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(this::onLoginFormResponse, this::onError); 
    } 

    private void onLoginFormResponse(ResponseBody response) { 
     try { 
      attemptLogin(LoginForm.parse(response.string())); 
     } catch (IOException exception) { 
      Timber.w(exception, "Failed to parse login form"); 
     } 
    } 

    private void attemptLogin(LoginForm loginForm) { 
     mAuthenticationService 
       .login(loginForm.getLoginTicket(), loginForm.getExecution(), loginForm.getEventIdentifier(), 
         mUsername, mPassword, loginForm.getSubmitValue()) 
       .subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(this::onLoginResponse, this::onError); 
    } 

    public void onLoginResponse(ResponseBody response) { 
     Timber.d("LOGIN RESPONSE"); 
     try { 
      Timber.d(response.string()); 
     } catch (IOException exception) { 
      Timber.w(exception, "Failed to retrieve attemptLogin response"); 
     } 

     mSubscriptions.add(NetworkUtility.getAuthentication() 
       .subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(this::onAuthenticationChanged, this::onError)); 
    } 

    public void onAuthenticationChanged(Boolean authenticated) { 
     if(authenticated) { 
      Timber.d("Authentication success"); 

      if(mStage == Stage.CREDENTIALS) { 
       if (mSaveUsernameToggle.isChecked()) { 
        storeUsername(); 
       } else { 
        clearUsername(); 
       } 

       if (mUseFingerprintToggle.isChecked()) { 
        mGenerated = initializeKeyPair(true); 
        storePassword(); 
       } else { 
        clearPassword(); 
        finishIntent(); 
       } 
      } else { 
       finishIntent(); 
      } 
     } else { 
      Timber.d("Authentication failed"); 
      setStage(Stage.CREDENTIALS); 
      mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600)); 
      mCaptionView.setText(getString(R.string.msg_login_failed)); 
      mPasswordView.setText(""); 
     } 
    } 

    private void finishIntent() { 
     mProgressBar.setVisibility(View.INVISIBLE); 
     Intent intent = getArguments().getParcelable(EXTRA_INTENT); 
     if(intent != null) { 
      startActivity(intent); 
     } dismiss(); 
    } 

    private void onError(Throwable throwable) { 
     Timber.w(throwable, "Login attempt failed"); 
     mProgressBar.setVisibility(View.INVISIBLE); 
     mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600)); 
     mCaptionView.setText("Login attempt failed\nPlease check your internet connection and try again"); 
     mPasswordView.setText(""); 
    } 

    private void storeUsername() { 
     String username = mUsernameView.getText().toString(); 
     mUsernamePreference.set(username); 
     if(mPreferences.getBoolean(getString(R.string.key_auth_push), false).get()) { 
      UAirship.shared().getPushManager().getNamedUser().setId(username); 
     } 
    } 

    private void clearUsername() { 
     UAirship.shared().getPushManager().getNamedUser().setId(null); 
     mUsernamePreference.delete(); 
    } 

    private void storePassword() { 
     Timber.d("STORE PASSWORD"); 
     mSubscriptions.add(mGenerated.subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .doOnCompleted(() -> { 
        try { 
         Timber.d("Store password"); 
         initializeCipher(Cipher.ENCRYPT_MODE); 

         String password = mPasswordView.getText().toString(); 
         byte[] bytes = password.getBytes(); 
         byte[] encrypted = mCipher.doFinal(bytes); 
         String encoded = Base64.encodeToString(encrypted, Base64.NO_WRAP); 

         mPasswordPreference.set(encoded); 

         finishIntent(); 

        } catch (IllegalBlockSizeException | BadPaddingException exception) { 
         Timber.e(exception, "Failed to encrypt password"); 
        } 
       }).subscribe()); 
    } 

    private String decryptPassword(Cipher cipher) throws IllegalBlockSizeException, BadPaddingException { 
     String encoded = mPasswordPreference.get(); 

     Timber.d("ENCODED STRING " + encoded); 

     byte[] encrypted = Base64.decode(encoded, Base64.NO_WRAP); 

     byte[] bytes = cipher.doFinal(encrypted); 

     return new String(bytes); 
    } 

    private void clearPassword() { 
     mPasswordPreference.delete(); 
    } 

    private boolean isFingerprintAvailable() { 
     return mUseFingerprintPreference.isSet() && mUseFingerprintPreference.get() 
       && mFingerprintManager.hasEnrolledFingerprints() 
       && mSaveUsernamePreference.isSet() 
       && mPasswordPreference.isSet(); 
    } 

    private void displaySettingsDialog() { 
     new AlertDialog.Builder(getContext()) 
       .setTitle(R.string.title_dialog_secure_lock) 
       .setMessage(R.string.msg_fingerprint_unavailable) 
       .setPositiveButton(R.string.btn_settings, (dialog, which) -> { 
        startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS)); 
        dialog.dismiss(); 
       }).setNegativeButton(R.string.btn_cancel, (dialog, which) -> { 
      dialog.dismiss(); 
     }).create().show(); 
    } 

    @TargetApi(Build.VERSION_CODES.M) 
    private boolean initializeCipher(int opmode) { 
     try { 
      mKeyStore.load(null); 

      /** 
      * A known bug in the Android 6.0 (API Level 23) implementation of Bouncy Castle 
      * RSA OAEP causes the cipher to default to an SHA-1 certificate, making the SHA-256 
      * certificate of the public key incompatible 
      * To work around this issue, explicitly provide a new OAEP specification upon 
      * initialization 
      * @see <a href="https://code.google.com/p/android/issues/detail?id=197719">Issue 197719</a> 
      */ 
      AlgorithmParameterSpec spec = generateOAEPParameterSpec(); 
      Key key; 

      if(opmode == Cipher.ENCRYPT_MODE) { 
       Key publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey(); 

       /** 
       * A known bug in Android 6.0 (API Level 23) causes user authentication-related 
       * authorizations to be enforced even for public keys 
       * To work around this issue, extract the public key material to use outside of 
       * the Android Keystore 
       * @see <a href="http://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html">KeyGenParameterSpec Known Issues</a> 
       */ 
       key = KeyFactory.getInstance(publicKey.getAlgorithm()) 
         .generatePublic(new X509EncodedKeySpec(publicKey.getEncoded())); 
      } else { 
       key = mKeyStore.getKey(CIPHER_KEY_ALIAS, null); 
      } 

      mCipher.init(opmode, key, spec); 
      return true; 
     } catch (KeyPermanentlyInvalidatedException exception) { 
      Timber.w(exception, "Failed to initialize Cipher"); 
      handleKeyPermanentlyInvalidated(); 
      return false; 
     } catch (IOException | KeyStoreException | UnrecoverableEntryException 
       | InvalidKeySpecException | CertificateException | InvalidKeyException 
       | NoSuchAlgorithmException | InvalidAlgorithmParameterException exception) { 
      throw new RuntimeException("Failed to initialize Cipher", exception); 
     } 
    } 

    private OAEPParameterSpec generateOAEPParameterSpec() { 
     return new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); 
    } 

    private void handleKeyPermanentlyInvalidated() { 
     mCaptionView.setText(getString(R.string.msg_fingerprint_invalidated)); 
     mGenerated = initializeKeyPair(true); 
     clearPassword(); 
    } 

    private Observable<KeyPair> initializeKeyPair(boolean generate) { 
     return Observable.create(subscriber -> { 
      try { 
       mKeyStore.load(null); 

       if(!generate || mKeyStore.containsAlias(CIPHER_KEY_ALIAS)) { 
        PublicKey publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey(); 
        PrivateKey privateKey = (PrivateKey) mKeyStore.getKey(CIPHER_KEY_ALIAS, null); 
        subscriber.onNext(new KeyPair(publicKey, privateKey)); 
       } else { 
        subscriber.onNext(createKeyPair()); 
       } 

       subscriber.onCompleted(); 
      } catch (IOException | KeyStoreException | UnrecoverableKeyException 
        | CertificateException | NoSuchAlgorithmException 
        | InvalidAlgorithmParameterException exception) { 
       Timber.e(exception, "Failed to generate key pair"); 
       subscriber.onError(exception); 
      } 
     }); 
    } 

    @TargetApi(Build.VERSION_CODES.M) 
    private KeyPair createKeyPair() throws InvalidAlgorithmParameterException { 
     // Set the alias of the entry in Android KeyStore where the key will appear 
     // and the constrains (purposes) in the constructor of the Builder 
     Timber.d("Initialize key pair"); 
     mKeyPairGenerator.initialize(
       new KeyGenParameterSpec.Builder(CIPHER_KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT) 
        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) 
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) 
         .setUserAuthenticationRequired(true) 
         .build()); 

     return mKeyPairGenerator.generateKeyPair(); 
    } 

} 

更新

好了,所以我想通了,这是导致错误的KeyPermanentlyInvalidatedException。如果我注释掉处理该异常的catch块,代码可以在任何设备上正常运行。问题是,我需要能够处理对API的设备23+是例外:

catch (KeyPermanentlyInvalidatedException exception) { 
    Timber.w(exception, "A new fingerprint was added to the device"); 
    handleKeyPermanentlyInvalidated(); 
    return false; 
} 
+0

我们无法真正帮助您编辑代码。请发布一个[mcve]来展示你的问题,比如你正在崩溃的'login()'方法的实际实现,以及'LoginFragment'的'initializeCipher()'方法(这似乎是无法识别的东西谎言)。 – CommonsWare

+0

@CommonsWare我用代码更新了问题。 – Bryan

回答

8

我的猜测是,要么FingerprintCallback.Callback延伸的API等级23+接口或LoginFragment具有引用API等级23场+东西。

您的规则是否能够在版本保护块内安全地调用API Level 23+方法是正确的。但是,你不能:从类

  • 继承不存在与设备上
  • 落实不存在与设备上的接口
  • 有没有在设备上存在的领域,其类型
  • 接受构造函数或方法参数,其类型不存在于设备上(我们实际调用这些参数的地方)
  • 有方法返回值,其类型在设备上不存在(我们实际称之为这些)

在很多情况下,我们不需要任何这种情况,在这种情况下,只需在调用API Level 23+方法之前检查Build.VERSION.SDK_INT就足够了。

如果您需要在项目符号列表中进行一些操作,那很好,但是您需要将这些项目隔离为仅在API Level 23+设备上使用的类。例如,假设问题是FingerprintCallback.Callback扩展了一些API Level 23+接口。 LoginFragment上的FingerprintCallback.Callback可能不是实现FingerprintCallback.Callback,而是您可以将其实现为匿名内部类,并且只执行创建匿名内部类实例的代码(如果Build.VERSION.SDK_INT足够高)。然后,您只需在较新的设备上引用FingerprintCallback.Callback,并且您应该是安全的。

+0

我看到你在说什么,但它不应该是'FingerprintCallback',因为这是一个扩展FingerprintManagerCompat.AuthenticationCallback的类,它是[Fingerprint Support Library]的一部分(https://developer.android的.com /参考/机器人/支撑/ V4 /硬件/指纹/包summary.html)。我也没有看到任何引用API 23+的类的字段。我将不得不查看代码,看看我是否不符合其他要求之一。 – Bryan

+1

@Bryan:如果你被困住了,将LoginFragment分成2-3个类。可以有'LoginFragment'和'FingerprintLoginFragment'子类,或者'LoginFragmentBase'带'LoginFragment'和'FingerprintLoginFragment'子类。把所有的指纹填入'FingerprintLoginFragment'中。然后,当它添加()该片段时,请根据设备API级别选择片段类。这种方法也适用于以下情况:在FingerprintLoginFragment可以使用任何API级别23+的东西时,您可以使用数量大的'Build.VERSION.SDK_INT'检查。 – CommonsWare

+0

我发现问题的来源是'initializeCipher()'try/catch块中的'KeyPermanentlyInvalidatedException'。但是这种方法在API 23之前从来没有在设备上调用(否则我不认为代码可以在设备API 20+上运行)。我想我最终可能会像你所建议的那样只做两堂课,但是为什么会出现这种情况呢? – Bryan

1

正如你所说的问题是与catch块

catch (KeyPermanentlyInvalidatedException exception) { 
    Timber.w(exception, "A new fingerprint was added to the device"); 
    handleKeyPermanentlyInvalidated(); 
    return false; 
} 

正因为如此异常被API等级23添加,但我不知道为什么校验错误在初始化时把自己推。

反正你能赶上使用

catch (InvalidKeyExceptionexception) { 
    .... 
    return false; 
} 

因为KeyPermanentlyInvalidatedException延伸InvalidKeyExceptionexception

+1

是的,我最终这样做了,但我需要特别抓住'KeyPermanentlyInvalidatedException'。 [此原因](https://developer.android.com/reference/android/security/keystore/KeyPermanentlyInvalidatedException.html)是,这个异常用于捕获用户应该用密码重新进行身份验证的任何时间。在许多其他情况下,InvalidKeyException可能会被抛出,因此,正如@CommonsWare所提到的,我首先检查API是否为23+,然后检查异常是否是catch块中的'KeyPermanentlyInvalidatedException'的'instanceof'。 – Bryan

4

我有同样的错误并解决它通过以下方式除外:

catch (Exception e) { 
    if (e instanceof KeyPermanentlyInvalidatedException) { 
     //your error handling goes here 
    } 

它不是” t vey很好,但它的作品

+0

是的,它的工作原理。证实。不喜欢这样做,但我有更大的鱼来炒。这只是我在4.x上的一个问题。所以在几年之后,我可以删除这个难看的代码。 – KickingLettuce

相关问题