如何在Java和Android开发中使用WeakReference?

189
我已经做了2年的Java开发工作。
但是我从来没有在我的代码中使用过WeakReference。如何使用WeakReference使我的应用程序更高效,特别是Android应用程序?

可能会有一个针对Android的差异:https://dev59.com/SnVC5IYBdhLWcg3wbQfA#JqOeEYcBWogLw_1bRr7Y - Pacerier
4个回答

235
在Android中使用WeakReference和在普通Java中使用它没有任何区别。每当您需要对对象进行引用,但不希望该引用保护对象免受垃圾回收器的影响时,应考虑使用它。一个经典的例子是高内存使用时要进行垃圾回收的缓存(通常使用WeakHashMap实现)。也请务必查看SoftReferencePhantomReference。 编辑: Tom提出了关于使用WeakHashMap实现缓存的一些担忧。这里有一篇文章说明了问题:WeakHashMap不是缓存!Tom是正确的,因为由于WeakHashMap缓存而导致Netbeans性能不佳而发出了投诉。我仍然认为使用WeakHashMap实现缓存,然后将其与使用SoftReference手动实现的自定义缓存进行比较,这将是一个很好的学习经验。在真实世界中,您可能不会使用这两个解决方案之一,因为使用像Apache JCS这样的第三方库更有意义。

16
不行!不行!不行!使用WeakHashMap作为缓存是致命的。条目可能会在创建后立即被删除。当你进行测试时,这可能不会发生,但在实际使用中很可能会发生。需要注意的是,这可能会导致NetBeans出现有效的100% CPU停止。 - Tom Hawtin - tackline
4
@Tom,我更新了我的答案。不过公平地说,我技术上是正确的,即使你正确指出它不是一个好选择,缓存通常也是用 WeakHashMap 实现的。 - dbyrne
1
dbyrne的回答非常好,谢谢。我认为没有理由让chris不接受这个答案。 - san
@dbyrne 第一个链接(理解...)有时会加载失败。可能值得在答案中警告,以便人们知道要尝试刷新几次。感谢您提供的出色答案。 - yarian
5
失效的链接指向 java.net。 - Suragch
显示剩余5条评论

74

[编辑2] 我找到了另一个很好的WeakReference示例。高效显示位图培训指南中的在UI线程之外处理位图页面,展示了AsyncTask中WeakReference的一种用法。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

它说:

ImageView的WeakReference确保AsyncTask不会阻止ImageView及其引用的任何内容被垃圾回收。不能保证任务完成时ImageView仍然存在,因此您还必须在onPostExecute()中检查引用。如果用户在任务完成之前导航离开活动或发生配置更改,则可能不再存在ImageView。

愉快的编码!

[编辑] 我找到了一个非常好的WeakReference示例,来自facebook-android-sdkToolTipPopup类只是一个简单的小部件类,用于在锚点视图上方显示工具提示。我拍了一张截图。

scrumptious screenshot

该类非常简单(约200行),值得一看。在该类中,使用WeakReference类来持有对锚视图的引用是很有意义的,因为这使得即使提示框实例的生命周期比其锚视图长时,锚视图也可以被垃圾回收。

祝编程愉快!:)


让我分享一个使用WeakReference类的工作示例。这是来自Android框架小部件AutoCompleteTextView的一小段代码片段。

简而言之,WeakReference类用于持有View对象,以防止在此示例中发生内存泄漏

我将复制并粘贴PopupDataSetObserver类,它是AutoCompleteTextView的嵌套类。它非常简单,注释很好地解释了该类。祝你编程愉快! :)

    /**
     * Static inner listener that keeps a WeakReference to the actual AutoCompleteTextView.
     * <p>
     * This way, if adapter has a longer life span than the View, we won't leak the View, instead
     * we will just leak a small Observer with 1 field.
     */
    private static class PopupDataSetObserver extends DataSetObserver {
    private final WeakReference<AutoCompleteTextView> mViewReference;
    private PopupDataSetObserver(AutoCompleteTextView view) {
        mViewReference = new WeakReference<AutoCompleteTextView>(view);
    }
    @Override
    public void onChanged() {
        final AutoCompleteTextView textView = mViewReference.get();
        if (textView != null && textView.mAdapter != null) {
            // If the popup is not showing already, showing it will cause
            // the list of data set observers attached to the adapter to
            // change. We can't do it from here, because we are in the middle
            // of iterating through the list of observers.
            textView.post(updateRunnable);
        }
    }

    private final Runnable updateRunnable = new Runnable() {
        @Override
        public void run() {
            final AutoCompleteTextView textView = mViewReference.get();
            if (textView == null) {
                return;
            }
            final ListAdapter adapter = textView.mAdapter;
            if (adapter == null) {
                return;
            }
            textView.updateDropDownForFilter(adapter.getCount());
        }
    };
}

PopupDataSetObserver 被用于设置适配器。

    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
    if (mObserver == null) {
        mObserver = new PopupDataSetObserver(this);
    } else if (mAdapter != null) {
        mAdapter.unregisterDataSetObserver(mObserver);
    }
    mAdapter = adapter;
    if (mAdapter != null) {
        //noinspection unchecked
        mFilter = ((Filterable) mAdapter).getFilter();
        adapter.registerDataSetObserver(mObserver);
    } else {
        mFilter = null;
    }
    mPopup.setAdapter(mAdapter);
}

最后一件事。我也想知道在Android应用程序中使用WeakReference的工作示例,并且我可以在其官方示例应用程序中找到一些示例。但是,我确实无法理解其中一些的用法。例如,ThreadSampleDisplayingBitmaps应用程序在其代码中使用WeakReference,但经过运行几个测试,我发现get()方法从未返回null,因为引用的视图对象在适配器中被回收,而不是被垃圾回收。


1
感谢您提供这些出色的示例 - 我真的觉得这应该是问题的被接受的答案。干杯! - Ackshaey Singh
@AckshaeySingh 谢谢! :) - 김준호

19

其他一些答案似乎不完整或过长。这里是一个通用的答案。

如何在Java和Android中使用WeakReference

您可以执行以下步骤:

  1. 创建一个 WeakReference 变量
  2. 设置弱引用
  3. 使用弱引用

代码

MyClass 具有对 AnotherClass 的弱引用。

public class MyClass {

    // 1. Create a WeakReference variable
    private WeakReference<AnotherClass> mAnotherClassReference;

    // 2. Set the weak reference (nothing special about the method name)
    void setWeakReference(AnotherClass anotherClass) {
        mAnotherClassReference = new WeakReference<>(anotherClass);
    }

    // 3. Use the weak reference
    void doSomething() {
        AnotherClass anotherClass = mAnotherClassReference.get();
        if (anotherClass == null) return;
        // do something with anotherClass
    }

}

AnotherClassMyClass 拥有强引用。

public class AnotherClass {
    
    // strong reference
    MyClass mMyClass;
    
    // allow MyClass to get a weak reference to this class
    void someMethod() {
        mMyClass = new MyClass();
        mMyClass.setWeakReference(this);
    }
}

注意事项

  • 你需要使用弱引用是为了让垃圾回收器在不再需要对象时处理它们。如果两个对象互相保留强引用,那么它们就无法被垃圾回收。这就是内存泄漏。
  • 如果两个对象需要相互引用,通常寿命较短的对象A应该有一个指向寿命较长的对象B的弱引用,而B则需要对A保持强引用。在上面的示例中,MyClass是A,AnotherClass是B。
  • 一个替代使用WeakReference的方法是让另一个类实现一个接口。这是使用监听器/观察者模式来完成的。

实际示例


令人困惑的解释。//允许 MyClass 获取对这个类的弱引用 void someMethod() { mMyClass = new MyClass(); mMyClass.someMethod(this); } 是什么意思? - likejudo
@likejudo,你说得对。我改进了一些变量和方法的命名。现在怎么样? - Suragch
doSomething 函数中调用 get 函数之前,需要检查 weakreference 对象本身是否为 null - Behrouz.M

9

“规范化”映射是指在内存中保留一个对象实例,而所有其他实例通过指针或类似的机制查找该特定实例。这就是弱引用可以发挥作用的地方。 简单来说,WeakReference对象可用于创建指向系统中对象的指针,同时允许垃圾回收器在它们超出范围后回收这些对象。例如,如果我有以下代码:

class Registry {
     private Set registeredObjects = new HashSet();

     public void register(Object object) {
         registeredObjects.add( object );
     }
 }

任何我注册的对象都不会被垃圾收集器回收,因为它在“registeredObjects”的集合中存储了对它的引用。另一方面,如果我这样做:
class Registry {
     private Set registeredObjects = new HashSet();

     public void register(Object object) {
         registeredObjects.add( new WeakReference(object) );
     }
 }

当GC想要回收Set中的对象时,它就能够这样做。您可以使用此技术进行缓存、编目等操作。有关GC和缓存的更深入讨论,请参见下面的参考文献。
参考文献:Garbage collector and WeakReference

如上所述,使用它作为缓存可能不是一个好主意。 - DarkNeuron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接