避免与监听器泄漏活动
如果在 Activity 中实现或创建侦听器,请始终注意具有侦听器注册的对象的生命周期。
考虑一个应用程序,我们在用户登录或注销时有几个不同的活动/片段。这样做的一种方法是拥有一个可以订阅的 UserController
的单例实例,以便在用户状态发生变化时得到通知:
public class UserController {
private static UserController instance;
private List<StateListener> listeners;
public static synchronized UserController getInstance() {
if (instance == null) {
instance = new UserController();
}
return instance;
}
private UserController() {
// Init
}
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void logout() {
for (StateListener listener : listeners) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listeners) {
listener.userLoggedIn();
}
}
public interface StateListener {
void userLoggedIn();
void userLoggedOut();
}
}
然后有两个活动,SignInActivity
:
public class SignInActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
startMainActivity();
}
@Override
public void userLoggedOut() {
showLoginForm();
}
...
public void onLoginClicked(View v) {
userController.login();
}
}
而 MainActivity
:
public class MainActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
...
public void onLogoutClicked(View v) {
userController.logout();
}
}
此示例会发生的情况是,每次用户登录然后再次注销时,都会泄漏 MainActivity
实例。泄漏的发生是因为在 UserController#listeners
中有对活动的引用。
请注意: 即使我们使用匿名内部类作为侦听器,该活动仍会泄漏:
...
this.userController.registerUserStateChangeListener(new UserController.StateListener() {
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
});
...
活动仍然会泄漏,因为匿名内部类具有对外部类的隐式引用(在本例中为活动)。这就是为什么可以从内部类调用外部类中的实例方法的原因。实际上,没有引用外部类的唯一类型的内部类是静态内部类。
简而言之,非静态内部类的所有实例都包含对创建它们的外部类的实例的隐式引用。
解决这个问题有两种主要方法,可以通过添加一个从 UserController#listeners
中删除侦听器的方法,或者使用 WeakReference
来保存侦听器的引用。
备选方案 1:删除侦听器
让我们从创建一个新方法 removeUserStateChangeListener(StateListener listener)
开始:
public class UserController {
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void removeUserStateChangeListener(StateListener listener) {
listeners.remove(listener);
}
...
}
然后让我们在 activity 的 onDestroy
方法中调用这个方法:
public class MainActivity extends Activity implements UserController.StateListener{
...
@Override
protected void onDestroy() {
super.onDestroy();
userController.removeUserStateChangeListener(this);
}
}
通过此修改,当用户登录和注销时,MainActivity
的实例不再泄露。但是,如果文档不清楚,那么下一个开始使用 UserController
的开发人员可能会错过在活动被销毁时需要取消注册侦听器,这导致我们采用第二种方法来避免这些类型的泄漏。
备选方案 2:使用弱引用
首先,让我们首先解释一下弱引用是什么。顾名思义,弱引用对对象持有弱引用。与作为强引用的普通实例字段相比,弱引用不会阻止垃圾收集器 GC 移除对象。在上面的例子中,如果 UserController
使用了 WeakReference
来引用听众,那么这将允许 MainActivity
在被破坏后进行垃圾收集。
简而言之,弱引用告诉 GC,如果没有其他人对此对象有强引用,请继续将其删除。
让我们修改 UserController
使用列表 WeakReference
来跟踪它的听众:
public class UserController {
...
private List<WeakReference<StateListener>> listeners;
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(new WeakReference<>(listener));
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
WeakReference referencesToRemove = null;
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null && listener == listenerToRemove) {
referencesToRemove = listenerRef;
break;
}
}
listeners.remove(referencesToRemove);
}
public void logout() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedOut();
} else {
referencesToRemove.add(listenerRef);
}
}
}
public void login() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedIn();
} else {
referencesToRemove.add(listenerRef);
}
}
}
...
}
通过这种修改,是否删除了侦听器并不重要,因为 UserController
没有对任何侦听器的强引用。但是,每次编写此样板代码都很麻烦。因此,让我们创建一个名为 WeakCollection
的泛型类:
public class WeakCollection<T> {
private LinkedList<WeakReference<T>> list;
public WeakCollection() {
this.list = new LinkedList<>();
}
public void put(T item){
//Make sure that we don't re add an item if we already have the reference.
List<T> currentList = get();
for(T oldItem : currentList){
if(item == oldItem){
return;
}
}
list.add(new WeakReference<T>(item));
}
public List<T> get() {
List<T> ret = new ArrayList<>(list.size());
List<WeakReference<T>> itemsToRemove = new LinkedList<>();
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == null) {
itemsToRemove.add(ref);
} else {
ret.add(item);
}
}
for (WeakReference ref : itemsToRemove) {
this.list.remove(ref);
}
return ret;
}
public void remove(T listener) {
WeakReference<T> refToRemove = null;
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == listener) {
refToRemove = ref;
}
}
if(refToRemove != null){
list.remove(refToRemove);
}
}
}
现在让我们重新编写 UserController
来代替使用 WeakCollection<T>
:
public class UserController {
...
private WeakCollection<StateListener> listenerRefs;
...
public void registerUserStateChangeListener(StateListener listener) {
listenerRefs.put(listener);
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
listenerRefs.remove(listenerToRemove);
}
public void logout() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedIn();
}
}
...
}
如上面的代码示例所示,WeakCollection<T>
删除了使用 WeakReference
而不是普通列表所需的所有样板代码。最重要的是:如果错过了对 UserController#removeUserStateChangeListener(StateListener)
的调用,则监听器及其引用的所有对象都不会泄漏。