你不必使用弱引用以避免内存泄漏

本文翻译自https://medium.com/google-developer-experts/weakreference-in-android-dd1e66b9be9d#.7gyh5qy6l

我的一位同事最近提到,他们看到了一个演讲,说:“如果你是一个Android开发者,你不使用使用弱引用,将会有问题。”
我个人认为,这不仅是一个错误的论点,而且完全是一个误导。WeakReference应该是修复内存泄漏的最后手段。
下面是谷歌开发专家Enrique López Mañas发布的文章:“
Finally understanding how references work in Android and Java
(英文不好,弱弱的依赖翻译工具翻译一下,正文如下)
几个星期前,我参加了mobiconf,这是一个最好的移动开发者的会议之一,我有幸在波兰参加。在他不拘一格的演讲“The best (good) practices”期间,我的朋友和同事Jorge Barroso提出了让我说说自己的想法关于:”如果你是一个Android开发者,你不使用使用弱引用,你将会有问题。”
就一个好时机的例子,几个月前,我确实发表了我的最后一本书,和Diego Grancini一起合著的—–“Android High Performance(安卓高性能)“,其中最有激情的章节之一是一个谈论在安卓系统的内存管理。在这一章中,我们讨论了在移动设备中的内存如何工作,内存泄漏是如何发生的,我们可以应用哪些技术来避免它们和为什么避免内存泄漏如此重要。自从我开始开发android,我总是观察到一种倾向,人们不知不觉的避免或把内存泄漏和内存管理相关的处理的优先级低于其它一切。如果功能标准得到满足,为什么要自增烦恼呢?我们总是在匆忙开发新的功能,我们宁愿在我们的下一个Sprint demo 演示视觉可见的东西,而不是关心没有人会在第一眼看到的东西。
一个很好的论点,这不可避免地导致收购技术债务。我甚至会补充说,技术债务也有一定的影响,在现实世界中我们不能用单位测试衡量:失望,在同开发商之间的摩擦,低质量的软件运和损失的动机。这种影响是很难衡量的原因是因为通常他们发生在一个长的周期内。它的发生有点像政治家:如果我只会任期8年,为什么我要担心在12后发生了什么事?不同的是在软件开发中一切动作速度更快一些。
要完全写出在软件开发中采用适当的和正确的思维方式,可能需要写很多,而且已经有许多你可以探索书籍和文章了。然而,简要阐述的内存引用的不同类型,它们的意义是什么,他们如何能被应用在Android将是一个简短的任务,这就是我想做的文章。

First of all: what is a reference in Java?

A reference is the direction of an object that is annotated, so you
can access it.

Java has by default 4 types of references: strong, soft, weak and phantom.

有些人认为,只有两种类型的引用,强和弱,弱引用可以呈现2个程度的弱引用。我们倾向于把生活中的一切都与一个植物学家的分类的毅力。无论它对你的工作更好,但首先你需要了解他们。然后你可以找到你自己的分类。

What does each type of reference mean?(每种引用类型意味着什么?)
Strong reference: 强引用是java的普通引用。任何时候我们创建一个新的对象,一个强引用默认创建。例如,当我们这样做:

MyObject object = new MyObject();

一个新的对象是MyObject被创造,则一个它的强引用被存储在对象中。此对象是 strongly reachable ,意味着它可以通过一个强引用到达,这将防止垃圾回收器销毁它。但是现在我们来看一个反对我们观点的例子:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        new MyAsyncTask().execute();
    }

    private class MyAsyncTask extends AsyncTask {
        @Override
        protected Object doInBackground(Object[] params) {
            return doSomeStuff();
        }
        private Object doSomeStuff() {
            //do something to get result
            return new MyObject();
        } 
    }
}

AsyncTask将在oncreate()方法创建和执行。但在这里,我们有一个问题:内部类需要访问外部类在其整个生命周期。当activity被销毁时会发生什么?AsyncTask拥有activity的引用,以至于activity不能被GC回收。这就是我们所说的内存泄漏。

实际上,内存泄漏不仅发生在activity本身被销毁时,而且发生在由于配置改变或内存不足而被系统强制销毁时,等等。如果AsyncTask是复杂的(即拥有activity中view的引用等)甚至可能导致死机,因为view引用为null。

那么如何才能防止这个问题再次发生呢?我们来看一下其它类型的引用:
WeakReference: 弱引用是一个没有足够强大保持对象一直存在内存中的引用。如果我们不确定是否对一个对象使用强引用,这时就出现了弱引用,该对象将被垃圾回收器收集。为了便于理解,最好是抛开理论用一个实际的例子,用WeakReference去改写我们以前的代码来避免内存泄漏:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new MyAsyncTask(this).execute();
    }
    private static class MyAsyncTask extends AsyncTask {
        private WeakReference<MainActivity> mainActivity;    

        public MyAsyncTask(MainActivity mainActivity) {   
            this.mainActivity = new WeakReference<>(mainActivity);            
        }
        @Override
        protected Object doInBackground(Object[] params) {
            return doSomeStuff();
        }
        private Object doSomeStuff() {
            //do something to get result
            return new Object();
        }
        @Override
        protected void onPostExecute(Object object) {
            super.onPostExecute(object);
            if (mainActivity.get() != null){
                //adapt contents
            }
        }
    }
}

请注意一个主要的区别:内部类的activity的引用现在如下:

private WeakReference<MainActivity> mainActivity;

这样会发生什么?当activity不再存在,因为它是通过一个WeakReference的方式,它可以被垃圾回收器回收。因此,没有内存泄漏会发生。

现在,我希望你对使用弱引用有更进一步的了解了,你会发现一个有用的类WeakHashMap。它如同一个HashMap,除了钥匙(key,不是values)是指通过使用弱引用。这使他们非常有用,如高速缓存。

我们之前提到了更多的引用。让我们看看它们都用在哪里,有什么优点:
SoftReference:我们可以把软引用看作为一个稍强一点的WeakReference。而WeakReference会立即被收集,软引用会请求GC尽可能不回收它除非没有其他的选择。垃圾收集器算法是个很令人兴奋的东西,你可以潜心研究它几个小时不觉得累。但基本上,垃圾回收器会说,“我将一直收回WeakReference。如果对象是一个软引用,我会根据系统条件决定去怎么处理”。这使得软引用在缓存实现时非常有用:只要内存足够,我们不必手动删除对象。为了了解软引用,下面是另一个blog中的例子
《《《《java中的每个对象都有对其他对象的引用。只要一个对象引用了它,则该对象将不会被回收。当应用程序内存溢出而且GC无法删除那些没有真正引用的对象来释放内存,只有softrefences将被删除。所以, 基本 ,只有软引用它将 项目保持 在内存尽可能长的时间,直到没有其他的方法来释放内存。
创建一个软引用一个对象,你可以使用下面的方式:

SoftReference<String> ref = new SoftReference<String>("Hello world");

检索数据:

String value = ref.get();

if (value == null) {
  // The reference is cleaned up by the Garbage Collector.
  // Initialize the object again.
}

// Use the value (from the SoftReference or from re-initializing.
.......

SoftReferenceCache
我们如何创建一个缓存使用softreferences?我们将做一个HashMap和java泛型。
首先我们从一个叫 softreferencecache会接收两个泛型参数的类开始。一个key一个value,在构造函数中我们将创建HashMap。

public class SoftReferenceCache<K, V> {
  private final HashMap<K, SoftReference<V>> mCache;

  public SoftReferenceCache() {
    mCache = new HashMap<K, SoftReference<V>>();
  }
}

向缓存中添加新的键值对的方法

public void put(K key, V value) {
  mCache.put(key, new SoftReference<V>(value));
}

根据key 取value的方法

public V get(K key) {
  V value = null;

  SoftReference<V> reference = mCache.get(key);

  if (reference != null) {
    value = reference.get();
  }

  return value;
}

使用这个缓存类如下

SoftReferenceCache<Integer, Person> mPersonCache = new SoftReferenceCache<Integer, Person>();

mPersonCache.put(0, new Person("Peter");
mPersonCache.put(1, new Person("Jan");
mPersonCahce.put(2, new Person("Kees");

Person p = (Person)mPersonCache.get(1); // This will retrieve the Person object of Jan.
/**
 * SoftRefenceCache
 * @param <K> The type of the key's.
 * @param <V> The type of the value's.
 */
public class SoftReferenceCache<K, V> {
  private final HashMap<K, SoftReference<V>> mCache;

  public SoftReferenceCache() {
    mCache = new HashMap<K, SoftReference<V>>();
  }

  /**
   * Put a new item in the cache. This item can be gone after a GC run.
   * 
   * @param key
   *            The key of the value.
   * @param value
   *            The value to store.
   */
  public void put(K key, V value) {
    mCache.put(key, new SoftReference<V>(value));
  }

  /**
   * Retrieve a value from the cache (if available).
   * 
   * @param key
   *            The key to look for.
   * @return The value if it's found. Return null if the key-value pair is not
   *         stored yet or the GC has removed the value from memory.
   */
  public V get(K key) {
    V value = null;

    SoftReference<V> reference = mCache.get(key);

    if (reference != null) {
      value = reference.get();
    }

    return value;
  }
}

》》》》》》》》》》》》》》》》》》》》》》》》》》》

回到正题,接下来是

PhantomReference:哎,phantomreferences!我想我可以用手指头就可以数的过来他们在生产环境中使用的次数。一个对象如果只通过phantomreference引用,那么不管何时垃圾回收器都可以回收它。那我们为什么还要用它呢?phantomreference可准确检测一个对象是否被从内存中移除。


大神 Enrique López Mañas的文章大概这些,当然我省去了一些,翻译的不好之处请见谅。

这是非常好的文章,用例子讲解了java中引用是如何工作的。文章没有说,我们必须使用WeakReference但也没有给其它选择。我觉得我必须给出其它选择来证明不是必须使用WeakReference。

我相信使用WeakReference并不是在每个地方都是最佳选择。使用一个WeakReference修复内存泄漏表明缺乏建模或建筑。虽然在文章中给出的例子修复了潜在的内存泄漏,但还有其他方式。我将给出几个示例实现,以避免当您有一个长时间运行的后台任务时内存泄漏。

一个简单的例子来避免内存泄漏的AsyncTask会是这样的:

以下是我的 Activity:

public class MainActivity extends Activity {
  private MyAsyncTask task;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    task = new MyAsyncTask();
    task.setListener(createListener());
    task.execute();
  }
  @Override
  protected void onDestroy() {
    task.setListener(null);
    super.onDestroy();
  }
  private MyAsyncTask.Listener createListener() {
    return new MyAsyncTask.Listener() {
      @Override
      public void onSuccess(Object object) {
        // adapt contents
      }
    };
  }
}

以下是我的 AsyncTask:

class MyAsyncTask extends AsyncTask {
  private Listener listener;
  @Override
  protected Object doInBackground(Object[] params) {
    return doSomeStuff();
  }
  private Object doSomeStuff() {
    //do something to get result
    return new Object();
  }
  @Override
  protected void onPostExecute(Object object) {
    if (listener != null) {
      listener.onSuccess(object);
    }
  }
  public void setListener(Listener listener) {
    this.listener = listener;
  }
  interface Listener {
    void onSuccess(Object object);
  }
}

就是以接口的方式来回调,这种实现是非常基本的,但我认为这是不够的,以展示另一种解决方案。

以下是一个基于rxjava 非常简单的实现,当我们没有WeakReference时:

public class MainActivity extends Activity {

  private Subscription subscription;

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

    subscription = Observable
        .fromCallable(new Callable<Object>() {
          @Override
          public Object call() throws Exception {
            return doSomeStuff();
          }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Action1<Object>() {
          @Override
          public void call(Object o) {
            // adapt contents
          }
        });
  }

  private Object doSomeStuff() {
    //do something to get result
    return new Object();
  }

  @Override
  protected void onDestroy() {
    subscription.unsubscribe();
    super.onDestroy();
  }

}

请注意,如果我们不取消订阅,我们可能仍然有内存泄漏。

最后我想给几个来自Novoda的工程实例。他们是伟大的学习资源。你能猜到的,他们没有任何WeakReference :)

第一个实例

第二个实例

我相信经验法则(对我来说)当使用内部类时要使用静态内部类。特别是如果他们在后台做长时间运行的话。可以的话,我们尽量把这些类变成了一个完整的类。

本文翻译自https://medium.com/google-developer-experts/weakreference-in-android-dd1e66b9be9d#.7gyh5qy6l

猜你喜欢

转载自blog.csdn.net/feigecal/article/details/53536808