Android 常见内存泄露 & 解决方案

前言

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存因为某种缘由程序未释放或没法释放,形成系统内存的浪费,致使程序运行速度减慢甚至系统崩溃 (OOM) 等严重后果。html

那什么状况下不能被回收呢?java

目前 java 垃圾回收主流算法是虚拟机采用 GC Roots Tracing 算法。算法的基本思路是:经过一系列的名为 GC Roots (GC 根节点)的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径,当一个对象到GC Roots没有任何引用链相连(图论说:从GC Roots 到这个对象不可达)时, 证实此对象是不可用的。web

关于可达性的对象,即是能与 GC Roots 构成连通图的对象,以下图:算法

这里写图片描述

根搜索算法的基本思路就是经过一系列名为 "GC Roots" 的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链 ( Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证实此对象是不可用的。数据库

从上图,reference一、reference二、reference3 都是 GC Roots,能够看出:缓存

reference1-> 对象实例1;多线程

reference2-> 对象实例2;异步

reference3-> 对象实例4;ide

reference3-> 对象实例4 -> 对象实例6;函数

能够得出对象实例一、二、四、6都具备 GC Roots 可达性,也就是存活对象,不能被 GC 回收的对象。

而对于对象实例三、5直接虽然连通,但并无任何一个 GC Roots 与之相连,这即是 GC Roots 不可达的对象,这就是 GC 须要回收的垃圾对象。

在了解 GC 以后,开始去了解 Android 的内存泄露状况了。

Android 内存泄露场景 

 下面会详细介绍一些常见的内存泄露场景,以及对应的修复办法。

 非静态内部类的静态实例

好比咱们在 Activity 内部定义了一个内部类 InnerClass,同时定义了一个静态变量 inner,并给予赋值。假设你在 onDestory 的时候没有将 inner 置 null;那么就会引发内存泄露。缘由是静态变量持有了内部类的实例,内部类会对外部类有个引用,从而致使 Activity 得不到释放。

    private static Object inner;
       
        void createInnerClass() {
           class InnerClass {
            } 
           inner = new InnerClass();
        }
    
    View icButton = findViewById(R.id.ic_button);
    icButton.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            createInnerClass();
            nextActivity();
        }
    });

记得在生命周期结束的时候,将不须要的静态变量置 null。

多线程相关的匿名内部类/非静态内部类

和非静态内部类同样,匿名内部类也会持有外部类实例的引用。多线程相关的类有 AsyncTask 类,Thread 类和 Runnable 接口的类等,它们的匿名内部类若是作耗时操做
就可能发生内存泄露,这里以 AsyncTask 的匿名内部类举例,以下所示:

    void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {
            @Override protected Void doInBackground(Void... params) {
                while(true);
            }
        }.execute();
    }
    
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    View aicButton = findViewById(R.id.at_button);
    aicButton.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            startAsyncTask();
            nextActivity();
        }
    });

当异步任务在后台执行耗时任务期间,Activity 不幸被销毁了(好比:用户退出,系统回收),这个被 AsyncTask 持有的 Activity 实例就不会被垃圾回收器回收,直到异步任务结束。
解决方法是继承 AsyncTask 新建一个静态内部类,用静态内部类建立实例就不会存在对外部实例的引用了。

 

Handler 内存泄露

一样道理,Handler 的 message 被传递到消息队列 MessageQueue 中,在 Message 消息没有被处理以前,handler 的实例也不没法被回收,若是 handler 实例不是静态的,就会致使引用它的 activity 或者 service 不能被回收,因而就会发生内存泄漏。

    void createHandler() {
        new Handler() {
            @Override public void handleMessage(Message message) {
                super.handleMessage(message);
            }
        }.sendMessageDelayed(Message.obtain(), 60000);
    }
    
    
    View hButton = findViewById(R.id.h_button);
    hButton.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            createHandler();
            nextActivity();
        }
    });

 对于上述问题,有两种解决办法,一种是使用一个静态的 handler 内部类,而且其持有的对象都改为弱引用形式进行引用。还有一种是在销毁 activity 的时候,将发送的消息进行移除。

myHandler.removeCallbackAndMessages(null);

这种有个问题就是 Handler 中的消息可能没法所有被处理完。

另外还有一个要注意的是,最好不要直接使用 View#post 来作一些操做。若是要用,确保要用的话,确保 view 已经被 attach 到了 window。

具体能够参考:View的post方法致使的内存泄漏分析

静态 Activity 或 View

在类中定义了静态 Activity变量,把当前运行的 Activity实例赋值于这个静态变量。
若是这个静态变量在 Activity生命周期结束后没有清空,就致使内存泄漏。由于 static 变量是贯穿这个应用的生命周期的,因此被泄漏的  Activity 就会一直存在于应用的进程中,不会被垃圾回收器回收。
static Activity activity;
    
    void setStaticActivity() {
      activity = this;
    }
    
    View saButton = findViewById(R.id.sa_button);
    saButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        setStaticActivity();
        nextActivity();
      }
    });

为了可以被回收,须要在不须要使用的时候进行置 null 操做。好比销毁当前 activity 的时候。

特殊状况:若是一个 View 初始化耗费大量资源,并且在一个 Activity 生命周期内保持不变,那能够把它变成 static,加载到视图树上 (View Hierachy),像这样,当 Activity 被销毁时,应当释放资源。

static view;
    
    void setStaticView() {
      view = findViewById(R.id.sv_button);
    }
    
    View svButton = findViewById(R.id.sv_button);
    svButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        setStaticView();
        nextActivity();
      }
    });

一样的,为了解决内存泄露的问题,在 Activity 销毁的时候把这个 static view 置 null 便可,可是仍是不建议用这个 static view的方法。

Eventbus 等注册监听形成的内存泄露

相信不少同窗都在项目里面会用到 Eventbus。对于一些没有经验的同窗在使用的时候常常会出现一些问题。好比说在 onCreate 的时候进行注册,却忘了反注册,或者说,在onStop的时候进行反注册,这些都会致使 Eventbus 的内存泄露。

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    EventBus.getDefault().register(this);// 注意在onCreate()方法中注册
}

@Override
public void onDestroy() {
    EventBus.getDefault().unregister(this);// 注意在onDestory()方法中注册
    super.onDestroy();
}

注册和反注册(取消注册)是对应的,必需要添加,不然会引发组件的内存泄漏。由于注册的时候组件是被 EventBus 内部的单例队列所持有引用的。

若是你是在 View 里面注册 Eventbus 的,记得是在 View 的生命周期 onAttachedToWindow 和 onDetachedFromWindow 的时候进行注册和反注册。

最近跟个人同事进行聊天的时候发现,他们为了解决 eventbus 致使的内存泄露问题(已经成对注册和反注册仍是存在内存泄露问题),因而打算建立一个 object 的实例,用这个来进行注册与反注册,这样即便发生内存泄露也只会占用很小的内存空间。

单例引发的内存泄露

项目中,常常会存在不少单例。有时候须要咱们将当前 Activity 实例传给单例,而后去作一些事情。以下面的代码:

public class SingleInstance {
    private Context mContext;
    private static SingleInstance instance;
 
    private SingleInstance(Context context) {
        this.mContext = context;
    }
 
    public static SingleInstance getInstance(Context context) {
        if (instance == null) {
            instance = new SingleInstance(context);
        }
        return instance;
    }
}

 上述单例中传入一个 context ,就会致使 context 的生命时长和应用的生命时长同样。就会形成内存泄露。

对于这种有三种解决办法:

一、采用弱引用的方式进行引用,确保可以被回收;

二、在对应的 context 要被销毁的时候,进行置 null;确保不会长于本来的生命时长;

三、看是否可以使用 APP context;这样就不会存在内存泄露的问题了。

资源对象没关闭形成内存泄漏

当咱们打开资源时,通常都会使用缓存。好比读写文件资源、打开数据库资源、使用 Bitmap 资源等等。当咱们再也不使用时,应该关闭它们,使得缓存内存区域及时回收。虽然有些对象,若是咱们不去关闭,它本身在 finalize() 函数中会自行关闭。可是这得等到 GC 回收时才关闭,这样会致使缓存驻留一段时间。若是咱们频繁的打开资源,内存泄漏带来的影响就比较明显了。

解决办法:及时关闭资源

 

WebView 

不一样的Android 版本的 webView 会有差别,加上不一样的厂商定制的 ROM 的 webView 差别,这就致使 webView 存在很大的兼容性问题。weView 都会存在内存泄露问题,在应用中只要使用一次,内存就不会被释放。一般的作法是为 webView 单独开一个进程,使用 AIDL 与应用的主进程进程通讯。webView 进程能够根据业务的需求,在合适的时机进行销毁。

 

 参考文献:

一、《Android进阶解密》

二、Android内存泄漏的八种可能 

相关文章
相关标签/搜索