1. 程式人生 > >Activity被回收掉之後的網路請求回撥處理方法詳解

Activity被回收掉之後的網路請求回撥處理方法詳解

想起寫這麼一篇博文的前提是上週去面試了一家公司,其中有這麼一個問題印象深刻,結合當時在網上看到的解決辦法我就說了一個錯誤答案,結果當場就被面試官給指出了錯誤,所以回來後和我的領導一起討論了這麼一個問題,他提出了一個很好地解決思路,於是乎我便寫了這麼一段程式碼,對於能夠真正的解決這個問題的,我相信這是最正確的,如果有更好的,我收回這句話,哈哈哈~

問:Android中進行網路請求,如果當網路請求完成後回撥,這個activity已經被回收了,如何處理?

我當時的錯誤答案:在activity的回撥方法裡通過isFinishing()方法判斷這個activity是否已經被銷燬了,如果銷燬的話就不進行資料的載入或顯示,或者在我們的presenter中判斷,如果activity回收了,我們可以取消網路請求,不進行回撥。

問:怎麼能通過isFinishing()方法呢?activity已經被回收了,通過this.isFinishing()方法不會報空指標異常嗎?

我當時的想法:嗯,想想好像是這樣的……然後就不知道怎麼說了……

所以回來後我也和別人討論瞭如何解決這個問題,還是厲害的人的思路比較廣闊,他給我提出了一個建議,我們當時是直接在presenter中對activity進行了引用,這種引用屬於強引用,所以當我們的activity被回收之後,我們的presenter持有的強引用不能被記憶體回收,容易造成記憶體洩露,並且這個時候回撥,我們的activity已經被回收不存在了,所以這個時候載入資料就會丟擲異常,那麼應該怎麼解決這個問題呢?

在這個案例中,思路就是在presenter中對activity物件實行弱引用,也就是不直接持有activity物件的引用,那麼當我們的activity被銷燬後,我們的presenter所持有的弱引用也能夠被記憶體回收,基於這個思路,我寫下了以下的程式碼並進行了驗證模擬activity被回收的場景。

  • 首先是activity_main.xml檔案佈局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="模擬網路請求" /> </RelativeLayout>
  • 建立一個介面ICallBackView
public interface ICallBackView {
    void getMessageSuccess(String msg);
}
  • MainActivity實現這個介面
public class MainActivity extends Activity implements ICallBackView {

    private static final String TAG = "MainActivity";
    private MyPresenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        mPresenter = new MyPresenter(this);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.requestMessage();
                finish();
            }
        });
    }

    /**
     * 模擬網路請求成功的回撥
     *
     * @param msg
     */
    @Override
    public void getMessageSuccess(String msg) {
        Log.e(TAG, "getMessageSuccess: " + msg);
    }
}
  • 建立我們的presenter
public class MyPresenter {

    private static final String TAG = "MyPresenter";
    private WeakReference<ICallBackView> weakReference;

    public MyPresenter(ICallBackView callBackView) {
        weakReference = new WeakReference<>(callBackView);
    }

    /**
     * 模擬請求網路
     */
    public void requestMessage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模擬網路阻塞
                    Log.e(TAG, "run: 正在進行網路請求......");
                    Thread.sleep(3000);

                    ICallBackView view = weakReference.get();
                    if (view != null) {
                        //說明引用還存在,可以進行回撥
                        view.getMessageSuccess("請求成功,這是返回的內容......");
                    } else {
                        //說明引用已經被記憶體回收了
                        Log.e(TAG, "run: 當前引用為null,不進行回撥......");
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

程式碼比較簡單,當我們的按鈕點選模擬網路請求,我們在presenter中模擬網路請求耗時,然後通過弱引用判斷activity是否還在,當然我們這裡演示所用,不得已在點選按鈕後將activity關閉了,正常情況下我們還需要在程式碼中判斷一下activity是否被finish掉了,如下程式碼所示:

ICallBackView view = weakReference.get();
if (view != null) {
    //說明引用還存在,可以進行回撥
    if (!((MainActivity) view).isFinishing()) {
        view.getMessageSuccess("請求成功,這是返回的內容......");
    }
} else {
    //說明引用已經被記憶體回收了
    Log.e(TAG, "run: 當前引用為null,不進行回撥......");
}

我們這裡為了演示回撥,所以沒有加上判斷activity是否被finish掉,請大家注意並理解。

下面我們來執行程式看看後臺的輸入的日誌,我們先進行睡眠3秒,時間較短,activity被finish掉後可能還沒有被回收掉:
這裡寫圖片描述

可以看到,雖然MainActivity被finish掉了,但是還沒來得及回收掉,所以還能進行回撥(當然這裡activity被finish掉了,不應該進行回撥,不過這裡我們要模擬activity被回收的場景,所以不得已這樣做,大家理解)。

下面我們模擬一下網路請求比較耗時的場景,睡眠10秒,來看看如何輸出日誌:
這裡寫圖片描述

可以看到,當我們的網路請求比較耗時時,我們的activity已經finish掉並且已經被回收了,這裡我們的弱引用就拿不到物件activity的引用,所以這裡就不會進行網路回撥,避免了一些異常的產生。

總結

首先大家理解弱引用的使用,當我們的引用物件為null時,弱引用持有的物件引用就會為null,結合我們的使用場景,我們在準備進行網路回撥的時候,應該先判斷一下弱引用的引用是否為空,並且應該判斷一下要回調的activity是否被finish或destroy掉了,這才是正確的使用方式,如果弱引用持有的引用不為null並且activity沒有被finish掉的話,就進行回撥方法的呼叫,這樣才能最大限度上保持頁面不會引用資料回到導致的各種異常。

寫在最後,如果文中有哪些地方寫的不對或者大家有更好的方式解決這種問題的話,歡迎大家留言討論,一起進步!