前言
前人(大佬)种树,后人(本人)乘凉
正文
定义
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。
内存泄漏形象的比喻是“操作系统可提供给所有进程的存储空间正在被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃。所以“内存泄漏”是从操作系统的角度来看的。这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。由程序申请的一块内存,如果没有任何一个指针指向它,那么这块内存就泄漏了。
——来自《百度百科》
影响
1.导致OOM 2.糟糕的用户体验 3.鸡肋的App存活率
成效
1.内存泄露是一个持续的过程,随着版本的迭代,效果越明显 2.由于某些原因无法改善的泄露(如框架限制),则尽量降低泄露的内存大小 3.内存泄露实施后的版本,一定要验证,不必马上推行到正式版,可作为beta版持续观察是否影响/引发其他功能/问题
内存泄露实施后,项目的收获:
1.OOM减少30%以上 2.平均使用内存从80M稳定到40M左右 3.用户体验上升,流畅度提升 4.存活率上升,推送到达率提升
类型
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(new StrictMode.ThreadPolicy
.Builder()
.detectAll()
.penaltyLog()
.build());
StrictMode
.setVmPolicy(new StrictMode.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()中及时回收内存
@Override
protected void onDestroy() {
super.onDestroy();
recycleImageView(imgv_load_ad
);
}
public static void recycleImageView(View view
){
if(view
==null
) return;
if(view
instanceof ImageView){
Drawable drawable
=((ImageView
) view
).getDrawable();
if(drawable
instanceof BitmapDrawable){
Bitmap bmp
= ((BitmapDrawable
)drawable
).getBitmap();
if (bmp
!= null
&& !bmp
.isRecycled()){
((ImageView
) view
).setImageBitmap(null
);
bmp
.recycle();
bmp
=null
;
}
}
}
}
IO流未关闭
分析:通过日志可知FileOutputStream()未关闭问题代码:
public static void copyFile(File source
, File dest
) {
FileChannel inChannel
= null
;
FileChannel outChannel
= null
;
Log
.i(TAG
, "source path: " + source
.getAbsolutePath());
Log
.i(TAG
, "dest path: " + dest
.getAbsolutePath());
try {
inChannel
= new FileInputStream(source
).getChannel();
outChannel
= new FileOutputStream(dest
).getChannel();
inChannel
.transferTo(0, inChannel
.size(), outChannel
);
} catch (IOException e
) {
e
.printStackTrace();
}
}
解决方案:
及时关闭IO流,避免泄露
public static void copyFile(File source
, File dest
) {
FileChannel inChannel
= null
;
FileChannel outChannel
= null
;
Log
.i(TAG
, "source path: " + source
.getAbsolutePath());
Log
.i(TAG
, "dest path: " + dest
.getAbsolutePath());
try {
inChannel
= new FileInputStream(source
).getChannel();
outChannel
= new FileOutputStream(dest
).getChannel();
inChannel
.transferTo(0, inChannel
.size(), outChannel
);
} catch (IOException e
) {
e
.printStackTrace();
} finally {
if (inChannel
!= null
) {
try {
inChannel
.close();
} catch (IOException e
) {
e
.printStackTrace();
}
}
if (outChannel
!= null
) {
try {
outChannel
.close();
} catch (IOException e
) {
e
.printStackTrace();
}
}
}
}
<code style
="" class="hljs"><span style
="">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
.<init>(FileOutputStream
.java
:89)
at java
.io
.FileOutputStream
.<init>(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)</span
></code
>
单例模式泄露
分析:通过截图我们发现SplashActivity被ActivityUtil的实例activityStack持有引用代码:
ActivityUtil.getAppManager().add(this);
解决方案:
在SplashActivity的onDestroy()生命周期移除引用
@Override
protected void onDestroy() {
super.onDestroy();
ActivityUtil
.getAppManager().remove(this);
}
静态变量持有Context实例泄露
分析:长生命周期持有短什么周期引用导致泄露,详见上文四大组件Context和Application的context使用示例引用代码:
private static HttpRequest req
;
public static void HttpUtilPost(Context context
, int TaskId
, String url
, String requestBody
,ArrayList
<HttpHeader> Headers
, RequestListener listener
) {
req
= new HttpRequest(context
, url
, TaskId
, requestBody
, Headers
, listener
);
req
.post();
}
解决方案: 改为弱引用 pass:弱引用随时可能为空,使用前先判空 示例代码:
public static void cancel(int TaskId
) {
if(req
!= null
&& req
.get() != null
){
req
.get().AsyncCancel(TaskId
);
}
}
private static WeakReference
<HttpRequest> req
;
public static void HttpUtilPost(Context context
, int TaskId
, String url
, String requestBody
,ArrayList
<HttpHeader> Headers
, RequestListener listener
) {
req
= new WeakReference<HttpRequest>(new HttpRequest(context
, url
, TaskId
, requestBody
, Headers
, listener
));
req
.get().post();
}
改为长生命周期
private static HttpRequest req
;
public static void HttpUtilPost(Context context
, int TaskId
, String url
, String requestBody
,ArrayList
<HttpHeader> Headers
, RequestListener listener
) {
req
= new HttpRequest(context
.getApplicationContext(), url
, TaskId
, requestBody
, Headers
, listener
);
req
.post();
}
Context泄露
Callback泄露 服务未解绑注册泄露
分析:一般发生在注册了某服务,不用时未解绑服务导致泄露引用代码:
private void initSensor() {
sm
= (SensorManager
) container
.activity
.getSystemService(Context
.SENSOR_SERVICE
);
acceleromererSensor
= sm
.getDefaultSensor(Sensor
.TYPE_PROXIMITY
);
acceleromererListener
= new SensorEventListener() {
......
};
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
@Override
protected void onDestroy() {
super.onDestroy();
handler
.removeCallbacksAndMessages(null
);
}
异步线程泄露
分析:一般发生在线程执行耗时操作时,如下载,此时Activity关闭后,由于其被异步线程引用,导致无法被正常回收,从而内存泄露引用代码:
new Thread() {
public void run() {
imageArray
= loadImageFromUrl(imageUrl
);
}.start();
解决方案:
把线程作为对象提取出来在Activity的onDestroy()方法阻塞线程
thread
= new Thread() {
public void run() {
imageArray
= loadImageFromUrl(imageUrl
);
};
thread
.start();
@Override
protected void onDestroy() {
super.onDestroy();
if(thread
!= null
){
thread
.interrupt();
thread
= null
;
}
}
总结
转载于(Android 内存泄露实践分析)
原文发表于:Testerhome;
作者:ycwdaaaa ;
原文链接:https://testerhome.com/topics/5822 -侵删