如何使用泄漏金丝雀

Posted

技术标签:

【中文标题】如何使用泄漏金丝雀【英文标题】:How to use Leak Canary 【发布时间】:2016-02-12 18:39:33 【问题描述】:

我知道这可能是一个愚蠢的问题,但我在开发 android 方面还很陌生,我目前在我的应用程序中遇到 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()

    

假设我有一个 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 function
        // How do I use LeakCanary to watch these objects ?

    

现在我如何使用 LeakCanary 来查看导致内存泄漏的对象。感谢您的帮助和帮助。

【问题讨论】:

请记住,OutOfMemoryError 并不一定意味着您有内存泄漏。您可能只是试图绘制过多的资源,或者您只是在资产中使用了过多的内存等。如果您使用的是模拟器,首先尝试简单地增加 RAM 以查看是否可以解决问题。 不,这是由内存泄漏引起的,我已经使用 DDMS 检查堆转储,即使活动已关闭,分配的内存仍然存在,没有垃圾收集,它只是保持越来越大,直到应用崩溃。 我有同样的问题,但没有找到答案。我看过几个视频。但是,没有一个显示如何启动应用程序并查看泄漏对象的路径的基础知识。例如,您应该从 Android Studio 还是从设备启动应用程序?它可以在模拟器上运行吗?我收到了通知,但为什么我在泄漏应用程序中看不到它们?我有多个 Leaks 应用程序,我应该删除一些吗?这似乎是一个很棒的系统,但基础还不清楚。 ***.com/a/56750594/10284292 【参考方案1】:

leak canary 的好处在于它的自动化程度。 默认情况下,它已经“监视”了未正确 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);

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

希望对你有帮助。

【讨论】:

我复制并粘贴了您的 ExampleApplicatio,但我仍然没有收到任何通知..? 当你杀死活动时,是否会调用 onDestroy ?我相信,如果您通过刷卡和杀死应用程序或类似方式进行销毁,则可能不会调用 ondestroy。我有一个类似的问题。所以,我添加了一个用于测试目的的注销按钮,它将以编程方式破坏我的活动,因此调用 onDestroy 并且我得到了我没有得到的泄漏;正常的应用程序终止没有得到【参考方案2】:

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

只需将以下依赖项添加到您的 gradle 中。无需更多代码。

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

【讨论】:

所以没有更多的 LeakCanary.install(this),我也找不到这个 API? 是的,你不需要 LeakCanry.Install(this) 能分享下使用leakcanary的源码是2.0以上的吗? 错字:cananry【参考方案3】:

我对如何使用 LeakCanary 也有同样的疑问。我只是想看一个如何启动它的基本示例,并查看我的第一个泄漏对象的路径。

如何使用 LeakCanary

下面是 LeakCanary 如何工作的基本示例:

How to Use 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 ignores leak detection when debugging。虽然 LeakCanary 说它提供了 "no-op" version of its library for release,但由于调试对我不起作用,我将上面的 releaseImplementation 从推荐的 com.squareup.leakcanary:leakcanary-android-no-op:1.5.4 更改为 com.squareup.leakcanary:leakcanary-android:1.5.4

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

另外,我注意到Sometimes it could take up to a few minutes for the analysis to complete and show a memory leak notification on your phone。

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

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

How to verify a memory leak fix using LeakCanary(16 分 34 秒)

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

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

【讨论】:

老实说,不是帮助这个答案产生更多的混乱,至少我也是。 请随时提供更多详细信息,因为我不知道您有什么困惑。 “我不得不以常规运行模式启动应用程序,而不是调试模式。我还不得不偏离基本指令,”所以有一种“官方”方式(所谓的“基本指令”) ") 这样做,但无论出于何种原因,您只是按照自己的方式进行。这很令人困惑。还有一个很好的理由是这个库不在发布时运行,而只是在调试时运行,但你在发布时使用它。最重要的是,您的答案主要是基于链接的答案,一个很长的答案,但除非他观看视频,否则它并没有提供有关如何解决 OP 问题的足够信息。 :) 是的,我制作了这个视频,因为我觉得有很多新手需要看的东西,还有很多更容易在视频中展示的事件序列。我不确定“基本说明”现在是否更好,但如果是这样,请务必使用它们。我不记得为什么我选择发布而不是调试,但我想我当时在发布时遇到了麻烦。感谢您的反馈。【参考方案4】:

要使用 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/

【讨论】:

【参考方案5】:

我在应用程序中使用这里

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

【讨论】:

【参考方案6】:

我使用 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) 最后验证设置是否成功:Leak an activity ;)

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() 的实际参数列表和形式参数列表的长度不同

以上是关于如何使用泄漏金丝雀的主要内容,如果未能解决你的问题,请参考以下文章

android 内存泄漏检测工具 LeakCanary 泄漏金丝雀

如何解决框架布局中的内存泄漏问题?

使用泄漏金丝雀构建错误

Android泄漏金丝雀日志

Android泄漏金丝雀 - 泄漏空活动

无法在泄漏金丝雀跟踪中找到原因