2

Hello I found 2 memory leaks that I'd like to resolve.

After receiving the callback onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result), I want to setResult() and call finish() on ActivityB to go back to ActivityA.
Although this does work fine, it seems to cause 2 memory leaks after calling finish().

2 LeakCanary screenshots

Here is the relevant code to reproduce the memory leak (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();
}

1 Answers1

0

The problem lies with the initialization of the executor,

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

Update to

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

Internally, Google is using a Handler to perform actions in a new thread. The context association shouldn't be your Activity (or Fragment).

/**
 * 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");
        }
    }
}
portfoliobuilder
  • 7,556
  • 14
  • 76
  • 136