内存泄露的优化

内存泄漏优化主要指两个方面:

  • 避免写出内存泄露的代码
  • 利用分析工具,像是LeakCanary,MAT,Memory Profiler找出潜在的内存泄漏的问题。

避免写出内存泄露的代码

避免写出内存泄露的代码,需要熟悉常见的内存泄露的场景,下面对常见的引起内存泄露的场景进行介绍。

静态变量导致的内存泄露

静态成员变量只有在类卸载的时候才会被回收,一般情况只有当当前进程结束的时候,类才会被卸载,所以一般情况下静态的成员变量只有在应用结束运行的时候才会被回收掉。如果静态成员变量持有某些对象的引用,那么被引用的对象不会被回收。

例子:

public class MainActivity extends Activity {
    static Context mContext = null;

    @Override
    public void onCreate(BundlesavedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext=this;
    }
}

或者

public class MainActivity extends Activity {
    static View view = null;

    @Override
    public void onCreate(BundlesavedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        view = new View(this);
    }
 
}

或者

public class MainActivity extends Activity {
    static Demo sInstance = null;

    @Override
    public void onCreate(BundlesavedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (sInstance == null) {
           sInstance= new Demo();
        }
    }
    class Demo {
        void doSomething() {
            System.out.print("dosth.");
        }
    }
}

单例模式引起的内存泄露

由于单例的静态特性,使得单例的生命周期和应用的生命周期一样,如果单例对象持有某个对象的引用,即使该对像已经不可用了,该对象也不能被回收,这就会引起内存泄露。
例子一:

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

上述并不是一个正确的单例的写法,只是为了说明引起内存泄露的情况。上述单例创建的时候需要传入一个context对象。Android中context有两种,两种上下文

当传入Application对应的Context的时候,因为ApplicationContext的生命周期和应用的生命周期一致,所以不会引起内存泄露。
当传入Activity对应的context的时候,当Activity被销毁之后,因为单例对象持有对context的引用,导致Activity无法被回收掉,引起内存泄露。

修改:

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

例子二:

public class TestManager {

    private ArrayList<OnDataArrivedListener> mOnDataArrivedListeners = new ArrayList<>();

    public interface OnDataArrivedListener{
        public void onDataArrived(Object object);
    }

    private TestManager(){}

    private static class SingleInstanceHolder{
        public static final TestManager SINGLEINSTANCE = new TestManager();
    }

    public static TestManager getInstance(){
       return SingleInstanceHolder.SINGLEINSTANCE;
    }

    public  synchronized void registeListener(OnDataArrivedListener onDataArrivedListener){
        if(!mOnDataArrivedListeners.contains(onDataArrivedListener)){
            mOnDataArrivedListeners.add(onDataArrivedListener);
        }
    }
    public synchronized void unregisteListener(OnDataArrivedListener onDataArrivedListener){
        mOnDataArrivedListeners.remove(onDataArrivedListener);
    }
    
}



public class MainActivity extends AppCompatActivity implements TestManager.OnDataArrivedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        TestManager.getInstance().registeListener(this);
    }

    @Override
    public void onDataArrived(Object object) {
        
    }
}

由于缺少反注册,Activity对象被单例模式的TestManager所持有,导致内存泄漏。

摘自《Android第一行代码》中的例子。

属性动画导致的内存泄露

属性动画中,使用了无限循环的动画效果,当Activity销毁的时候,没有调用cancel方法撤销动画,导致属性动画持有View,而View又持有Activity,从而导致内存泄露。

public class MainActivity extends AppCompatActivity {

    private TextView mTextView;

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

        mTextView = (TextView) findViewById(R.id.textView);

        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mTextView,"rotation",0,360).setDuration(2000);

        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);

        objectAnimator.start();

    }
    
     @Override
    protected void onDestroy() {
        super.onDestroy();
        
       // objectAnimator.cancel();
    }

}

摘自《Android第一行代码》中的例子。

非静态内部类引起的内存泄露

AsyncTask引起的内存泄露

AsyncTask引起的内存泄露

Thread引起的内存泄露

内存泄露之Thread

Handler造成的内存泄露

Handler引起的内存泄露

利用工具分析内存泄露

LeakCanary

LeakCanary使用详细教程(附Demo)
LeakCanary: 让内存泄露无所遁形
LeakCanary 中文使用说明
使用LeakCanary检测安卓中的内存泄漏(实战)
Android内存优化(六)LeakCanary使用详解
全新 LeakCanary 2 ! 完全基于 Kotlin 重构升级 !

MAT

Android群英传之神兵利器 - 6.14 内存泄露分析
Eclipse Memory Analyzer在Mac启动报错
Android内存泄露分析利器——MAT-有道备份

基本步骤:

打开Histogram或者DominatorTree-过滤-寻找数量或者RetainedHeap比较大的对象-查看引用链

AndroidStudio(利用Android Memory Monitor)

基于Android Studio的内存泄漏检测与解决全攻略
Android 应用内存泄漏的定位、分析与解决策略

观察内存泄露的demo:Optimization

AndroidStudio(利用Android Profiler)

Android Studio Profiler 使用的“官方”教程来了!

Android Studio Memory Profiler

使用内存性能分析器查看应用的内存使用情况

参考链接:

内存泄露浅析(一)
Android内存泄漏总结.md
常见的八种导致 APP 内存泄漏的问题
Android性能优化之常见的内存泄漏

Android性能优化之常见的内存泄漏
内存泄露从入门到精通三部曲之常见原因与用户实践

Android性能优化之常见的内存泄漏
Android之Handler内存泄漏分析及解决
Android 应用内存泄漏的定位、分析与解决策略

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器