概述

内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。 

首先使用leakcanary来检测内存泄漏

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'

使用当前版本的leakcanary无需install直接可以使用(下一篇文章将会具体讲解leakcanary的使用及其原理)

第一种非静态内部类创建静态实例导致的内存泄漏

非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。

public class TestLeakActivity extends Activity {

    private static TestResource mResource = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        MyApplication.getRefWatcher(this).watch(this);
        setContentView(R.layout.activity_test_leak);
        initData();
    }

    private void initData() {
        if (mResource == null){
            mResource = new TestResource();
        }
        findViewById(R.id.finish_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
    private class TestResource{

    }
}

当应用启动之后,关闭按钮发现leakcanary打印出如下(No是没有Yes是有内存的泄漏,UNKNOWN表示可能出现了内存泄漏)

====================================
 HEAP ANALYSIS RESULT
 ====================================
 1 APPLICATION LEAKS

 References underlined with "~~~" are likely causes.
 Learn more at https://squ.re/leaks.

 105602 bytes retained by leaking objects
 Signature: d8d8d1ffbf342e9bb5d82c1f67b33795b6105
 ┬───
 │ GC Root: Local variable in native code
 │
 ├─ android.os.HandlerThread instance
 │    Leaking: NO (PathClassLoader↓ is not leaking)
 │    Thread name: 'LeakCanary-Heap-Dump'
 │    ↓ HandlerThread.contextClassLoader
 ├─ dalvik.system.PathClassLoader instance
 │    Leaking: NO (TestLeakActivity↓ is not leaking and A ClassLoader is never leaking)
 │    ↓ PathClassLoader.runtimeInternalObjects
 ├─ java.lang.Object[] array
 │    Leaking: NO (TestLeakActivity↓ is not leaking)
 │    ↓ Object[].[519]
 ├─ com.example.myapplication.TestLeakActivity class
 │    Leaking: NO (a class is never leaking)
 │    ↓ static TestLeakActivity.mResource
 │                              ~~~~~~~~~
 ├─ com.example.myapplication.TestLeakActivity$TestResource instance
 │    Leaking: UNKNOWN
 │    Retaining 105614 bytes in 1636 objects
 │    this$0 instance of com.example.myapplication.TestLeakActivity with mDestroyed = true
 │    ↓ TestLeakActivity$TestResource.this$0
 │                                    ~~~~~~
 ╰→ com.example.myapplication.TestLeakActivity instance
      Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received
      Activity#onDestroy() callback and Activity#mDestroyed is true)
      Retaining 105602 bytes in 1635 objects
      key = 6382ce72-b981-4a84-a5f7-fd1971479092
      watchDurationMillis = 12949
      retainedDurationMillis = 7919
      mApplication instance of com.example.myapplication.MyApplication
      mBase instance of android.app.ContextImpl, not wrapping known Android context
 ====================================
 0 LIBRARY LEAKS

 A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
 See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
 ====================================

从GC ROOTS向下搜索,一直去找引用链,如果某一个对象跟GC Roots没有任何引用链相连时,就证明对象是”不可达“的,可以被回收。

打印出来的log发现在TestLeakAcitvity执行完ondestory是有一个UNKNOWN内存的泄漏TestResource.this

解决方案

可以将非静态内部类改成静态内部类

private static class TestResource{

}

第二种单例模式导致的内存泄漏

创建一个单例

public class Singleton {
    private static Singleton sInstance;
    private Context mContext;

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

    public static Singleton getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new Singleton(context);
        }
        return sInstance;
    }
    public void test(){
        mContext.getContentResolver();
    }
}

在Activity中执行

public class TestLeakActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_leak);
        Singleton.getInstance(this).test();
    }
}

执行完单例之后看一下leakcanary

LeakCanary: 1 APPLICATION LEAKS
LeakCanary: ┬───
LeakCanary: │ GC Root: Local variable in native code
LeakCanary: │ ...            
LeakCanary: ├─ com.example.myapplication.Singleton instance
LeakCanary: │    Leaking: UNKNOWN
LeakCanary: │    Retaining 112915 bytes in 1674 objects
LeakCanary: │    mContext instance of com.example.myapplication.TestLeakActivity with mDestroyed = true
LeakCanary: │    ↓ Singleton.mContext
LeakCanary: │                ~~~~~~~~
LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
LeakCanary:      Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received
LeakCanary:      Activity#onDestroy() callback and Activity#mDestroyed is true)
LeakCanary:      Retaining 112903 bytes in 1673 objects
LeakCanary:      key = 297d72a4-5e9d-41bf-baba-6856105c73f0
LeakCanary:      watchDurationMillis = 5176
LeakCanary:      retainedDurationMillis = 139
LeakCanary:      mApplication instance of com.example.myapplication.MyApplication
LeakCanary:      mBase instance of android.app.ContextImpl, not wrapping known Android context
LeakCanary: ====================================
LeakCanary: 0 LIBRARY LEAKS

发现UNKNOW 出现地方为Singleton中的mContext,说明当前的mContext可能没有释放掉,但是后续又看到YES说明当前确实没有释放掉

解决方案

将context变成ApplicationContext,当应用关掉之后,会自动回收ApplicationContext

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

第三种动画

public class TestLeakActivity extends Activity {
    private ImageView mAlphaImage;
    private ValueAnimator warningAnimation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_leak);
        initData();
        startValueAnimatorAnim(mAlphaImage);
    }
    
    private void initData() {
        mAlphaImage = (ImageView)findViewById(R.id.alphaImage);
        findViewById(R.id.finish_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBackPressed();
            }
        });
    }
    /**
     * 在一段时间内生成连续的值完成view的缩放
     * @param v
     */
    public void startValueAnimatorAnim(final View v) {
        //不改变属性大小,只在一段事件内生成连续的值
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);
        animator.setDuration(50000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //百分比对应的值
                float value = (float) animation.getAnimatedValue();
                v.setScaleX(0.5f + value / 200);
                v.setScaleY(0.5f + value / 200);
            }
        });
        animator.start();
    }
}

发生的条件是在50秒内关闭当前应用,就会出现内存泄漏 

LeakCanary: Found 1 paths to retained objects
...
LeakCanary: ┬───
LeakCanary: │ GC Root: System class
LeakCanary: │
LeakCanary: │ ...            
LeakCanary: ├─ com.example.myapplication.TestLeakActivity$3 instance
LeakCanary: │    Leaking: UNKNOWN
LeakCanary: │    Anonymous class implementing android.animation.ValueAnimator$AnimatorUpdateListener
LeakCanary: │    ↓ TestLeakActivity$3.this$0
LeakCanary: │                         ~~~~~~
LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
LeakCanary:      Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)

说明该动画的AnimatorUpdateListener 监听还持有TestLeakActivity

解决方案

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

原因是当前还持有事件监听的引用,执行完cancel会去清除当前监听的引用。

第四种Handler导致的内存泄漏

本地测试发现,这种是不一定会内存泄漏的,除非MessageQueue还持有当前应用的activity 才会内存泄漏,具体出现原因以及修改方案我从网上找到一篇写的比较好的文章

路径如下(Handler为什么可能会造成内存泄漏以及可用的四种解决方法)

第五种匿名内部类引起的内存泄漏

public class TestLeakActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_leak);
        
         new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模拟耗时操作
                    Thread.sleep(15000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

发生条件在15秒内关闭程序就会发生内存泄漏

LeakCanary: 1 APPLICATION LEAKS
LeakCanary: ┬───
LeakCanary: │ GC Root: Local variable in native code
LeakCanary: │
LeakCanary: ├─ java.lang.Thread instance
LeakCanary: │    Leaking: UNKNOWN
LeakCanary: │    Thread name: 'Thread-1'
LeakCanary: │    ↓ Thread.target
LeakCanary: │             ~~~~~~
LeakCanary: ├─ com.example.myapplication.TestLeakActivity$1 instance
LeakCanary: │    Leaking: UNKNOWN
LeakCanary: │    Anonymous class implementing java.lang.Runnable
LeakCanary: │    ↓ TestLeakActivity$1.this$0
LeakCanary: │                         ~~~~~~
LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
LeakCanary:      Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)

说明当前Runnable 还持有TestLeakActivity

解决方案

public class TestLeakActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_leak);
        new Thread(new MyRunnable()).start();
    }



    private static class MyRunnable implements Runnable {

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

静态内存类不会持有当前的引用

推荐内部类(AsyncTask Timer 等内存泄漏文章例子:android-内部类导致的内存泄漏实战解析)

第六种对象的注册但是没有反注册导致的内存泄漏

public class TestLeakActivity extends Activity {
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_leak);
        initData();
    }

    @Override
    protected void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Log.i("LeakCanary", "message is " + event.getMessage());
        // 更新界面
        textView.setText(event.getMessage());
    }
    
    private void initData() {
        textView = (TextView)findViewById(R.id.text);
        findViewById(R.id.finish_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new MessageEvent("Hello EventBus!"));
                onBackPressed();
            }
        });
    }
   
}

发生条件注册EventBus 但是没有反注册

D LeakCanary: 1 APPLICATION LEAKS
D LeakCanary: ┬───
D LeakCanary: │ GC Root: Local variable in native code
D LeakCanary: ├─ org.greenrobot.eventbus.EventBus class
D LeakCanary: │    Leaking: NO (a class is never leaking)
D LeakCanary: │    ↓ static EventBus.defaultInstance
D LeakCanary: │                      ~~~~~~~~~~~~~~~
D LeakCanary: ├─ org.greenrobot.eventbus.EventBus instance
D LeakCanary: │    Leaking: UNKNOWN
D LeakCanary: │    ↓ EventBus.typesBySubscriber
D LeakCanary: │               ~~~~~~~~~~~~~~~~~
D LeakCanary: ├─ java.util.HashMap instance
D LeakCanary: │    Leaking: UNKNOWN
D LeakCanary: │    ↓ HashMap.table
D LeakCanary: │              ~~~~~
D LeakCanary: ├─ java.util.HashMap$Node[] array
D LeakCanary: │    Leaking: UNKNOWN
D LeakCanary: │    ↓ HashMap$Node[].[0]
D LeakCanary: │                     ~~~
D LeakCanary: ├─ java.util.HashMap$Node instance
D LeakCanary: │    Leaking: UNKNOWN
D LeakCanary: │    ↓ HashMap$Node.key
D LeakCanary: │                   ~~~
D LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
D LeakCanary:      Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)

解决方案

@Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

像这种反注册的例子(广播,otto,以及ContentProvider等等,都需要在onDestory里面反注册) 

第七种静态集合引发的内存泄漏

public class TestLeakActivity extends Activity {
    
    private static List<Activity> list;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_leak);
        list = new ArrayList<>();
        for (int i=0;i<100;i++){
            list.add(this);
        }
    }
}

当关闭应用程序发生内存泄漏

LeakCanary: 1 APPLICATION LEAKS
 LeakCanary: ┬───
 LeakCanary: │ GC Root: Local variable in native code
 LeakCanary: ├─ java.util.ArrayList instance
 LeakCanary: │    Leaking: UNKNOWN
 LeakCanary: │    ↓ ArrayList.elementData
 LeakCanary: │                ~~~~~~~~~~~
 LeakCanary: ├─ java.lang.Object[] array
 LeakCanary: │    Leaking: UNKNOWN
 LeakCanary: │    ↓ Object[].[0]
 LeakCanary: │               ~~~
 LeakCanary: ╰→ com.example.myapplication.TestLeakActivity instance
 LeakCanary:      Leaking: YES (ObjectWatcher was watching this because com.example.myapplication.TestLeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)

 发现当前ArrayList持有Activity

解决方案

protected void onDestroy() {
        if (list!=null){
            list.clear();
        }  
    }

第八种资源未关闭

本地测试没有复现出来内存泄漏,可能是我的资源比较少,或者比较小,释放的快

在android中,资源性对象比如Cursor、File、Bitmap、视频等,系统都用了一些缓冲技术,在使用这些资源的时候,如果我们确保自己不再使用这些资源了,要及时关闭,否则可能引起内存泄漏。因为有些操作不仅仅只是涉及到Dalvik虚拟机,还涉及到底层C/C++等的内存管理,不能完全寄希望虚拟机帮我们完成内存管理。

以上就是总结的内存泄漏!下一章将讲解LeakCanary的使用,及其原理