您好,登錄后才能下訂單哦!
頭疼的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.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
將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開發們能帶來一定的幫助,如果有疑問大家可以留言交流。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。