Android——内存篇:Android中5种最常见的内存泄漏问题以及解决办法
Android中的内存泄漏:
先说一下为什么会出现内存泄漏:
Android程序开发中,如果一个对象已经不需要被使用了,本该被回收时,而这时另一个对象还在持有对该对象的引用,这样就会导致无法被GC回收,就会出现内存泄漏的情况。
内存泄漏时Android程序中出现OOM问题的主要原因之一。所以我们在编写代码时,一定要细心处理好这一类的问题。
下面说一下Android开发中最常见的5个内存泄漏问题:
一:单例设计模式造成的内存泄漏:
单例设计模式我就不多说了,这个是最基本的设计模式,相信大家都会使用,但是时候我们在使用单例设计模式时没有注意到其中的细节,就会造成内存泄漏。
单例设计模式的静态特性会使他的生命周期和应用程序的生命周期一样长,这就说明了如果一个对象不在使用了,而这时单例对象还在持有该对象的引用,这时GC就会无法回收该对象,造成了内存泄露的情况。
下面是错误的单例设计模式的代码:
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context; } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; }}
上面的代码是一个最普通的单例模式,但是需要注意两个问题: 1、如果我们传入的Context是Application的Context的话,就没有任何问题,因为Application的Context生命周期和应用程序生命周期一样长。
2、如果我们传入的Context是Activity的Context的话,这时如果我们因为需求销毁了该Activity的话,Context也会随着Activity被销毁,但是单例还在持有对该类对象的引用,这时就会造成内存泄漏。
所以,正确的单例模式写法应该是这样的:
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext(); } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; }}
这样的话不管我们传入什么样的Context,最终使用的都是Application的Context,单例的生命周期和应用一样长,这样就不会造成内存泄漏了。
二、非静态内部类创建的静态实例造成的内存泄漏
有时候因为需求我们会去频繁的启动一个Activity,这时为了避免频繁的创建相同的数据源,我们通常会做如下处理:
public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mManager == null){ mManager = new TestResource(); } //... } class TestResource { //... }}
这样就在Activity中创建了非静态内部类,非静态内部类默认持有Activity类的引用,但是他的生命周期还是和应用程序一样长,所以当Activity销毁时,静态内部类的对象引用不会被GC回收,就会造成了内存溢出,解决办法:
1、将内部类改为静态内部类。
2、将这个内部类封装成一个单例,Context使用Application的Context
三、Handler造成的内存泄漏:
先看一下不规范的Handler写法:
public class MainActivity extends AppCompatActivity { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); } private void loadData(){ //...request Message message = Message.obtain(); mHandler.sendMessage(message); }}
这里的handler也是一个非静态匿名内部类,他跟上面的一样,也会持有Activity的引用,我们知道handler是运行在一个Looper线程中的,而Looper线程是轮询来处理消息队列中的消息的,假设我们处理的消息有十条,而当他执行到第6条的时候,用户点击了back返回键,销毁了当前的Activity,这个时候消息还没有处理完,handler还在持有Activity的引用,这个时候就会导致无法被GC回收,造成了内存泄漏。正确的做法是:
public class MainActivity extends AppCompatActivity {//new一个自定义的Handler private MyHandler mHandler = new MyHandler(this); private TextView mTextView ;//自定义静态内部类继承自Handler private static class MyHandler extends Handler { private WeakReference reference;//在构造函数中使用弱引用来引用context对象 public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if(activity != null){ activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView)findViewById(R.id.textview); loadData(); } private void loadData() { //...request Message message = Message.obtain(); mHandler.sendMessage(message); }@Override protected void onDestroy() { super.onDestroy();//移除队列中所有的Runable和消息//这里也可以使用mHandler.removeMessage和mHandler.removeCallBacks来移除指定的Message和Runable mHandler.removeCallbacksAndMessages(null); }}
创建一个静态内部类继承自handler,然后再在构造参数中对handler持有的对象做弱引用,这样在回收时就会回收了handler持有的对象,这里还做了一处修改,就是当我 们的回收了handler持有的对向,即销毁了该Activity时,这时如果handler中的还有未处理的消息,我们就需要在OnDestry方法中移除消息队列中的消息。
四、线程造成的内存泄漏 线程使用不恰当造成的内存泄漏也是很常见的,下面举两个例子:
//——————test1 new AsyncTask() { @Override protected Void doInBackground(Void... params) { SystemClock.sleep(10000); return null; } }.execute();//——————test2 new Thread(new Runnable() { @Override public void run() { SystemClock.sleep(10000); } }).start();
上面是两个内部类,当我们的Activity销毁时,这两个任务没有执行完毕,就会使Activity的内存资源无法被回收,造成了内存泄漏。 正确的做法是使用静态内部类:如下 static class MyAsyncTask extends AsyncTask { private WeakReference weakReference; public MyAsyncTask(Context context) { weakReference = new WeakReference<>(context); } @Override protected Void doInBackground(Void... params) { SystemClock.sleep(10000); return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); MainActivity activity = (MainActivity) weakReference.get(); if (activity != null) { //... } } } static class MyRunnable implements Runnable{ @Override public void run() { SystemClock.sleep(10000); } }//—————— new Thread(new MyRunnable()).start(); new MyAsyncTask(this).execute();
这样就避免了内存泄漏,当然在Activity销毁时也要记得在OnDestry中调用AsyncTask.cancal()方法来取消相应的任务。避免在后台运行浪费资源。
五、资源未关闭造成的内存泄漏
在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源时,一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存, 否则这些资源不会被GC回收,就会造成内存泄漏。 这些是总结出来的比较常见的内存泄漏问题,之后还会有补充,如果大家有什么好的意见可在下方写评论!谢谢!
更多相关文章
- Android内存管理机制和内存泄漏分析及优化
- 内存泄漏,关于异步回调导致的内存泄漏,使用LeakCanary检测内存泄漏
- Android的内存泄漏和调试
- Android异步加载全解析之Bitmap
- android基本的数据库创建和使用
- 查看Android内存的8中方法
- Android单个进程内存分配策略
- Android中三级缓存实现原理及LruCache 源码分析
- Android(安卓)Intent和按钮响应事件的几种方式