亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時間:2017年03月06日 11:41:00   作者:Xing  
這篇文章主要介紹了關(guān)于在Activity/Fragment結(jié)束時處理異步回調(diào)的解決方案,文中介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考價值,需要的朋友們下面來一起看看吧。

頭疼的IllegalArgumentException

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

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

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

      主線程發(fā)起請求

      ->網(wǎng)絡(luò)框架開啟工作線程進(jìn)行網(wǎng)絡(luò)請求

      ->工作線程拿到請求結(jié)果

      ->將請求結(jié)果通過Handler返回主線程

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

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

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)

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

目前的解決方案

有人會問,框架設(shè)計者怎么會沒有考慮到這個問題?實際上他們確實考慮了這個問題。目前來說有兩種解決方案:

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

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

在Activity結(jié)束時取消請求

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

/**
 * 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是在主線程調(diào)起網(wǎng)絡(luò)請求時,通過Request.setTag(Object)傳進(jìn)去的。tag可以是任意類型,通常來說可以使用下面兩種類型:

  • Context
  • PATH

Context

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

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

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

PATH

既然持有Context的問題比較嚴(yán)重,那么我們可以根據(jù)請求路徑來唯一識別一個請求。發(fā)起請求的對象需要維持一個列表,記錄當(dāng)前發(fā)出的請求路徑,在請求回來時再從該列表通過路徑來刪除該請求。在Activity結(jié)束但請求未發(fā)出或者未返回時,再將于這個Activity綁定的列表中的所有請求取消。

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

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

Activity.isFinishing()

在異步請求的流程中,我們注意到最后兩步:

      ->將請求結(jié)果通過Handler返回主線程

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

在這最后兩步執(zhí)行的過程中,我們可以加入判斷,如果頁面被銷毀了,那么直接返回,不通知主線程更新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...
 }
});

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

有沒有更好的解決方案?請看下文。

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

Lifeful接口設(shè)計

我們定義Lifeful,一個不依賴于Context、也不依賴于PATH的接口。

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

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

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

/**
 * Created by xingli on 9/22/16.
 *
 * 生命周期具體對象生成器。
 */
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();
}

提供一個該接口的默認(rèn)實現(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;
 }
}

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

/**
 * Created by xingli on 9/22/16.
 *
 * 生命周期相關(guān)幫助類。
 */

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;
 }
 /**
  * 對象的生命周期結(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

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

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

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

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

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

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

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

總結(jié)

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

相關(guān)文章

  • Android?PowerManagerService?打開省電模式

    Android?PowerManagerService?打開省電模式

    這篇文章主要介紹了Android?PowerManagerService打開省電模式,文章通告省電模式的打開過程、什么是?battery?saver?sticky?模式兩部分展開詳情,感興趣的朋友可以參考一下
    2022-08-08
  • Android獲取app應(yīng)用程序大小的方法

    Android獲取app應(yīng)用程序大小的方法

    本文通過一段代碼給大家介紹android獲取app應(yīng)用程序大小的方法,由于android對這種方法進(jìn)行了封裝,我們沒有權(quán)限去調(diào)用這個方法,只能通過aidl,然后用java的反射機(jī)制去調(diào)用系統(tǒng)級方法,感興趣的朋友一起學(xué)習(xí)吧
    2015-11-11
  • Android判斷是否為飛行模式簡單方法

    Android判斷是否為飛行模式簡單方法

    這篇文章主要介紹了Android判斷是否為飛行模式簡單方法,本文使用最簡單的方法實現(xiàn)判斷是否為飛行模式,需要的朋友可以參考下
    2015-04-04
  • Android實現(xiàn)記事本功能

    Android實現(xiàn)記事本功能

    這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)記事本功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • RxJava+Retrofit實現(xiàn)網(wǎng)絡(luò)請求封裝的方法

    RxJava+Retrofit實現(xiàn)網(wǎng)絡(luò)請求封裝的方法

    Retrofit是當(dāng)前應(yīng)用非常廣泛的網(wǎng)絡(luò)請求框架,通常結(jié)合RxJava來進(jìn)行網(wǎng)絡(luò)請求,本文將展示一個采用RxJava+Retrofit的網(wǎng)絡(luò)請求demo,感興趣的可以了解一下
    2019-04-04
  • Android仿QQ未讀消息--紅點拖拽刪除【源代碼】

    Android仿QQ未讀消息--紅點拖拽刪除【源代碼】

    本文Demo是一款仿qq未讀消息拖拽刪除的例子,繼承RelativeLayout的WaterDrop實現(xiàn)了圓形圖標(biāo)功能;繼承ImageView的CircleImageView圓形圖片功能。效果非常不錯,很適合有圓形設(shè)計的朋友參考
    2017-04-04
  • Android實現(xiàn)兩圓點之間來回移動加載進(jìn)度

    Android實現(xiàn)兩圓點之間來回移動加載進(jìn)度

    這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)兩圓點之間來回移動加載進(jìn)度,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • Android中用MediaRecorder進(jìn)行錄影的實例代碼

    Android中用MediaRecorder進(jìn)行錄影的實例代碼

    這篇文章主要介紹了Android中用MediaRecorder進(jìn)行錄影的實例代碼,有需要的朋友可以參考一下
    2014-01-01
  • viewpager+photoview實現(xiàn)圖片查看器

    viewpager+photoview實現(xiàn)圖片查看器

    這篇文章主要為大家詳細(xì)介紹了viewpager+photoview實現(xiàn)圖片查看器,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Android使用內(nèi)置WebView打開TextView超鏈接的實現(xiàn)方法

    Android使用內(nèi)置WebView打開TextView超鏈接的實現(xiàn)方法

    這篇文章主要介紹了Android使用內(nèi)置WebView打開TextView超鏈接的實現(xiàn)方法,文中給出了詳細(xì)的示例代碼,對各位Android開發(fā)者們具有一定的參考價值,需要的朋友們下面來一起看看吧。
    2017-03-03

最新評論