我的Android进阶之旅------>Android Activity的singleTask载入模式和onActivityResult方法之间的冲突
今天调试一个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好友发送界面,无法继续发送。本文为Sodino全部,转载请注明出处:http://blog.csdn.net/sodino/article/details/22101881
代码分析:
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也就能够理解了。
======================================================================================================
======================================================================================================
以下这篇文字正好解释了这个现象。转载于:http://www.cnblogs.com/tt_mc/p/3586834.html
探索
在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方法与之存在冲突。
參考
- http://stackoverflow.com/questions/8960072/onactivityresult-with-launchmode-singletask
- http://developer.android.com/reference/android/app/Activity.html#startActivityForResult%28android.content.Intent,%20int%29
- http://stackoverflow.com/questions/7910840/android-startactivityforresult-immediately-triggering-onactivityresult
- http://stackoverflow.com/questions/3354955/onactivityresult-called-prematurely
====================================================================================
作者:欧阳鹏 欢迎转载,与人分享是进步的源泉。
转载请保留原文地址:http://blog.csdn.net/ouyang_peng
====================================================================================