使用BiometricPrompt验证PIN时调用Activity被销毁的问题排查
问题分析与解决方案
核心原因推测
问题仅出现在推送触发的TransactionActivity中,登录流程无异常,结合提供的PendingIntent代码,核心问题大概率和以下两点相关:
Intent.FLAG_ACTIVITY_NO_HISTORY标记:当系统弹出PIN验证界面时,TransactionActivity进入后台,该标记会让系统直接销毁Activity,导致验证完成后没有可返回的页面,直接回到主屏幕。- Activity启动模式与PendingIntent的交互:推送启动的Activity没有合适的栈管理策略,验证完成后无法重新唤起原Activity。
解决方案
1. 调整Intent的Flags
移除FLAG_ACTIVITY_NO_HISTORY,改用更温和的栈管理标记,同时确保Activity在验证完成后能正确回到前台:
fun createPendingIntent(context: Context, transaction: Transaction): PendingIntent = Intent(context, TransactionActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP putExtra(INTENT_EXTRA_TRANSACTION_APPROVAL, transaction) }.let { intent -> val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.getActivity(context, 1484, intent, flags) }
FLAG_ACTIVITY_CLEAR_TOP+FLAG_ACTIVITY_SINGLE_TOP:如果TransactionActivity已经存在于栈中,会复用现有实例并清除其上方的所有Activity,避免重复创建。- 保留
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:符合交易验证页面不留在最近任务列表的需求。
2. 给TransactionActivity设置合适的启动模式
在AndroidManifest.xml中给TransactionActivity添加launchMode="singleTask":
<activity android:name=".TransactionActivity" android:launchMode="singleTask" android:excludeFromRecents="true"> </activity>
这个配置会让Activity始终保持单实例,系统验证完成后能正确回到该实例,避免因栈管理问题导致的Activity销毁。
3. 处理Activity销毁后的认证回调
即使Activity被意外销毁,也要确保认证结果能被正确接收。可以通过ViewModel保存认证状态:
// 在TransactionActivity的ViewModel中保存认证状态 class TransactionViewModel : ViewModel() { val authenticationResult = MutableLiveData<AuthenticationResult?>() var hasInitiatedAuthentication = false fun setResult(result: AuthenticationResult) { authenticationResult.postValue(result) } } // 修改createBiometricPrompt的回调逻辑 fun FragmentActivity.createBiometricPrompt(viewModel: TransactionViewModel): BiometricPrompt { return BiometricPrompt( this, object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { viewModel.setResult(AuthenticationResult.Error(errorCode, errString)) } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { viewModel.setResult(AuthenticationResult.Succeeded(result)) } override fun onAuthenticationFailed() { Timber.i("onAuthenticationFailed") } } ) }
然后在TransactionActivity的onCreate中观察ViewModel的结果,即使Activity被重建,也能获取到认证结果并继续处理交易逻辑:
class TransactionActivity : FragmentActivity() { private val viewModel: TransactionViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_transaction) viewModel.authenticationResult.observe(this) { result -> result?.let { // 处理认证结果,比如完成交易或提示错误 } } } override fun onResume() { super.onResume() if (!viewModel.hasInitiatedAuthentication) { viewModel.hasInitiatedAuthentication = true lifecycleScope.launch { val result = authenticate(this@TransactionActivity) viewModel.setResult(result) } } } }
4. 验证BiometricPrompt的调用时机
确保在Activity的onResume阶段调用authenticate(),避免在onCreate早期调用时,Activity还未完全初始化就触发系统验证,导致生命周期异常。
验证步骤
- 优先测试调整Intent Flags和Activity启动模式的方案,这是最直接解决栈管理问题的方法。
- 如果问题仍存在,再添加ViewModel的结果保存逻辑,确保认证结果不丢失。
内容的提问来源于stack exchange,提问作者Aaron
相关产品推荐
相关产品推荐

