05
2017
10

Android内存泄漏优化总结

Android内存泄漏优化总结

什么是内存?

Android中的内存是手机的RAM。主要包括如下几个部分:
1、寄存器
位于Cpu内部,速度最快。
2、栈
用于存放基本类型以及引用变量。
3、堆
主要用于存放数组和new出来的对象,它由垃圾回收器进行管理(GC)。对内存的优化也是处理的堆内存。
4、静态存储区
存储应用运行一直存在的数据(static变量)
5、常量池
存放定义的常量(static final )

说明:局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。—— 因为它们属于方法中的变量,生命周期随方法而结束。
成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)—— 因为它们属于类,类对象终究是要被new出来使用的。

JAVA引用的分类

:上节中提到引用的概念, Java中的引用分为4种:
1、强引用 。对于这类引用GC任何时候不会对其进行内存回收,在内存不足的情况下宁愿抛出Out of Memory(OOM内存溢出)。类似这样的都是强引用:

private final MessageReceived mMessageReceived=new MessageReceived(this);

2、软引用。内存不足的时候回收该引用关联的对象。使用SoftReference修饰的是软引用类似如下所示:

  SoftReference<User> sr = new SoftReference<User>(new User("1","2"));

3、弱引用。垃圾回收时,无论内存是否足够,对该类引用所关联的对象全部进行内存回收。使用WeakReference修饰的是弱引用,类似如下所示:

sCtx=new WeakReference<Context>(context).get();

4、虚引用。任何时候都可能被垃圾回收器回收。虚引用一般不使用
所以对于内存的优化我们针对的是堆内存一般考虑软引用和弱引用。接下来分析如果不对内存问题进行处理优化将会出现什么问题。

会导致什么问题?

:Android系统给每个应用的虚拟机分配的内存是有限的,不同的手机ROM有不同的值。换句话说 如果应用占用的内存不被释放,这将导致传说中的内存泄漏(memory leak),使得可用内存越来越小。当应用频繁的需要内存时,系统就会通过不断的进行垃圾回收(GC)试图去获取内存,这个过程中将导致内存抖动界面卡顿。当系统再也无法给应用分配内存时,系统就会无情的抛出out of memory(OOM)即内存溢出(OOM不一定是内存泄漏引起的),从而APP 闪退
:通过以上分析可以知道内存优化的关键是解决内存释放问题,内存释放主要是解决内存泄漏问题达到及时释放内存以及避免对内存需求超过系统分配的最大值。接下来将详细说明常见内存泄漏的解决办法以及如何规避超大内存需求的问题。其中本文的内存泄漏检测工具为leakcanary。

内存泄漏优化的方式

1、单例模式导致的内存泄漏

:单例模式的内存泄漏一般是由上下文的生命周期引起的。因为单例模式的静态特性导致它的生命周期和应用的生命周期一样长,当单例引用了比它生命还短的对象时候,将导致这个短生命周期的对象无法被释放掉,从而导致内存泄漏。如下示例代码,模拟了单例导致的内存泄漏。

public class Singleton4Static {

    private static Context sCtx;

    private Singleton4Static(Context context){
    //初始化相关的
    }
    private static class Holder{
        private static final Singleton4Static sInstance=new Singleton4Static(sCtx);

    }

    public static Singleton4Static  getInstance(Context context){
       sCtx=context;
        return  Holder.sInstance;
    }

}

说明:该单例类是一个静态内部方式实现的(优点加载不初始化,使用才初始化实例真正达到懒加载)。
如在Activity组件中调用该单例将导致静态变量引用了Activity实例,导致Activity实例无法释放,从而内存泄漏。如下所示:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //单例引用了比他生命周期还短的对象
    Singleton4Static.getInstance(this);

}

启动APP,然后退出界面后,等待几秒钟内存泄漏工具就能将详细报告显示出来。如下所示:
这里写图片描述

说明:MainActivity的实例被单例类的静态变量引用了,导致了内存泄漏有4.8kb的内存大小无法释放。
解决方式:
当然解决办法也很简单,只需要将引用对象的生命和单例类一致就OK了。
通常的做法是将传入单例的的上下文参数做下转换调用它的getApplicationContext()即可。如下public class Singleton4Static {

private static Context sCtx;

private Singleton4Static(Context context){
//初始化相关的
}
private static class Holder{
    private static final Singleton4Static sInstance=new Singleton4Static(sCtx);

}

public static Singleton4Static  getInstance(Context context){
    //上下文转换
   sCtx=context.getApplicationContext();
    return  Holder.sInstance;
}

}将之前的sCtx=context转换成sCtx=context.getApplicationContext();
或者将引用的对象转换成弱引用也可以达到解决问题的目的如下所示:

public class Singleton4Static {

    private static Context sCtx;

    private Singleton4Static(Context context){
    //初始化相关的
    }
    private static class Holder{
        private static final Singleton4Static sInstance=new Singleton4Static(sCtx);

    }

    public static Singleton4Static  getInstance(Context context){
        //上下文转换
      sCtx=new WeakReference<Context>(context).get();
        return  Holder.sInstance;
    }

}

说明:
将之前的sCtx=context转换成sCtx=new WeakReference(context).get();;

2、匿名内部类导致的内存泄漏

匿名类、非静态内部类都会引用它们所属的外部类,当这些内部类进行一些耗时操作的时候就会导致外部类的实例无法被释放掉,从而导致内存泄漏的发生。
(1)非静态匿名内部类导致的内存泄漏
示例代码如下所示:

public class InnerClassActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inner_class);
        doLeak();

    }

    private  void doLeak(){

        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();
    }


   }

说明:当退出界面时,非静态内部类new Thread(new Runnable())有耗时操作将导致外部类InnerClassActivity的实例无法释放,从而内存泄漏。如下图所示:
这里写图片描述
说明:非静态匿名内部类Thread引用了外部类InnerClassActivity实例导致内存泄漏61kb内存无法释放。
解决方法:
将非静态匿名内部类修改成静态匿名内部类:示例代码如下所示:

public class InnerClassActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inner_class);
        doLeak();
    }
    private static void doLeak(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();
    }
   }

说明:将doLeak方法设置为静态方法,即实现了将非静态匿名内部类修改为静态匿名内部类。这样就可以解决非静态匿名内部类内存泄漏问题。

(2)进程间通信用于回调的AIDL接口导致的内存泄漏,其本质还是匿名内部类导致的内存泄漏。示例代码如下所示:

private MessageReceiver messageReceiver = new MessageReceiver.Stub() {


   @Override
   public void onMessageReceived(final ChatMessage receivedMessage) throws RemoteException {
//....
}
...
}

说明:在ChattingActivity类中定义的成员变量messageReceiver,其中MessageReceiver.Stub()是MessageReceiver.aidl生成的一个内部类。这段代码将会导致严重的内存泄漏.
这里写图片描述
说明:这是上述代码匿名内部类MessageReceiver.Stub()引用ChattingActivity实例导致的内存泄漏,438kb内存无法释放。
解决方法:
将匿名内部类定义成静态内部类即可。

static  class SMessageReceiver extends MessageReceiver.Stub{
private WeakReference<ChattingActivity> mChattingActivity;
SMessageReceiver(ChattingActivity chattingActivity){
   mChattingActivity=new WeakReference<ChattingActivity>(chattingActivity);
}

@Override
public void onMessageReceived(final ChatMessage receivedMessage) throws RemoteException {
//...
}
...
}

说明:将ChattingActivity类中定义的成员变量messageReceiver的类型先定义成静态内部类SMessageReceiver ,然后定义messageReceiver=new SMessageReceiver(this)。这样便可以解决内存泄漏。如果要调用外部类的属性和方法可以把外面对象通过弱引用的方式传递进来。
(3)AsynckTask导致的内存泄漏
:对于Android系统的异步任务类,使用起来非常方便,但是如果按如下方式使用,并且执行耗时操作。如果退出界面后耗时操作还没有执行完,这将导致内存泄漏。

new AsyncTask<Void,Void,Void>(){


    @Override
    protected Void doInBackground(Void... voids) {
        SystemClock.sleep(15000);//模拟耗时操作
        return null;
    }


}.execute();

说明:利用延时来模拟耗时操作。在activity类中调用后在耗时操作未执行完前退出界面,将导致内存泄漏。如下所示:
这里写图片描述
说明:匿名内部类异步任务引用了外部类InnerClassActivity,退出界面InnerClassActivity时,异步任务的耗时操作还执行完成,所有外部类无法释放,导致内存泄漏。
解决方法:
将匿名内部类定义成静态内部类即可解决问题。示例代码如下所示:

static class StaticAsynckTask extends AsyncTask<Void,Void,Void>{


    @Override
    protected Void doInBackground(Void... voids) {

        SystemClock.sleep(15000);
        return null;
    }
}

说明:用延时模拟耗时操作,将匿名内部类改成静态内部类实现。
定义对象并调用,匿名的异步任务导致的内存泄漏解除。如下所示:

StaticAsynckTask mTask=new StaticAsynckTask();
mTask.execute();
3、Handler引起的内存泄漏

:当Handler中有延迟的的任务或是等待执行的任务队列过长,由于消息持有对Handler的引用,而Handler又持有对其外部类的潜在引用,这条引用关系会一直保持到消息得到处理,而导致了Activity无法被垃圾回收器回收,而导致了内存泄露。
如下代码会导致Handler内存泄漏:

public class HandlerLeakActivity extends Activity {


    private static final int MSG =0X01 ;
    private static final String TAG = "HandlerLeakActivity";
    private Handler mHandler=new Handler(){

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e(TAG,"--------handleMessage----"+msg.what);

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_leak);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(5000);
                Log.e(TAG,"测试Handler导致的内存泄漏");

            }
        },5*1000*60);

    }
}

说明:mHandler.postDelayed(new Runnable())存在一个五分钟延时的任务,就是这个延时的任务导致了handler无法释放,handler又引用了外部类HandlerLeakActivity,所有在延时任务没有执行前推出界面,将导致HandlerLeakActivity无法释放,内存泄漏了。如下所示:
这里写图片描述
解决方法:
将Handler定义成静态内部类,在界面的onDestroy方法移除消息回调等。

public class HandlerLeakActivity extends Activity {
private static final int MSG =0X01 ;
private static final String TAG = “HandlerLeakActivity”;
LeakHandler mLeakHandler=new LeakHandler();
LeakRunable mLeakRunable=new LeakRunable();
static class LeakRunable implements Runnable {
@Override
public void run() {
//业务逻辑

    }
}
static class  LeakHandler  extends Handler{

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.e(TAG,"--------handleMessage----"+msg.what);

    }

}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler_leak);
    mLeakHandler.postDelayed(mLeakRunable,5*1000*60);

}
@Override
protected void onDestroy() {
    super.onDestroy();
    if(null!=mLeakHandler){
        mLeakHandler.removeCallbacksAndMessages(null);
        mLeakHandler=null;
    }
}

}
:静态态内部类 LeakHandler, LeakRunable。在生命周期结束的onDestroy方法中移除消息。
通过以上方式解决了Handler的内存泄漏问题,但是如果要调用外部类的方法该怎么办?
答案很简单,只要在消息处理的业务逻辑类中传入外部类的弱引用,即可通过这个传入的引用调用外部类的方法了。如下所示:

public LeakRunable(HandlerLeakActivity _HandlerLeakActivity){

    mHandlerLeakActivity=new WeakReference<HandlerLeakActivity>(_HandlerLeakActivity);
}

    @Override
    public void run() {
        //业务逻辑
        mHandlerLeakActivity.get().test();
    }
}

说明:在业务逻辑类的构造方法中传入了外部类的弱引用,然后调用了外部类的test方法。

4、注册监听器的泄漏

:注册和解注册一定要配对出现,否则就会出现内存泄漏。例如我们在activity的的onCreate方法注册了动态广播,那么在ondestroy中就的解注册该动态广播

5、IO流数据库之类导致的内存泄漏

:OutputStream、 InputStream以及相关的子类需要在finally块中进行及时关闭释放资源。数据库主要是Cursor游标、数据库的关闭。

6、集合中对象没清理造成的内存泄漏

:我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。所以退出界面集合需要清空并设置为null。

7、WebView造成的泄漏

:对于显示webview的组件activity设置单独的运行进程,可以处理简单无交互的webview显示网页,在退出界面的时候杀死进程或者退出整个进程即可。对于复杂的需要和启动方主进程交互的需要使用aidl方式进行进程间通信。

参考:http://hukai.me/android-performance-oom/

上一篇:Android内存泄漏与优化(下) 下一篇:类加载器ClassLoader