您的位置:首页 > 其它

Anfroid性能优化之内存泄露篇

2016-08-02 22:59 141 查看
首先看看内存泄露与内存溢出的概念:

1、内存泄漏:

当出现对Activity、View或drawable等类的对象长期持有无用的引用,就会造成被引用的对象无法在GC时回收,而是长期占用堆空间,此时就会发生内存泄漏。

简单来说,就是保留下来却永远不再使用的对象引用。

2、内存溢出:

如果应用程序在消耗光了所有的可用堆空间(16M到48M),那么再试图在堆上分配新对象时就会引起OOM(Out Of Memory Error)异常,此时应用程序就会崩溃退出。

3、两者的区别:

简单的说,就是内存溢出是占用内存太大,超过了其可以承受的范围;

内存泄漏是回收不及时甚至是没有被回收,而在推空间中产生的许多无用的引用。

于是过多的内存泄漏就会导致内存溢出,从而迫使程序崩溃退出。

下面重点来谈谈android中有关内存泄露的问题。

虽然Java有专门的GC来回收不再使用的对象,但是我们不能完全依靠GC来彻底回收对象,在编码的过程中如不遵守一定的规则就会造成内存泄露。主要有以下三种:

1,静态变量持有对象,特别是生命周期比整个应用短的对象

2,非静态内部类隐式持有外部类对象,特别是内部类生命周期比外部类长

3,资源打开未关闭,如文件,数据库等

一, 静态变量持有对象,特别是生命周期比整个应用短的对象

泄漏 activity 最简单的方法就是在 activity 类中定义一个 static 变量,并且将其指向一个运行中的 activity 实例。如果在 activity 的生命周期结束之前,没有清除这个引用,那它就会泄漏了。这是因为activity(例如 MainActivity) 的类对象是静态的,一旦加载,就会在 APP 运行时一直常驻内存,因此如果类对象不卸载,其静态成员就不会被垃圾回收。

二非静态内部类隐式持有外部类对象,特别是内部类生命周期比外部类长。

1 handler内存泄露

最常见的就是在actvity中使用handler。创建非静态内部的时候,该内部类默认会持有外部类的引用,如果外部类的生命周期比较短,那么内部类就不会释放外部类对象的引用,这就造成了内存泄露。

public class HandlerActivity extends Activity{
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 1000);
finish();
}


原因分析:

1. Handler
的生命周期与Activity
不一致。当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。

2. handler
引用 Activity
阻止了GC对Acivity的回收。

当Activity finish后,meaasge会继续存在UI线程的messagequeue中,。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的Activity泄露。

publicboolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = newRuntimeException(
this + "sendMessageAtTime() called with no mQueue");
Log.w("Looper",e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg,uptimeMillis);
}


修改方法:

· 使用显形的引用,1.静态内部类。 2.
外部类
· 使用弱引用 2.WeakReference

static class ExerciseHandler extends Handler{
private SoftReference<ExerciseActivity> exerciseActivitySoftReference = null;

public ExerciseHandler(ExerciseActivity exerciseActivity){
exerciseActivitySoftReference = new SoftReference<ExerciseActivity>(exerciseActivity);
}

@Override
public void handleMessage(Message msg) {
ExerciseActivity exerciseActivity = exerciseActivitySoftReference.get();
if(null != exerciseActivity){
super.handleMessage(msg);
switch (msg.what) {
case MSG_XX:
exerciseActivity.***;
break;
default:
break;
}
}
} public void onDestroy() {
mHandler.removeMessages(MESSAGE_1);
mHandler.removeMessages(MESSAGE_2);
mHandler.removeMessages(MESSAGE_3);

// ... ...

mHandler.removeCallbacks(mRunnable);

// ... ...
}


三.资源打开未关闭,如文件,数据库等

IO操作后,没有关闭文件导致的内存泄露,比如Cursor、FileInputStream、FileOutputStream使用完后没有关闭,这种问题在Android Studio 2.0中能够通过静态代码分析检查出来,直接改善就可以了;

自定义View中使用TypedArray后,没有recycle,这种问题也可以在Android Studio 2.0中能够通过静态代码分析检查出来,直接改善就可以了

四 Context
内存泄露
某些地方使用了四大组件的context,在离开这些组件后仍然持有其context导致的内存泄露,这种问题属于共识,在编写代码的过程中就应该按照规则来,使用Application的Context就可以解决这类内存泄露的问题了。
常见的解决办法:
通过工具检查程序运行后的内存泄露
通过上面的步骤,应用中的大部分内存泄露问题都能够得到解决,还有一些内存泄露,需要运行程序,分析运行后的内存快照来解决,比如注册之后没有反注册、类中的静态成员变量导致的内存泄露、SDK中的内存泄露等。解决这类问题可以分两步进行:

通过内存泄露检测工具先定位是哪有问题,内存泄露的检测有两种比较便捷的方式:1、一种是使用开源项目Leakcanary,需要添加到代码中,运行后生成分析结果;2、另一种方式是使用adb shell dumpsys meminfo packagename -d命令,在进入一个界面之前查看一遍Activity和View的数量,在退出这个界面之后再查看一遍Activity和View的数量,对比进入前和进入后Activity和View数量的变化情况,如果有差异,则说明存在内存泄露(在使用命令查看Activity和View的数量之前,记得手动触发GC)。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: