共计 14019 个字符,预计需要花费 36 分钟才能阅读完成。
目录介绍:
- 01. 什么是内存泄漏
- 02. 内存泄漏造成什么影响
- 03. 内存泄漏检测的工具有哪些
- 04. 关于 Leakcanary 使用介绍
- 05. 错误使用单例造成的内存泄漏
- 06.Handler 使用不当造成内存泄漏
- 07.Thread 未关闭造成内容泄漏
- 08. 错误使用静态变量导致引用后无法销毁
- 09.AsyncTask 造成的内存泄漏
- 10. 非静态内部类创建静态实例造成内存泄漏
- 11. 不需要用的监听未移除会发生内存泄露
- 12. 资源未关闭造成的内存泄漏
- 13. 广播注册之后没有被销毁
- 14. 错误使用 context 上下文引起内存泄漏
- 15. 静态集合使用不当导致的内存泄漏
- 16. 动画资源未释放导致内存泄漏
- 17. 系统 bug 之 InputMethodManager 导致内存泄漏
好消息
- 博客笔记大汇总【16 年 3 月到至今】,包括 Java 基础及深入知识点,Android 技术博客,Python 学习笔记等等,还包括平时开发中遇到的 bug 汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是 markdown 格式的!同时也开源了生活博客,从 12 年起,积累共计 N 篇 [近 100 万字,陆续搬到网上],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong2…
- 如果觉得好,可以 star 一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
01. 什么是内存泄漏
- 一些对象有着有限的声明周期,当这些对象所要做的事情完成了,我们希望它们会被垃圾回收器回收掉。但是如果有一系列对这个对象的引用存在,那么在我们期待这个对象生命周期结束时被垃圾回收器回收的时候,它是不会被回收的。它还会占用内存,这就造成了内存泄露。持续累加,内存很快被耗尽。
- 比如:当 Activity 的 onDestroy() 方法被调用后,Activity 以及它涉及到的 View 和相关的 Bitmap 都应该被回收掉。但是,如果有一个后台线程持有这个 Activity 的引用,那么该 Activity 所占用的内存就不能被回收,这最终将会导致内存耗尽引发 OOM 而让应用 crash 掉。
02. 内存泄漏造成什么影响
- 它是造成应用程序 OOM 的主要原因之一。由于 android 系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就
03. 内存泄漏检测的工具有哪些
- 最常见的是:Leakcanary
04. 关于 Leakcanary 使用介绍
- leakCanary 是 Square 开源框架,是一个 Android 和 Java 的内存泄露检测库,如果检测到某个 activity 有内存泄露,LeakCanary 就是自动地显示一个通知,所以可以把它理解为傻瓜式的内存泄露检测工具。通过它可以大幅度减少开发中遇到的 oom 问题,大大提高 APP 的质量。
- 关于如何配置,这个就不说呢,网上有步骤
05. 错误使用单例造成的内存泄漏
- 在平时开发中单例设计模式是我们经常使用的一种设计模式,而在开发中单例经常需要持有 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();
- 1、如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。
- 2、如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。
06.Handler 使用不当造成内存泄漏
- handler 是工作线程与 UI 线程之间通讯的桥梁,只是现在大量开源框架对其进行了封装,我们这里模拟一种常见使用方式来模拟内存泄漏情形。
-
解决 Handler 内存泄露主要 2 点
- 有延时消息,要在 Activity 销毁的时候移除 Messages
- 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者 Activity 使用弱引用。
-
问题代码
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; } }
-
第二种解决方案
- 使用弱引用解决 handler 内存泄漏问题,关于代码案例,可以参考我的开源项目:https://github.com/yangchong2…
// 自定义 handler public static class HandlerHolder extends Handler { WeakReference<OnReceiveMessageListener> mListenerWeakReference; /** * @param listener 收到消息回调接口 */ HandlerHolder(OnReceiveMessageListener listener) {mListenerWeakReference = new WeakReference<>(listener); } @Override public void handleMessage(Message msg) {if (mListenerWeakReference!=null && mListenerWeakReference.get()!=null){mListenerWeakReference.get().handlerMessage(msg); } } } // 创建 handler 对象 private HandlerHolder handler = new HandlerHolder(new OnReceiveMessageListener() { @Override public void handlerMessage(Message msg) {switch (msg.what){ case 1: TextView textView1 = (TextView) msg.obj; showBottomInAnimation(textView1); break; case 2: TextView textView2 = (TextView) msg.obj; showBottomOutAnimation(textView2); break; } } }); // 发送消息 Message message = new Message(); message.what = 1; message.obj = textView; handler.sendMessageDelayed(message,time); 即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。
-
07.Thread 未关闭造成内容泄漏
- 当在开启一个子线程用于执行一个耗时操作后,此时如果改变配置(例如横竖屏切换)导致了 Activity 重新创建,一般来说旧 Activity 就将交给 GC 进行回收。但如果创建的线程被声明为非静态内部类或者匿名类,那么线程会保持有旧 Activity 的隐式引用。当线程的 run() 方法还没有执行结束时,线程是不会被销毁的,因此导致所引用的旧的 Activity 也不会被销毁,并且与该 Activity 相关的所有资源文件也不会被回收,因此造成严重的内存泄露。
-
因此总结来看,线程产生内存泄露的主要原因有两点:
- 1. 线程生命周期的不可控。Activity 中的 Thread 和 AsyncTask 并不会因为 Activity 销毁而销毁,Thread 会一直等到 run() 执行结束才会停止,AsyncTask 的 doInBackground() 方法同理
- 2. 非静态的内部类和匿名类会隐式地持有一个外部类的引用
-
例如如下代码,在 onCreate() 方法中启动一个线程,并用一个静态变量 threadIndex 标记当前创建的是第几个线程
public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (true) {Log.e(TAG, "Hi--" + j); try {Thread.sleep(2000); } catch (InterruptedException e) {e.printStackTrace(); } } } }).start();} }
- 旋转几次屏幕,可以看到输出结果为:
04-04 08:15:16.373 23731-23911/com.yc.leakdemo E/ThreadActivity: Hi--2 04-04 08:15:16.374 23731-26132/com.yc.leakdemo E/ThreadActivity: Hi--4 04-04 08:15:16.374 23731-23970/com.yc.leakdemo E/ThreadActivity: Hi--3 04-04 08:15:16.374 23731-23820/com.yc.leakdemo E/ThreadActivity: Hi--1 04-04 08:15:16.852 23731-26202/com.yc.leakdemo E/ThreadActivity: Hi--5 04-04 08:15:18.374 23731-23911/com.yc.leakdemo E/ThreadActivity: Hi--2 04-04 08:15:18.374 23731-26132/com.yc.leakdemo E/ThreadActivity: Hi--4 04-04 08:15:18.376 23731-23970/com.yc.leakdemo E/ThreadActivity: Hi--3 04-04 08:15:18.376 23731-23820/com.yc.leakdemo E/ThreadActivity: Hi--1 04-04 08:15:18.852 23731-26202/com.yc.leakdemo E/ThreadActivity: Hi--5 ...
- 即使创建了新的 Activity,旧的 Activity 中建立的线程依然还在执行,从而导致无法释放 Activity 占用的内存,从而造成严重的内存泄漏。想要避免因为 Thread 造成内存泄漏,可以在 Activity 退出后主动停止 Thread
-
例如,可以为 Thread 设置一个布尔变量 threadSwitch 来控制线程的启动与停止
public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private int threadIndex; private boolean threadSwitch = true; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (threadSwitch) {Log.e(TAG, "Hi--" + j); try {Thread.sleep(2000); } catch (InterruptedException e) {e.printStackTrace(); } } } }).start();} @Override protected void onDestroy() {super.onDestroy(); threadSwitch = false; } }
-
如果想保持 Thread 继续运行,可以按以下步骤来:
- 1. 将线程改为静态内部类,切断 Activity 对于 Thread 的强引用
- 2. 在线程内部采用弱引用保存 Context 引用,切断 Thread 对于 Activity 的强引用
public class ThreadActivity extends AppCompatActivity { private static final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new MyThread(this).start();} private static class MyThread extends Thread { private WeakReference<ThreadActivity> activityWeakReference; MyThread(ThreadActivity threadActivity) {activityWeakReference = new WeakReference<>(threadActivity); } @Override public void run() {if (activityWeakReference == null) {return;} if (activityWeakReference.get() != null) { int i = threadIndex; while (true) {Log.e(TAG, "Hi--" + i); try {Thread.sleep(2000); } catch (InterruptedException e) {e.printStackTrace(); } } } } } }
08. 错误使用静态变量导致引用后无法销毁
- 在平时开发中,有时候我们创建了一个工具类。比如分享工具类,十分方便多处调用,因此使用静态方法是十分方便的。但是创建的对象,建议不要全局化,全局化的变量必须加上 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 持有而无法回收造成内存泄露。静态变量引用不当会导致内存泄漏 静态变量 Activity 和 View 会导致内存泄漏,在下面这段代码中对 Activity 的 Context 和 TextView 设置为静态对象,从而产生内存泄漏。public class MainActivity extends AppCompatActivity { private static Context context; private static TextView textView; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = this; textView = new TextView(this); } }
09.AsyncTask 造成的内存泄漏
- 早时期的时候处理耗时操作多数都是采用 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();}
10. 非静态内部类创建静态实例造成内存泄漏
- 有的时候我们可能会在启动频繁的 Activity 中,为了避免重复创建相同的数据资源,可能会出现这种写法
-
问题代码
private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mResource == null){mResource = new TestResource(); } } class TestResource {// 里面代码引用上下文,Activity.this 会导致内存泄漏}
-
解决办法
- 将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用 Context,请按照上面推荐的使用 Application 的 Context。
-
分析问题
- 这样就在 Activity 内部创建了一个非静态内部类的单例,每次启动 Activity 时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该 Activity 的引用,导致 Activity 的内存资源不能正常回收。
11. 不需要用的监听未移除会发生内存泄露
-
问题代码
//add 监听,放到集合里面 tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() { @Override public void onWindowFocusChanged(boolean b) {// 监听 view 的加载,view 加载出来的时候,计算他的宽高等。} });
-
解决办法
// 计算完后,一定要移除这个监听 tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
-
注意事项:
tv.setOnClickListener();// 监听执行完回收对象,不用考虑内存泄漏 tv.getViewTreeObserver().addOnWindowFocusChangeListene,add 监听,放到集合里面,需要考虑内存泄漏
12. 资源未关闭造成的内存泄漏
- BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback 等在 Activity onDestroy 或者某类生命周期结束之后一定要 unregister 或者 close 掉,否则这个 Activity 类会被 system 强引用,不会被内存回收。值得注意的是,关闭的语句必须在 finally 中进行关闭,否则有可能因为异常未关闭资源,致使 activity 泄漏。
13. 广播注册之后没有被销毁
- 比如我们在 Activity 中注册广播,如果在 Activity 销毁后不取消注册,那么这个广播会一直存在系统中,同上面所说的非静态内部类一样持有 Activity 引用,导致内存泄露。因此注册广播后在 Activity 销毁后一定要取消注册。
-
在注册观察则模式的时候,如果不及时取消也会造成内存泄露。比如使用 Retrofit+RxJava 注册网络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候取消注册。
public class MeAboutActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.registerReceiver(mReceiver, new IntentFilter()); } private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) {// 接收到广播需要做的逻辑} }; @Override protected void onDestroy() {super.onDestroy(); this.unregisterReceiver(mReceiver); } }
14. 错误使用 context 上下文引起内存泄漏
-
先来看看造成内存泄漏的代码
- 通过查看 Toast 类的源码可以看到,Toast 类内部的 mContext 指向传入的 Context。而 ToastUtils 中的 toast 变量是静态类型的,其生命周期是与整个应用一样长的,从而导致 activity 得不到释放。因此,对 Context 的引用不能超过它本身的生命周期。
/** * 吐司工具类 避免点击多次导致吐司多次,最后导致 Toast 就长时间关闭不掉了 * @param context 注意:这里如果传入 context 会报内存泄漏;传递 activity..getApplicationContext() * @param content 吐司内容 */ private static Toast toast; @SuppressLint("ShowToast") public static void showToast(Context context, String content) {if (toast == null) {toast = Toast.makeText(context , content, Toast.LENGTH_SHORT); } else {toast.setText(content); } toast.show();}
-
解决办法
- 是改为使用 ApplicationContext 即可,因为 ApplicationContext 会随着应用的存在而存在,而不依赖于 Activity 的生命周期
15. 静态集合使用不当导致的内存泄漏
- 有时候我们需要把一些对象加入到集合容器(例如 ArrayList)中,当不再需要当中某些对象时,如果不把该对象的引用从集合中清理掉,也会使得 GC 无法回收该对象。如果集合是 static 类型的话,那内存泄漏情况就会更为严重。因此,当不再需要某对象时,需要主动将之从集合中移除。
16. 动画资源未释放导致内存泄漏
-
问题代码
public class LeakActivity extends AppCompatActivity { private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); textView = (TextView)findViewById(R.id.text_view); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360); objectAnimator.setRepeatCount(ValueAnimator.INFINITE); objectAnimator.start();} }
-
解决办法
- 在属性动画中有一类无限循环动画,如果在 Activity 中播放这类动画并且在 onDestroy 中去停止动画,那么这个动画将会一直播放下去,这时候 Activity 会被 View 所持有,从而导致 Activity 无法被释放。解决此类问题则是需要早 Activity 中 onDestroy 去去调用 objectAnimator.cancel() 来停止动画。
@Override protected void onDestroy() {super.onDestroy(); mAnimator.cancel();}
17. 系统 bug 之 InputMethodManager 导致内存泄漏
- 每次从 MainActivity 退出程序时总会报 InputMethodManager 内存泄漏,原因系统中的 InputMethodManager 持有当前 MainActivity 的引用,导致了 MainActivity 不能被系统回收,从而导致了 MainActivity 的内存泄漏。查了很多资料,发现这是 Android SDK 中输入法的一个 Bug,在 15<=API<=23 中都存在,目前 Google 还没有解决这个 Bug。
其他介绍
01. 关于博客汇总链接
- 1. 技术博客汇总
- 2. 开源项目汇总
- 3. 生活博客汇总
- 4. 喜马拉雅音频汇总
- 5. 其他汇总
02. 关于我的博客
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/…
- 简书:http://www.jianshu.com/u/b7b2…
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo…
- 开源中国:https://my.oschina.net/zbj161…
- 泡在网上的日子:http://www.jcodecraeer.com/me…
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
- segmentfault 头条:https://segmentfault.com/u/xi…
- 掘金:https://juejin.im/user/593943…
项目开源地址:https://github.com/yangchong2…
项目开源地址:https://github.com/yangchong2…
正文完
发表至: java
2019-06-25