Android 避免主线程执行网络请求之Activity/Fragment 结束后处理异步回调
大家都知道Android涉及到与UI相关的操作只能在主线程执行 android4.0以后就禁止在主线程进行网络请求了,在主线程里面执行Http请求都会报NetworkOnMainThreadException的异常. 于是乎,我们现在用的Volley,Android-Async-Http,Xutils,Okhttp,Retrofit..等网络框架都是支持异步网络请求的.(大致步骤: 子线程网络请求--得到结果(成功/失败) -- 通过Handler将结果返回主线程 -- 主线程更新ui )
目前常见的方法: 使用Thread ,Runnable,Handler启动一个子线程进行网络请求,然后再用handler通知主线程更新ui
这个过程看似很正常,但是会出现一个常见的奔溃bug : IllegalArgumentException 如果这个请求执行的时间过久(由于网络延迟等原因),Activity或者Fragment已经不存在了(被销毁了),而线程并不知道这件事,这时候请求数据结果回来以后,将数据通过Handler抛给了主线程,在异步回调里一般都会执行数据的更新或者进度条的更新等操作,但页面已经不存在了,导致奔溃...
目前的解决方案: Activity : 1 在activity结束的时候结束请求 2 异步回调时通过Activity.isFinishing()判断activity是否已销毁 Fragment Fragment.isDeisDetached()
Activity举例: 以Volley
为例,在RequestQueue
这个类中,提供了通过tag
来取消与之相关的网络请求。
tag
是在主线程调起网络请求时,通过Request.setTag(Object)
传进去的。tag
可以是任意类型(常用的 Context , Path ... )
Context
将Context
作为tag带入请求中,当持有Context
的对象销毁时,可以通知该请求线程取消。一个典型的使用场景就是在Activity的
onDestroy()
方法调用Request.cancelAll(this)
来取消与该Context
相关的所有请求。就Volley
来说,它会在请求发出之前以及数据回
来之后这两个时间段判断请求是否被cancel。但是作为Android开发者,应该知道,持有Context引用的线程是危险的,如果线程发
生死锁,Context引用会被线程一直持有,导致该Context得不到释放,容易引起内存泄漏。如果该Context的实例是一个Activity,
那么这个结果是灾难性的 ,所以线程通过弱引用持有Context。当系统内存不足需要GC时,会优先回收持有弱引用的对象。然而这样
做还是存在隐患,有内存泄漏的风险。
PATH
既然持有Context的问题比较严重,那么我们可以根据请求路径来唯一识别一个请求。发起请求的对象需要维持一个列表,记录当前
发出的请求路径,在请求回来时再从该列表通过路径来删除该请求。在Activity结束但请求未发出或者未返回时,再将于这个Activity
绑定的列表中的所有请求取消。这个方案执行起来如何呢?每个Activity都需要维持一个当前页面发出的请求列表,在Activity结束时
再取消列表中的请求。面对一个应用几十上百个Activity,这样的实现无疑是蛋疼的。有没有解决办法?有。通过良好的设计,可以
避免这个问题。可以使用一个单例的路径管理类来管理所有的请求,所有发出的请求需要在这个管理类里注册,请求可以与当前发出
的页面的类名(Class)进行绑定,从而在页面销毁时,注销所有与该页面关联的请求。
Activity.isFinishing()
大致步骤: 子线程网络请求--得到结果(成功/失败) -- 通过Handler将结果返回主线程 -- 主线程更新ui
最后两步
我们可以加入判断,如果页面被销毁了,那么直接返回,不通知主线程更新UI了,这样就可以完美解决问题了。
以上方案有一个弊端,在每个网络回调执行的时候,都需要提前判断Activity.isFinishing()
。如果有一个没有按照规定判断,那么这个App就有可能存在上述隐患,并且在原有的网络基础框架上修改这么多的网络回调是不太现实的。
在网上参考到了更好的解决方案:
基于Lifeful接口的异步回调框架
Lifeful接口设计
我们定义Lifeful,一个不依赖于Context、也不依赖于PATH的接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/*
* 判断生命周期是否已经结束的一个接口。
*/
public interface Lifeful {
/**
* 判断某一个组件生命周期是否已经走到最后。一般用于异步回调时判断Activity或Fragment生命周期是否已经结束。
*
* @return
*/
boolean isAlive();
}
|
实际上,我们只需要让具有生命周期的类(一般是Activity或Fragment)实现这个接口,然后再通过这个接口来判断这个实现类是否还存在,就可以与Context解耦了。
接下来定义一个接口生成器,通过弱引用包装Lifeful接口的实现类,并返回所需要的相关信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/*
* 生命周期具体对象生成器。
*/
public interface LifefulGenerator<Callback> {
/**
* @return 返回回调接口。
*/
Callback getCallback();
/**
* 获取与生命周期绑定的弱引用,一般为Context,使用一层WeakReference包装。
*
* @return 返回与生命周期绑定的弱引用。
*/
WeakReference<Lifeful> getLifefulWeakReference();
/**
* 传入的引用是否为Null。
*
* @return true if {@link Lifeful} is null.
*/
boolean isLifefulNull();
}
|
提供一个该接口的默认实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/*
* 默认生命周期管理包装生成器。
*/
public class DefaultLifefulGenerator<Callback> implements LifefulGenerator<Callback> {
private WeakReference<Lifeful> mLifefulWeakReference;
private boolean mLifefulIsNull;
private Callback mCallback;
public DefaultLifefulGenerator(Callback callback, Lifeful lifeful) {
mCallback = callback;
mLifefulWeakReference = new WeakReference<>(lifeful);
mLifefulIsNull = lifeful == null;
}
@Override
public Callback getCallback() {
return mCallback;
}
public WeakReference<Lifeful> getLifefulWeakReference() {
return mLifefulWeakReference;
}
@Override
public boolean isLifefulNull() {
return mLifefulIsNull;
}
}
|
接着通过一个静态方法判断是否对象的生命周期:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
/*
* 生命周期相关帮助类。
*/
public class LifefulUtils {
private static final String TAG = LifefulUtils.class.getSimpleName();
public static boolean shouldGoHome(WeakReference<Lifeful> lifefulWeakReference, boolean objectIsNull) {
if (lifefulWeakReference == null) {
Log.e(TAG, "Go home, lifefulWeakReference == null");
return true;
}
Lifeful lifeful = lifefulWeakReference.get();
/**
* 如果传入的Lifeful不为null,但弱引用为null,则这个对象被回收了。
*/
if (null == lifeful && !objectIsNull) {
Log.e(TAG, "Go home, null == lifeful && !objectIsNull");
return true;
}
/**
* 对象的生命周期结束
*/
if (null != lifeful && !lifeful.isAlive()) {
Log.e(TAG, "Go home, null != lifeful && !lifeful.isAlive()");
return true;
}
return false;
}
public static <T> boolean shouldGoHome(LifefulGenerator<T> lifefulGenerator) {
if (null == lifefulGenerator) {
Log.e(TAG, "Go home, null == lifefulGenerator");
return true;
} if (null == lifefulGenerator.getCallback()) {
Log.e(TAG, "Go home, null == lifefulGenerator.getCallback()");
return true;
}
return shouldGoHome(lifefulGenerator.getLifefulWeakReference(), lifefulGenerator.isLifefulNull());
}
}
|
具有生命周期的Runnable
具体到跟线程打交道的异步类,只有Runnable
(Thread
也是其子类),因此只需要处理Runnable
就可以了。我们可以通过Wrapper
包装器模式,在处理真正的Runnable类之前,先通过Lifeful接口判断对象是否还存在,如果不存在则直接返回。对于Runnable
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/*
* 与周期相关的异步线程回调类。
*/
public class LifefulRunnable implements Runnable {
private LifefulGenerator<Runnable> mLifefulGenerator;
public LifefulRunnable(Runnable runnable, Lifeful lifeful) {
mLifefulGenerator = new DefaultLifefulGenerator<>(runnable, lifeful);
}
@Override
public void run() {
if (LifefulUtils.shouldGoHome(mLifefulGenerator)) {
return;
}
mLifefulGenerator.getCallback().run();
}
}
|
Lifeful的实现类
最后说一下Lifeful类的实现类,主要包括Activity和Fragment,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class BaseActivity extends Activity implements Lifeful {
@Override
public boolean isAlive() {
return activityIsAlive();
}
public boolean activityIsAlive() {
if (currentActivity == null) return false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return !(currentActivity.isDestroyed() || currentActivity.isFinishing());
} else {
return !currentActivity.isFinishing();
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class BaseFragment extends Fragment implements Lifeful {
@Override
public boolean isAlive() {
return activityIsAlive();
}
public boolean activityIsAlive() {
Activity currentActivity = getActivity();
if (currentActivity == null) return false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return !(currentActivity.isDestroyed() || currentActivity.isFinishing());
} else {
return !currentActivity.isFinishing();
}
}
}
|
除了这两个类以外,别的类如果有生命周期,或者包含生命周期的引用,也可以实现Lifeful接口(如View,可以通过onAttachedToWindow()
和onDetachedToWindow()
)。
包含生命周期的异步调用
对于需要用到异步的地方,调用也很方便。
1
2
3
4
5
6
7
|
// ThreadCore是一个用于线程调度的ThreadPoolExecutor封装类,也用于主线程和工作线程之间的切换
ThreadCore.getInstance().postOnMainLooper(new LifefulRunnable(new Runnable() {
@Override
public void run() {
// 实现真正的逻辑。
}
}, this));
|
总结
本文主要针对Android中具有生命周期的对象在已经被销毁时对应的异步线程的处理方式进行解耦的过程。通过定义Lifeful接口,
实现了不依赖于Context或其他容易造成内存泄漏的对象,却又能与对象的生命周期进行绑定的方法。
参考以下文章:
1. http://note.tyz.ren/blog/post/zerozhiqin/%E5%A6%82%E4%BD%95%E5%9C%A8%E5%9B%9E%E8%B0%83%E6%97%B6%E5%88%A4%E6%96%ADActivity%EF%BC%8CFragment%EF%BC%8CImageView%E7%AD%89%E7%AD%89%E6%98%AF%E5%90%A6%E5%B7%B2%E7%BB%8F%E8%A2%AB%E5%85%B3%E9%97%AD
2. http://www.jianshu.com/p/e7e1755d76b2
Android 避免主线程执行网络请求之Activity/Fragment 结束后处理异步回调相关推荐
- Android强制在主线程进行网络请求
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentVie ...
- Android 4主线程访问网络
最近做一个Android项目,利用Jsoup读取和解析网页数据,同样的程序在Android2.3上运行完全正常,而跑到Android4上面,bug出现了... 看了一下异常:android.os.Ne ...
- 安卓HttpURLConnection 进行http请求(传递数据 获取数据 主线程禁止网络请求)以get方式为例
注意: 1.安卓主线程禁止联网操作,如果在主线程中使用HttpURLConnection需通过new Thread()在新的线程中使用. 2.使用HttpURLConnection时必须设计异常处理. ...
- android子线程没有运行完,android假如主线程依赖子线程A的执行结果,如何让A执行完成,之后主线程再往下执行呢?...
/* String ObjectResult="原先的结果"; //使用VOLLY框架(与问题无关) JsonObjectRequest jsonObjectRequest = n ...
- Android开发之MVVM模式实践:协程与网络请求的结合
前言 大家好,我是小益!在经过前两章对协程的介绍后,我们终于又回到了 MVVM的封装 .协程在Android开发中最常用的场景应该是网络请求了,其次是一些使用 Thread 的场景,本章内容我们将着重 ...
- android广播怎样运行在子线程,android假如主线程依赖子线程A的执行结果,如何让A执行完成,之后主线程再往下执行呢?...
抛开你这段代码不看,单根据你的标题来回答: android假如主线程依赖子线程A的执行结果,如何让A执行完成,之后主线程再往下执行呢? 需要在子线程执行完成的地方,通过主线程的Handler发送一条消 ...
- Android4.0 以后不允许在主线程进行网络连接
Android4.0 以后不允许在主线程进行网络连接,否则会出现 android.os.NetworkOnMainThreadException.因此,必须另起一个线程进行网络连接方面的操作. pac ...
- android okgo 参数map,OkGo 网络请求框架介绍与使用说明
前言 使用 Android Studio 用户 一般来说,只需要添加第一个 okgo 的核心包即可,其余的三个库根据自己的需要选择添加. //必须使用 compile 'com.lzy.net:okg ...
- android搭建网络框架,Android 搭建MVP+Retrofit+RxJava网络请求框架(三)
上一篇中主要是将mvp+rxjava+retrofit进行了结合,本篇主要是对mvp框架的优化:建议先去看上一篇:Android 搭建MVP+Retrofit+RxJava网络请求框架(二) 针对vi ...
最新文章
- 【工具】统计jar包和apk中的java方法数
- [Linux网络编程学习笔记]套接字地址结构
- JS自动刷新当前页面
- linux运行前探秘之四,Linux运行前探秘之四_内核解压缩_三_
- centos7安装python-pip
- Avalonia跨平台入门第二篇
- 宅家过年 | 程序员消遣活动指南
- 移动设备HTML5页面布局
- Oralce 时间TIMESTAMP的比较
- LeetCode-Linked List Cycle II
- ffmpeg添加到环境变量_在 Mac 上为 FFmpeg 配置环境变量
- context c语言作用,理解 Go context
- 对象复制语意学(Object Copy Semantics)
- 联想硬盘保护系统安装
- 极限与连续知识点总结_大一上学期《高等数学》知识整理-第一章 极限与连续...
- python柱状图挨在一起_Excel图表,怎么把柱形图紧挨着?-excle柱状图挨在一起
- CSDN送你一份春节压岁钱,请在 24H 内领取!
- 如何系统化学Python?
- 位运算与位运算的常见用法
- incaseformat蠕虫病毒昨日“发作“,23日可能还会发作
热门文章
- unity修改飞行数据_数据预测和文化,或者我如何在没有飞行汽车的情况下学会生活...
- win10系统卷影复制服务器,清理“系统还原和卷影复制”释放Win10系统磁盘空间...
- Windowstogo(WTG)配置教程
- oracle采购会计分录,采购、接收、应付业务和会计分录
- 浅谈文本生成或者文本翻译解码策略
- 机器人EV3初级、中级、高级课程课件
- 在线支付,出款和收款
- 逗留汉城 (9/12)
- 简单使用discord.com教程
- 3Dmax学习质感细节立体_记录一下