Context相关的内存泄露问题

Posted 小陈乱敲代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Context相关的内存泄露问题相关的知识,希望对你有一定的参考价值。

我在android应用中遇到过得内存泄露问题,它们大多数时候是因为同一个错误:对Context维持了一个长时间的引用

在Android中,Context可用于很多操作,但主要是用于加载和访问资源。这就是为什么所有的widget都要在它们的构造方法中接收Context参数。在一个常规的Android应用中,通常有两种Context:Activity和Application

一般开发者将前者传递给需要Context的类和方法:

@Override
protected void onCreate(Bundle state) 
  super.onCreate(state);
  
  TextView label = new TextView(this);
  label.setText("Leaks are bad");
  
  setContentView(label);

这意味着View有一整个Activity的引用。因此可访问到Activity持有的任何东西。因此,如果你泄露了Context(泄露(leak)意味着你维持了一个指向它的引用导致GC不能回收它),你就泄露了很多内存。如果你不小心的话,非常容易就会泄露整个Activity。

当屏幕的方向改变时,系统默认会销毁当前的Activity并保存它的状态,然后创建一个新的Activity。在这个过程中,Android会从资源中重新加载应用的UI。现在假设你写了一个有大bitmap的应用,你不想每次旋转时都加载bitmap。维持这个实例、不需要每次重新加载的最简单的办法就是将它维持在一个静态域中。

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) 
  super.onCreate(state);
  
  TextView label = new TextView(this);
  label.setText("Leaks are bad");
  
  if (sBackground == null) 
    sBackground = getDrawable(R.drawable.large_bitmap);
  
  label.setBackgroundDrawable(sBackground);
  
  setContentView(label);

这个代码是非常便捷但是也非常错误。它泄露了第一次因屏幕方向改变而创建的Activity。当一个Drawable对象依附到一个View对象上时,View对象被作为一个callback设置到drawable对象上了。在上面的代码片段中,这就意味着drawable有一个TextView的引用,而TextView有activity的引用(Context),activity有很多东西的原因(取决于你的代码)

这个例子是Context泄露的最简单的例子,你可以看看我们在Home screen的源码中是如何处理这种问题的(看unbindDrawables()方法),通过在activity销毁时,将存储的drawable的callback为null。有趣的是,有多种情况能导致我们创建泄露的Context,这样很糟糕。它们使得我们很快就耗尽内存。

有两种简单的方法可避免Context相关的内存泄露。

最明显的方法是避免在Context的作用域之外使用它。上面那个例子展示了静态引用或是内部类对外部类的隐式引用都是同样危险的。
第二种方法就是使用Application Context。这个Context会一直存活只要你的应用是活着的,并且不依赖于Activity的生命周期。如果你打算维持一个长时间存在的并且需要Context的对象时,记住使用应用的Context。获取方法:Context.getApplicationContext()或Activity.getApplication()
概况来说,为了避免Context相关的内存泄露,记住下面几点:

不要维持一个长时间存在对Activity的Context的引用(Activity的引用和Activity有着一样的生命周期)
使用Application的Context而不是Activity的Context
避免在Activity中使用非静态内部类,如果你不想控制他们的生命周期。使用静态内部类,并在它的内部创建一个对Activity的弱引用。
下面的例子由译者补充

使用非静态内部类,Android Studio报可能内存泄露的警告:

//解决方法
//使用静态内部类,并在其中创建对Activity的弱引用
 private static class MyHandler extends Handler

        //对Activity的弱引用
        private final WeakReference<HandlerActivity> mActivity;

        public MyHandler(HandlerActivity activity)
            mActivity = new WeakReference<HandlerActivity>(activity);
        

        @Override
        public void handleMessage(Message msg) 
            HandlerActivity activity = mActivity.get();
            if(activity==null)
                super.handleMessage(msg);
                return;
            
            switch (msg.what) 
                case DOWNLOAD_FAILED:
                    Toast.makeText(activity, "下载失败", Toast.LENGTH_SHORT).show();
                    break;
                case DOWNLOAD_SUCCESS:
                    Toast.makeText(activity, "下载成功", Toast.LENGTH_SHORT).show();
                    Bitmap bitmap = (Bitmap) msg.obj;
                    activity.imageView.setVisibility(View.VISIBLE);
                    activity.imageView.setImageBitmap(bitmap);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            
        
    

    private final MyHandler mHandler = new MyHandler(this);

以上是关于Context相关的内存泄露问题的主要内容,如果未能解决你的问题,请参考以下文章

go使用context包避免goroutine泄露问题

Context使用不当造成内存泄露

Android开发 关于静态static类与static方法持有Context是否导致内存泄露的疑问

4类 JavaScript 内存泄露及如何避免

关于Context的使用场景总结

Andoid内存泄露