startActivityForResult 与launchmode有关

本文探讨了在Android开发中遇到的onActivityResult方法无响应的问题,并详细解释了解决方案。文章指出,在使用startActivityForResult时,请求码必须大于0才能正常回调;此外,目标Activity的启动模式不应设置为singleTask或singleInstance。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可以经由过程应用 startActivityForResult() 和 onActivityResult() 办法来传递或接管参数。 
然而在“轻听”项目中,还没比及被调用的 Activity 返回,onActivityResult() 就被履行了。 
找了好久,终于经由过程小道消息得知,这与 Activity 的加载模式(launchMode)有关,该属性可以在 AndroidManifest.xml 中设置。 
本来将其设为 singleInstance,经测试,所有须要传递或接管的 Activity 不容许设置该属性,或只能设为标准模式,不然体系将在 startActivityForResult() 后直接调用 onActivityResult()。 

调用startActivityForResult后,onActivityResult无响应的题目 
两个activity传递数据和返回数据时,恳求方的onActivityResult始终无响应,经由过程debug调试模式也没见调用该办法。查看了各类设备和法度代码,均未发明有错误之处。后面找了很多材料,总算看到一个伴侣说是调用startActivityForResult的参数题目,即调用时如许: 
startActivityForResult(intent, 0); 
是第二个参数的题目,该参数必须大于0才干在返回值,并激活onActivityResult办法。 
我最开端是用的一个activity默认的常量:RESULT_OK,跟踪了代码后发明,该常量的值为-1,当然没法激活 onActivityResult办法了,随后随便批改为一个大于0的整数,法度即通跑成功。 
startActivityForResult(intent, 1); //如许就行了 


1.startActivityForResult(Intent intent, Int requestCode); 


2.setResut(int resultCode, Intent intent); 


3.onActivityResult(int requestCode, int resultCode, Intent intent); 





重视事项: 


1.比来应用startActivityForResult,失足,跟踪后发明activity并没有被启动,而是直接履行 onActivityResult。后查看日记发明 


“05-19 02:11:19.822: WARN/ActivityManager(67): Activity is launching as a new task, so cancelling activity result.” 


本来是我把要启动的activity的launchmode设置成singleTask了。 


把目标activity的launchmode去掉,正确。 


总结:目标activity的launchmode不克不及设置成singleTask。 


2.requestCode值>=0,不然,startActivityForResult就变成了 startactivity 


从SDK我们可以看到如下深奥的解释: http://developer.android.com/guide/topics/manifest/activity-element.html#lmode  
The other modes — singleTask and singleInstance — are not appropriate for most applications, since they result in an interaction model that is likely to be unfamiliar to users and is very different from most other applications. 

从柯元旦的《Android 内核剖析》的第十章“Ams内部原理“10.1.3中有这样的一段话:请注意:SINGLE_TASK标识以及SINGLE_INSTANCE两个标识必须在r.result==0的条件中,即这两个标识只能用在startActivity()的方法中,而不能使用在startActivityForResult方法中。因为从Task的角度看,Android认为不同Task之间的Activity是不能传递数据的,所以不能使用NEW_TASK标识,但还是要调用forResult方法。
<think>我们正在处理一个具体问题:在singleTask启动模式下,登录页面如何正确使用startActivityForResult。 引用[4]提到:当Activity的启动模式设置为singleTask时,使用startActivityForResult可能会遇到拿不到返回值的情况。这是因为在5.0(API 21)之后,系统对于singleTask模式的Activity使用startActivityForResult的行为发生了变化。 问题分析: 在Android 5.0之前,即使Activity的启动模式是singleTask,使用startActivityForResult也能正常接收到结果。但是5.0及以后,如果被启动的Activity(即目标Activity)的启动模式是singleTask(或singleInstance),那么会立即回调onActivityResult,并且结果码为RESULT_CANCELED(0),而不会等待目标Activity调用setResult。 原因: 系统在启动singleTask模式的Activity时,会先检查任务栈中是否已经存在该Activity的实例。如果存在,则会将该实例之上的所有Activity都销毁,然后将其置于栈顶,并触发onNewIntent()。这个过程会导致当前Activity(即调用startActivityForResult的Activity)被销毁并重新创建?实际上并不是,但系统会认为这个启动过程中断了之前的请求,因此立即返回RESULT_CANCELED。 解决方案: 1. 避免在singleTask的Activity上使用startActivityForResult。 如果可能,考虑改变设计,例如使用回调接口、事件总线(如LiveData、RxJava等)或全局变量来传递结果。 2. 如果必须使用,则需要处理立即返回的RESULT_CANCELED,并在onNewIntent中重新发起请求。 具体步骤: a. 在调用startActivityForResult的Activity(假设为A)中,检查onActivityResult返回的结果码。如果结果码是RESULT_CANCELED,并且请求码是我们用于登录的请求码,那么我们可以认为是因为singleTask模式导致的取消。 b. 此时,我们需要在登录Activity(假设为B)中,通过某种方式(例如在onNewIntent中)知道它是由A启动的,并且需要设置结果。然后,当B完成操作后,手动设置结果并调用finish,这样A就能在onActivityResult中再次接收到结果(这次是正常的结果)。 但是,这种方法比较复杂,而且容易出错。 3. 另一种方法是:不使用startActivityForResult,而是使用显式的Intent启动登录Activity,并在登录Activity完成时,通过广播、事件总线或者回调接口(比如使用接口回调,但需要处理好生命周期)来通知。 4. 如果登录Activity是singleTask,并且由多个地方启动,那么我们可以考虑在登录Activity中维护一个回调列表。但是,这需要小心处理内存泄漏和生命周期。 5. 使用ActivityResult API(AndroidX Activity Result API)来处理,但同样需要处理singleTask带来的问题。 鉴于上述复杂性,通常建议避免在singleTask的Activity上使用startActivityForResult。 然而,如果我们的登录页必须是singleTask,并且需要返回结果,那么我们可以采用以下方法: 步骤: 1. 在启动登录Activity时,不要使用startActivityForResult,而是使用startActivity。 2. 在登录Activity中,完成登录后,通过广播或者事件总线将结果发送出去。 3. 在启动登录Activity的Activity(即需要接收结果的Activity)中注册广播接收器或订阅事件,并在接收到结果时进行处理。 使用广播的示例: - 在需要接收结果的Activity中(比如A)注册一个广播接收器,用于接收登录结果。 - 在A中启动登录Activity(B)时,使用startActivity。 - 在B中,当登录成功或失败时,发送一个有序广播(或普通广播,但有序广播可以确保只有特定的接收者接收)。 - 在广播接收器中,处理结果并更新A的UI。 注意:广播接收器需要在适当的时候注册和注销(比如在onResume和onPause中)。 使用事件总线(如LiveData)的示例(假设使用ViewModel和单例的事件总线): - 创建一个全局的事件总线(例如一个单例类,内部使用LiveData或RxJava的Subject)。 - 在A中订阅登录结果事件(在onCreate或onResume中订阅,在onPause或onDestroy中取消订阅)。 - 启动B(登录Activity)时,使用startActivity。 - 在B中,设置结果时,通过事件总线发送事件。 但是,事件总线需要小心处理生命周期,避免在Activity不可见时更新UI,或者使用Lifecycle-aware组件(如LiveData)来避免。 6. 使用回调接口,但是需要将回调Activity绑定,并处理Activity重建的情况。 考虑到登录页的特殊性(可能由多个不同的Activity启动),使用广播或全局事件总线可能是更合适的选择。 代码示例(广播方式): 在Activity A中: ```java public class ActivityA extends AppCompatActivity { private static final String LOGIN_RESULT_ACTION = "com.example.LOGIN_RESULT"; private BroadcastReceiver loginResultReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(LOGIN_RESULT_ACTION)) { boolean success = intent.getBooleanExtra("success", false); // 处理登录结果 // 注意:记得取消注册,避免重复接收 unregisterReceiver(loginResultReceiver); } } }; @Override protected void onResume() { super.onResume(); IntentFilter filter = new IntentFilter(LOGIN_RESULT_ACTION); registerReceiver(loginResultReceiver, filter); } @Override protected void onPause() { super.onPause(); // 注意:在onPause中注销,避免泄露 try { unregisterReceiver(loginResultReceiver); } catch (Exception e) { // 可能未注册 } } private void startLoginActivity() { Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); } } ``` 在LoginActivity中(singleTask模式): ```java public class LoginActivity extends AppCompatActivity { // ... 登录逻辑 private void onLoginSuccess() { // 发送广播 Intent resultIntent = new Intent("com.example.LOGIN_RESULT"); resultIntent.putExtra("success", true); sendBroadcast(resultIntent); finish(); } private void onLoginFailure() { Intent resultIntent = new Intent("com.example.LOGIN_RESULT"); resultIntent.putExtra("success", false); sendBroadcast(resultIntent); finish(); } } ``` 注意:广播是全局的,如果有多个Activity同时注册接收同一个广播,那么它们都会收到。为了避免这种情况,可以在发送广播时使用有序广播,并设置只让特定的接收者接收(比如通过权限),或者使用不同的action(例如在action中加入启动者的类名,但这样不够灵活)。 另一种思路:在启动LoginActivity时,将需要返回结果的Activity的类名或一个唯一标识(如requestCode)通过Intent传递给LoginActivity,然后LoginActivity在发送广播时指定这个类名或标识,接收方检查是否匹配。 但是,考虑到Activity可能被销毁重建,使用类名和标识可能不够稳定。 因此,更推荐使用事件总线(如LiveData)的方式,因为可以精确控制事件的分发。例如,我们可以使用一个单例的登录结果事件持有者: ```java public class LoginResultEventHolder { private static LoginResultEventHolder instance; private MutableLiveData<Boolean> loginResult = new MutableLiveData<>(); public static LoginResultEventHolder getInstance() { if (instance == null) { instance = new LoginResultEventHolder(); } return instance; } public MutableLiveData<Boolean> getLoginResult() { return loginResult; } public void postLoginResult(boolean success) { loginResult.postValue(success); } } ``` 在ActivityA中: ```java public class ActivityA extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 观察登录结果 LoginResultEventHolder.getInstance().getLoginResult().observe(this, success -> { if (success) { // 处理登录成功 } else { // 处理登录失败 } // 注意:为了避免重复接收,可以在处理完后将LiveData置空或者移除观察者 // 但通常一个登录结果只需要处理一次,所以我们可以移除观察者 LoginResultEventHolder.getInstance().getLoginResult().removeObservers(this); }); } private void startLoginActivity() { Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); } } ``` 在LoginActivity中: ```java public class LoginActivity extends AppCompatActivity { private void onLoginSuccess() { LoginResultEventHolder.getInstance().postLoginResult(true); finish(); } private void onLoginFailure() { LoginResultEventHolder.getInstance().postLoginResult(false); finish(); } } ``` 注意:使用LiveData作为事件总线时,需要注意事件会被多个观察者接收,而且如果观察者处于活跃状态(STARTED或RESUMED),就会立即收到。因此,我们需要确保只有启动登录的Activity才会处理这个事件。我们可以通过设计事件持有者来区分不同的请求(例如,每个请求一个唯一的LiveData实例),但这里为了简单,我们假设同一时间只有一个登录请求。 更健壮的做法是使用一个事件总线库(如RxJava或EventBus),它们支持发送带标签的事件。 总结:在singleTask的Activity中,使用startActivityForResult是不可靠的(尤其是在5.0以上)。因此,我们推荐使用事件总线或广播的方式传递结果。 引用: [^4]: 5.0之后,android的LaunchModeStartActivityForResult的关系发生了一些改变。两个Activity,A和B,现在由A页面跳转到B页面,如果B的启动模式是singleTask,那么A的onActivityResult会立即被调用,并且结果码为RESULT_CANCELED。</think>在 Android `singleTask` 启动模式下,登录页使用 `startActivityForResult` 会面临特殊挑战,主要原因在于 **实例复用机制** 和 **任务栈管理规则**。以下是完整解决方案和最佳实践: --- ### 问题根源分析 1. **`singleTask` 的特性冲突** - 当登录页已存在时,系统会复用现有实例并触发 `onNewIntent()`,而非创建新实例 - 标准 `startActivityForResult` 的返回机制依赖于新实例的创建流程(引用[4]) 2. **结果回调丢失** - 复用实例时,`onActivityResult()` 会立即收到 `RESULT_CANCELED`(引用[4]) - 预期结果(如登录凭证)无法通过常规回调传递 --- ### 完整解决方案 #### 方案一:重写 `onNewIntent()` 手动处理结果 ```java // 在登录页(LoginActivity)中 @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); // 更新当前Intent // 检查是否来自startActivityForResult if (intent.getBooleanExtra("IS_FOR_RESULT", false)) { // 手动设置结果并结束 Intent result = new Intent(); result.putExtra("TOKEN", getToken()); setResult(RESULT_OK, result); finish(); // 关键:立即结束页面[^4] } } ``` #### 方案二:启动时添加特殊标识 ```java // 在调用方(如MainActivity)中 void launchLoginForResult() { Intent intent = new Intent(this, LoginActivity.class); intent.putExtra("IS_FOR_RESULT", true); // 添加标识 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 清理栈顶 startActivityForResult(intent, LOGIN_REQUEST_CODE); } ``` #### 方案三:结果回调统一管理 ```java // 在Application类中创建全局回调 public class MyApp extends Application { private static ResultCallback resultCallback; public static void setLoginResultCallback(ResultCallback callback) { resultCallback = callback; } public static void dispatchLoginResult(String token) { if (resultCallback != null) { resultCallback.onResult(token); resultCallback = null; // 防止重复调用 } } } // 在登录成功时调用 MyApp.dispatchLoginResult(authToken); // 在调用方注册回调 @Override protected void onResume() { super.onResume(); MyApp.setLoginResultCallback(token -> { // 处理登录结果 }); } ``` --- ### 关键注意事项 1. **生命周期协调** - 在 `onNewIntent()` 中调用 `finish()` 后,系统会立即触发 `onActivityResult()`(引用[5]) - 确保结果处理代码在 `onResume()` 之后执行,避免 NPE 2. **并发请求处理** ```java // 使用Map管理多个请求源 private static Map<Class<?>, ResultCallback> callbackMap = new HashMap<>(); public static void registerCallback(Class<?> source, ResultCallback cb) { callbackMap.put(source, cb); } public static void dispatchToSource(Class<?> source, String token) { if (callbackMap.containsKey(source)) { callbackMap.get(source).onResult(token); callbackMap.remove(source); } } ``` 3. **`FLAG_ACTIVITY_CLEAR_TOP` 的必要性** - 必须添加此标志位,确保登录页上方的所有页面被清除(引用[2]) - 避免返回时出现中间页面残留 --- ### 替代方案推荐(最佳实践) ```mermaid graph TD A[调用方] -->|1. 启动| B(登录页) B -->|2. 成功| C[全局事件总线] C -->|3. 推送| D[所有订阅者] D -->|4. 处理结果| A ``` 使用事件总线(如 `LiveData` 或 `RxJava`): ```java // 创建全局事件总线 public class LoginResultBus { private static MutableLiveData<String> resultLiveData = new MutableLiveData<>(); public static LiveData<String> getResultStream() { return resultLiveData; } public static void postResult(String token) { resultLiveData.postValue(token); } } // 在登录页提交结果 LoginResultBus.postResult(authToken); // 在调用方观察结果 LoginResultBus.getResultStream().observe(this, token -> { // 更新UI }); ``` --- ### 各方案适用场景 | **方案** | **适用场景** | **优点** | **缺点** | |------------------------|----------------------------------------|------------------------------|------------------------| | `onNewIntent+finish` | 简单场景,单调用方 | 无需额外架构 | 多调用方处理复杂 | | 全局回调管理 | 多模块需要登录结果 | 精准控制接收方 | 需手动管理生命周期 | | 事件总线 | 大型应用,多订阅方 | 解耦彻底,扩展性强 | 需引入额外库 | > 💡 **推荐**:对于新项目建议直接采用事件总线方案(如 `LiveData`),存量项目可使用 `onNewIntent()+finish()` 渐进式改造[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值