You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Sharesheet优化:先选分享目标再下载及对话框定制

解决Android视频分享的两个核心问题:先选分享目标再下载 + 自定义组件选择对话框

一、先弹分享选择框,再下载视频的实现方法

整体思路

跳过先下载的步骤,先把所有支持视频分享的应用列出来让用户选;等用户选定目标应用后,再启动文件下载,下载完成后直接把视频定向分享到该应用。

具体实现步骤

  1. 找出所有能分享视频的应用
    查询系统里能处理ACTION_SEND且支持video/*类型的应用,拿到它们的包名、图标、名称,用来做自定义选择框。

  2. 展示自定义分享选择对话框
    用AlertDialog或者底部弹窗展示这些应用,用户点击后记录选中的应用信息。

  3. 下载完成后定向分享
    启动下载任务,监听下载完成事件;下载成功后构造分享Intent,直接指定目标应用的组件,跳过系统选择器直接启动分享。

代码示例

1. 查询支持分享的应用

private fun getShareableVideoApps(context: Context): List<ResolveInfo> {
    val shareIntent = Intent(Intent.ACTION_SEND).apply {
        type = "video/*"
    }
    // 获取所有支持视频分享的应用
    return context.packageManager.queryIntentActivities(shareIntent, PackageManager.MATCH_DEFAULT_ONLY)
}

2. 展示自定义选择对话框

fun showShareChooser(context: Context, onAppSelected: (ResolveInfo) -> Unit) {
    val apps = getShareableVideoApps(context)
    val appNames = apps.map { 
        context.packageManager.getApplicationLabel(it.activityInfo.applicationInfo) 
    }
    val appIcons = apps.map { 
        context.packageManager.getApplicationIcon(it.activityInfo.applicationInfo) 
    }

    AlertDialog.Builder(context)
        .setTitle("分享到")
        .setAdapter(
            ArrayAdapter(context, android.R.layout.select_dialog_item, appNames) { view, position, _ ->
                val textView = view as TextView
                // 给选项加上应用图标
                textView.setCompoundDrawablesWithIntrinsicBounds(appIcons[position], null, null, null)
                textView.compoundDrawablePadding = 16
            }
        ) { _, position ->
            val selectedApp = apps[position]
            onAppSelected(selectedApp)
        }
        .show()
}

3. 选完应用后下载并分享

// 调用自定义选择器
showShareChooser(context) { selectedApp ->
    // 启动视频下载(替换成你自己的DownloadManager逻辑)
    val downloadId = startVideoDownload(context, videoUrl)
    
    // 监听下载完成事件
    registerDownloadReceiver(context, downloadId) { localUri ->
        // 下载完成后,构造定向分享的Intent
        val shareIntent = Intent(Intent.ACTION_SEND).apply {
            type = "video/*"
            putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.my_video))
            // 扫描文件确保系统能识别(替换成你原有的MediaScanner逻辑)
            MediaScannerConnection.scanFile(context, arrayOf(localUri.path), null) { _, scannedUri ->
                putExtra(Intent.EXTRA_STREAM, scannedUri)
            }
            // 指定目标应用的组件,避免再次弹出系统选择框
            component = ComponentName(
                selectedApp.activityInfo.packageName,
                selectedApp.activityInfo.name
            )
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        context.startActivity(shareIntent)
    }
}

// 简化的下载启动逻辑
private fun startVideoDownload(context: Context, url: String): Long {
    val request = DownloadManager.Request(Uri.parse(url))
        .setTitle("视频下载")
        .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "temp_video.mp4")
    val downloadManager = context.getSystemService(DownloadManager::class.java)
    return downloadManager.enqueue(request)
}

// 监听下载完成的广播接收器
private fun registerDownloadReceiver(context: Context, downloadId: Long, onDownloadSuccess: (Uri) -> Unit) {
    val receiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) ?: -1
            if (id == downloadId) {
                val downloadManager = context?.getSystemService(DownloadManager::class.java)
                val cursor = downloadManager?.query(DownloadManager.Query().setFilterById(id))
                cursor?.use {
                    if (it.moveToFirst()) {
                        val status = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_STATUS))
                        if (status == DownloadManager.STATUS_SUCCESSFUL) {
                            val localUri = it.getString(it.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
                            onDownloadSuccess(Uri.parse(localUri))
                        }
                    }
                }
                context?.unregisterReceiver(this)
            }
        }
    }
    context.registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}

二、自定义"Complete action using"对话框

问题原因

像Instagram这类应用有多个组件(比如Feed、Stories都能接收视频分享),系统找不到默认组件时,就会弹出这个对话框让用户选。

解决方法

方案1:自定义选择器直接展示所有组件

修改查询应用的方法,去掉MATCH_DEFAULT_ONLY参数,获取应用的所有可分享组件,让用户直接选具体的分享入口:

private fun getShareableVideoApps(context: Context): List<ResolveInfo> {
    val shareIntent = Intent(Intent.ACTION_SEND).apply {
        type = "video/*"
    }
    // 获取所有匹配的组件,包括非默认的
    return context.packageManager.queryIntentActivities(shareIntent, 0)
}

这样自定义选择框里会直接显示“Instagram Feed”“Instagram Stories”这类选项,用户选完就不会触发系统的组件选择对话框。

方案2:指定应用的默认分享组件

如果不想让用户选组件,可以直接指定应用的默认分享入口(注意组件名可能随应用版本变化,需要测试验证):

// 示例:指定Instagram的默认分享组件
shareIntent.component = ComponentName("com.instagram.android", "com.instagram.share.common.ShareHandlerActivity")

方案3:单独处理特定应用

针对高频分享的应用(比如Instagram、TikTok),可以单独获取它们的所有分享组件,在自定义选择器里单独展示,提升用户体验。

关键注意事项

  • Android 10及以上版本,必须用FileProvider或者MediaStore获取文件URI,避免触发FileUriExposedException。
  • 下载完成后一定要用MediaScannerConnection扫描文件,确保系统能识别到文件,避免分享时出现“文件找不到”的问题。
  • 加载应用图标时可以用异步加载,避免UI卡顿。

内容的提问来源于stack exchange,提问作者LilMoke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 08:30:44