Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 我的Android進階之旅------)Android Activity的singleTask加載模式和onActivityResult方法之間的沖突

我的Android進階之旅------)Android Activity的singleTask加載模式和onActivityResult方法之間的沖突

編輯:關於Android編程

今天調試一個bug的時候,情景如下:

一個Activity A,需要用startActivityForResult方法開啟Activity B。Activity B的launch mode被設置為singleTask,那麼在Activity B開啟之後的瞬間(未等B返回任何result),Activity A中的onActivityResult方法就會被調用,並且收到一個RESULT_CANCEL的request code。

然後在ActivityB中做了一些邏輯之後,在Activity B通過setResult方法返回Activity A的時候,Activity A中的onActivityResult方法就不再被調用。導致數據不刷新。後來去查看AndroidManifest.xml文檔才發現Activity A,Activity B的lunch mode都定義為singleTask。後來把Activity A,Activity B的lunch mode都改為standard後就正常了。

 

======================================================================================================

======================================================================================================

 

下面這篇文字正好解釋了startActivityForResult啟動singleTask的Activity,則onActivitResult()立即回調且resultCode為RESULT_CANCEL的現象 

 

問題現象:

在剛安裝完demo應用未登錄任何帳號時,通過系統內的分享功能想將文件/圖片等內容"發送給好友"或"發送到我的電腦",觸發登錄界面,但登錄成功後,沒有跳轉到選擇demo好友發送界面,無法繼續發送。

 代碼分析:

demo中JumpActivity處理著各種外部應用分享入口,通過調試發現進行分享時會判斷是否登錄過,如果未登錄則會跳轉至LoginActivity進行登錄。如下代碼:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void doShare(booleancheckLogin) { // 系統級分享
  2. Intent intent = getIntent(); ... ...
  3. ... ...
  4. // 沒登錄 if (checkLogin &&!demo.isLogin()){
  5. Intent i = newIntent(this, LoginActivity.class); i.putExtra("isActionSend",true);
  6. i.putExtras(extra); i.putExtras(i);
  7. startActivityForResult(i,SHARE_LOGIN_REQUEST); return;
  8. } ... ...
  9. }

查閱代碼得知登錄成功後,則JumpActivity.onActivityResult()將會得到requestCode值為SHARE_LOGIN_REQUEST的回調。為此,在onActivityResult()回調處設置斷點,再次跟進。

 

設置斷點,執行分享操作進行調試,發現每次執行完startActivityForResult(),則onActivityResult()便立刻被回調了,且resultCode值為RESULT_CANCEL。至些,問題開始有了頭緒。

通過排查,發現LoginActivity在之前有被改動過,其launchMode賦值為singleTask。分享功能就是在這次改動之後失效了的。只要恢復launchMode為standard,即可讓onActivityResult()在LoginActivity登錄成功後正常回調回來,執行分享操作,恢復功能。

 

至此,問題得到解決,但問題原因仍是一頭霧水:

為什麼通過startActivityForResult()方式去啟動launchMode=singleTask的Activity,onActivityResult()會被立即回調且resultCode值為RESULT_CANCEL??

 

原因解析:

經查文檔,發現文檔中另一相似的方法startActivityForResult(Intent,int,Bundle)有說明如下:

Note that this method should only be used with Intent protocols thatare defined to return a result. In other protocols (such as ACTION_MAIN orACTION_VIEW), you may not get the result when you expect. For example,if the activity you are launching uses thesingleTask launch mode, it will not run in your task and thus you willimmediately receive a cancel result.

 

但這點注釋讓人理解得仍不是很透徹。繼續搜索,發現文檔(點擊這裡)裡說了下面的這一種現象。

在下圖中,存在著前兩個棧,其中直接顯示在屏幕上與用戶交互的Back Stack,及另一個隱藏在後台的Background Task,該棧棧頂的Activity Y其launchMode為singleTask

如果在Activity 2中調用BackgroundTask中已經啟動過的Activity Y,則Background Task內占據屏幕並且該Task下所有的棧都會保留當前的棧位置及順序push進Back Task形成新的結構,順序由上至下為Activity Y→Activity X→Activity 2→Activity 1。

在Activity Y界面按返回鍵,則ActivityY出棧,Activity X占據屏幕!注意,由Activity2調用的Activity Y,但返回鍵後,回退顯示的是Activity X!所以即使在Activity Y執行setResult(),Activity 2也是無法接收到的。換回文章開頭的問題,即在JumpActivity處啟動LoginActivity(已經被設置singleTask了),則LoginActivity的setResult()結果有可能不會傳給JumpActivity。

繼續按返回鍵,則才回到Activity 2。

\

 

問題結論:

由此,我們再回到先前的問題。在這種Tasks的入棧與出棧設計下,由於可能有Activity X的存在,所以在Activity 2啟動Activity Y時,則直接回調了onActivityResult()並給出了RESULT_CANCEL也就可以理解了。


 

 

======================================================================================================

======================================================================================================

 

 

探索

 

在Google上搜索android activity onactivityresult singTop找到了一些問題。


stackoverflow

stackoverflow上有些人跟我遇到的問題類似。比如說有一位開發者把Activity設置成了singleTask模式,onActivityResult就收不到任何結果了。當他把singleTask模式改回標准模式,又恢復正常。

這個問題下面給出的答案中,有一位說startActivityForResult的文檔中有這麼一句話:

For example, if the activity you are launching uses the singleTask launch mode, it will not run in your task and thus you will immediately receive a cancel result.

意思是:比如說,如果你正加載的activity使用了singleTask的加載模式,它不會在你的棧中運行,而且這樣你會馬上收到一個取消的結果。

即在onActivityResult裡馬上得到一個RESULT_CANCEL.

他還補充說沒有很好的補救方法。可以試試用監聽廣播的方法。

另一個stackoverflow的問題中,有人直接回答了不能再singleInstance或singleTop模式下使用startActivityForResult()方法,不僅被采納了,票數還不低。

剩下的一個stackoverflow問題中,有人說把singleTask改成singleTop就會正常,得到高達59票並被采納。實際上我用的就是singTop,可是onActivityResult還是無緣無故被調用了。


startActivityForResult的文檔:

public void startActivityForResult (Intent intent, int requestCode, Bundle options)

Added in API level 16

Launch an activity for which you would like a result when it finished. When this activity exits, your onActivityResult() method will be called with the given requestCode. Using a negative requestCode is the same as calling startActivity(Intent) (the activity is not launched as a sub-activity).

加載一個Activity,當它結束時你會得到結果。當這個Activty退出了,你的onActivityResult()方法會根據給出的requestCode被調用。使用一個負的requestCode和調用startActivity(intent)一樣(activity不被加載成子activity)

Note that this method should only be used with Intent protocols that are defined to return a result. In other protocols (such as ACTION_MAIN or ACTION_VIEW), you may not get the result when you expect. For example, if the activity you are launching uses the singleTask launch mode, it will not run in your task and thus you will immediately receive a cancel result.

注意這個方法只能用於被定義要返回結果的Intent協議。在其他協議中(譬如ACTION_MAIN或ACTION_VIEW),你可能在你想得到結果時得不到。比如,當你正載入的Activity使用的singleTask加載模式,它不會在你的棧中運行,這樣你會立馬得到一個取消的結果。

As a special case, if you call startActivityForResult() with a requestCode >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your activity, then your window will not be displayed until a result is returned back from the started activity. This is to avoid visible flickering when redirecting to another activity.

有一個特例是,當你在初始的onCreate()方法或onResume()方法中用一個大於等於0的請求碼調用startActivityForResult(),你的窗口在被啟動的Activity返回結果前不會顯示。這是為了避免跳轉到另一Activity時可見的閃爍。

This method throws ActivityNotFoundException if there was no Activity found to run the given Intent.

如果運行所給Intent的Activity沒被找到,該方法會拋出ActivityNotFoundException異常。


Activity的加載模式

Use Cases Launch Mode Multiple Instances? Comments Normal launches for most activities "standard" Yes Default. The system always creates a new instance of the activity in the target task and routes the intent to it. "singleTop" Conditionally If an instance of the activity already exists at the top of the target task, the system routes the intent to that instance through a call to itsonNewIntent()method, rather than creating a new instance of the activity. Specialized launches
(not recommended for general use) "singleTask" No The system creates the activity at the root of a new task and routes the intent to it. However, if an instance of the activity already exists, the system routes the intent to existing instance through a call to itsonNewIntent()method, rather than creating a new one. "singleInstance" No Same as "singleTask", except that the system doesn't launch any other activities into the task holding the instance. The activity is always the single and only member of its task.

singleTop模式,可用來解決棧頂多個重復相同的Activity的問題。

singleTask模式和後面的singleInstance模式都是只創建一個實例的。

當intent到來,需要創建singleTask模式Activity的時候,系統會檢查棧裡面是否已經有該Activity的實例。如果有直接將intent發送給它。

singleInstance模式解決了這個問題(繞了這麼半天才說到正題)。讓這個模式下的Activity單獨在一個task棧中。這個棧只有一個Activity。導游應用和google地圖應用發送的intent都由這個Activity接收和展示。

總結

後來我改變了onActivityResult裡面ResultCode為RESULT_OK時刷新界面的具體實現方法,可是onActivityResult還是會自己被調用,只是暫時沒觸發任何bug,可它還是個定時炸彈啊。

以後在選擇Activity的加載模式時,要考慮onActivtyResult方法與之存在沖突。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved