Android常见内存泄漏总结

Post on Jan 18, 2023 by Wei Lin

static变量持有对象

原理

静态变量的生命周期跟整个程序的生命周期一致。只要静态变量没有被销毁也没有置null,其对象就一直被保持引用,也就不会被垃圾回收,从而出现内存泄漏。


如以下代码中的static对象context,持有了MainActivity的引用。

public class MainActivity extends AppCompatActivity {

    private static Context context;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button mybotton = (Button) findViewById(R.id.mybotton);

        context = this;
    }
}

操作:退出MainActivity(执行onDestroy),等待5s后LeakCanary会报出内存泄漏。

内存泄漏5.png

解决方法

(1) 方法1:不使用static

将static修改为普通变量,不要在Android中使用static修饰View,或持有View及Context引用的类。


(2) 方法2:在onDestroy时将static变量置为null

(3) 方法3:被持有对象Context生命周期同APP生命周期一致

案例-static变量持有Activity

静态变量持有对象引用导致的内存泄漏,在开发中最常见的就是在单例模式中持有Context引用,实际上同static变量持有Activity情况一样。


(1) 代码示例

如下代码,调用UserClass.getInstance(this),通过传入参数context初始化,获取一个UserClass实例。

如果UserClass实例的生命周期比context的生命周期更长,就会导致context无法被回收,于是产生内存泄漏。


单例类SingletonObject.java

/**
 * 单例模式下,static对象持有Context引用
 */
public class SingletonObject {
    private static SingletonObject mInstance;
    private Context mContext;

    private SingletonObject(Context context) {
        this.mContext = context; // 会造成内存泄露
    }

    public static SingletonObject getInstance(Context context) {
        if (mInstance == null) {
            synchronized (SingletonObject.class) {
                if (mInstance == null) {
                    mInstance = new SingletonObject(context);
                }
            }
        }
        return mInstance;
    }

    public void func() {

    }
}


在Activity中创建单例对象:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);

    singleton = SingletonObject.getInstance(this);
    });
}

之后Destroy该Activity。


(2) LeakCanary输出

07-24 14:42:46.858 16277 16448 D LeakCanary: ====================================
07-24 14:42:46.858 16277 16448 D LeakCanary: HEAP ANALYSIS RESULT
07-24 14:42:46.858 16277 16448 D LeakCanary: ====================================
07-24 14:42:46.858 16277 16448 D LeakCanary: 1 APPLICATION LEAKS
07-24 14:42:46.858 16277 16448 D LeakCanary: 
07-24 14:42:46.858 16277 16448 D LeakCanary: References underlined with "~~~" are likely causes.
07-24 14:42:46.858 16277 16448 D LeakCanary: Learn more at https://squ.re/leaks.
07-24 14:42:46.858 16277 16448 D LeakCanary: 
07-24 14:42:46.858 16277 16448 D LeakCanary: 61620 bytes retained by leaking objects
07-24 14:42:46.858 16277 16448 D LeakCanary: Signature: 867b56275b1035df321aec52f55caec09e2de6b3
07-24 14:42:46.858 16277 16448 D LeakCanary: ┬───
07-24 14:42:46.858 16277 16448 D LeakCanary: │ GC Root: Thread object
07-24 14:42:46.858 16277 16448 D LeakCanary: │
07-24 14:42:46.858 16277 16448 D LeakCanary: ├─ android.os.HandlerThread instance
07-24 14:42:46.858 16277 16448 D LeakCanary: │    Leaking: NO (PathClassLoader↓ is not leaking)
07-24 14:42:46.858 16277 16448 D LeakCanary: │    Thread name: 'LeakCanary-Heap-Dump'
07-24 14:42:46.858 16277 16448 D LeakCanary: │    ↓ Thread.contextClassLoader
07-24 14:42:46.858 16277 16448 D LeakCanary: ├─ dalvik.system.PathClassLoader instance
07-24 14:42:46.858 16277 16448 D LeakCanary: │    Leaking: NO (SingletonObject↓ is not leaking and A ClassLoader is never leaking)
07-24 14:42:46.858 16277 16448 D LeakCanary: │    ↓ ClassLoader.runtimeInternalObjects
07-24 14:42:46.858 16277 16448 D LeakCanary: ├─ java.lang.Object[] array
07-24 14:42:46.858 16277 16448 D LeakCanary: │    Leaking: NO (SingletonObject↓ is not leaking)
07-24 14:42:46.858 16277 16448 D LeakCanary: │    ↓ Object[699]
07-24 14:42:46.858 16277 16448 D LeakCanary: ├─ com.demoapp.memoryleakdemo.SingletonObject class
07-24 14:42:46.858 16277 16448 D LeakCanary: │    Leaking: NO (a class is never leaking)
07-24 14:42:46.858 16277 16448 D LeakCanary: │    ↓ static SingletonObject.mInstance
07-24 14:42:46.858 16277 16448 D LeakCanary: │                             ~~~~~~~~~
07-24 14:42:46.858 16277 16448 D LeakCanary: ├─ com.demoapp.memoryleakdemo.SingletonObject instance
07-24 14:42:46.858 16277 16448 D LeakCanary: │    Leaking: UNKNOWN
07-24 14:42:46.858 16277 16448 D LeakCanary: │    Retaining 61.6 kB in 1138 objects
07-24 14:42:46.858 16277 16448 D LeakCanary: │    mContext instance of com.demoapp.memoryleakdemo.SecondActivity with mDestroyed = true
07-24 14:42:46.858 16277 16448 D LeakCanary: │    ↓ SingletonObject.mContext
07-24 14:42:46.858 16277 16448 D LeakCanary: │                      ~~~~~~~~
07-24 14:42:46.858 16277 16448 D LeakCanary: ╰→ com.demoapp.memoryleakdemo.SecondActivity instance
07-24 14:42:46.858 16277 16448 D LeakCanary:      Leaking: YES (ObjectWatcher was watching this because com.demoapp.memoryleakdemo.SecondActivity received
07-24 14:42:46.858 16277 16448 D LeakCanary:      Activity#onDestroy() callback and Activity#mDestroyed is true)
07-24 14:42:46.858 16277 16448 D LeakCanary:      Retaining 61.6 kB in 1137 objects
07-24 14:42:46.858 16277 16448 D LeakCanary:      key = 9b1dfd04-94b9-42fe-8fa6-67c7c2690cdc
07-24 14:42:46.858 16277 16448 D LeakCanary:      watchDurationMillis = 12309
07-24 14:42:46.858 16277 16448 D LeakCanary:      retainedDurationMillis = 7302
07-24 14:42:46.858 16277 16448 D LeakCanary:      mApplication instance of android.app.Application
07-24 14:42:46.858 16277 16448 D LeakCanary:      mBase instance of android.app.ContextImpl
07-24 14:42:46.858 16277 16448 D LeakCanary: ====================================


(3) 解决方法1:使用APP Context

保证传入的context生命周期与静态对象static UserClass一致,如下改为APP的生命周期,这样就与Activity无关了。

private UserClass(Context context) {
    this.mContext = context.getApplicationContext();
}


(4) 解决方法2:使用弱引用WeakReference

需要注意的是,使用弱引用后,最好在需要使用的时候才通过weakContext.get()取出对象,只在局部使用,不要赋值给单例对象的成员对象,否则也会单例的实例长期引用而得不到释放。

如下代码:

public class SingletonObject {
    private static SingletonObject mInstance;
    private Context mContext;
    private WeakReference<Context> weakContext;

    private SingletonObject(Context context) {
        //        this.mContext = context; // 会造成内存泄露
        //        this.mContext = context.getApplicationContext(); // 使用APP生命周期,不会内存泄漏
        weakContext = new WeakReference<>(context);
    }

    public static SingletonObject getInstance(Context context) {

        if (mInstance == null) {
            synchronized (SingletonObject.class) {
                if (mInstance == null) {
                    mInstance = new SingletonObject(context);
                }
            }
        }
        return mInstance;
    }

    public void func() {
        // 不要使用static SingletonObject mInstance的成员变量获取引用
        // mContext = weakContext.get(); 

        Context context = weakContext.get();
        if (context != null) {
            // TODO
        }
    }
}

案例-static view持有Activity

(1) 原理

当某个View初始化时耗费大量资源,而且要求Activity生命周期内保持不变,这个时候程序员可能会把view变成static,加载到视图树上(View Hierachy),像这样,当Activity被销毁时,应当释放资源。但很可能会带来泄露问题,View是跟Context紧密关联,使用不当就会出现泄露问题。


Context是什么时候给到View的呢?创建View的时候,View的构造函数中需要持有Context。有的时候是我们写代码时自己new View(),更多的时候是setContentView()时将Activity作为context传给View。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
}


(2) 解决方法

方法1:不要在Android中使用static修饰View;

方法2:在onDestroy时将static view 置为null。

非静态内部类持有外部引用

非静态内部类包括:成员内部类,局部(方法)内部类、 匿名内部类。

原理

非静态内部类对外部类会存在一个隐式引用。当该内部类中存在多线程任务时,若不需要再用到外部类(如Activity执行了onDestroy),而该隐式引用仍然保持了对外部类的引用,导致GC无法回收,于是发生内存泄漏。

如在使用Runnable、Handler、AsyncTask时,都有可能会使用匿名内部类。

解决方法

(1) 不使用内部类

将内部类放到外部的单独java文件中。


(2) 使用静态内部类

private static class Inner extends Thread {...}


(3) 退出时关闭子线程

@Override
protected void onDestroy() {
    super.onDestroy();
    inner.interrupt();
}

非静态内部类使用示例

代码示例:

(1) 示例1—成员内部类执行异步任务

public class SecondActivity extends Activity {

    private class Inner extends Thread {
        public void run() {
            try {
                sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

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

        findViewById(R.id.botton).setOnClickListener(v -> {
            Inner inner = new Inner();
            inner.start();
        });
    }
}


(2) 示例2—局部内部类执行异步任务

public class SecondActivity extends Activity {

    public void method(){
        class Inner extends Thread {

            @Override
            public void run() {
                try {
                    sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        new Inner().start();
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        method();
    }
}

局部内部类使用场景较少,可以忽略。


(3) 示例3—匿名内部类执行异步任务

public class SecondActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

案例-Handler造成的内存泄漏

Handler在Android中是使用频率较高的一种机制,它是工作线程与UI线程之间通讯的桥梁。

而Handler造成的内存泄漏的情况其本质是由于非静态内部类持有对象引用导致的内存泄漏,包含以下几种场景:

  • 创建Handler对象,通过Handler.postDelayed(new Runnable(){},delayMillis)发送消息;
  • 使用匿名Handler内部类;
  • 使用非静态内部类自定义Handler子类。

场景1—延迟执行消息

​ 使用Handler内部类并调用sendMessageDelayed()或postDelayed()延时执行消息。由于延迟执行,可能会造成Handler内部类隐式持有的引用的生命周期大于Activity的生命周期,在Activity退出之后,GC便无法对内存进行释放,于是发生了内存泄漏。


(1) 代码示例

内部类InnerHandler:

class InnerHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        Log.d(TAG,"Get Message");
    }
}

findViewById(R.id.handler_leak).setOnClickListener(v -> {
    // Handler造成内存泄露,内部类且延时执行
    InnerHandler handler = new InnerHandler();
    handler.sendMessageDelayed(Message.obtain(), 10000);
});


(2) LeakCanary输出

07-24 16:00:22.843  7816  8006 D LeakCanary: ====================================
07-24 16:00:22.843  7816  8006 D LeakCanary: HEAP ANALYSIS RESULT
07-24 16:00:22.843  7816  8006 D LeakCanary: ====================================
07-24 16:00:22.843  7816  8006 D LeakCanary: 1 APPLICATION LEAKS
07-24 16:00:22.843  7816  8006 D LeakCanary: 
07-24 16:00:22.843  7816  8006 D LeakCanary: References underlined with "~~~" are likely causes.
07-24 16:00:22.843  7816  8006 D LeakCanary: Learn more at https://squ.re/leaks.
07-24 16:00:22.843  7816  8006 D LeakCanary: 
07-24 16:00:22.843  7816  8006 D LeakCanary: 7865 bytes retained by leaking objects
07-24 16:00:22.843  7816  8006 D LeakCanary: Signature: a822275d9d3ea6c7dc9ac455e66928918cef24c8
07-24 16:00:22.843  7816  8006 D LeakCanary: ┬───
07-24 16:00:22.843  7816  8006 D LeakCanary: │ GC Root: Thread object
07-24 16:00:22.843  7816  8006 D LeakCanary: │
07-24 16:00:22.843  7816  8006 D LeakCanary: ├─ android.os.HandlerThread instance
07-24 16:00:22.843  7816  8006 D LeakCanary: │    Leaking: NO (PathClassLoader↓ is not leaking)
07-24 16:00:22.843  7816  8006 D LeakCanary: │    Thread name: 'LeakCanary-Heap-Dump'
07-24 16:00:22.843  7816  8006 D LeakCanary: │    ↓ Thread.contextClassLoader
07-24 16:00:22.843  7816  8006 D LeakCanary: ├─ dalvik.system.PathClassLoader instance
07-24 16:00:22.843  7816  8006 D LeakCanary: │    Leaking: NO (ToastEventListener↓ is not leaking and A ClassLoader is never leaking)
07-24 16:00:22.843  7816  8006 D LeakCanary: │    ↓ ClassLoader.runtimeInternalObjects
07-24 16:00:22.843  7816  8006 D LeakCanary: ├─ java.lang.Object[] array
07-24 16:00:22.843  7816  8006 D LeakCanary: │    Leaking: NO (ToastEventListener↓ is not leaking)
07-24 16:00:22.843  7816  8006 D LeakCanary: │    ↓ Object[2053]
07-24 16:00:22.843  7816  8006 D LeakCanary: ├─ leakcanary.ToastEventListener class
07-24 16:00:22.843  7816  8006 D LeakCanary: │    Leaking: NO (a class is never leaking)
07-24 16:00:22.843  7816  8006 D LeakCanary: │    ↓ static ToastEventListener.toastCurrentlyShown
07-24 16:00:22.843  7816  8006 D LeakCanary: │                                ~~~~~~~~~~~~~~~~~~~
07-24 16:00:22.843  7816  8006 D LeakCanary: ├─ android.widget.Toast instance
07-24 16:00:22.843  7816  8006 D LeakCanary: │    Leaking: UNKNOWN
07-24 16:00:22.843  7816  8006 D LeakCanary: │    Retaining 7.9 kB in 136 objects
07-24 16:00:22.843  7816  8006 D LeakCanary: │    mContext instance of com.demoapp.memoryleakdemo.MainActivity with mDestroyed = false
07-24 16:00:22.843  7816  8006 D LeakCanary: │    ↓ Toast.mNextView
07-24 16:00:22.843  7816  8006 D LeakCanary: │            ~~~~~~~~~
07-24 16:00:22.843  7816  8006 D LeakCanary: ╰→ android.widget.FrameLayout instance
07-24 16:00:22.843  7816  8006 D LeakCanary:      Leaking: YES (ObjectWatcher was watching this because android.widget.FrameLayout received
07-24 16:00:22.843  7816  8006 D LeakCanary:      View#onDetachedFromWindow() callback)
07-24 16:00:22.843  7816  8006 D LeakCanary:      Retaining 7.9 kB in 134 objects
07-24 16:00:22.843  7816  8006 D LeakCanary:      key = 5405cdad-a05c-4c5e-93ff-af27575c68d6
07-24 16:00:22.843  7816  8006 D LeakCanary:      watchDurationMillis = 51683
07-24 16:00:22.843  7816  8006 D LeakCanary:      retainedDurationMillis = 46677
07-24 16:00:22.843  7816  8006 D LeakCanary:      View not part of a window view hierarchy
07-24 16:00:22.843  7816  8006 D LeakCanary:      View.mAttachInfo is null (view detached)
07-24 16:00:22.843  7816  8006 D LeakCanary:      View.mWindowAttachCount = 1
07-24 16:00:22.843  7816  8006 D LeakCanary:      mContext instance of com.demoapp.memoryleakdemo.MainActivity with mDestroyed = false
07-24 16:00:22.843  7816  8006 D LeakCanary: ====================================

场景2-使用post发送消息

如果使用的是自定义的非内部类的Handler类,则不会隐式持有外部类的引用,但如果使用post方式发送消息,由于需要使用Runable接口,而Runable此时是匿名内部类,所以也会隐式持有外部类的引用。

handler.post(new Runnable() {
    @Override
    public void run() {
        
    }
});

如果调用的是postDelayed()或执行消息时耗时太久,而外部类Activity被finish(),则也会造成内存泄漏。

解决方法1:不使用内部Handler类

在外部类自定义一个MyHandler,则实例化的时候不会包含所在类的隐式引用,但注意在通过post()使用Runnable接口时,Runnable是一个匿名内部类。

解决方法2:使用静态内部类

使用静态内部类static class MyHandler extends Handler {…}。

但静态内部类无法持有外部非静态对象的引用,因此无法在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference),GC回收的时候会忽略弱引用。

class MyHandler extends Handler {
    WeakReference<Context> activityReference;
    public MyHandler(Context context) {
        activityReference = new WeakReference<Context>(context);
    }

    @Override
    public void handleMessage(Message msg) {
        Context context = activityReference.get();
        if (context != null) {
            //TODO
        }
    }
}

解决方法3-使用外部Runable

不使用匿名内部类,特别是在使用new Runnable() {}时,可以在外部实现Runnable接口中的run()。

案例-非静态内部类创建子线程&耗时任务

非静态内部类和多线程同时使用容易造成内存泄漏,如上述示例。

在使用子线程时通常会通过以下方式创建子线程:

  • 使用成员内部类继承Thread类并重写Runnable接口,然后再实例化;
  • 或者通过匿名内部类创建Thread对象,同时会重写Runnable接口。


当子线程中的任务时长超过了外部类所在线程的生命周期时,由于非静态内部类又同时持有外部类的引用,于是便会发生内存泄漏。情况如上示例1、3。

代码复现-内部Thread类

(1) 代码

内部线程类InnerThread:

private class InnerThread extends Thread {
    public void run() {
        try {
            sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


开启子线程任务:

findViewById(R.id.thread_leak).setOnClickListener(v -> {
    // 非静态内部类Thread导致的内存泄漏
    InnerThread innerThread=new InnerThread();
    innerThread.start();
});


(2) LeakCanary输出

07-24 16:18:31.593 12808 12952 D LeakCanary: ====================================
07-24 16:18:31.593 12808 12952 D LeakCanary: HEAP ANALYSIS RESULT
07-24 16:18:31.593 12808 12952 D LeakCanary: ====================================
07-24 16:18:31.593 12808 12952 D LeakCanary: 1 APPLICATION LEAKS
07-24 16:18:31.593 12808 12952 D LeakCanary: 
07-24 16:18:31.593 12808 12952 D LeakCanary: References underlined with "~~~" are likely causes.
07-24 16:18:31.593 12808 12952 D LeakCanary: Learn more at https://squ.re/leaks.
07-24 16:18:31.593 12808 12952 D LeakCanary: 
07-24 16:18:31.593 12808 12952 D LeakCanary: 225759 bytes retained by leaking objects
07-24 16:18:31.593 12808 12952 D LeakCanary: Signature: 43fb2b26c7c8de621843440a9b8bafc46249dfa8
07-24 16:18:31.593 12808 12952 D LeakCanary: ┬───
07-24 16:18:31.593 12808 12952 D LeakCanary: │ GC Root: Thread object
07-24 16:18:31.593 12808 12952 D LeakCanary: │
07-24 16:18:31.593 12808 12952 D LeakCanary: ├─ com.demoapp.memoryleakdemo.SecondActivity$InnerThread instance
07-24 16:18:31.593 12808 12952 D LeakCanary: │    Leaking: UNKNOWN
07-24 16:18:31.593 12808 12952 D LeakCanary: │    Retaining 225.9 kB in 4452 objects
07-24 16:18:31.593 12808 12952 D LeakCanary: │    Thread name: 'Thread-3'
07-24 16:18:31.593 12808 12952 D LeakCanary: │    this$0 instance of com.demoapp.memoryleakdemo.SecondActivity with mDestroyed = true
07-24 16:18:31.593 12808 12952 D LeakCanary: │    ↓ SecondActivity$InnerThread.this$0
07-24 16:18:31.593 12808 12952 D LeakCanary: │                                 ~~~~~~
07-24 16:18:31.593 12808 12952 D LeakCanary: ╰→ com.demoapp.memoryleakdemo.SecondActivity instance
07-24 16:18:31.593 12808 12952 D LeakCanary:      Leaking: YES (ObjectWatcher was watching this because com.demoapp.memoryleakdemo.SecondActivity received
07-24 16:18:31.593 12808 12952 D LeakCanary:      Activity#onDestroy() callback and Activity#mDestroyed is true)
07-24 16:18:31.593 12808 12952 D LeakCanary:      Retaining 225.8 kB in 4448 objects
07-24 16:18:31.593 12808 12952 D LeakCanary:      key = 9a8970b2-182e-49b6-8f26-e54456a3cbe3
07-24 16:18:31.593 12808 12952 D LeakCanary:      watchDurationMillis = 7878
07-24 16:18:31.593 12808 12952 D LeakCanary:      retainedDurationMillis = 2874
07-24 16:18:31.593 12808 12952 D LeakCanary:      mApplication instance of android.app.Application
07-24 16:18:31.593 12808 12952 D LeakCanary:      mBase instance of androidx.appcompat.view.ContextThemeWrapper
07-24 16:18:31.593 12808 12952 D LeakCanary: ====================================

解决方法1-不使用内部Thread类

不使用成员内部类,使用在外部继承Thread类并重写Runnable接口。


自定义外部MyThread类:

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


创建并开启线程

findViewById(R.id.thread_leak).setOnClickListener(v -> {
    // 非静态内部类Thread导致的内存泄漏
    InnerThread innerThread=new InnerThread();
    innerThread.start();

    // 使用外部继承的Thread类避免内存泄漏
    MyThread thread=new MyThread();
    thread.start();
});

解决方法2-使用静态内部Thread类

参考Handler。

案例-HandlerThread泄漏

(1) HandlerThread介绍

HandlerThread的封装其实也是为了解决线程通信,本质还是线程,只不多内部自动建立了Looper。普通子Thread如果需要接收消息,需要手动调用Looper.prepare()、Looper.loop()方法。而HandlerThread只需要创建并开启,就可以进行线程通信了。

HandlerThread对象通常也会被定义为内部类,所以也容易出现内存泄漏问题。


(2) 避免内存泄漏

  • 方法1:退出时调用HandlerThread.quit()

在Activity的onDestroy方法中,手动调用HandlerThread的quit方法。

当我们调用HandlerThread的quit方法方法时,实际就是执行了MessageQueue中的removeAllMessagesLocked方法,把MessageQueue消息池中的所有消息全部清空,无论是延时消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。


在HandlerThread中还有一个方法:quitSafely,实际执行的是removeAllFutureMessageLocked()方法,只会清空MessageQueue消息池中所有的延迟消息,并将所有非延迟消息派发出去让Handler处理,相比于quit()更安全一些。

具体调用哪个方法,一般看实际业务需求。但既然Activity都退出了,在执行消息也没什么意义了。


  • 方法2:将HandlerThread定义为静态内部类


  • 方法3:使用ApplicationContext

资源未关闭

原理

​ 对于使用了BraodcastReceiver, ContentObserver, File, Cursor, Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

解决方法

在onDestroy()中销毁或将引用置null。

案例-BroadcastReceiver

如下代码,在Activity中注册广播,需要在onDestroy()调用unregisterReceiver(myReceiver)才能防止内存泄漏。

public class SecondActivity extends Activity {
    String TAG = "SecondActivity";
    MyReceiver myReceiver;

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

        findViewById(R.id.botton).setOnClickListener(v -> {
            myReceiver = new MyReceiver();
            IntentFilter intentFilter = new IntentFilter("com.example.broadcast.ACTION1");
            registerReceiver(myReceiver, intentFilter);
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(myReceiver);
    }
}


如果不取消注册,会打印以下日志:

com.demoapp.memoryleakdemo E/ActivityThread: Activity com.demoapp.memoryleakdemo.SecondActivity has leaked IntentReceiver com.demoapp.memoryleakdemo.MyReceiver@b6929cf that was originally registered here. Are you missing a call to unregisterReceiver()?
    android.app.IntentReceiverLeaked: Activity com.demoapp.memoryleakdemo.SecondActivity has leaked IntentReceiver com.demoapp.memoryleakdemo.MyReceiver@b6929cf that was originally registered here. Are you missing a call to unregisterReceiver()?

实际测试中发现,LeakCanary并不会发出泄漏警告,因为LeakCanary只对Activity等类进行监控。

其它情况

集合类

集合类添加对象后,于是持有该对象的引用。如果该集合对象的生命周期长于其中的对象,就会导致该集合中的对象无法被回收,从而发生内存泄漏。


解决方法:

在集合中使用完该元素之后将其从集合中删除,等所有元素都使用完之后,将集合置空。

objectList.clear();

objectList = null;

其它

非静态内部类持有外部引用

非静态内部类依赖于外部类,没有外部类就不能创建内部类,所以内部类需要持有外部类的引用,可以从编译结果中验证一下。


内部类虽然和外部类写在同一个文件中, 但是编译完成后, 还是生成各自的class文件,内部类通过this访问外部类的成员。

  • 在编译时,编译器自动为内部类添加一个成员变量(this.this$0),这个成员变量的类型和外部类的类型相同,这个成员变量就是指向外部类对象(this)的引用;
  • 编译器自动为内部类的构造方法添加一个参数(Outer var1), 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值;
  • 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用(this.this$0 = var1;)。


在Outer.java中写入以下代码:

public class Outer {
    private class Inner{
        public String str;
        public Inner(String str){
            this.str=str;
        }
        public void innerFunc(String str){
            System.out.println(str);
        }
    }
}


使用javac Outer.java命令进行编译。编译后产生两个class文件,分别为Outer.class和Outer$Inner.class。

public class Outer {
    public Outer() {
    }

    private class Inner {
        public String str;

        public Inner(String var2) {
            this.str = var2;
        }

        public void innerFunc(String var1) {
            System.out.println(var1);
        }
    }
}

class Outer$Inner {
    public String str;

    public Outer$Inner(Outer var1, String var2) {
        this.this$0 = var1;
        this.str = var2;
    }

    public void innerFunc(String var1) {
        System.out.println(var1);
    }
}


如Outer$Inner.class文件所示,其中this.this中的第一个this就是内部类,this.this表示外部类的引用。


一般在非静态内部类中执行异步(多线程)任务才会导致内存泄漏。因为若不是多线程任务,则外部类的生命周期必然>=内部类生命周期,最终情况下肯定不会发生内存泄漏。

为什么静态内部类不持有外部类的引用?

在Outer.java中写入以下代码:

public class Outer {
    private static class Inner{
        public String str;
        public Inner(String str){
            this.str=str;
        }
        public void innerFunc(String str){
            System.out.println(str);
        }
    }
}

使用javac Outer.java命令进行编译。编译后产生两个class文件,分别为Outer.class和Outer$Inner.class。

public class Outer {
    public Outer() {
    }

    private class Inner {
        public String str;

        public Inner(String var2) {
            this.str = var2;
        }

        public void innerFunc(String var1) {
            System.out.println(var1);
        }
    }
}

class Outer$Inner {
    public String str;

    public Outer$Inner(String var1) {
        this.str = var1;
    }

    public void innerFunc(String var1) {
        System.out.println(var1);
    }
}

如Outer$Inner.class文件所示,其中内部类的class文件中并不像非静态内部类那样有this.this。内部类与外部类在编译后是独立存在的。

WeakReference使用

​ WeakReference 如字面意思,弱引用。当一个对象仅仅被weak reference(弱引用)指向, 而没有任何其他strong reference(强引用)指向的时候, 如果这时GC运行, 那么这个对象就会被回收,不论当前的内存空间是否足够,这个对象都会被回收。


初始化:

WeakReference<Context> weakContext;
weakContext = new WeakReference<>(context);

取出被引用对象:

Context mContext = weakContext.get();
if (mContext != null) {
    // TODO
}


如果弱引用的对象被回收,则get()方法将返回null。所以在获取弱引用对象后,一定要判断是否为null,以免出现NullPointerException异常导致Crash。

注:在执行weakContext.get()后mContext变量则获取到了强引用,之后仍要注意内存泄漏问题。