Activity基類實現儲存Bundle資料,避免空指標及重複勞動

neverwoods發表於2018-01-05

講給新手的一些話

相信有一定Android開發經驗的朋友或多或少的都遇到過 Activity 中某些物件莫名其妙的出現了空指標的異常。這種異常通常發生在 Activity 切換到了後臺,然後又做了其他一些記憶體開銷比較大的事,比如玩遊戲,比如開啟了很多其他應用。因為在後臺,所以這個 Activity 的優先順序就降低了,在記憶體比較吃緊的時候極有可能被回收。還不熟悉這一套機制的朋友在這裡可能就會遇到一些問題了。

雖然記憶體被回收了,但是由於這是系統的決定,而非程式碼觸發的行為,當使用者再啟動這個應用的時候,並不會重新走啟動流程,而是直接回到離開時候的 Activity。又因為 Activity 已經被回收了,所以理所當然的會重新建立、重走從 onCreate 開始的生命週期。

針對這種情況,Android 有 onSaveInstanceState、onRestoreInstanceState 這一對生命週期用來儲存和取出來還原 Activity 至被回收之前的狀態,具體詳情可參考: http://blog.csdn.net/initphp/article/details/11973651

正文

onSaveInstanceState、onRestoreInstanceState 真的可以解決所有問題了嗎?
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
    super.onCreate(savedInstanceState, persistentState);
    Data data = getIntent().getParcelableExtra("data");
    if (data.type == 0) {
        setContentView(R.layout.activity_to);
        //TODO
    } else {
        setContentView(R.layout.activity_from);
        //TODO
    }
}
複製程式碼

以上程式碼的意圖很清晰,是根據上一個介面傳遞過來的引數 data 中的 type 來決定載入什麼佈局。這段程式碼合不合理暫且不考慮,想一下這種情景下記憶體被回收了會發生什麼就很有意思了。

1.getIntent()無法再獲取到上一介面傳遞過來的 bundle 資料,此時 data 物件必然為空; 2.onRestoreInstanceState 的執行時機是在 onCreate 之後的,也就是說,如果我用在 onCreate 的生命週期中用 getIntent() 方法獲取上一個介面傳遞過來的引數,然後立即使用的話,其實 onRestoreInstanceState 是還沒來得及呼叫的; 3.空指標呼之欲出 T_T

So ?
我曾經是這樣做的
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
   super.onCreate(savedInstanceState, persistentState);
   
   if (savedInstanceState == null) {
       data = getIntent().getParcelableExtra("data");
   } else {
       data = savedInstanceState.getParcelable("data");
   }
   
   if (data.type == 0) {
       setContentView(R.layout.activity_to);
       //TODO
   } else {
       setContentView(R.layout.activity_from);
       //TODO
   }
}
複製程式碼

savedInstanceState 是儲存的 Activity 的狀態,如果為 null,則代表是正常進入,否則即為我們考慮的記憶體被回收後重啟的情況。

這段程式碼就是根據不同的情況,去從 getIntent() 或 savedInstanceState 中再去獲取 data 物件。當然,前提條件是我有這樣子做:

@Override
protected void onSaveInstanceState(Bundle outState) {
   outState.putParcelable("data", data);
   super.onSaveInstanceState(outState);
}
複製程式碼

即在被回收前將 data 物件儲存至 bundle 中。

整體看上去還算簡單對吧? 我慶幸於我從此脫離了NullPointer的苦海,但這只是苦難的開始 T_T

真 · 正文

實際開發過程當中,bundle 中不一定只有 data 這麼一個引數,有幾個引數的情況也很常見,於是上述程式碼就會擴張開來。

程式碼行數約等於 引數數量*2(取) + 引數數量 *1(存)

也就是說,每增加一個傳遞的引數,我就要多寫三行程式碼,毫無營養價值的三行程式碼。這是第一層意義上的重複勞動。

然後呢,每一個要接收 Bundle 引數的 Activity,我都要寫這麼幾段程式碼,邏輯基本上完全相同,這是第二層意義上的重複勞動。

有一句話說得好:不在沉默中爆發,就在沉默中滅亡。終於有一天我忍無可忍,開始想辦法怎麼才能更優(偷)雅(懶)的去解決這個問題。

要避免每個 Activity 都寫同樣的邏輯程式碼,第一個想到的就是在基類中去寫
private Bundle savedBundle;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   initBundle(savedInstanceState);
   initData(savedBundle);
}
複製程式碼

可以看到 onCreate 方法中除了執行父類的方法之外,就只執行了 initBundle 和 initData 這兩個方法。

/**
* 傳入 onCreate 中的 bundle,根據是否為空進行相應處理
* 如果為 null,代表 activity 還沒有儲存過 bundle
* @param savedInstanceState
*/
private void initBundle(Bundle savedInstanceState) {
   if (savedInstanceState == null) {
       savedBundle = getIntent().getExtras();
   } else {
       savedBundle = savedInstanceState.getBundle("savedBundle");
   }

   //如果沒有任何引數,則初始化 savedBundle,避免呼叫時 null pointer
   if (savedBundle == null) {
       savedBundle = new Bundle();
   }
}

/**
* 儲存 bundle
* @param outState
*/
protected void onSaveInstanceState(Bundle outState) {
   outState.putBundle("savedBundle", this.savedBundle);
   super.onSaveInstanceState(outState);
}
複製程式碼

是不是和之前的處理方式很像?唯一的區別就是在 getIntent() 的時候,我一股腦的把所有資料都取了出來,並賦值給 savedBundle,儲存的時候也只儲存 savedBundle 這麼一個物件。

#####我並沒有生產新的資料,我只是資料的搬運工

對了,還有一個 initData 方法是幹嘛的?

/**
* 提供給子類取值的方法
* @param savedBundle
*/
protected void initData(Bundle savedBundle) {

}
複製程式碼

原來什麼都沒做。。

####How to use ? 在繼承自基類的 Activity 中,覆寫 initData 方法即可

@Override
protected void initData(Bundle savedBundle) {
   super.initData(savedBundle);
   int data = savedBundle.getInt("data");
   Log.i("bundle", " data =  " + data);
}
複製程式碼

要什麼引數直接從 initData 方法提供的 savedBundle 中去取,安全無汙染,省力更省心。

後記

其實沒什麼技術含量,我本人也沒什麼太厲害的技術可以吹噓。但和最初的 NullPointer 比起來,和為了處理 NullPointer 一次又一次的做重複工作比起來,還是好多了吧?

相關文章