1. 程式人生 > >Activity被回收掉之後的網路回撥處理

Activity被回收掉之後的網路回撥處理

早上來到公司剛開啟電腦,就被叫到會議室,召開緊急會議,線上出現重大bug,根據線上日誌統計,崩潰率上升了0.3個百分點(我們專案是集成了騰訊Bugly統計日誌,有興趣的同學可以去了解下)。

這個可不得了,聽說領導被老闆叫過去訓了好久,領導憋了一肚子火,我們的日子你大概能想象得到。沒辦法,那的趕緊找出bug來源,解決掉後趕緊發修復補丁(感謝現在的熱修復技術,讓我們免除了再次發包的痛苦)。

經過我們的查詢,崩潰位置很快就被找到,是一處Fragment中的網路請求回撥,在網路請求回撥成功後,進行資料填充,報了空指標,造成了崩潰。可是咋看那一句也不可能造成空指標啊,控制元件findview後已經例項化,不可能為空,資料網路請求成功也不可能為空,那到底是為什麼報空呢?測試組和我們對那一部分一起進行了模擬測試,可是未出現崩潰的現象,奇了怪了。突然想到是不是因為我們在公司都是連線的WIFI,訊號好,所以沒出現線上的問題,我們立即進行測試,切換到弱網環境,果然出現了(這也是在測試階段我們未發現這個問題的原因,建議大家在測試時要再多種環境下進行測試)。

那到底是為什麼呢?結合我們的重現,發現是因為介面被回收了,而網路請求還線上程中進行,當執行緒請求成功後那就會回撥返回資料,但是這個時候介面已經被回收掉了,控制元件為空了,所以報了空。問題找到後那趕緊解決啊。

既然是因為介面回收掉了,那我們在網路回撥後填充資料前進行一個介面是否被回收掉的判斷,就像這樣:

if (mactivity == null || mactivity.isFinishing()) {
    return;
}

然後進行測試,果然問題解決了,趕緊發補丁吧,領導的怒火終於平息了一些,我們這些開發也鬆了口氣,但是仍舊不能掉以輕心,我們繼續緊盯線上日誌,要看到問題徹底解決了我們才能放下心裡的石頭。果然,在發完補丁後一段時間,同樣的崩潰問題出現的越來越少了,看來問題解決了,偷偷的看下領導,臉上也開始有笑容了,恩,這一關終於熬過去了。

過了幾天,崩潰率也下去了,看來這一問題是徹底解決了,心裡的石頭這時候才徹底放下。漸漸地大家都忘記了這一問題,畢竟在大家看來這已經是解決了,但是我在看線上日誌的時候,突然看到了這一問題,納尼,還有,雖然很少,只有幾例,但是還是存在。我立即偷偷的和幾個同事商量這個問題,得出的結果是熱修復有一定的失敗機率,可能是因為那幾個使用者熱修復補丁載入失敗,恩,很有可能,那既然這樣大家就不用再多想了,等下次發包使用者更新了那就徹底沒有這個問題了。

可是我還是不太放心,我又在網上檢視相關的文章,資料,終於讓我找到一位的文章,連結在這:
(參考文章連結)

主要原因是因為我們的判斷不夠謹慎,我們只判斷了 ==null 和 .isFinishing() 兩個條件,點進isFinishing() 方法裡面看一下:

/**
 * Check to see whether this activity is in the process of finishing,
 * either because you called {@link #finish} on it or someone else
 * has requested that it finished.  This is often used in
 * {@link #onPause} to determine whether the activity is simply pausing or
 * completely finishing.
 *
 * @return If the activity is finishing, returns true; else returns false.
 *
 * @see #finish
 */
public boolean isFinishing() {
    return mFinished;
}

這個方法主要是返回一個標記值 mFinished ,那我們在Activity 類中全域性搜尋下這個中介值mFinished ,可以發下他是起始為false,但是隻有當activity 呼叫 finish() 時,在這個 mFinished 值會被置為true :

/**
 * Finishes the current activity and specifies whether to remove the task associated with this
 * activity.
 */
private void finish(boolean finishTask) {
    if (mParent == null) {
        int resultCode;
        Intent resultData;
        synchronized (this) {
            resultCode = mResultCode;
            resultData = mResultData;
        }
        if (false) Log.v(TAG, "Finishing self: token=" + mToken);
        try {
            if (resultData != null) {
                resultData.prepareToLeaveProcess();
            }
            if (ActivityManagerNative.getDefault()
                    .finishActivity(mToken, resultCode, resultData, finishTask)) {
                mFinished = true;
            }
        } catch (RemoteException e) {
            // Empty
        }
    } else {
        mParent.finishFromChild(this);
    }
}

而我們知道 finish() 是主動呼叫的的,也就是說只有你在程式碼中呼叫了 finish() 這個方法,它才會被置為true,也就是說如果系統記憶體緊張,回收掉了它,那就不會走 finish() 這個方法,這個時候mFinished 還是為false,這個時候用 isFinishing() 判斷 的話得不到正確的結果,那這種情況該如何判斷呢?在API 17的時候,Google 添加了一個 isDestroyed() 的判斷,專門針對這一情況 :

/**
 * Returns true if the final {@link #onDestroy()} call has been made
 * on the Activity, so this instance is now dead.
 */
public boolean isDestroyed() {
    return mDestroyed;
}

所以為了保險起見,我們還需要加上 isDestroyed() 判斷,做到萬無一失。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    if (mactivity.isDestroyed()) {
        return;
    }
}