解决 singleTask onActivityResult() 无效的问题

在 Android 4.X 系统上,如果你将一个 Activity A 的 launchMode 设置为 singleTask 或 singleInstance ,那么当你在 A 中调用 startActivityForResult() 的时候,是不会像你想象的那样,在 onActivityResult() 中获得你想要的返回结果的,就像官方文档说的那样:

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 B 来解决这个问题,思路如下:

  1. 在 A 中通过 startActivity() 调用 B ,向 B 中传递你所需的 Intent I ;

  2. 在 B 中通过 startActivityForResult() 调用 I ;

  3. 在 B 中通过 onActivityResult() 获取 I 的返回结果,通过 Otto 向 A 发送结果,最后再 finish B 。

当然作为中介,我们需要对 B 进行一些配置。下面是一段简单的实例代码。

首先我们可以构造一个简单的 Agent 类,用于在 A 和 B 之间传递 Intent :

public class Agent implements Parcelable {
    private Intent intent;
    private int requestCode;

    // 用于判断是否需要在 AgentActivity 中使用 Intent.setComponentName() ;
    // 通常我们使用 Intent ,
    // 都是类似 Intent intent = new Intent(Context, Class) 这样使用,
    // 所以对于传递给 B 的 Intent ,需要修改对应的 Context ;
    // 对于调用系统服务,比如相机,可设置为 false ;
    // 对于 App 内部的 Intent ,则设置为 true ;
    // 但通常来说如果只是调用 App 内部的 Intent ,
    // 其实也没有必要使用中介,直接使用 Otto 就好了。
    private boolean cls;

    public Agent(Intent intent, int requestCode, boolean cls) {
        this.intent = intent;
        this.requestCode = requestCode;
        this.cls = cls;
    }

    public Intent getIntent() {
        return intent;
    }

    public int getRequestCode() {
        return requestCode;
    }

    public boolean isCls() {
        return cls;
    }
    
    // Parcelable 接口的实现...
} 

接着我们创建 AgentActivity ,即上文提到的中介 Activity B :

public class AgentActivity extends Activity {
    public static final String EXTRA_AGENT = "extra_agent";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getIntent() == null) {
            finish();
            return;
        }

        Agent agent = getIntent().getParcelableExtra(EXTRA_AGENT);
        if (agent == null || agent.getIntent() == null) {
            finish();
            return;
        }
        
        // 判断是否需要替换从 A 传递进来的 Intent 的 Context 。
        Intent intent = agent.getIntent();
        if (agent.isCls()) {
            intent.setComponent(
                new ComponentName(this, intent.getComponent().getClassName())
            );
        }

        // 通过 B 调用 A 中构造的 Intent 。
        startActivityForResult(intent, agent.getRequestCode());
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // BusProvider 是 Otto 单例的实现, AgentEvent 是自定义的 Otto 事件。
        BusProvider.getInstance().post(
            new AgentEvent(requestCode, resultCode, data)
        );

        finish();
    }
} 

接下来是 AgentEvent 的实现:

public class AgentEvent {
    private int requestCode;
    private int resultCode;
    private Intent data;

    public AgentEvent(int requestCode, int resultCode, Intent data) {
        this.requestCode = requestCode;
        this.resultCode = resultCode;
        this.data = data;
    }

    public int getRequestCode() {
        return requestCode;
    }

    public int getResultCode() {
        return resultCode;
    }

    public Intent getData() {
        return data;
    }
} 

最后我们再在 AndroidManifest.xml 里面添加 AgentActivity 的声明:

<!-- 将 launchMode 设置为 standard ,同时将 theme 设置为不可见。-->
<activity android:name="YOUR_PACKAGE.AgentActivity"
          android:launchMode="standard"
          android:theme="@android:style/Theme.NoDisplay">
</activity> 

现在所有的准备工作都做完了,你只需要在 A 中这样使用就 OK :

public class A extend Activity {

    // 为了保证在整个生命周期内能接收到 Otto 的事件,
    // 选择在 onCreate() 和 onDestroy() 中注册与注销订阅。
    @Override
    public void onCreate() {
        ...
        
        BusProvider.getInstance().register(this);
    }
    
    @Override
    public void onDestroy() {
        ...
        
        BusProvider.getInstance().unregister(this);
    }
    
    // 通过 AgentActivity 调用系统相机的示例。
    private void startAgentToCamera() {
        Intent toCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        
        Agent agent = new Agent(toCamera, YOUR_REQUEST_CODE, false);
        Intent toAgent = new Intent(getActivity(), AgentActivity.class);
        toAgent.putExtra(AgentActivity.EXTRA_AGENT, agent);
        startActivity(toAgent);
    }
    
    // 响应来自 AgentActivity 的事件。
    @Subscribe
    public void onAgentEvent(AgentActivity.AgentEvent event) {
        int requestCode = event.getRequestCode();
        int resultCode = event.getResultCode();
        Intent data = event.getData();
    
        // 处理你接收到的事件...
    }
} 

当然从上面的例子我们也可以看出,使用一个中介 Activity 的功能不止于此,你还可以用来处理一些只有 Activity 才能接收的 Intent Action 等。当然具体怎么使用,就请发挥你自己的想象力啦~

返回