Android内存泄露及解决方法总结

作者: Jinwong | 来源:发表于2017-07-23 20:57 被阅读417次

    1. 概述

    Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。

    2. 常见泄露类型

    2.1. 集合类泄露

    如果集合类仅仅有添加元素,而没有相应的删除机制,会导致内存被占用。当将集合中元素置空,但是集合因为持有对元素的引用,导致内存回收不,而发生内存泄露。解决方法是,可先删除元素然后置空,或者直接将集合置空。

    Android 中常见的集合类内存泄露有ValueAnimator调用addUpdateListener,EditText调用addTextChangedListener()而未注销监听导致内存泄露,他们代码如下

    
         //ValueAnimator
       public void addUpdateListener(AnimatorUpdateListener listener) {
            if (mUpdateListeners == null) {
                mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
            }
            mUpdateListeners.add(listener);
        }
        
        //EditText
        public void addTextChangedListener(TextWatcher watcher) {
            if (mListeners == null) {
                mListeners = new ArrayList<TextWatcher>();
            }
    
            mListeners.add(watcher);
        }
    

    2.2 静态变量引起的内存泄漏

    在java中静态变量的生命周期是在类加载时开始,类卸载时结束。换句话说,在android中其生命周期是在进程启动时开始,进程死亡时结束。所以在程序的运行期间,如果进程没有被杀死,静态变量就会一直存在,不会被回收掉。如果静态变量强引用了某个Activity中变量,那么这个Activity就同样也不会被释放,即便是该Activity执行了onDestroy(不要将执行onDestroy和被回收划等号)。这类问题的解决方案为:

    1. 寻找与该静态变量生命周期差不多的替代对象。
    2. 若找不到,将强引用方式改成弱引用

    2.2.1 单例引起的Context内存泄漏

    由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。如果单例中引用activity类型Context而非ApplicationContext,会导致ondestroy后不能被回收

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

    可以让传入的context 转化为ApplicationContext。

    2.2.2 静态Activity和View,drawable

    静态变量Activity和View会导致内存泄漏,在下面这段代码中对Activity的Context和TextView设置为静态对象,从而产生内存泄漏。

    public class MainActivity extends AppCompatActivity {
     
        private static Context context;
        private static TextView textView;
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            context = this;
            textView = new TextView(this);
        }
    }
    

    静态变量drawable 也类似,但android 4.0的Drawable.Java对setCallback的实现进行了软引用,避免了内存泄露:

      public final void setCallback(Callback cb){
    
            mCallback = newWeakReference<Callback> (cb);
    
    }
    

    2.2.3 非静态内部类创建静态实例

    public class MainActivity extends AppCompatActivity {
        private static TestResource mResource = null;
         @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if(mManager == null){
                mManager = new TestResource();
            }
                //...
        }
       
       class TestResource {
        //...
       }
    }
    

    因为非静态内部类默认会持有外部类的引用,而该非静态内部类TestResource又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。

    2.3. 线程造成内存泄露

    android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类/非静态内部类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露

    2.3.1 多线程

    如果多线程中含有外部activity的引用,当activity销毁后,而多线程任务还未执行完,因为线程持有对activity的引用而导致activity不能被回收掉

    2.3.2 内部线程造成内存泄露

    public class LeakActivity extends AppCompatActivity {
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_leak);
            leakFun();
        }
     
        private void leakFun(){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(Integer.MAX_VALUE);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    

    可以将leakFun()加上static 改成静态方法,让匿名内部类不会持有LeakActivity.this应用。

    2.3.1 handler

    public class LeakAty extends Activity {
     
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_leak);
        fetchData();
     
      }
     
      private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
          switch (msg.what) {
          case 0:
            // 刷新数据
            break;
          default:
            break;
          }
     
        };
      };
     
      private void fetchData() {
        //获取数据
        mHandler.sendEmptyMessage(0);
      }
    }
    

    mHandler 为匿名内部类实例,会引用外围对象LeakAty.this,如果该Handler在Activity退出时依然还有消息需要处理,那么这个Activity就不会被回收。

    如果当Handler为非静态内部类也会导致内存泄露,因为非静态内部类也会持有外部类的引用,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。

    2.3.2 AsyncTask

    AsyncTask 情况类似handler,都有由于非静态内部类,或者匿名内部类持有activity的引用,当activity退出时任务还未完成继续持有对activity的引用,导致activity不能回收。

    2.4. 动画导致的内存泄露

    Android在动画使用过程如果不注意也会经常导致内存泄露。

    例如属性动画onDestroy中未停止动画,这时候Activity会被View所持有,从而导致Activity无法被释放。解决方法onDestroy去去调用objectAnimator.cancel()来停止动画。

    public class LeakActivity extends AppCompatActivity {
    
       private TextView textView;
       @Override
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_leak);
           textView = (TextView)findViewById(R.id.text_view);
           ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
           objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
           objectAnimator.start();
       }
    }
    

    2.5 资源未关闭或注销引用内存泄露

    常见由于资源未关闭/注销导致内存泄露有

    • BraodcastReceiver
    • ContentObserver
    • File
    • 游标 Cursor
    • Stream
    • Bitmap
      ...

    这些资源应该打开,用完后马上关闭,或者在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

    2.6. 不良代码

    • 构造 Adapter 时,没有使用缓存的 convertView ,每次都在创建新的 converView
    • EventBus,RxJava等一些第三开源框架的使用,若是在Activity销毁之前没有进行解除订阅将会导致内存泄漏。
      ···

    3. 内存泄露解决措施

    3.1. 工具方面:

    1. LeakCanary 检测 Android 的内存泄漏
    2. Android Studio 的Monitor监测内存使用情况
    3. MAT分析heap的总内存占用大小来初步判断是否存在泄露

    3.2 代码方面

    1. 引入弱引用
    2. 使用合适的Context,尽量使用ApplicationContext
    3. 在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。
    4. 尽量避免使用 static成员变量
    5. 使用的资源及时关闭、注销
    6. 避免非静态内部类,可改成静态内部类
    7. 避免匿名内部类

    4. 参考:

    1. Android 内存泄漏总结
    2. Android内存泄漏终极解决篇(下)

    相关文章

      网友评论

        本文标题:Android内存泄露及解决方法总结

        本文链接:https://www.haomeiwen.com/subject/jxamkxtx.html