首页 > 解决方案 > BiometricPrompt:BiometricFragment 和 Activity 内存泄漏

问题描述

您好,我发现了 2 个我想解决的内存泄漏。

收到回调后onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result),我想setResult()调用finish()ActivityB 回到 ActivityA。
尽管这确实可以正常工作,但在调用finish().

2 LeakCanary 截图

以下是重现内存泄漏(ActivityB)的相关代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_b);;
    BiometricPrompt biometricPrompt = biometricPromptInstance();
    biometricPrompt.authenticate(buildPromptInfo());
}

private BiometricPrompt biometricPromptInstance(){
    Executor executor = ContextCompat.getMainExecutor(this);;
    BiometricPrompt.AuthenticationCallback callback = new BiometricPrompt.AuthenticationCallback() {
        @Override
        public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {

        }

        @Override
        public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
            setResult(RESULT_OK);

            //Causing leak:  ////////////////////////////////////////////////
            finish();
            /////////////////////////////////////////////////////////////////
        }

        @Override
        public void onAuthenticationFailed() {

        }
    };
    return new BiometricPrompt(this, executor, callback);
}

private BiometricPrompt.PromptInfo buildPromptInfo(){
    return new BiometricPrompt.PromptInfo.Builder()
            .setTitle("Login")
            .setSubtitle("Log in using your biometric credential")
            .setNegativeButtonText("Cancel")
            .build();
}

标签: androidmemory-leaksandroidxandroid-fingerprint-apiandroid-biometric-prompt

解决方案


问题在于执行器的初始化,

// initialize biometric executor
executor = ContextCompat.getMainExecutor(this)

更新到

// initialize biometric executor
executor = ContextCompat.getMainExecutor(applicationContext)

在内部,Google 使用 Handler 在新线程中执行操作。上下文关联不应该是您的活动(或片段)。

/**
 * Return an {@link Executor} that will run enqueued tasks on the main
 * thread associated with this context. This is the thread used to dispatch
 * calls to application components (activities, services, etc).
 */
public static Executor getMainExecutor(Context context) {
    if (Build.VERSION.SDK_INT >= 28) {
        return context.getMainExecutor();
    }
    return new MainHandlerExecutor(new Handler(context.getMainLooper()));
}

private static class MainHandlerExecutor implements Executor {
    private final Handler mHandler;

    MainHandlerExecutor(@NonNull Handler handler) {
        mHandler = handler;
    }

    @Override
    public void execute(Runnable command) {
        if (!mHandler.post(command)) {
            throw new RejectedExecutionException(mHandler + " is shutting down");
        }
    }
}

推荐阅读