一、什么是内存泄漏
内存泄漏(Memory Leak)是指一块已经被开辟的堆内存由于某种原因程序未释放或无法释放,使得这部分内存长时间不能被使用,最终导致程序运行速度减慢甚至崩溃。
Android应用中的内存泄漏是指在应用中申请的内存空间,没有被程序释放,一直存在于内存当中,最终导致应用卡顿、崩溃等问题。内存泄漏往往难以发现,但是一旦发现,一定要及时修复,否则成倍的内存泄漏最终会导致应用的崩溃。
二、如何发现内存泄漏
Android提供了一些工具,如内存分析器,帮助我们检测Android应用中的内存泄漏。
在Android Studio中,我们可以使用Android Profiler来检测内存泄漏。在Profier中,可以查看应用的内存使用情况、资源使用情况和网络数据传输等信息。其中,在内存选项卡中,可以分别查看Java Heap、Native Heap和GPU Memory的情况,以及Allocation Tracker和Heap Dump的信息。
//使用LeakCanary监测内存泄漏: dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' }
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } LeakCanary.install(this); // Normal app init code... } }
三、内存泄漏的常见场景
1. 对象持有
当一个Activity结束时,如果这个Activity中包含的控件仍被其他地方所持有,这个Activity所占用的内存就无法释放。比如当一个Activity持有了一些Bitmap,并将其传递给了其他对象使用,但是没有及时trim内存或者内存不足时,这些Bitmap就会导致内存泄漏。
public class ExampleActivity extends Activity { private ImageView mImageView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_example); mImageView = findViewById(R.id.image); // 使用了R.drawable.large_image这张大图 mImageView.setImageResource(R.drawable.large_image); } }
在以上代码中,如果这张大图没有及时回收,这将会导致内存一直暴增,程序异常。
2. 静态变量持有对象
静态变量是不能被GC回收的,如果静态变量持有了Activity等Context对象,那么在Activity被回收之前,GC将不能回收Activity所占用的内存。
以下代码演示了静态变量引用了Activity,从而导致Activity不能正常实现回收,API 21及以上的系统机制,可将该Activity加入“历史记录列表”,便可被回收。
public class ExampleActivity extends Activity { private static Activity sCurrentActivity; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_example); sCurrentActivity = this; } @Override protected void onDestroy() { super.onDestroy(); sCurrentActivity = null; } }
3. Handler导致泄露
Handler类在Android开发中经常被用来做一些异步的操作,带有延时的Runnable容易导致内存被泄漏。
public class ExampleActivity extends Activity { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // do some thing; } }; private void postSomeThingDelay() { mHandler.postDelayed(new Runnable() { @Override public void run() { } }, 2000); } }
在以上代码中,如果Activity被销毁,则handler将长期持有它,并阻止它被回收。
4. 监听器导致泄露
监听器是一个常用的功能,在Android中有诸如setOnClickListener、addTextChangedListener等监听器。如果一个对象把它自己传给第三方,并注册一个监听器,那么这个对象将不能被GC回收。
public class ExampleActivity extends Activity { private EditText editText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText = findViewById(R.id.edit_text); editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { } }); } }
在以上代码中,如果EditText对象被销毁之前,内存泄漏将被固定。
四、解决内存泄漏
发现内存泄漏后,要尽快进行解决,以下是几点解决方案。
1. 及时释放对象持有的资源
在Activity的onStop()或onDestroy()中,需要释放那些在onCreate()中创建的资源。如Image、Bitmap等。
2. 定期调用System.gc()
System.gc()的作用是告诉GC执行送达,但并不强制GC立刻执行,GC并不是万能的,它需要系统去为其提供垃圾收集的条件。所以并不建议调用。
3. 使用WeakReference和软引用
使用这两种引用类型可以避免长时间持有Activity对象,当对象没有其他引用时,会被GC自动回收。
public class ExampleActivity extends Activity { private WeakReferencemContext; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = new WeakReference (this); } private void doSomething() { if (mContext.get() != null) { // do something } } }
4. 使用HandlerThread
将线程和Handler分离使用,这样Handler执行结束时,Thread不会立刻结束。
public class ExampleActivity extends Activity { private HandlerThread mHandlerThread; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandlerThread = new HandlerThread("MyHandlerThread"); mHandlerThread.start(); Handler mHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { // do some thing } }; } @Override protected void onDestroy() { super.onDestroy(); mHandlerThread.quit(); } }
5. 使用LeakCanary进行定位
LeakCanary是一个强大的Android库,可以在应用内部发现内存泄漏。其会在弱引用对象被回收时,记录下调用堆栈(如果发现调用堆栈在不断增大,那么很可能存在泄漏),当我们检测到一个对象已经被泄露时,可以通过查看堆转储文件,查找泄露原因。
总结
内存泄漏是Android开发中常见的问题,对应的解决方案也有很多,但是依然需要开发者掌握一些技巧才能更好地避免内存泄漏。多检查自己的代码,适时使用各种工具进行检测和解决。