Activity/Fragment结束时处理异步回调的解决方案

头疼的IllegalArgumentException

在Android开发的过程中,涉及到与UI相关的操作只能在主线程执行,否则就会抛出以下异常:

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

当然这属于基本常识,也不是本文讨论的重点,但后续的所有讨论都围绕这一基本常识进行。在开发Android应用时,如果所有的代码都在主线程执行,很容易就会出现ANR,并且Android在4.0以后已经禁止在主线程中执行网络请求,因此或多或少地需要与多线程打交道。无论是使用当前热火朝天的OkHttp(Retrofit),还是使用过时的Volley或者Android-Async-Http,它们都支持异步请求。这些异步请求的请求流程一般如下:

主线程发起请求

->网络框架开启工作线程进行网络请求

->工作线程拿到请求结果

->将请求结果通过Handler返回主线程

->主线程更新UI,完成一次网络请求

这个流程看似正常,实则暗含危机。下面的崩溃就是其中一个例子。

java.lang.IllegalArgumentException: View=com.android.internal.policy.impl.PhoneWindow$DecorView{24e9c19a V.E..... R......D 0,0-1026,348} not attached to window manager at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:403) at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:322) at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:84) at android.app.Dialog.dismissDialog(Dialog.java:368) at android.app.Dialog.dismiss(Dialog.java:351) at com.kaola.spring.ui.kaola.AvatarNicknameSetActivity.e(Unknown Source) at com.kaola.spring.ui.kaola.AvatarNicknameSetActivity.c(Unknown Source) at com.kaola.spring.ui.kaola.d.a(Unknown Source) at com.kaola.common.c.d$b.handleMessage(Unknown Source) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5539) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:960) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

这个崩溃是怎么发生的呢?原因很简单,工作线程执行网络请求,如果这个请求执行的时间过久(由于网络延迟等原因),Activity或者Fragment已经不存在了(被销毁了),而线程并不知道这件事,这时候请求数据结果回来以后,将数据通过Handler抛给了主线程,在异步回调里一般都会执行数据的更新或者进度条的更新等操作,但页面已经不存在了,所以就gg了。。。

目前的解决方案

有人会问,框架设计者怎么会没有考虑到这个问题?实际上他们确实考虑了这个问题。目前来说有两种解决方案:

在Activity结束时取消请求

在异步回调的时候,通过Activity.isFinishing()方法判断Activity是否已经被销毁

这两种方案本质是一样的,就是判断Activity是否被销毁,如果销毁,则要么取消回调,要么不执行回调。

在Activity结束时取消请求

以Volley为例,在RequestQueue这个类中,提供了通过tag来取消与之相关的网络请求。

/** * Cancels all requests in this queue with the given tag. Tag must be non-null * and equality is by identity. */ public void cancelAll(final Object tag) { if (tag == null) { throw new IllegalArgumentException("Cannot cancelAll with a null tag"); } cancelAll(new RequestFilter() { @Override public boolean apply(Request<?> request) { return request.getTag() == 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了,这样就可以完美解决问题了。

类似的一个例子如下:

mMessageManager.getBoxList(new BaseManager.UIDataCallBack<JSONObject>() { @Override public void onSuccess(JSONObject object) { if(isFinishing()){ return; } do what you want... } @Override public void onFail(int code, String msg) { if(isFinishing()){ return; } do what you want... } });

尽管解决了问题,但是麻烦又来了,如果只有一个人开发还好,需要时刻记住每个网络回调执行的时候,都需要提前判断Activity.isFinishing()。然而一个App往往有多个开发者一起协作完成,如果有一个开发者没有按照规定判断,那么这个App就有可能存在上述隐患,并且,在原有的网络基础框架上修改这么多的网络回调是不太现实的。

有没有更好的解决方案?请看下文。

基于Lifeful接口的异步回调框架

Lifeful接口设计

我们定义Lifeful,一个不依赖于Context、也不依赖于PATH的接口。

/** * Created by xingli on 9/21/16. * * 判断生命周期是否已经结束的一个接口。 */ public interface Lifeful { /** * 判断某一个组件生命周期是否已经走到最后。一般用于异步回调时判断Activity或Fragment生命周期是否已经结束。 * * @return */ boolean isAlive(); }

实际上,我们只需要让具有生命周期的类(一般是Activity或Fragment)实现这个接口,然后再通过这个接口来判断这个实现类是否还存在,就可以与Context解耦了。

接下来定义一个接口生成器,通过弱引用包装Lifeful接口的实现类,并返回所需要的相关信息。

/** * Created by xingli on 9/22/16. * * 生命周期具体对象生成器。 */ public interface LifefulGenerator<Callback> { /** * @return 返回回调接口。 */ Callback getCallback(); /** * 获取与生命周期绑定的弱引用,一般为Context,使用一层WeakReference包装。 * * @return 返回与生命周期绑定的弱引用。 */ WeakReference<Lifeful> getLifefulWeakReference(); /** * 传入的引用是否为Null。 * * @return true if {@link Lifeful} is null. */ boolean isLifefulNull(); }

提供一个该接口的默认实现:

/** * Created by xingli on 9/22/16. * * 默认生命周期管理包装生成器。 */ 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; } }

接着通过一个静态方法判断是否对象的生命周期:

/** * Created by xingli on 9/22/16. * * 生命周期相关帮助类。 */ 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:

/** * Created by xingli on 9/21/16. * * 与周期相关的异步线程回调类。 */ 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,

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(); } } } 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() ) 。

包含生命周期的异步调用

对于需要用到异步的地方,调用也很方便。

// ThreadCore是一个用于线程调度的ThreadPoolExecutor封装类,也用于主线程和工作线程之间的切换 ThreadCore.getInstance().postOnMainLooper(new LifefulRunnable(new Runnable() { @Override public void run() { // 实现真正的逻辑。 } }, this));

总结

本文主要针对Android中具有生命周期的对象在已经被销毁时对应的异步线程的处理方式进行解耦的过程。通过定义Lifeful接口,实现了不依赖于Context或其他容易造成内存泄漏的对象,却又能与对象的生命周期进行绑定的方法。好了,以上就是这篇文章的全部内容了,希望本文的内容对各位Android开发们能带来一定的帮助,如果有疑问大家可以留言交流。

时间: 2024-10-30 22:02:40

Activity/Fragment结束时处理异步回调的解决方案的相关文章

服务器-Android如何在一个异步任务类结束时再次开启一个异步任务类

问题描述 Android如何在一个异步任务类结束时再次开启一个异步任务类 本人Android新手,在学习中遇到了一个难题,希望大神给我一点启发,问题是这样的,我从服务器获取JSON数据进行解析,在onPostExecute中获取了一个图片的URL,当我在onPostExecute进行图片的读取时程序报错,查找了一些资料,说需要在onPostExecute里发送一个消息在开启一个任务类,请问大神,该怎么在onPostExecute里发送一个消息通知线程开启任务类呢? 解决方案 直接在onPostE

使用生成器展平异步回调结构,JS篇

1. 前言 2012 年的时候,我去详细了解过 Python 的 Tornado 框架中的 gen.py 这套工具,http://www.zouyesheng.com/generator-for-async.html ,因为觉得它用于异步环境的编程中实在太方便了,而且,适用性上几乎没有成本,你的定义部分代码完全不需要因为这套工具而作任何改动,这套工具完全是"使用时"的一种可选形式. 那时我想的就是,如果在遍地是 callback 的 Javascript 中也有这样的东西可用就好了.后

Android异步回调中的UI同步性问题

Android程序编码过程中,回调无处不在.从最常见的Activity生命周期回调开始,到BroadcastReceiver.Service以及Sqlite等.Activity.BroadcastReceiver和Service这些基本组件的回调路径和过程也就是通常意义上所谓的"生命周期".同时,在处理具体的业务逻辑时,常常设计到不同线程之间的通信,如下载图片完成后通知 UI线程更新UI,凡此类场景,无论使用哪一种具体的线程间通信方式(Handler/Message.Handler/p

Android异步回调中的UI同步性问题分析_Android

Android程序编码过程中,回调无处不在.从最常见的Activity生命周期回调开始,到BroadcastReceiver.Service以及Sqlite等.Activity.BroadcastReceiver和Service这些基本组件的回调路径和过程也就是通常意义上所谓的"生命周期".同时,在处理具体的业务逻辑时,常常设计到不同线程之间的通信,如下载图片完成后通知 UI线程更新UI,凡此类场景,无论使用哪一种具体的线程间通信方式(Handler/Message.Handler/p

使用委托实现同步回调与异步回调

使用委托可以执行的一项有用操作是实现回调.回调是传入函数的方法,在函数结束执行时调用该方法. 例如,有一个执行一系列数学操作的函数.在调用该函数时,也向其传递一个回调方法,从而在函数完成其计算工作时,调用回调方法,向用户通知计算结果.   同步回调    首先声明两个方法: AddTwoNumbers():接受两个整型实参以及一个类型委托 ResultCallback():接受一个字符串,并显示出来.代码如下: using System; using System.Collections.Gen

Android异步回调中的UI同步性问题分析

Android程序编码过程中,回调无处不在.从最常见的Activity生命周期回调开始,到BroadcastReceiver.Service以及Sqlite等.Activity.BroadcastReceiver和Service这些基本组件的回调路径和过程也就是通常意义上所谓的"生命周期".同时,在处理具体的业务逻辑时,常常设计到不同线程之间的通信,如下载图片完成后通知 UI线程更新UI,凡此类场景,无论使用哪一种具体的线程间通信方式(Handler/Message.Handler/p

ASP.NET服务器控件封装-【事件】-1.1【事件回发.异步回调】

最近,正在学习ASP.NET服务器控件封装相关的知识,把自己学到的和大家分 享下. 本次内容的概要如下: 1.事件以及为什么需要事件驱动机制: 2.回发的原理: 3.异步回调的原理: 4.事件回发的实现: 5.异步回调的实现. 了解了本次内容的概要,接下来就分节次说明了. 1.事件以及为什么需要事件驱动机制 在C#语言详解一书中对事件的定义是"事件是一种使对象或类能够提供通知的 成员",在这里换句话 说就是页面中已注册事件的对象能够对用户的操作进行捕获并处理.那么为什么 需要引用事件机

Android 如何通过一个fragment结束另一个fragment的死循环线程

问题描述 Android 如何通过一个fragment结束另一个fragment的死循环线程 有一个activity,包含两个fragment,两个fragment各自包含一个死循环线程,我想通过一个fragment结束这个activity(通过finish),顺便把这个fragment里的死循环线程结束了(通过flag=false和thread.interrupt()).再次启动这个activity,被结束的死循环线程能正常启动,但是没有结束的死循环线程不能正常工作了,我catch到错误,但不

深入探析koa之异步回调处理篇

在上一篇中我们梳理了koa当中中间件的洋葱模型执行原理,并实现了一个可以让洋葱模型自动跑起来的流程管理函数.这一篇,我们再来研究一下koa当中异步回调同步化写法的原理,同样的,我们也会实现一个管理函数,是的我们能够通过同步化的写法来写异步回调函数. 1. 回调金字塔及理想中的解决方案 我们都知道javascript是一门单线程异步非阻塞语言.异步非阻塞当然是它的一个优点,但大量的异步操作必然涉及大量的回调函数,特别是当异步嵌套的时候,就会出现回调金字塔的问题,使得代码的可读性非常差.比如下面一个