持续更新,嘿嘿~ Android内存泄漏解析
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。
内存泄漏形象的比喻是“操作系统可提供给所有进程的存储空间正在被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃。所以“内存泄漏”是从操作系统的角度来看的。这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。由程序申请的一块内存,如果没有任何一个指针指向它,那么这块内存就泄漏了。
——来自《百度百科》
导致OOM
糟糕的用户体验
鸡肋的App存活率
内存泄露是一个持续的过程,随着版本的迭代,效果越明显
由于某些原因无法改善的泄露(如框架限制),则尽量降低泄露的内存大小
内存泄露实施后的版本,一定要验证,不必马上推行到正式版,可作为beta版持续观察是否影响/引发其他功能/问题
内存泄露实施后,项目的收获:
OOM减少30%以上
平均使用内存从80M稳定到40M左右
用户体验上升,流畅度提升
存活率上升,推送到达率提升
IO
FileStream
Cursor
Bitmap
Context
单例
Callback
Service
BraodcastReceiver
ContentObserver
Handler
Thread
慎用Context
四大组件Context和Application的context使用参见下表
善用Reference
Java四种引用由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用
表格说明
复用ConvertView
对象释放
遵循谁创建谁释放的原则
示例:显示调用clear列表、对象赋空值
原理
根本原因
关注堆内存
怎么解决
详见方案
实践分析
详见实践
StrictMode
使用方法:AppContext的onCreate()方法加上
StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());StrictMode.setVmPolicy(newStrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
主要检查项:内存泄露、耗时操作等
Leakcanary
Leakcanary + StrictMode + monkey(推荐)
使用阶段:功能测试完成后,稳定性测试开始时
使用方法:安装集成了Leakcanary的包,跑monkey
收获阶段:一段时间后,会发现出现N个泄露
实战分析:逐条分析每个泄露并改善/修复
StrictMode:查看日志搜索StrictMode关键字
Adb命令
手动触发GC
通过adb shell dumpsys meminfo packagename -d查看
查看Activity以及View的数量
越接近0越好
对比进入Activity以及View前的数量和退出Activity以及View后的数量判断
Android Monitor
MAT
Bitmap泄露
Bitmap泄露一般会泄露较多内存,视图片大小、位图而定
经典场景:App启动图
解决内存泄露前后内存相差10M+,可谓惊人
解决方案:
App启动图Activity的onDestroy()中及时回收内存
@OverrideprotectedvoidonDestroy(){// TODO Auto-generated method stubsuper.onDestroy();recycleImageView(imgv_load_ad);}publicstaticvoidrecycleImageView(Viewview){if(view==null)return;if(viewinstanceofImageView){Drawabledrawable=((ImageView)view).getDrawable();if(drawableinstanceofBitmapDrawable){Bitmapbmp=((BitmapDrawable)drawable).getBitmap();if(bmp!=null&&!bmp.isRecycled()){((ImageView)view).setImageBitmap(null);bmp.recycle();bmp=null;}}}}
IO流未关闭
分析:通过日志可知FileOutputStream()未关闭
问题代码:
publicstaticvoidcopyFile(Filesource,Filedest){FileChannelinChannel=null;FileChanneloutChannel=null;Log.i(TAG,"source path: "+source.getAbsolutePath());Log.i(TAG,"dest path: "+dest.getAbsolutePath());try{inChannel=newFileInputStream(source).getChannel();outChannel=newFileOutputStream(dest).getChannel();inChannel.transferTo(0,inChannel.size(),outChannel);}catch(IOExceptione){e.printStackTrace();}}
解决方案:
及时关闭IO流,避免泄露
publicstaticvoidcopyFile(Filesource,Filedest){FileChannelinChannel=null;FileChanneloutChannel=null;Log.i(TAG,"source path: "+source.getAbsolutePath());Log.i(TAG,"dest path: "+dest.getAbsolutePath());try{inChannel=newFileInputStream(source).getChannel();outChannel=newFileOutputStream(dest).getChannel();inChannel.transferTo(0,inChannel.size(),outChannel);}catch(IOExceptione){e.printStackTrace();}finally{if(inChannel!=null){try{inChannel.close();}catch(IOExceptione){e.printStackTrace();}}if(outChannel!=null){try{outChannel.close();}catch(IOExceptione){e.printStackTrace();}}}}
E/StrictMode: A resource was acquired at attached stack trace but never released.
See java.io.Closeable for information on avoiding resource leaks.
java.lang.Throwable: Explicit termination method 'close' not called
at dalvik.system.CloseGuard.open(CloseGuard.java:180)
at java.io.FileOutputStream.(FileOutputStream.java:89)
at java.io.FileOutputStream.(FileOutputStream.java:72)
at com.heyniu.lock.utils.FileUtil.copyFile(FileUtil.java:44)
at com.heyniu.lock.db.BackupData.backupData(BackupData.java:89)
at com.heyniu.lock.ui.HomeActivity$11.onClick(HomeActivity.java:675)
at android.support.v7.app.AlertController$ButtonHandler.handleMessage(AlertController.java:157)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
单例模式泄露
分析:通过截图我们发现SplashActivity被ActivityUtil的实例activityStack持有
引用代码:
ActivityUtil.getAppManager().add(this);
持有代码:
publicvoidadd(Activityactivity){if(activityStack==null){synchronized(ActivityUtil.class){if(activityStack==null){activityStack=newStack<>();}}}activityStack.add(activity);}
解决方案:
在SplashActivity的onDestroy()生命周期移除引用
@OverrideprotectedvoidonDestroy(){super.onDestroy();ActivityUtil.getAppManager().remove(this);}
静态变量持有Context实例泄露
分析:长生命周期持有短什么周期引用导致泄露,详见上文四大组件Context和Application的context使用
示例引用代码:
privatestaticHttpRequestreq;publicstaticvoidHttpUtilPost(Contextcontext,intTaskId,Stringurl,StringrequestBody,ArrayListHeaders,RequestListenerlistener){// TODO Auto-generated constructor stubreq=newHttpRequest(context,url,TaskId,requestBody,Headers,listener);req.post();}
解决方案:
改为弱引用
pass:弱引用随时可能为空,使用前先判空
示例代码:
publicstaticvoidcancel(intTaskId){if(req!=null&&req.get()!=null){req.get().AsyncCancel(TaskId);}}
privatestaticWeakReferencereq;publicstaticvoidHttpUtilPost(Contextcontext,intTaskId,Stringurl,StringrequestBody,ArrayListHeaders,RequestListenerlistener){// TODO Auto-generated constructor stubreq=newWeakReference(newHttpRequest(context,url,TaskId,requestBody,Headers,listener));req.get().post();}
改为长生命周期
privatestaticHttpRequestreq;publicstaticvoidHttpUtilPost(Contextcontext,intTaskId,Stringurl,StringrequestBody,ArrayListHeaders,RequestListenerlistener){// TODO Auto-generated constructor stubreq=newHttpRequest(context.getApplicationContext(),url,TaskId,requestBody,Headers,listener);req.post();}
Context泄露
Callback泄露
服务未解绑注册泄露
分析:一般发生在注册了某服务,不用时未解绑服务导致泄露
引用代码:
privatevoidinitSensor(){// 获取传感器管理器sm=(SensorManager)container.activity.getSystemService(Context.SENSOR_SERVICE);// 获取距离传感器acceleromererSensor=sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);// 设置传感器监听器acceleromererListener=newSensorEventListener(){......};sm.registerListener(acceleromererListener,acceleromererSensor,SensorManager.SENSOR_DELAY_NORMAL);}
解决方案:
在Activity的onDestroy()方法解绑服务
@Override
protected void onDestroy(){
super.onDestroy();
sm.unregisterListener(acceleromererListener,acceleromererSensor);}
Handler泄露
分析:由于Activity已经关闭,Handler任务还未执行完成,其引用了Activity的实例导致内存泄露
引用代码:
handler.sendEmptyMessage(0);
解决方案:
在Activity的onDestroy()方法回收Handler
@OverrideprotectedvoidonDestroy(){super.onDestroy();handler.removeCallbacksAndMessages(null);}
图片后续遇到再补上
异步线程泄露
分析:一般发生在线程执行耗时操作时,如下载,此时Activity关闭后,由于其被异步线程引用,导致无法被正常回收,从而内存泄露
引用代码:
newThread(){publicvoidrun(){imageArray=loadImageFromUrl(imageUrl);}.start();
解决方案:
把线程作为对象提取出来
在Activity的onDestroy()方法阻塞线程
thread=newThread(){publicvoidrun(){imageArray=loadImageFromUrl(imageUrl);};thread.start();@OverrideprotectedvoidonDestroy(){super.onDestroy();if(thread!=null){thread.interrupt();thread=null;}}