Ответ CommonsWare устраняет первую причину утечки памяти в Activity и оказал большую помощь в поиске второй.
Вторая причина заключается в том, что FingerprintManager содержит строгую ссылку на объект обратного вызова в FingerprintManager.mAuthenticationCallback
и не освобождает его до тех пор, пока другой вызов authenticate()
не предоставит другой объект обратного вызова.
Это известная проблема , которую они еще не исправили по состоянию на 17 декабря 2018 г..
Мой обходной путь (kludge) - сделать еще один вызов authenticate()
с пустым объектом обратного вызова, созданным в контексте приложения, а затем немедленно вызвать onAuthenticationFailed()
для пустого объекта обратного вызова.
Это грязно, и я бы определенно проголосовал за лучшее, более элегантное решение.
Объявите где-нибудь статическую переменную (в классе с именем App
в этом примере) для хранения пустого объекта обратного вызова.
public static FingerprintManager.AuthenticationCallback EmptyAuthenticationCallback;
Создайте экземпляр в onCreate()
подкласса приложения, если это необходимо.Обратите внимание, что для этого требуется API 23+, поэтому убедитесь, что ваше приложение не пытается использовать его в более низких API.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
App.EmptyAuthenticationCallback = new FingerprintManager.AuthenticationCallback() {};
}
В анонимном объекте FingerprintManager.AuthenticationCallback()
добавьте метод clearCallbackReference()
.
private void clearCallbackReference() {
final String methodName = "clearCallbackReference()";
// FingerprintManager holds a strong reference to the callback
// which in turn holds a strong reference to the Activity
// and thus causes the Activity to be leaked.
// This is a known bug in the FingerprintManager class.
// http://code.google.com/p/android/issues/detail?id=215512
// And the CancellationSignal object does not clear the callback reference either.
//
// To clear it we call authenticate() again and give it a new callback
// (created in the application context instead of the Activity context),
// and then immediately "fail" the authenticate() call
// since we aren't wanting another fingerprint from the user.
try {
Log.d(TAG, methodName);
fingerprintManager.authenticate(null, null, 0, App.EmptyAuthenticationCallback, null);
App.EmptyAuthenticationCallback.onAuthenticationFailed();
}
catch (Exception ex) {
// Handle the exception..
}
}
Измените ваши onAuthenticationSucceeded()
& onAuthenticationError()
методы в FingerprintManager.AuthenticationCallback()
для вызова clearCallbackReference()
.
Пример:
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
final String methodName = "onAuthenticationSucceeded()";
try {
Log.d(TAG, methodName + ": Authentication succeeded for Action '" + action + "'.");
super.onAuthenticationSucceeded(result);
// Do your custom actions here if needed.
}
catch (Exception ex) {
// Handle the exception..
}
finally {
clearCallbackReference();
}
}
В onAuthenticationError()
мой блок finallyвыглядит так, потому что иногда errMsgId 5 "Fingerprint operation canceled."
является фиктивной ошибкой.Обычно он запускается сразу после вызова authenticate()
, но операция на самом деле не отменяется.
finally {
if (errMsgId != 5 || (canceler != null && canceler.isCanceled()))
clearCallbackReference();
}
canceler
- это объект CancellationSignal, передаваемый в качестве параметра.