news 2026/6/10 18:11:24

Leakcanary检测内存泄漏汇总

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Leakcanary检测内存泄漏汇总
1.什么是内存泄漏
2.内存泄漏造成什么影响
3.内存泄漏检测的工具有哪些
4.关于Leakcanary使用介绍
5.Leakcanary捕捉常见的内存泄漏及解决办法
5.1 错误使用单例造成的内存泄漏
5.2 错误使用静态变量,导致引用后无法销毁【工具类使用不当导致内存泄漏】
5.3 Handler造成的内存泄漏
5.4 线程造成的内存泄漏
5.5 由WebView引起的内存泄漏
5.6 资源未关闭造成的内存泄漏
5.7 未注销EventBus导致的内存泄漏
5.8 静态集合使用不当导致的内存泄漏
5.9 使用弱引用避免内存泄漏
6.其他

1.什么是内存泄漏?

一些对象有着有限的声明周期,当这些对象所要做的事情完成了,我们希望它们会被垃圾回收器回收掉。但是如果有一系列对这个对象的引用存在,那么在我们期待这个对象生命周期结束时被垃圾回收器回收的时候,它是不会被回收的。它还会占用内存,这就造成了内存泄露。持续累加,内存很快被耗尽。
比如:当Activity的onDestroy()方法被调用后,Activity以及它涉及到的View和相关的Bitmap都应该被回收掉。但是,如果有一个后台线程持有这个Activity的引用,那么该Activity所占用的内存就不能被回收,这最终将会导致内存耗尽引发OOM而让应用crash掉。

2.内存泄漏会造成什么影响?

它是造成应用程序OOM的主要原因之一。由于android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。

3.内存泄漏检测的工具有哪些

最常见的是:Leakcanary

4.关于Leakcanary使用介绍

LeakCanary是Square开源框架,是一个Android和Java的内存泄露检测库,如果检测到某个 activity 有内存泄露,LeakCanary 就是自动地显示一个通知,所以可以把它理解为傻瓜式的内存泄露检测工具。通过它可以大幅度减少开发中遇到的oom问题,大大提高APP的质量。
集成:

1. 添加依赖

在你的项目的 build.gradle 文件中添加 LeakCanary 的依赖:

dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' // 如果需要在 release 版本中也检测内存泄漏,可以添加以下依赖 releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:2.7' }

2. 初始化

在 Application 类的 onCreate 方法中初始化 LeakCanary:

class MyApplication : Application() { override fun onCreate() { super.onCreate() LeakCanary.initialize(this) } }

3.配置 LeakCanary(可选)

LeakCanary.config { //忽略特定的类 excludeObjectClasses(*arrayOf("java.lang.Class")) //忽略特定的引用链 excludeLeak("java.lang.Class") //自定义内存泄漏通知的图标 notificationIcon(R.drawable.your_icon) //更多配置... }

4.分析结果保存到文件(可选)

import android.content.Context; import com.squareup.leakcanary.LeakCanary; import com.squareup.leakcanary.OnHeapAnalyzedListener; import com.squareup.leakcanary.AnalysisResult; import com.squareup.leakcanary.HeapAnalysis; import java.io.File; import java.io.FileWriter; import java.io.IOException; public class CustomLeakListener implements OnHeapAnalyzedListener { private final Context context; public CustomLeakListener(Context context) { this.context = context; } @Override public void onHeapAnalyzed(HeapAnalysis heapAnalysis) { File outputFile = new File(context.getExternalFilesDir(null), "leak_analysis_result.txt"); try (FileWriter writer = new FileWriter(outputFile, true)) { writer.write(heapAnalysis.toString()); writer.write("\n\n"); } catch (IOException e) { e.printStackTrace(); } } }

5.Leakcanary捕捉常见的内存泄漏及解决办法

5.1 错误使用单例造成的内存泄漏

在平时开发中单例设计模式是我们经常使用的一种设计模式,而在开发中单例经常需要持有Context对象,如果持有的Context对象生命周期与单例生命周期更短时,或导致Context无法被释放回收,则有可能造成内存泄漏,错误写法如下:
* 问题引起内存泄漏代码

public class LoginManager { private static LoginManager mInstance; private Context mContext; private LoginManager(Context context) { this.mContext = context; //修改代码:**this.mContext = context.getApplicationContext();** } public static LoginManager getInstance(Context context) { if (mInstance == null) { synchronized (LoginManager.class) { if (mInstance == null) { mInstance = new LoginManager(context); } } } return mInstance; } public void dealData() {} }

在一个Activity中调用的,然后关闭该Activity则会出现内存泄漏。

LoginManager.getInstance(this).dealData();

报错如图:

解决办法:
要保证Context和AppLication的生命周期一样,修改后代码如下:

this.mContext = context.getApplicationContext();

原因分析
创建单例对象,并且在创建的时候需要传入一个Context对象,而这时候如果我们使用Activity、Service等Context对象,由于单例对象的生命周期与进程的生命周期相同,会造成我们传入的Activity、Service对象无法被回收,这时候就需要我们传入Application对象,或者在方法中使用Application对象

5.2 错误使用静态变量,导致引用后无法销毁

在平时开发中,有时候我们创建了一个工具类。比如分享工具类,十分方便多处调用,因此使用静态方法是十分方便的。但是创建的对象,建议不要全局化,全局化的变量必须加上static。这样会引起内存泄漏。
在Activity中引用后,关闭该Activity会导致内存泄漏

DoShareUtil.showFullScreenShareView(PNewsContentActivity.this, title, title, shareurl, logo);

问题代码:

报错如图:

解决办法
静态方法中,创建对象或变量,不要全局化,全局化后的变量或者对象会导致内存泄漏;popMenuView和popMenu都不要全局化

**非静态内部类,静态实例化**

public class MyActivity extends AppCompatActivity { //静态成员变量 public static InnerClass innerClass = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); innerClass = new InnerClass(); } class InnerClass { public void doSomeThing() {} } }

这里内部类InnerClass隐式的持有外部类MyActivity的引用,而在MyActivity的onCreate方法中调用了。
这样innerClass就会在MyActivity创建的时候是有了他的引用,而innerClass是静态类型的不会被垃圾回收,
MyActivity在执行onDestory方法的时候由于被innerClass持有了引用而无法被回收,所以这样MyActivity就总是被innerClass持有而无法回收造成内存泄露。

5.3 Handler造成的内存泄漏【轮播图无限循环轮播,一定要关闭,否则内存泄漏】

handler是工作线程与UI线程之间通讯的桥梁,只是现在大量开源框架对其进行了封装,我们这里模拟一种常见使用方式来模拟内存泄漏情形。

public class MainActivity extends AppCompatActivity { private Handler mHandler = new Handler(); private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text); //模拟内存泄露 mHandler.postDelayed(new Runnable() { @Override public void run() { mTextView.setText("yangchong"); } }, 2000); } }

造成内存泄漏原因分析
上述代码通过内部类的方式创建mHandler对象,此时mHandler会隐式地持有一个外部类对象引用这里就是MainActivity,当执行postDelayed方法时,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,MessageQueue是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。

报错如图:


解决办法
要想避免Handler引起内存泄漏问题,需要我们在Activity关闭退出的时候的移除消息队列中所有消息和所有的Runnable。
上述代码只需在onDestroy()函数中调用

mHandler.removeCallbacksAndMessages(null);
@Override protected void onDestroy() { super.onDestroy(); if(handler!=null){ handler.removeCallbacksAndMessages(null); handler = null; } }

5.4 线程造成的内存泄漏


早时期的时候处理耗时操作多数都是采用Thread+Handler的方式,后来逐步被AsyncTask取代,直到现在采用RxJava的方式来处理异步。这里以AsyncTask为例,可能大部分人都会这样处理一个耗时操作然后通知UI更新结果:

public class MainActivity extends AppCompatActivity { private AsyncTask<Void, Void, Integer> asyncTask; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text); testAsyncTask(); finish(); } private void testAsyncTask() { asyncTask = new AsyncTask<Void, Void, Integer>() { @Override protected Integer doInBackground(Void... params) { int i = 0; //模拟耗时操作 while (!isCancelled()) { i++; if (i > 1000000000) { break; } Log.e("LeakCanary", "asyncTask---->" + i); } return i; } @Override protected void onPostExecute(Integer integer) { super.onPostExecute(integer); mTextView.setText(String.valueOf(integer)); } }; asyncTask.execute(); } }

造成内存泄漏原因分析
在处理一个比较耗时的操作时,可能还没处理结束MainActivity就执行了退出操作,但是此时AsyncTask依然持有对MainActivity的引用就会导致MainActivity无法释放回收引发内存泄漏

报错如图:


解决办法
在使用AsyncTask时,在Activity销毁时候也应该取消相应的任务AsyncTask.cancel()方法,避免任务在后台执行浪费资源,进而避免内存泄漏的发生

private void destroyAsyncTask() { if (asyncTask != null && !asyncTask.isCancelled()) { asyncTask.cancel(true); } asyncTask = null; } @Override protected void onDestroy() { super.onDestroy(); destroyAsyncTask(); }

5.5 由WebView引起的内存泄漏


WebView解析网页时会申请Native堆内存用于保存页面元素,当页面较复杂时会有很大的内存占用。如果页面包含图片,内存占用会更严重。并且打开新页面时,为了能快速回退,之前页面占用的内存也不会释放。有时浏览十几个网页,都会占用几百兆的内存。这样加载网页较多时,会导致系统不堪重负,最终强制关闭应用,也就是出现应用闪退或重启

public class MainActivity5 extends AppCompatActivity { private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web); mWebView = (WebView) findViewById(R.id.web); mWebView.loadUrl("http://www.cnblogs.com/whoislcj/p/5720202.html"); } }

造成内存泄漏原因分析
加载网页有缓存,当加载了许多网页,并且手机配置比较低时,造成的内存泄漏就对手机影响很大

查看报错结果如下:

解决办法

@Override protected void onDestroy() { if (mWebView != null) { mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); mWebView.clearHistory(); ViewGroup parent = (ViewGroup) mWebView.getParent(); if(parent!=null){ parent.removeView(mWebView); } mWebView.destroy(); mWebView = null; } super.onDestroy(); }
5.6 资源未关闭造成的内存泄漏


对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

5.7 未注销EventBus导致的内存泄漏
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_common); EventBus.getDefault().register(this); } @Subscribe public void onEvent(MessageEvent msg) { }
@Override protected void onDestroy() { super.onDestroy(); //未移除注册的EventBus //EventBus.getDefault().unregister(this); }
5.8 静态集合使用不当导致的内存泄漏


添加Activity到栈,或者移除Activity出栈。导致内存泄漏

public class ActivityCollector { public static List<Activity> activities = new ArrayList<Activity>(); public static void addActivity(Activity activity) { activities.add(activity); } public static void removeActivity(Activity activity) { activities.remove(activity); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_common); ActivityCollector.addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); //静态集合没有移除元素 //ActivityCollector.removeActivity(this); }

5.9 使用弱引用避免内存泄漏


在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去

public class WeakReferenceActivity extends AppCompatActivity { //将Handler声明为静态 没有了Activity的引用, 无法直接引用其变量或方法, //使用弱引用WeakReference来解决这个问题 private static class DBHandler extends Handler { //弱引用, 而不是使用外部类this或者传进来 private final WeakReference<WeakReferenceActivity> mActivity; public DBHandler(WeakReferenceActivity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { WeakReferenceActivity activity = mActivity.get(); if (activity != null) { Toast.makeText(activity, "what: " + msg.what, Toast.LENGTH_SHORT).show(); } } } private final DBHandler mHandler = new DBHandler(this); private static final Runnable sRunnable = new Runnable() { @Override public void run() { Toast.makeText(App.getInstance(), "sRunnable run()", Toast.LENGTH_SHORT).show(); } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_weak_reference); mHandler.postDelayed(sRunnable, 1000 * 20); } public void onClick(View view) { mHandler.sendEmptyMessage(new Random().nextInt(10)); } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 10:53:47

redis入门全网最详细:Spring Data Redis 常用 API

Spring Data Redis 常用 API 整理 本文整理 Spring Data Redis 核心操作 API&#xff0c;基于 Spring Boot 环境&#xff0c;代码可直接复制使用&#xff0c;涵盖字符串、哈希、列表、集合、有序集合及通用操作等核心场景。 一、基础准备 1.1 依赖引入&#xff08;Maven&…

作者头像 李华
网站建设 2026/6/9 14:26:24

EmotiVoice本地部署避坑指南:常见问题与解决方案

EmotiVoice本地部署避坑指南&#xff1a;常见问题与解决方案 在语音AI技术飞速发展的今天&#xff0c;我们正见证一场从“能说话”到“会表达”的范式转变。早期的文本转语音&#xff08;TTS&#xff09;系统虽然解决了基础发声问题&#xff0c;但机械单调、缺乏情感的输出始终…

作者头像 李华
网站建设 2026/6/9 16:01:48

16、印度煤炭资源需求预测与供应链管理中的量子计算革命

印度煤炭资源需求预测与供应链管理中的量子计算革命 1. 引言 煤炭是全球最普遍且储量丰富的化石燃料,是对世界经济有重大贡献的全球性产业。超 50 个国家为经济目的开采煤炭,超 70 个国家消费煤炭。全球每年燃烧的约 58 亿吨煤炭中,约 75%用于发电。预计到 2030 年,煤炭使…

作者头像 李华
网站建设 2026/6/10 10:59:02

13、量子计算中的线性代数与量子比特基础

量子计算中的线性代数与量子比特基础 1. 矩阵转置与共轭转置 矩阵转置是线性代数中的基本操作。例如,对于矩阵 (A = \begin{bmatrix}1 & 2 & 3 \ 4 & 5 & 6\end{bmatrix}),其转置 (A^T = \begin{bmatrix}1 & 4 \ 2 & 5 \ 3 & 6\end{bmatrix})。…

作者头像 李华
网站建设 2026/6/10 10:58:33

16、量子编程中的Qiskit与随机数生成

量子编程中的Qiskit与随机数生成 1. 访问令牌与作业请求 在获取访问令牌后,需在请求中添加HTTP头 X-Access-Token: ACESS_TOKEN ,接着将特定负载复制粘贴到REST客户端负载中,提交请求并等待响应。若一切顺利,会得到如下格式的响应: {"id": "chobhqan…

作者头像 李华