今天調試一個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的現象 ,下面轉載于:http://blog.csdn.net/sodino/article/details/22101881
在剛安裝完demo應用未登入任何帳号時,通過系統内的分享功能想将檔案/圖檔等内容"發送給好友"或"發送到我的電腦",觸發登入界面,但登入成功後,沒有跳轉到選擇demo好友發送界面,無法繼續發送。
demo中JumpActivity處理着各種外部應用分享入口,通過調試發現進行分享時會判斷是否登入過,如果未登入則會跳轉至LoginActivity進行登入。如下代碼:

private void doShare(booleancheckLogin) {
// 系統級分享
Intent intent = getIntent();
... ...
// 沒登入
if (checkLogin &&!demo.isLogin()){
Intent i = newIntent(this, LoginActivity.class);
i.putExtra("isActionSend",true);
i.putExtras(extra);
i.putExtras(i);
startActivityForResult(i,SHARE_LOGIN_REQUEST);
return;
}
}
查閱代碼得知登入成功後,則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上有些人跟我遇到的問題類似。比如說有一位開發者把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還是無緣無故被調用了。
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異常。
Use Cases
Launch Mode
Multiple Instances?
Comments
Normal launches for most activities
"<code>standard</code>"
Yes
Default. The system always creates a new instance of the activity in the target task and routes the intent to it.
"<code>singleTop</code>"
Conditionally
Specialized launches
(not recommended for general use)
"<code>singleTask</code>"
No
"<code>singleInstance</code>"
Same as "<code>singleTask"</code>, 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方法與之存在沖突。
<a target="_blank" href="http://stackoverflow.com/questions/8960072/onactivityresult-with-launchmode-singletask">http://stackoverflow.com/questions/8960072/onactivityresult-with-launchmode-singletask</a>
<a target="_blank" href="http://developer.android.com/reference/android/app/Activity.html#startActivityForResult%28android.content.Intent,%20int%29">http://developer.android.com/reference/android/app/Activity.html#startActivityForResult%28android.content.Intent,%20int%29</a>
<a target="_blank" href="http://stackoverflow.com/questions/7910840/android-startactivityforresult-immediately-triggering-onactivityresult">http://stackoverflow.com/questions/7910840/android-startactivityforresult-immediately-triggering-onactivityresult</a>
<a target="_blank" href="http://stackoverflow.com/questions/3354955/onactivityresult-called-prematurely">http://stackoverflow.com/questions/3354955/onactivityresult-called-prematurely</a>
====================================================================================
作者:歐陽鵬 歡迎轉載,與人分享是進步的源泉!
====================================================================================