一:概述
前几天app
总是空指针奔溃,发现为某个 Fragment
中的控件为null
导致的。而且这个Fragment
是能够正常显示的。那么为什么还会空指针呢?
二:问题排查
- 这个
app
使用了AndroidAnnotations
注解。要了解为什么Fragment
中控件为null
的原因,就需要找到框架AndroidAnnotations
在Fragment
新建时初始化控件方式,以及在Fragment
销毁时对控件的处理方法。 - 经排查,发现
Fragment
销毁时AndroidAnnotations
会把所有的控件置为null
。 - 再次进入
Fragment
时 ,Fragment
为新建的Fragment
实例,并没有复用原来的Fragment
,里面的控件也正确初始化了。 - 那么,显然问题不在这里。
app
还采用了RxJava、RxAndroid
,并且Rx
观察者的call
方法里有对Fragment
的控件调用,那么很可能内存泄漏了,Fragment 并没有正确的销毁。Fragment
中的Rx
观察者能正常接收到订阅信息,然后去执行对控件的操作,此时控件已经为null
,那么空指针已经不可避免。
三: 取消 Rxjava、RxAndroid 订阅的方法
一般用过 Rxjava
的人都知道,在Activity
、Fragment
销毁时,需要取消订阅。取消订阅的方式有多种,如下
用框架管理 RxJava ,如 使用
trello/RxLifecycle
开源库或知乎同名开源库zhihu/RxLifecycle
,可参考这篇文章,这里不多赘述,网上的文章也挺多的。不使用开源库,使用类
CompositeSubscription
的add 、unsubscribe
等方法管理取消订阅
四: 使用类 unsubscribe 取消订阅
由于引入框架还需要添加依赖,无疑会增加安装包的大小,所以我这里选择使用封装 RxJava
本身自带的API
去管理取消订阅。
1. 了解 CompositeSubscription
-
CompositeSubscription
用来表示一起取消订阅的一组订阅的订阅。从构造方法可以知道CompositeSubscription
内部存在一个HashSet
用来存储需要一起取消订阅的Subscriptions
- 调用
add
方法将Subscription
加入到Hashset
中。若Hashset
中的Subscriptions
还没有取消订阅,则把新的Subscription
加入到Hashset
中。如果已经取消订阅,则也取消该Subscription
的订阅
public void add(final Subscription s) {
if (s.isUnsubscribed()) {
return;
}
if (!unsubscribed) {
synchronized (this) {
if (!unsubscribed) {
if (subscriptions == null) {
subscriptions = new HashSet<Subscription>(4);
}
subscriptions.add(s);
return;
}
}
}
// call after leaving the synchronized block so we're not holding a lock while executing this
s.unsubscribe();
}
- 调用
unsubscribe
方法遍历取消Haset
中的所有订阅,包括它自己。如果此时还有新的Subscription
加入进来,则也取消该Subscription
的订阅。
@Override
public void unsubscribe() {
if (!unsubscribed) {
Collection<Subscription> unsubscribe = null;
synchronized (this) {
if (unsubscribed) {
return;
}
unsubscribed = true;
unsubscribe = subscriptions;
subscriptions = null;
}
// we will only get here once
unsubscribeFromAll(unsubscribe);
}
}
private static void unsubscribeFromAll(Collection<Subscription> subscriptions) {
if (subscriptions == null) {
return;
}
List<Throwable> es = null;
for (Subscription s : subscriptions) {
try {
s.unsubscribe();
} catch (Throwable e) {
if (es == null) {
es = new ArrayList<Throwable>();
}
es.add(e);
}
}
Exceptions.throwIfAny(es);
}
2. 最佳实践
- 定义
BaseActivity
或BaseFragment
.具体看代码吧!
BaseActivity
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import rx.subscriptions.CompositeSubscription;
public class BaseActivity extends AppCompatActivity {
public CompositeSubscription compositeSubscription;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
compositeSubscription = new CompositeSubscription();
}
@Override
protected void onDestroy() {
super.onDestroy();
//RX生命周期管理
if (compositeSubscription != null && !compositeSubscription.isUnsubscribed())
compositeSubscription.unsubscribe();
}
}
BaseFragment
public class BaseFragment extends Fragment {
public CompositeSubscription compositeSubscription;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
compositeSubscription = new CompositeSubscription();
}
@Override
public void onDestroyView() {
super.onDestroyView();
//RX生命周期管理
if (compositeSubscription != null && !compositeSubscription.isUnsubscribed())
compositeSubscription.unsubscribe();
}
}
- 让需要使用
RxJava
的Activity
继承BaseActivity
,让需要使用RxJava
的Fragment
继承BaseFragment
. - 调用
CompositeSubscription
的add
方法把订阅产生的Subscription
加入到compositeSubscription
中去。
3. 分析:
- 在订阅产生时,将订阅加入到
CompositeSubscription
中。 - 在
Activity
、Fragment
销毁时,统一取消所有订阅。
五:总结:
注意:不管是使用框架还是RxJava
自带方法,都要在Activity
、Fragment
销毁时,取消订阅,防止内存泄漏、奔溃等问题。