Android 内存泄露成因和解决办法

Java内存泄露引起原因

内存泄露是指无用对象(不再使用的对象)持续占有内存 或 无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。

Java内存泄露根本原因

长生命周期的对象持有短生命周期对象的引用,此时就很有可能发生内存泄露。尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这是java中内存泄露的常见场景。

导致内存泄露的情形

  • 静态变量
  • 单例模式
  • 属性动画
  • 注册某个对象后未注销
  • 没清理集合中对象
  • 资源对象没关闭造成的内存泄露
  • Bitmap使用不当
  • 构造Adapter时,没有使用缓存的 convertView
  • 非静态内部类导致内存泄露
  • WebView造成内存泄露

静态变量

public class MainActivity extends Activity{  
        public static Context mContext;  
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            mContext = this;  
        }  
    }

解决办法:1使用Application的Context。 2慎用statistic关键字

单例模式

RetrofitHelper.getInstance(MainActivity.this)

这里写图片描述

解决办法:1使用Application的Context。不使用Activity的Context。

属性动画

属性动画中有一类无限循环的动画,如果在Activity 中播放此类动画却没有在onDestroy 中去停止动画,那么动画会一直播放下去的。尽管已近无法在界面上看到动画效果,但这个时候的Activity 的View会被动画持有,而View有持有Activity ,最终无法释放。

 public class HomeActivity extends Activity {  


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

    ObjectAnimator animator = ObjectAnimator.ofFloat(btn_home, "ratation", 0, 360).setDuration(2000);  
    animator.setRepeatCount(ValueAnimator.INFINITE);  
    animator.start();  
 }  
} 

解决方案:在当前Activity的onDestroy()方法中取消动画:animator.cancel()。

注册某个对象后未注销

注册广播、注册观察者,未进行注销会导致Gc无法回收内存泄露

解决方案:进行注销

没清理集合中对象

我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

解决方案:将集合置为 null ,避免使用static.

资源对象没关闭造成的内存泄露

资源性对象如Cursor、File、Socket,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。

解决方案:在 finalize(),调用它的close()函数

Bitmap使用不当

  • 及时销毁

    系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”

  • 设置一定的采样率

    设置一定的采样率,可以大大减小占用的内存

    解决方案: 在用完Bitmap时,要及时的recycle掉; 设置一定的采样率

构造Adapter时,没有使用缓存的 convertView

如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支

解决方案: 复用convertView

非静态内部类导致内存泄露

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

非静态内部类导致的内存泄露在Android开发中有一种典型的场景就是使用Handler,很多开发者在使用Handler是这样写的:

public class MainActivity extends AppCompatActivity {

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

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // 做相应逻辑
            }
        }
    };
}

熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueueLooper都是与线程相关联的,MessageQueueLooper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露

解决方案:1.通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式

public class MainActivity extends AppCompatActivity {

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private static class MyHandler extends Handler {

        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    // 做相应逻辑
                }
            }
        }
    }
}

mHandler通过弱引用的方式持有Activity,当GC执行垃圾回收时,遇到Activity就会回收并释放所占据的内存单元。这样就不会发生内存泄露了。

上面的做法确实避免了Activity导致的内存泄露,发送的msg不再已经没有持有Activity的引用了,但是msg还是有可能存在消息队列MessageQueue中,所以更好的是在Activity销毁时就将mHandler的回调和发送的消息给移除掉。

解决方案:2.在Activity销毁时就将mHandler的回调和发送的消息给移除掉

Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}

非静态内部类造成内存泄露还有一种情况就是使用Thread或者AsyncTask。

比如在Activity中直接new一个子线程Thread:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 模拟相应耗时逻辑
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

这种方式新建的子线程Thread和AsyncTask都是匿名内部类对象,默认就隐式的持有外部Activity的引用,导致Activity内存泄露。要避免内存泄露的话还是需要像上面Handler一样使用静态内部类+弱应用的方式

WebView造成内存泄露

关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。

另外在查阅WebView内存泄露相关资料时看到这种情况:

Webview下面的Callback持有Activity引用,造成Webview内存无法释放,即使是调用了Webview.destory()等方法都无法解决问题(Android5.1之后)。

解决方案:在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView

@Override
protected void onDestroy() {
    super.onDestroy();
    // 先从父控件中移除WebView
    mWebViewContainer.removeView(mWebView);
    mWebView.stopLoading();
    mWebView.getSettings().setJavaScriptEnabled(false);
    mWebView.clearHistory();
    mWebView.removeAllViews();
    mWebView.destroy();
}

详细分析过程请参考这篇文章:WebView内存泄漏解决方法

猜你喜欢

转载自blog.csdn.net/qq_26057629/article/details/81385932