40

我目前在我的应用程序中遇到 OutOfMemoryError。我曾尝试使用 MAT 进行调试,但在一些活动中仍然很难找到泄漏。然后我找到了 LeakCanary,它看起来更简单易用,但是我找不到任何初学者使用 Leak Canary 的分步指南,即使在 Google 上也是如此。我已经通过 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()

    }
}

假设我有一个活动,我希望 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 来查看是哪个对象导致了内存泄漏?

4

6 回答 6

22

泄漏金丝雀的好处是它的自动化程度。默认情况下,它已经“监视”了未正确 GC 的活动。因此,开箱即用,如果有任何活动泄漏,您应该会收到通知。

在我的项目中,我添加了一个额外的方法,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);
        }
    }
}

所以垃圾收集和内存泄漏以及金丝雀的重要事情是知道什么时候应该收集东西并要求监视该项目。

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

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

这种方式LeakCanary试图检查是否有任何片段泄漏内存。

因此,为了让您在您的应用程序上进一步实现,您可以/应该在您知道它应该被垃圾收集但您认为它可能不是垃圾收集的任务或实例上,并且您不确定在哪里,您也可以调用它:ExampleApplication.instance.mustDie(object);

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

我希望它有帮助。

于 2015-11-11T16:44:36.630 回答
10

发布此答案是因为不再需要所有其他答案,并且有更好的方法可以使用 Leak Canary 2 在您的应用程序上查找泄漏。

只需将以下依赖项添加到您的 gradle 中。不再需要代码。

debugImplementation 'com.squareup.leakcananry:leakcanary-android:2.7'
于 2020-03-21T17:08:41.750 回答
7

我对如何使用 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 说它为 release 提供了它的库的“无操作”版本,但由于 debug 对我不起作用,我将releaseImplementation上述内容从推荐的更改com.squareup.leakcanary:leakcanary-android-no-op:1.5.4com.squareup.leakcanary:leakcanary-android:1.5.4.

正如您在视频中看到的,我必须处理的另一个问题是授予对 LeakCanary 的写访问权限。我向下滑动并看到来自 LeakCanary 的通知,说它未能获得写入访问权限(更多信息)。我从未见过许可请求。因此,在一种情况下(视频中未显示),我通过转到“设置”应用程序,找到我的应用程序(而不是 LeakCanary 安装的名为“Leak”的应用程序)并打开写访问权限来授予我的应用程序写访问权限。在视频中我不需要这样做,因为我通过回复通知给予了许可。然后在使用我的应用程序时,我会定期检查新通知(通过向下滑动)。我看到诸如“XYZActivity 泄露 217 KB”之类的消息。我点击了它,它把我带到了那个泄漏应用程序。

此外,我注意到有时可能需要几分钟才能完成分析并在您的手机上显示内存泄漏通知

如何使用 LeakCanary 验证内存泄漏修复

现在我已经修复了一些内存泄漏,我使用 LeakCanary 来验证修复。但是,如果 LeakCanary 没有报告任何内容,我不一定相信那是因为我的泄漏已修复。可能只是 LeakCanary 坏了。

如何使用 LeakCanary 验证内存泄漏修复(16 分 34 秒)

使用 LeakCanary 验证内存泄漏的过程: 1. 使用 LeakCanary 确认内存泄漏 2. 修复内存泄漏并确认 LeakCanary 报告没有泄漏 3. 恢复您的修复并确认 LeakCanary 再次报告泄漏

由于 LeakCanary 在工作时显示的状态信息非常少,因此很难知道它是否在做任何事情。这使我认为我已经修复了内存泄漏,而事实上,我没有。以上三个步骤是我发现使用 LeakCanary 验证内存泄漏修复的最佳方法。

于 2018-07-12T19:45:54.213 回答
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

在此处输入图像描述 https://square.github.io/leakcanary/

于 2021-02-12T10:02:13.387 回答
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

于 2016-09-24T08:59:25.600 回答
0

我使用了 Leak-Canary,如下所示:

1)摇篮依赖:

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

它应该工作...

于 2019-01-15T09:25:10.917 回答