1. 程式人生 > >Activity啟動模式及Intent的Flag屬性對Intent傳值的影響

Activity啟動模式及Intent的Flag屬性對Intent傳值的影響

前些時候在支援公司其它部門專案開發的時候,有同事問我:通過IntentActivity之間進行資料傳遞,傳遞的Key和獲取的Key都沒錯,為什麼在目標Activity會獲取不到傳遞過來的資料?在Key沒錯的情況下獲取不到資料,那麼無疑是Activity的啟動模式及在跳轉時給Intent設定的Flag屬性引起的,於是乎有了今天這篇部落格。

原因一:Activity的啟動模式

那麼Activity以哪種啟動模式進行跳轉時,會導致目標Activity獲取不到傳遞過來的資料呢?在上一篇Activity啟動模式詳解部落格中講到Activity以不同的啟動模式進行啟動會根據啟動模式來建立相應的例項,也就是說,如果目標

Activity的例項已經存在並且符合要求,則不會再建立相應的例項,因此在Activity4種啟動模式中,以singleTop(有可能)、singleTasksingleInstance模式啟動的目標Activity,當置於後臺被再次啟動時都會導致目標Activity獲取不到傳遞過來的資料(這裡指的獲取不到是指在不借助其它操作時)。

這裡以singleTop模式來講解,當在StandardActivity中點選SingleTopActivity按鈕時,會將輸入的內容或者預設的內容通過Intent傳遞給目標SingleTopActivity然後獲取並顯示出來,主要程式碼程式碼如下所示:

public void skip2SingleTopActivity(View view){
    Intent intent = new Intent(StandardActivity.this,SingleTopActivity.class);
    content = et_content.getText().toString().trim();
    if(TextUtils.isEmpty(content)){
        content = "這是從StandardActivity傳遞過來的內容";
    }
    intent.putExtra(SingleTopActivity.SINGLE_TOP,content);
    startActivity(intent);
}
目標SingleTopActivity獲取並顯示資料的程式碼如下所示:
private void getBundleData() {
    Log.i(TAG, "getBundleData");
    Intent intent = getIntent();
    if (intent != null) {
        result = intent.getStringExtra(SINGLE_TOP);
        tv_content.setText("結果為:" + result);
    }
}
其中getBundleData方法是在onCreate方法中呼叫的,點選後的結果如下所示:


可以看到目標SingleTopActivity可以成功獲取傳遞過來的資料,現在重點來了,如果此時在SingleTopActivity中的EditText中輸入內容或者直接點選SingleTopActivity按鈕讓它繼續跳轉到自己會出現什麼情況呢?

跳轉的主要程式碼如下所示:

public void skip2SingleTopActivity(View view) {
    Intent intent = new Intent(SingleTopActivity.this, SingleTopActivity.class);
    String content = et_content.getText().toString().trim();
    if (TextUtils.isEmpty(content)) {
        content = "這是從SingleTopActivity傳遞過來的資料";
    }
    intent.putExtra(SINGLE_TOP, content);
    startActivity(intent);
}
此時SingleTopActivity中顯示的資料依然是從StandardActivity中傳過來的資料:


原因二:Intent設定的Flag屬性

Intent物件大致包含ComponentActionCategoryDataTypeExtraFlag7種屬性,其中IntentFlag屬性用於為該Intent新增一些額外的控制旗標,可以通過IntentaddFlags方法為Intent新增控制旗標。

其中常見的跟Activity跳轉有關的Flag旗杆有如下幾個:

1、FLAG_ACTIVITY_BROUGHT_TO_FRONT:經測試發現以該旗標啟動的目標Activity跟以旗標FLAG_ACTIVITY_NEW_TASK啟動的目標Activity一樣都會建立新的Activity例項。


2、FLAG_ACTIVITY_CLEAR_TOP:見名知意:清除當前Activity之上的所有例項,該Flag相當於Activity啟動模式中的singleTask,例如,一個Activity棧中包含有ABCD 4Activity例項,當在Activity D中以該旗標啟動Activity B時,此時Activity棧中只包含AB兩個Activity例項。

在ActivityD中以FLAG_ACTIVITY_CLEAR_TOP標誌啟動之前:

 

在ActivityD中以FLAG_ACTIVITY_CLEAR_TOP標誌啟動之後:

 

3、FLAG_ACTIVITY_NEW_TASK:預設啟動旗標,該旗標控制建立一個新的Activity例項,該Flag相當於Activity啟動模式中的standard

4、FLAG_ACTIVITY_SINGLE_TOP:從名字中不難看出該Flag相當於Activity載入模式中的singleTop模式,即原來Activity棧中有ABCD4Activity例項,當在Activity D中再次啟動Activity D時,Activity棧中依然還是ABCD4Activity例項。


5、FLAG_ACTIVITY_NO_HISTORY:如名字沒有歷史Activity一樣,以該旗標啟動的Activity不會保留在Activity棧中,如:Activity棧中有AB兩個Activity例項,當在Activity B中以該旗標啟動Activity C,在Activity C中再啟動Activity D,此時Activity棧中只有ABD三個Activity例項,即Activity C不會保留在Activity棧中。


當然也可以通過在Activity D中以FLAG_ACTIVITY_CLEAR_TOP旗標的方式啟動Activity C,如果Activity C還保留在Activity棧中的話,那麼此時棧中的肯定只有ABC這三個Activity的例項,但是實踐證明Activity棧中有ABDC這四個例項,也就進一步說明在Activity B中以FLAG_ACTIVITY_NO_HISTORY旗標啟動Activity C,再在Activity C中啟動Activity D後,Activity C不會在保留在Activity棧中,所以才會出現當在Activity D中以FLAG_ACTIVITY_CLEAR_TOP旗標的方式啟動Activity C時會建立新的Activity C例項,棧中情況如下所示:


6、FLAG_ACTIVITY_REORDER_TO_FRONT:即如果棧中已有該Activity則直接將該Activity帶到前臺。如:Activity棧中有ABCD四個Activity,如果在Activity D使用該旗標啟動Activity C,那麼啟動後Activity棧中的情形為:A-B-D-C

啟動前Activity棧中的情況

 

啟動後Activity棧中的情況

 

從原因一:Activity的啟動模式中可以發現,如果Activity棧中已經存在目標Activity的例項的話,當從後臺再次返回到Activity的棧頂時都有可能導致目標Activity獲取不到傳遞過來的資料,同樣的,原因二:Intent設定的Flag屬性如果也會讓目標Activity的例項保留在Activity棧中且滿足條件的話當再次啟動時也會導致目標Activity獲取不到傳遞過來的資料,這裡本來也將會通過以ActivitysingleTop啟動模式相對應的FLAG_ACTIVITY_SINGLE_TOP旗標來講解的,不過由於篇幅原因,所以打消了這種想法,如果真有需要的話,可以去下載原始碼檢視。

解決方案:

知道原因之後,那麼該如何解決這個問題呢?其實谷歌早就考慮到了這種問題,於是在Android api中的Activity類中給我們提供了一個叫onNewIntent的方法來解決這個問題:

 

當一個Activity的啟動模式是singleTop或者使用FLAG_ACTIVITY_SINGLE_TOP這個標記啟動的時候,並且Activity的棧頂就是待啟動的目標Activity的時候,會呼叫目標Activity的這個方法,如果需要在後續的目標Activity的生命週期中可以獲取最新的資料,可以在該方法中通過setIntent方法更新資料。(ps:不完全是按照翻譯)

知道了解決辦法以後,在目標SingleTopActivity重寫onNewIntent方法並在該方法中通過setIntent方法來更新資料以確保在目標SingleTopActivity後續的生命週期中可以獲取最新的資料,主要程式碼如下所示:

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    if (intent != null) {
        result = intent.getStringExtra(SINGLE_TOP);
        Log.e(TAG, "onNewIntent result==" + result);
        setIntent(intent);
    }
}
 

@Override
protected void onResume() {
    super.onResume();

    Log.e(TAG, "onResume");
    Intent intent = getIntent();
    if (intent != null) {
        result = intent.getStringExtra(SINGLE_TOP);
        tv_content.setText("結果為:" + result);
    }
}
此時,輸入內容並點選SingleTopActivity按鈕可以發現已經可以獲取最新的資料了,結果如下所示:

 

Log日誌如下所示:

 

至此,Activity啟動模式及IntentFlag屬性對Intent傳值的影響就介紹完了。

另外,為了方便開發,很多公司都會封裝一些常用的工具類,如:為了減少Activity例項的的建立,會對Activity的跳轉進行一系列的封裝的IntentUtils工具類中往往會給Intent新增addFlags方法以減少例項的建立,因此小夥伴們在使用自己公司封裝好的工具類時需要時刻留意。

後記:

其實,現在想想,或許對Intent進行封裝時新增Flag旗標也許不是一個明智的選擇,就拿FLAG_ACTIVITY_SINGLE_TOP來說,這個旗標也只是在當目標Activity已對處於Activity棧頂的時候才不會再次去建立目標Activity的例項,所以說如果新增Flag是為了減少例項的建立的話也不現實,並且在開發中很多終端頁都是藉助WebView載入h5來實現的,如果在終端頁中點選某個連結會繼續在當前Activity中開啟,這個時候就會遇到一個很尷尬的問題,雖然內容是變了,但是看不到Activity的任何切換並且此時點選返回鍵的時候是會直接退出當前Activity,而不是回到上一個h5顯示的頁面(也許你會說,可以監聽返回鍵然後在相應的事件中對WebView進行判斷,這樣就不會直接退出了,如果你要這樣想的話,我也是沒轍的),當然,如果你們公司的產品覺得這樣蠻好的,那又是另一回事了,畢竟這一切都是我個人的看法,況且:一切使用者至上!

原始碼