如何使用Leak Canary

43

我目前在我的应用程序中遇到了OutOfMemoryError问题。我已经尝试使用MAT进行调试,但是在少数活动中找到泄漏仍然太困难了。后来我发现了LeakCanary,它似乎更简单易用,但是即使在谷歌上也找不到任何初学者逐步使用LeakCanary的指南。我已通过我的build.gradle的依赖项安装了LeakCanary,并且到目前为止这就是我得到的:

ExampleApplication.java

public class ExampleApplication extends Application {

    public static RefWatcher getRefWatcher(Context context) {
        ExampleApplication application = (ExampleApplication) context.getApplicationContext();
        return application.refWatcher;
    }

    private RefWatcher refWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher = LeakCanary.install(this);
    }

    final class KeyedWeakReference extends WeakReference<Object> {
        public final String key;
        public final String name;

        KeyedWeakReference(Object referent, String key, String name,
                       ReferenceQueue<Object> referenceQueue) {
            super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
            this.key = checkNotNull(key, "key");
            this.name = checkNotNull(name, "name");
        }
    }

    public void watch(Object watchedReference, String referenceName) {
        checkNotNull(watchedReference, "watchReference");
        checkNotNull(referenceName, "referenceName");
        if(debuggerControl.isDebuggerAttached()) {
            return;
        }
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);
        final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
        watchExecutor.execute()

    }
}

假设我有一个Activity,我想让LeakCanary监听一个对象

SampleActivity.java

public class SampleActivity extends Activity implements View.OnClickListener {
    ImageView level001, level002;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.choose_level);

        level001 = (ImageView) findViewById(R.id.level001);
        level002 = (ImageView) findViewById(R.id.level002);

        // Do all kinds of functions
        // How do I use LeakCanary to watch these objects?

    }
}

现在我该如何使用LeakCanary来查看是哪个对象导致了内存泄漏?


请注意,OutOfMemoryError并不一定意味着您有内存泄漏。您可能只是尝试使用过多的资源或者使用太多内存以及使用资产等。如果您正在使用模拟器,请首先尝试将RAM增加,以查看是否解决了问题。 - NoChinDeluxe
不,这是由于内存泄漏引起的。我已经使用DDMS检查了堆转储,即使活动已关闭,分配的内存仍然存在,没有被垃圾回收,并且它会不断变大直到应用程序崩溃。 - Charas
我有同样的问题,但还没有找到答案。 我看了几个视频。 然而,没有一个视频显示如何启动应用程序并查看泄漏对象的基础知识。 例如,应该从Android Studio还是从设备启动应用程序? 它可以在模拟器上运行吗? 我收到了通知,但为什么在泄漏应用程序中看不到它们? 我有多个泄漏应用程序,应该删除一些吗? 这似乎是一个很棒的系统,但基础知识并不清楚。 - Michael Osofsky
https://stackoverflow.com/a/56750594/10284292 - ahsanali274
6个回答

22

LeakCanary的好处在于它的自动化工作方式。默认情况下,它已经“监视”那些没有被正确垃圾回收的活动。因此,如果有任何一个Activity泄漏,您应该会收到通知。

在我的项目中,我添加了一个额外的方法到Application中,就像这样:

public class ExampleApplication extends Application {
    public static ExampleApplication instance;
    private RefWatcher refWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        refWatcher = LeakCanary.install(this);
    }

    public void mustDie(Object object) {
        if (refWatcher != null) {
            refWatcher.watch(object);
        }
    }
}

因此,在垃圾收集和内存泄漏以及canary方面,重要的是要知道何时应该收集东西,并要求监视该项。

例如,我们正在使用以下代码的“基本片段”:

@Override
public void onDestroy() {
    super.onDestroy();
    ExampleApplication.instance.mustDie(this);
}

这样,LeakCanary 就可以检查是否有任何 fragment 泄漏了内存。

所以,如果您想在应用程序上进一步实现,在您知道应该回收但不确定其是否会被垃圾回收的任务或实例上,您也可以调用以下内容:ExampleApplication.instance.mustDie(object);

然后,您必须运行应用程序并旋转设备并强制发生泄漏,以便 LeakCanary 可以抓取 / 分析堆栈跟踪并为您提供有关如何修复问题的宝贵信息。

希望对您有所帮助。


我复制并粘贴了你的ExampleApplication,但我仍然没有收到任何通知..? - Charas
当您杀死活动时,onDestroy会被调用吗?我相信如果您通过滑动并杀死应用程序或类似方式销毁,则可能不会调用onDestroy。我有一个类似的问题。因此,我添加了一个注销按钮进行测试,该按钮将以编程方式销毁我的活动,从而调用onDestroy,并且我得到了泄漏,这是正常应用程序终止所没有的。 - onusopus

13

因为其他答案已经不再需要,使用Leak Canary 2可以更好地查找应用程序中的泄漏,因此发布此答案。

只需将以下依赖项添加到您的gradle中即可。无需添加其他代码。

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

最新版本的LeakCanary,请访问https://square.github.io/leakcanary/changelog/


所以不再有 LeakCanary.install(this) 了,我也找不到这个 API? - AppDeveloper
2
是的,你不需要LeakCanary.Install(this)。 - Prakash
你能分享使用LeakCanary版本高于2.0的源代码吗? - Gary Chen
3
错字:cananry 意思:应为“canary”。 翻译:错字:“cananry”,应改为“canary”。 - Daniel C Jacobs

9
我有关于如何使用LeakCanary的同样问题。我只想看到一个基本示例,了解如何启动它并查看我的第一个泄漏对象路径。
如何使用LeakCanary
以下是如何让LeakCanary工作的基本示例: 如何使用LeakCanary(4分13秒)
我遇到的问题之一是弄清楚我必须以常规运行模式而不是调试模式启动应用程序。我还必须偏离基本说明,并将我的应用程序级build.gradle文件设置如下:
dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
}

我认为debugImplementation对我没有起作用,因为LeakCanary在调试时忽略了泄漏检测。 尽管LeakCanary表示它提供了"no-op"版本的库以供发布, 但由于调试未能起作用,我将上面的releaseImplementation从建议的com.squareup.leakcanary:leakcanary-android-no-op:1.5.4更改为com.squareup.leakcanary:leakcanary-android:1.5.4
正如您在视频中所看到的,我需要处理的另一个问题是给LeakCanary提供写入访问权限。我向下滑动并看到来自LeakCanary的通知,称其未能获取写入访问权限(更多信息)。我从未看到过权限请求。因此,在某些情况下(视频中未显示),我通过进入设置应用程序,找到我的应用程序(而不是LeakCanary安装的名为“Leak”的应用程序),并打开写入访问权限来授予我的应用程序写入访问权限。在视频中,我不需要这样做,因为我通过响应通知来授予权限。然后,在使用我的应用程序时,我会定期检查新通知(通过向下滑动)。我看到了像“XYZActivity泄漏217 KB”这样的消息。我点击它,它带我进入了那个Leak应用程序。
此外,我注意到有时可能需要几分钟才能完成分析并在您的手机上显示内存泄漏通知(有关详细信息)
如何使用LeakCanary验证内存泄漏修复
现在我已经修复了一些内存泄漏问题,我使用LeakCanary来验证修复情况。但是,如果LeakCanary没有报告任何内容,我并不一定相信这是因为我的泄漏已经被修复了,这可能只是因为LeakCanary出了问题。 如何使用LeakCanary验证内存泄漏修复(16分34秒)
使用LeakCanary验证内存泄漏的步骤: 1. 使用LeakCanary确认内存泄漏是否存在 2. 修复内存泄漏,并确认LeakCanary没有报告任何泄漏 3. 恢复您的修复,并确认LeakCanary再次报告泄漏
由于LeakCanary在工作时几乎没有显示任何状态信息,很难知道它是否在工作。这使我认为我已经修复了内存泄漏,但事实上并没有。以上三个步骤是我发现使用LeakCanary验证内存泄漏修复的最佳方法。

说实话,这个答案并没有帮助我,反而让我更加困惑了。 - Fran Marzoa
请随意提供更多细节,因为我不知道什么让您感到困惑。 - Michael Osofsky
我不得不以常规运行模式而非调试模式启动应用程序。我还不得不偏离基本指令。所以有一种“官方”的方法(所谓的“基本指令”)来完成这个任务,但你出于某种原因选择了自己的方式。这很令人困惑。此外,这个库只能在调试模式下运行,而不是在发布模式下运行,这是有充分理由的,但你却在发布模式下使用它。最重要的是,你的回答大多是基于链接的回答,虽然很长,但除非他观看视频,否则它并没有提供足够的信息来解决OP的问题。 :) - Fran Marzoa
是的,我制作这个视频是因为我觉得新手需要看到很多东西,而且有很多事件序列在视频中更容易展示。我不确定现在的“基本指令”是否更好,但如果是的话,一定要使用它们。我不记得为什么选择发布而不是调试,但我想当时我遇到了发布方面的问题。感谢您的反馈。 - Michael Osofsky

3
要使用LeakCanary,请将leakcanary-android依赖项添加到您的应用程序的build.gradle文件中:
dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}

就这样,不需要进行任何代码更改!

通过在Logcat中过滤LeakCanary标签来确认LeakCanary是否在启动时运行:

D LeakCanary: LeakCanary is running and ready to detect leaks
Info

LeakCanary会自动检测以下对象的泄漏:

destroyed Activity instances
destroyed Fragment instances
destroyed fragment View instances
cleared ViewModel instances

enter image description here https://square.github.io/leakcanary/


1

我在应用程序中使用了这里

import android.content.Context;
import android.support.multidex.MultiDex;
import android.support.multidex.MultiDexApplication;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import school.my.com.school.BuildConfig;

public class AppController extends MultiDexApplication {

private RefWatcher refWatcher;

public static RefWatcher getRefWatcher(Context context) {
    AppController application = (AppController) context.getApplicationContext();
    return application.refWatcher;
}



@Override
public void onCreate() {
    super.onCreate();
    if(BuildConfig.DEBUG)
        refWatcher = LeakCanary.install(this);

}

}

您可以在此处使用Application代替MultiDexApplication


0
我像下面这样使用了Leak-Canary:
1)Gradle依赖项:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.4'

2) 应用程序类:

   @Override
public void onCreate() {
    super.onCreate();
    if (BuildConfig.DEBUG) {
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectCustomSlowCalls()
                .detectNetwork()
                .penaltyLog()
                .penaltyDeath()
                .build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .detectActivityLeaks()
                .detectLeakedClosableObjects()
                .detectLeakedRegistrationObjects()
                .detectLeakedSqlLiteObjects()
                .penaltyLog()
                .penaltyDeath()
                .build());

        LeakLoggerService.setupLeakCanary(this);
    }
}

3) LeakLoggerService类:将此类放置在Gradle创建的debug包中。

public class LeakLoggerService extends DisplayLeakService {

public static void setupLeakCanary(Application application) {
    if (LeakCanary.isInAnalyzerProcess(application)) {
        // This process is dedicated to LeakCanary for heap analysis.
        // You should not init your app in this process.
        return;
    }
    LeakCanary.install(application, LeakLoggerService.class,
            AndroidExcludedRefs.createAppDefaults().build());

}

@Override
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
    if (!result.leakFound || result.excludedLeak) {
        return;
    }

    Log.w("LeakCanary", leakInfo);
}

4) 将服务注册到清单文件和1个权限:

  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <service android:name=".LeakLoggerService" />

5) 最后验证设置是否成功:泄漏一个活动 ;)

public class Main2Activity extends AppCompatActivity {
static TextView label;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    label = new TextView(this);
    label.setText("asds");

    setContentView(label);
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 
}
}

结束此活动并使用标签:LeakCanary 检查日志

应该可以正常工作...


install()函数中实际参数列表和形式参数列表长度不一致。 - kAmol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接