想起写这么一篇博文的前提是上周去面试了一家公司,其中有这么一个问题印象深刻,结合当时在网上看到的解决办法我就说了一个错误答案,结果当场就被面试官给指出了错误,所以回来后和我的领导一起讨论了这么一个问题,他提出了一个很好地解决思路,于是乎我便写了这么一段代码,对于能够真正的解决这个问题的,我相信这是最正确的,如果有更好的,我收回这句话,哈哈哈~
问:Android中进行网络请求,如果当网络请求完成后回调,这个activity已经被回收了,如何处理?
我当时的错误答案:在activity的回调方法里通过isFinishing()方法判断这个activity是否已经被销毁了,如果销毁的话就不进行数据的加载或显示,或者在我们的presenter中判断,如果activity回收了,我们可以取消网络请求,不进行回调。
问:怎么能通过isFinishing()方法呢?activity已经被回收了,通过this.isFinishing()方法不会报空指针异常吗?
我当时的想法:嗯,想想好像是这样的……然后就不知道怎么说了……
所以回来后我也和别人讨论了如何解决这个问题,还是厉害的人的思路比较广阔,他给我提出了一个建议,我们当时是直接在presenter中对activity进行了引用,这种引用属于强引用,所以当我们的activity被回收之后,我们的presenter持有的强引用不能被内存回收,容易造成内存泄露,并且这个时候回调,我们的activity已经被回收不存在了,所以这个时候加载数据就会抛出异常,那么应该怎么解决这个问题呢?
在这个案例中,思路就是在presenter中对activity对象实行弱引用,也就是不直接持有activity对象的引用,那么当我们的activity被销毁后,我们的presenter所持有的弱引用也能够被内存回收,基于这个思路,我写下了以下的代码并进行了验证模拟activity被回收的场景。
- 首先是activity_main.xml文件布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="模拟网络请求" />
</RelativeLayout>
- 创建一个接口ICallBackView
public interface ICallBackView {
void getMessageSuccess(String msg);
}
- MainActivity实现这个接口
public class MainActivity extends Activity implements ICallBackView {
private static final String TAG = "MainActivity";
private MyPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
mPresenter = new MyPresenter(this);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mPresenter.requestMessage();
finish();
}
});
}
/**
* 模拟网络请求成功的回调
*
* @param msg
*/
@Override
public void getMessageSuccess(String msg) {
Log.e(TAG, "getMessageSuccess: " + msg);
}
}
- 创建我们的presenter
public class MyPresenter {
private static final String TAG = "MyPresenter";
private WeakReference<ICallBackView> weakReference;
public MyPresenter(ICallBackView callBackView) {
weakReference = new WeakReference<>(callBackView);
}
/**
* 模拟请求网络
*/
public void requestMessage() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟网络阻塞
Log.e(TAG, "run: 正在进行网络请求......");
Thread.sleep(3000);
ICallBackView view = weakReference.get();
if (view != null) {
//说明引用还存在,可以进行回调
view.getMessageSuccess("请求成功,这是返回的内容......");
} else {
//说明引用已经被内存回收了
Log.e(TAG, "run: 当前引用为null,不进行回调......");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
代码比较简单,当我们的按钮点击模拟网络请求,我们在presenter中模拟网络请求耗时,然后通过弱引用判断activity是否还在,当然我们这里演示所用,不得已在点击按钮后将activity关闭了,正常情况下我们还需要在代码中判断一下activity是否被finish掉了,如下代码所示:
ICallBackView view = weakReference.get();
if (view != null) {
//说明引用还存在,可以进行回调
if (!((MainActivity) view).isFinishing()) {
view.getMessageSuccess("请求成功,这是返回的内容......");
}
} else {
//说明引用已经被内存回收了
Log.e(TAG, "run: 当前引用为null,不进行回调......");
}
我们这里为了演示回调,所以没有加上判断activity是否被finish掉,请大家注意并理解。
下面我们来运行程序看看后台的输入的日志,我们先进行睡眠3秒,时间较短,activity被finish掉后可能还没有被回收掉:
可以看到,虽然MainActivity被finish掉了,但是还没来得及回收掉,所以还能进行回调(当然这里activity被finish掉了,不应该进行回调,不过这里我们要模拟activity被回收的场景,所以不得已这样做,大家理解)。
下面我们模拟一下网络请求比较耗时的场景,睡眠10秒,来看看如何输出日志:
可以看到,当我们的网络请求比较耗时时,我们的activity已经finish掉并且已经被回收了,这里我们的弱引用就拿不到对象activity的引用,所以这里就不会进行网络回调,避免了一些异常的产生。
总结
首先大家理解弱引用的使用,当我们的引用对象为null时,弱引用持有的对象引用就会为null,结合我们的使用场景,我们在准备进行网络回调的时候,应该先判断一下弱引用的引用是否为空,并且应该判断一下要回调的activity是否被finish或destroy掉了,这才是正确的使用方式,如果弱引用持有的引用不为null并且activity没有被finish掉的话,就进行回调方法的调用,这样才能最大限度上保持页面不会引用数据回到导致的各种异常。
写在最后,如果文中有哪些地方写的不对或者大家有更好的方式解决这种问题的话,欢迎大家留言讨论,一起进步!