【问题标题】:Memory Leak, but how can I pass a different context than the one of the activity to solve the Leak?内存泄漏,但是如何传递与解决泄漏的活动不同的上下文?
【发布时间】:2019-04-28 03:04:35
【问题描述】:

LeakCanary 检测到以下泄漏 似乎是:

GC ROOT android.hardware.fingerprint.FingerprintManager$1.this$0(android.hardware.fingerprint.IFingerprintServiceReceiver$Stub 的匿名子类) 参考android.hardware.fingerprint.FingerprintManager.mContext 泄露 com.alga.com.mohammed.views PasscodeActivity 实例

【问题讨论】:

  • 您可能可以传递应用程序上下文(通过调用Context.getApplicationContext()
  • 这可能已在您的代码中使用。 applicationContext 这个变量是什么?你如何初始化它?

标签: android kotlin memory-leaks android-fingerprint-api leakcanary


【解决方案1】:

尝试替换:

val fingerprintManagerInstance = this.getSystemService(FINGERPRINT_SERVICE) ?: return

与:

val fingerprintManagerInstance = applicationContext.getSystemService(FINGERPRINT_SERVICE) ?: return

看看你是否能得到更好的结果。

【讨论】:

    【解决方案2】:

    CommonsWare 的回答解决了 Activity 内存泄漏的第一个原因,并且对追查第二个原因很有帮助。

    第二个原因是 FingerprintManager 持有对 FingerprintManager.mAuthenticationCallback 中回调对象的强引用,并且在另一个 authenticate() 调用提供不同的回调对象之前不会释放它。

    这是a known issue,截至 2018 年 12 月 17 日,他们尚未修复。

    我的解决方法 (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..
        }
    }
    

    修改FingerprintManager.AuthenticationCallback() 中的onAuthenticationSucceeded()onAuthenticationError() 方法以调用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 对象,作为参数传入。

    【讨论】:

    • 很高兴看到你把它整理好了。 EmptyAuthenticationCallback 目前不做任何日志记录,因为它是一个单独的实例,其中没有自定义逻辑。这就是为什么我选择将其命名为“空”。您可以像在 Activity 中那样添加自定义逻辑。只要确保您的自定义逻辑不会捕获 Activity 上下文并导致另一个泄漏。
    • @Drocchio 我猜这是很多东西的结合——研究和测试。 CommonsWare 提供了第一个重要线索。我发现this explanation 很有帮助。他们正在讨论大致相同的问题,但它似乎在一个名为 PowerAuth 的包装框架内。学习their code fix 有帮助,但不是我可以直接使用的解决方案。然而,它导致回调被另一个身份验证调用释放。
    • @Drocchio 并且通过测试发现了对 onAuthenticationError() 的虚假调用,因为它导致事情无法正常工作。
    • 非常感谢您分享您的过程,从您的智能体验和洞察力的直觉中学习对于棘手的未来错误将非常有用,是的,commonsWare 是 commonsWare :) 祝您有个不错的新年。
    • +1,感谢您的回答!我发现我需要将回调重新分配给 null,但它在 api 中是 NonNullable 类型。没有想出使用应用上下文创建回调的新实例的解决方案。这完全是一个 hacky 解决方案,但有效。我没想到谷歌会在这个硬引用上犯下如此严重的错误。
    猜你喜欢
    • 2015-06-28
    • 1970-01-01
    • 1970-01-01
    • 2013-07-14
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    • 2018-04-11
    • 2020-04-07
    相关资源
    最近更新 更多