a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁(yè) > 系統(tǒng) > Android > 正文

Activity/Fragment結(jié)束時(shí)處理異步回調(diào)的解決方案

2019-12-12 03:28:42
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

頭疼的IllegalArgumentException

在Android開(kāi)發(fā)的過(guò)程中,涉及到與UI相關(guān)的操作只能在主線(xiàn)程執(zhí)行,否則就會(huì)拋出以下異常:

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

當(dāng)然這屬于基本常識(shí),也不是本文討論的重點(diǎn),但后續(xù)的所有討論都圍繞這一基本常識(shí)進(jìn)行。在開(kāi)發(fā)Android應(yīng)用時(shí),如果所有的代碼都在主線(xiàn)程執(zhí)行,很容易就會(huì)出現(xiàn)ANR,并且Android在4.0以后已經(jīng)禁止在主線(xiàn)程中執(zhí)行網(wǎng)絡(luò)請(qǐng)求,因此或多或少地需要與多線(xiàn)程打交道。無(wú)論是使用當(dāng)前熱火朝天的OkHttp(Retrofit),還是使用過(guò)時(shí)的Volley或者Android-Async-Http,它們都支持異步請(qǐng)求。這些異步請(qǐng)求的請(qǐng)求流程一般如下:

      主線(xiàn)程發(fā)起請(qǐng)求

      ->網(wǎng)絡(luò)框架開(kāi)啟工作線(xiàn)程進(jìn)行網(wǎng)絡(luò)請(qǐng)求

      ->工作線(xiàn)程拿到請(qǐng)求結(jié)果

      ->將請(qǐng)求結(jié)果通過(guò)Handler返回主線(xiàn)程

      ->主線(xiàn)程更新UI,完成一次網(wǎng)絡(luò)請(qǐng)求

這個(gè)流程看似正常,實(shí)則暗含危機(jī)。下面的崩潰就是其中一個(gè)例子。

java.lang.IllegalArgumentException: View=com.android.internal.policy.impl.PhoneWindow$DecorView{24e9c19a V.E..... R......D 0,0-1026,348} not attached to window managerat 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)

這個(gè)崩潰是怎么發(fā)生的呢?原因很簡(jiǎn)單,工作線(xiàn)程執(zhí)行網(wǎng)絡(luò)請(qǐng)求,如果這個(gè)請(qǐng)求執(zhí)行的時(shí)間過(guò)久(由于網(wǎng)絡(luò)延遲等原因),Activity或者Fragment已經(jīng)不存在了(被銷(xiāo)毀了),而線(xiàn)程并不知道這件事,這時(shí)候請(qǐng)求數(shù)據(jù)結(jié)果回來(lái)以后,將數(shù)據(jù)通過(guò)Handler拋給了主線(xiàn)程,在異步回調(diào)里一般都會(huì)執(zhí)行數(shù)據(jù)的更新或者進(jìn)度條的更新等操作,但頁(yè)面已經(jīng)不存在了,所以就gg了。。。

目前的解決方案

有人會(huì)問(wèn),框架設(shè)計(jì)者怎么會(huì)沒(méi)有考慮到這個(gè)問(wèn)題?實(shí)際上他們確實(shí)考慮了這個(gè)問(wèn)題。目前來(lái)說(shuō)有兩種解決方案:

  1. 在Activity結(jié)束時(shí)取消請(qǐng)求
  2. 在異步回調(diào)的時(shí)候,通過(guò)Activity.isFinishing()方法判斷Activity是否已經(jīng)被銷(xiāo)毀

這兩種方案本質(zhì)是一樣的,就是判斷Activity是否被銷(xiāo)毀,如果銷(xiāo)毀,則要么取消回調(diào),要么不執(zhí)行回調(diào)。

在Activity結(jié)束時(shí)取消請(qǐng)求

以Volley為例,在RequestQueue這個(gè)類(lèi)中,提供了通過(guò)tag來(lái)取消與之相關(guān)的網(wǎng)絡(luò)請(qǐng)求。

/** * 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; } });}

這個(gè)tag是在主線(xiàn)程調(diào)起網(wǎng)絡(luò)請(qǐng)求時(shí),通過(guò)Request.setTag(Object)傳進(jìn)去的。tag可以是任意類(lèi)型,通常來(lái)說(shuō)可以使用下面兩種類(lèi)型:

  • Context
  • PATH

Context

將Context作為tag帶入請(qǐng)求中,當(dāng)持有Context的對(duì)象銷(xiāo)毀時(shí),可以通知該請(qǐng)求線(xiàn)程取消。一個(gè)典型的使用場(chǎng)景就是在Activity的onDestroy()方法調(diào)用Request.cancelAll(this)來(lái)取消與該Context相關(guān)的所有請(qǐng)求。就Volley來(lái)說(shuō),它會(huì)在請(qǐng)求發(fā)出之前以及數(shù)據(jù)回來(lái)之后這兩個(gè)時(shí)間段判斷請(qǐng)求是否被cancel。

但是作為Android開(kāi)發(fā)者,應(yīng)該知道,持有Context引用的線(xiàn)程是危險(xiǎn)的,如果線(xiàn)程發(fā)生死鎖,Context引用會(huì)被線(xiàn)程一直持有,導(dǎo)致該Context得不到釋放,容易引起內(nèi)存泄漏。如果該Context的實(shí)例是一個(gè)Activity,那么這個(gè)結(jié)果是災(zāi)難性的。

有沒(méi)有解決辦法?有。線(xiàn)程通過(guò)弱引用持有Context。當(dāng)系統(tǒng)內(nèi)存不足需要GC時(shí),會(huì)優(yōu)先回收持有弱引用的對(duì)象。然而這樣做還是存在隱患,有內(nèi)存泄漏的風(fēng)險(xiǎn)。

PATH

既然持有Context的問(wèn)題比較嚴(yán)重,那么我們可以根據(jù)請(qǐng)求路徑來(lái)唯一識(shí)別一個(gè)請(qǐng)求。發(fā)起請(qǐng)求的對(duì)象需要維持一個(gè)列表,記錄當(dāng)前發(fā)出的請(qǐng)求路徑,在請(qǐng)求回來(lái)時(shí)再?gòu)脑摿斜硗ㄟ^(guò)路徑來(lái)刪除該請(qǐng)求。在Activity結(jié)束但請(qǐng)求未發(fā)出或者未返回時(shí),再將于這個(gè)Activity綁定的列表中的所有請(qǐng)求取消。

看起來(lái)方案不錯(cuò),但執(zhí)行起來(lái)如何呢?每個(gè)Activity都需要維持一個(gè)當(dāng)前頁(yè)面發(fā)出的請(qǐng)求列表,在Activity結(jié)束時(shí)再取消列表中的請(qǐng)求。面對(duì)一個(gè)應(yīng)用幾十上百個(gè)Activity,這樣的實(shí)現(xiàn)無(wú)疑是蛋疼的。

有沒(méi)有解決辦法?有。通過(guò)良好的設(shè)計(jì),可以避免這個(gè)問(wèn)題。可以使用一個(gè)單例的路徑管理類(lèi)來(lái)管理所有的請(qǐng)求。所有發(fā)出的請(qǐng)求需要在這個(gè)管理類(lèi)里注冊(cè),請(qǐng)求可以與當(dāng)前發(fā)出的頁(yè)面的類(lèi)名(Class)進(jìn)行綁定,從而在頁(yè)面銷(xiāo)毀時(shí),注銷(xiāo)所有與該頁(yè)面關(guān)聯(lián)的請(qǐng)求。

Activity.isFinishing()

在異步請(qǐng)求的流程中,我們注意到最后兩步:

      ->將請(qǐng)求結(jié)果通過(guò)Handler返回主線(xiàn)程

      ->主線(xiàn)程更新UI,完成一次網(wǎng)絡(luò)請(qǐng)求

在這最后兩步執(zhí)行的過(guò)程中,我們可以加入判斷,如果頁(yè)面被銷(xiāo)毀了,那么直接返回,不通知主線(xiàn)程更新UI了,這樣就可以完美解決問(wèn)題了。

類(lèi)似的一個(gè)例子如下:

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... }});

盡管解決了問(wèn)題,但是麻煩又來(lái)了,如果只有一個(gè)人開(kāi)發(fā)還好,需要時(shí)刻記住每個(gè)網(wǎng)絡(luò)回調(diào)執(zhí)行的時(shí)候,都需要提前判斷Activity.isFinishing()。然而一個(gè)App往往有多個(gè)開(kāi)發(fā)者一起協(xié)作完成,如果有一個(gè)開(kāi)發(fā)者沒(méi)有按照規(guī)定判斷,那么這個(gè)App就有可能存在上述隱患,并且,在原有的網(wǎng)絡(luò)基礎(chǔ)框架上修改這么多的網(wǎng)絡(luò)回調(diào)是不太現(xiàn)實(shí)的。

有沒(méi)有更好的解決方案?請(qǐng)看下文。

基于Lifeful接口的異步回調(diào)框架

Lifeful接口設(shè)計(jì)

我們定義Lifeful,一個(gè)不依賴(lài)于Context、也不依賴(lài)于PATH的接口。

/** * Created by xingli on 9/21/16. * * 判斷生命周期是否已經(jīng)結(jié)束的一個(gè)接口。 */public interface Lifeful { /** * 判斷某一個(gè)組件生命周期是否已經(jīng)走到最后。一般用于異步回調(diào)時(shí)判斷Activity或Fragment生命周期是否已經(jīng)結(jié)束。 * * @return */ boolean isAlive();}

實(shí)際上,我們只需要讓具有生命周期的類(lèi)(一般是Activity或Fragment)實(shí)現(xiàn)這個(gè)接口,然后再通過(guò)這個(gè)接口來(lái)判斷這個(gè)實(shí)現(xiàn)類(lèi)是否還存在,就可以與Context解耦了。

接下來(lái)定義一個(gè)接口生成器,通過(guò)弱引用包裝Lifeful接口的實(shí)現(xiàn)類(lèi),并返回所需要的相關(guān)信息。

/** * Created by xingli on 9/22/16. * * 生命周期具體對(duì)象生成器。 */public interface LifefulGenerator<Callback> { /** * @return 返回回調(diào)接口。 */ Callback getCallback(); /** * 獲取與生命周期綁定的弱引用,一般為Context,使用一層WeakReference包裝。 * * @return 返回與生命周期綁定的弱引用。 */ WeakReference<Lifeful> getLifefulWeakReference(); /** * 傳入的引用是否為Null。 * * @return true if {@link Lifeful} is null. */ boolean isLifefulNull();}

提供一個(gè)該接口的默認(rèn)實(shí)現(xiàn):

/** * Created by xingli on 9/22/16. * * 默認(rèn)生命周期管理包裝生成器。 */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; }}

接著通過(guò)一個(gè)靜態(tài)方法判斷是否對(duì)象的生命周期:

/** * Created by xingli on 9/22/16. * * 生命周期相關(guān)幫助類(lèi)。 */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,則這個(gè)對(duì)象被回收了。  */ if (null == lifeful && !objectIsNull) {  Log.e(TAG, "Go home, null == lifeful && !objectIsNull");  return true; } /**  * 對(duì)象的生命周期結(jié)束  */ 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

具體到跟線(xiàn)程打交道的異步類(lèi),只有Runnable(Thread也是其子類(lèi)),因此只需要處理Runnable就可以了。我們可以通過(guò)Wrapper包裝器模式,在處理真正的Runnable類(lèi)之前,先通過(guò)Lifeful接口判斷對(duì)象是否還存在,如果不存在則直接返回。對(duì)于Runnable:

/** * Created by xingli on 9/21/16. * * 與周期相關(guān)的異步線(xiàn)程回調(diào)類(lèi)。 */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的實(shí)現(xiàn)類(lèi)

最后說(shuō)一下Lifeful類(lèi)的實(shí)現(xiàn)類(lèi),主要包括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();  } }}

除了這兩個(gè)類(lèi)以外,別的類(lèi)如果有生命周期,或者包含生命周期的引用,也可以實(shí)現(xiàn)Lifeful接口(如View,可以通過(guò)onAttachedToWindow()onDetachedToWindow() ) 。

包含生命周期的異步調(diào)用

對(duì)于需要用到異步的地方,調(diào)用也很方便。

// ThreadCore是一個(gè)用于線(xiàn)程調(diào)度的ThreadPoolExecutor封裝類(lèi),也用于主線(xiàn)程和工作線(xiàn)程之間的切換ThreadCore.getInstance().postOnMainLooper(new LifefulRunnable(new Runnable() { @Override public void run() {  // 實(shí)現(xiàn)真正的邏輯。 }}, this));

總結(jié)

本文主要針對(duì)Android中具有生命周期的對(duì)象在已經(jīng)被銷(xiāo)毀時(shí)對(duì)應(yīng)的異步線(xiàn)程的處理方式進(jìn)行解耦的過(guò)程。通過(guò)定義Lifeful接口,實(shí)現(xiàn)了不依賴(lài)于Context或其他容易造成內(nèi)存泄漏的對(duì)象,卻又能與對(duì)象的生命周期進(jìn)行綁定的方法。好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)各位Android開(kāi)發(fā)們能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。

發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 黄a在线| 欧美精品网站 | 国产性久久 | 国产精品一区久久久久 | 一级黄网 | 久久久精品一区二区 | 欧美性生活视频 | 在线免费自拍 | 国产精品久久久一区二区 | 日本久久精品视频 | 欧美精品第十页 | 亚洲国产伊人 | 欧美精品免费在线 | 免费毛片在线播放 | 日本亚洲一区 | 自拍偷拍一区二区三区 | 欧美三级不卡 | 色婷婷影院 | 看亚洲a级一级毛片 | 久草成人 | 91久久国产综合久久91精品网站 | 国产私拍视频 | 91在线免费视频 | 日韩天堂 | 欧美精产国品一二三区 | 久久亚洲欧美日韩精品专区 | 欧美在线观看禁18 | 亚州中文字幕蜜桃视频 | 国产精品人成在线播放 | 中文字幕一二三区 | 欧美午夜精品久久久久免费视 | 精品在线播放 | 91人人干| 亚洲三级在线播放 | 性一级录像片片视频免费看 | 欧美a区 | 国产免费一级片 | 亚洲精品在线网站 | 一本之道高清码 | 欧美三级电影在线观看 | 国产高清自拍 |