Android基础之内存泄露
来源:互联网 发布:知乎七七网红duebass 编辑:程序博客网 时间:2024/05/19 18:16
上一篇介绍了Android内存溢出,今篇我来继续介绍一下关于Android内存优化的内存泄露。
内存泄露的基础理解
- 一般内存泄露的原因是:由忘记释放分配的内存导致的。(如Cursor忘记关闭等)
- 逻辑内存泄露的原因是:当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中。
这样一方面占用了宝贵的内存空间,这样容易导致后续需要分配内存的时候,空闲空间不足而出现内存溢出OOM。所以我么要理解好内存泄露,泄露形象理解成煤气泄露,是我们不用煤气的时候,应该是关掉阀口,但是在不正常的情况中,阀口没有关好,导致煤气泄露了。
Android中常见的内存泄漏汇总
我们关键需要做到的是:及时回收没有使用的对象
需手动关闭的对象没有关闭,在try/catch/finally中的处理
- HTTP
- File
- ContendProvider
- Bitmap
- Uri
- Socket
- Cursor
onDestory()或者onPause()中未及时关闭对象
部分实例:- Handler泄露:我们的Handler是要求写出static的,但是实际开发中我们并没有写到,因此我们要使用弱引用和在onDestory()中removeCallbacksAndMessages(null)手动关闭。
- 广播泄露:在手动注册广播时:需要退出的时候unregisterReceiver()。
- Service泄露:使用Service进行某耗时操作结束后,需要手动stopself()。
- WebView需要手动调用WebView.onPause()和WebView.destory()。
- 常用第三方/开源框架泄露:ShareSDK、JPush、BaiduMap、ButterKnife等需要注意在对应的Activity的生命周期中关闭。
这些我就不一一说明了,大家或多或少都有接触到,这些都是需要我们手动关闭,属于一般内存泄露。
- 然而我们可以借助LeakCanary、MAT等工具来检测应用程序是否存在以下几种情况内存泄漏。
单例造成的内存泄漏
由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。如下这个典例:
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; }}
这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长。
2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该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,而单例的生命周期和应用的一样长,这样就防止了内存泄漏
静态View
有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。当然了,也不是说不能使用静态View,但是在使用静态View时,需要确保在资源回收时,将静态View detach掉。
内部类
我们知道,非静态内部类持有外部类的一个引用。因此,如果我们在一个外部类中定义一个静态变量,这个静态变量是引用内部类对象。将会导致内存泄漏!因为这相当于间接导致静态引用外部类。
static InnerClass innerClass;@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); innerClass = this.new InnerClass(); }class InnerClass { //}
匿名类
与内部类一样,匿名类也会持有外部类的引用。
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { //另一个线程中持有Activity的引用,并且不释放 while (true) ; } }.execute();}
Handler类引起内存泄漏
在Activity中定义一下Handler类:
public class MainActivity extends Activity { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //TODO } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler.sendMessageDelayed(Message.obtain(), 60000);//延迟一分钟执行 finish(); } }
我们知道非静态内部类会持有外部类的引用,这里Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
要修改这个问题,把Handler类定义为静态,然后通过WeakReference来持有外部的Activity对象。还在onDestory()中清掉Handler消息。
public class MainActivity extends Activity { private CustomHandler mHandler; private static class CustomHandler extends Handler { private WeakReference<MainActivity> mWeakReference; public CustomHandler(MainActivity activity) { mWeakReference = new WeakReference<MainActivity>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); MainActivity activity = mWeakReference.get(); if (null != activity) switch (msg.what) { /// } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new CustomHandler(this); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
Thread对象引起内存泄露
同Handler对象可能造成内存泄露的原理一样,Thread的生命周期不一定是和Activity生命周期一致。
而且因为Thread主要面向多任务,往往会造成大量的Thread实例。
据此,Thread对象有2个需要注意的泄漏点:
- 创建过多的Thread对象
- Thread对象在Activity退出后依然在后台执行
解决方案是:
- 使用ThreadPoolExecutor,在同时做很多异步事件的时候是很常用的,这个不细说。
- 当Activity退出的时候,退出Thread。
TimerTask引起泄露
TimerTask对象在和Timer的schedule()方法配合使用的时候极容易造成内存泄露。要在合适的时候进行Cancel即可。
private void cancelTimer(){ if (mTimer != null) { mTimer.cancel(); mTimer = null; } if (mTimerTask != null) { mTimerTask.cancel(); mTimerTask = null; } }
监听器管理内存泄露
当我们需要使用系统服务时,比如执行某些后台任务、为硬件访问提供接口等等系统服务。我们需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。
void registerListener() { SensorManager sensorManager = (SensorManager) getSystemServic(SENSOR_SERVICE); Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL); sensorManager.registerListene(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);}
然而对于中级Android工程师的面试过程中,如果你是一名老鸟,这个问题想必已经深入你的心。但是你是一个对内存模模糊糊的工程师,你的答案可能让面试官并不满意。下面的总结对你或许有点帮助。
常见的内存泄露问题
- 单例造成的泄露。
- 静态View造成的泄露。
- 内部类持有外部类实例的强引用。
- Handler、Thread耗时操作劫持Activity的外部类。
- TimerTask对象没有及时回收和置空。
- 系统监听器的引起的泄露。
避免内存泄露的方法
- 注意Context上下文的使用。
- 谨慎使用static。
- 使用静态内部类来代替内部类。
- 静态内部类使用弱引用WeakReference来引用外部类。
- 在声明周期结束的时候释放资源。
- 优化布局文件,减少层级关系。
- 谨慎使用第三方框架。
- 谨慎使用多线程。
- 横竖屏切换引起的GC。
- 避免创建不必要的对象。
还有就是如何检测内存泄露问题了。具体操作下次再详细分析
- 善用Android Studio的标签Memory。
- LeakCanary的使用。
- MAT的使用。
- Android基础之内存泄露
- Android之内存泄露
- Android之内存泄露
- Android Handler之内存泄露
- Android优化浅谈之内存泄露
- Android之内存泄露与内存管理
- Android之内存泄露LeakCanary检测
- Android性能优化之内存泄露篇
- Android内存优化之内存泄露
- Android基础之内存溢出
- java之内存泄露
- AsyncTask之内存泄露
- Android Studio 插件之内存泄露检测LeakCanary使用
- android学习之内存泄露(更新中)
- 浅谈android的MVP设计模式之内存泄露问题
- Android Studio 插件之内存泄露检测LeakCanary使用
- Android之内存泄露、内存溢出、内存抖动分析
- Android Dalvik VM内存优化之内存泄露篇。
- javascript动态生成按钮并绑定点击事件
- Activity的声明周期测试
- 第一次的数电作业-----数据选择,代码转换,译码
- HDU 4609 3-idiots (FFT)
- android 验证码
- Android基础之内存泄露
- 解决TextView数据不能更新的问题以及Android Button事件响应函数的两种方法
- PHP学习笔记九之正则表达式(进阶篇)
- Android技术面试整理
- in-band和out-band的区别
- 【我的Android进阶之旅】Android插件化开发学习资料
- object c 单例模式
- iOS中图片压缩成指定的大小
- 一些web性能问题的思考解决方法。