PAYATHON 2026

Android ACTION_SEND 分享到 X 成功后为何仍返回 RESULT_CANCELED?

排障赵师傅

结论

处理 ACTION_SEND 的应用没有义务在分享成功后调用 setResult(RESULT_OK)。Android 也没有提供标准 API,让发送应用判断目标应用是否通过通用分享完成了发帖。

在所述 X 版本中,成功发布和取消编辑都会返回 RESULT_CANCELED,发送应用无法仅凭这个结果区分两种情况。业务状态应记为“发布结果未知”,不能直接判定为“用户取消”。

chooser 回调可以确认用户选择了 X,无法确认帖子是否已发布。如果 X 没有提供额外的结果协议,当前流程能获得的就是这个目标选择信号。

为什么发布成功后仍会返回 RESULT_CANCELED?

ACTION_SEND 没有规定发布结果契约

Android 的 ACTION_SEND 文档写明 Output: nothing。它定义了如何把文本、图片等数据交给另一个应用,没有要求接收应用返回“发送成功”或“用户取消”。

接收应用可以通过 setResult() 返回结果,发送应用则需要按双方约定解释结果。使用 ActivityResultContracts.StartActivityForResult() 并不会为 ACTION_SEND 增加这样的约定。

RESULT_CANCELED 也可能表示没有返回结果

Activity 的默认结果是 RESULT_CANCELED。官方文档说明,目标 Activity 显式返回该值、没有返回任何结果,或者运行时崩溃,都可能让调用方收到 RESULT_CANCELED。因此,单凭这个结果码无法证明用户点击了取消。Activity 结果说明

目标应用可能已经完成发布,只是在退出时没有设置其他结果码。内部 Activity 的跳转和结果传递方式也可能影响调用方收到的结果。现有日志不足以确定 X 内部走了哪条路径。

data=null 只说明没有附带结果数据,也无法据此判断是否发布成功。

chooser 回调能确认什么?

Intent.createChooser() 的 IntentSender 回调报告用户选择了哪个处理组件。如果 EXTRA_CHOSEN_COMPONENT 中出现:

com.twitter.android/com.x.android.lib.ComposerActivity

就能确认用户选择了该组件。这个回调不会等待 X 完成发帖,也不会获取 X 服务端的发布状态。官方文档还提醒,用户放弃 chooser 或切换任务等情况下,回调可能不会到达,应用不能假定每次都能收到。目标选择回调说明

API 35 新增的 ChooserResult 和 EXTRA_CHOOSER_RESULT 扩展了 Sharesheet 的结果信息,可以描述选择目标、复制、编辑等操作,但仍不提供目标应用内部的发帖结果。Sharesheet 结果回调说明

发送应用应如何处理?

  1. 在 chooser 广播回调中记录“用户选择了分享目标”。
  2. 保存 Activity 返回结果,用于诊断,不根据它直接显示“发布成功”或“发布失败”。
  3. 如果业务必须确认发布,需要使用接收应用明确支持的回调协议,或者另行接入能够返回发布结果的平台接口。

现有分享 Intent 和 chooser 的创建方式可以保留,结果处理部分可改为:

val broadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        val component = if (
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
        ) {
            intent?.getParcelableExtra(
                Intent.EXTRA_CHOSEN_COMPONENT,
                ComponentName::class.java
            )
        } else {
            @Suppress("DEPRECATION")
            intent?.getParcelableExtra<ComponentName>(
                Intent.EXTRA_CHOSEN_COMPONENT
            )
        }

        component?.let {
            Log.d(
                "ShareResult",
                "targetSelected=${it.flattenToString()}"
            )
            // 只记录选择目标,不更新为“发布成功”。
        }
    }
}

val launcher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    Log.d(
        "ShareResult",
        "activityResultCode=${result.resultCode}, data=${result.data}"
    )
    // ACTION_SEND 没有统一的发布结果契约。
    // 此处保留诊断日志,发布状态仍为未知。
}

广播注册和 PendingIntent 的创建沿用原有逻辑。目标选择事件应直接在 onReceive() 中处理,不必等 Activity 结果回调后再读取共享变量,也不要依赖两个回调的先后顺序。

如果不需要 Activity 返回的诊断信息,可以直接使用:

startActivity(
    Intent.createChooser(
        shareIntent,
        null,
        pendingIntent.intentSender
    )
)

这样仍能接收目标选择回调,但更换启动方式无法帮助应用判断发布结果。

X 的 Activity 迁移是否导致了问题?

在给出的环境中,分享入口从:

com.twitter.composer.ComposerActivity

变为:

com.x.android.lib.ComposerActivity

这一变化可以作为向 X 报告问题时的复现线索,但不足以证明结果码变化由迁移导致,也无法据此判断这是有意调整还是回归问题。

即使旧版本在成功发布后曾返回 RESULT_OK,也只能说明那些版本采用了这种实现。除非 X 明确承诺了结果协议,否则发送应用不能将其视为稳定的接口契约。

业务需要确认发布结果时,应先核实平台是否提供适用的官方接口或回调,再按其授权要求和结果语义接入。另行调用发布接口属于另一套发布流程,不能自动确认用户在 X 编辑界面中的操作结果。

界面提示和统计可以记录“已选择 X”,发布状态仍应保留为“未知”。onResume()、页面停留时间以及是否读取了图片,都不足以证明帖子已经发布。

备注:内容仅供参考。