2018-03-07

持续更新,嘿嘿~   Android内存泄漏解析

​内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。

内存泄漏形象的比喻是“操作系统可提供给所有进程的存储空间正在被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃。所以“内存泄漏”是从操作系统的角度来看的。这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。由程序申请的一块内存,如果没有任何一个指针指向它,那么这块内存就泄漏了。

​ ——来自《百度百科》

影响

导致OOM

糟糕的用户体验

鸡肋的App存活率

成效

内存泄露是一个持续的过程,随着版本的迭代,效果越明显

由于某些原因无法改善的泄露(如框架限制),则尽量降低泄露的内存大小

内存泄露实施后的版本,一定要验证,不必马上推行到正式版,可作为beta版持续观察是否影响/引发其他功能/问题

内存泄露实施后,项目的收获:

OOM减少30%以上

平均使用内存从80M稳定到40M左右

用户体验上升,流畅度提升

存活率上升,推送到达率提升

类型

IO

FileStream

Cursor

Bitmap

Context

单例

Callback

Service

BraodcastReceiver

ContentObserver

Handler

Thread

技巧

慎用Context

Context概念

四大组件Context和Application的context使用参见下表

善用Reference

Java引用介绍

Java四种引用由高到低依次为:强引用  >  软引用  >  弱引用  >  虚引用

表格说明

复用ConvertView

复用详解

对象释放

遵循谁创建谁释放的原则

示例:显示调用clear列表、对象赋空值

分析

​原理

Java内存分配机制

Java垃圾回收机制

​根本原因

关注堆内存

​怎么解决

详见方案

​实践分析

详见实践

方案

StrictMode

使用方法:AppContext的onCreate()方法加上

StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());StrictMode.setVmPolicy(newStrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());

主要检查项:内存泄露、耗时操作等

Leakcanary

GitHub地址

使用方法

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;}}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容