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

如何在Kotlin中通过RecyclerView实现图文分享至其他应用

问题描述

我正在学习Kotlin,希望实现点击RecyclerView列表项的分享按钮,将对应项的图片与文本分享至WhatsApp等其他应用。目前已编写Adapter代码、配置paths.xml及Manifest文件,但仍未成功实现功能,多方查找资料无果,寻求解决方案。


原代码

Adapter代码

package com.satset.satsetapps

import android.content.ContentResolver
import android.content.Intent
import android.content.res.Resources
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions


class CardViewHeroAdapter internal constructor(
    private val listHero: ArrayList<Hero>,
    private val contentResolver: ContentResolver?,
    private val resources: Resources?
) : RecyclerView.Adapter<CardViewHeroAdapter.CardViewViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewViewHolder {
        val view: View =
            LayoutInflater.from(parent.context).inflate(R.layout.item_cardview_hero, parent, false)
        return CardViewViewHolder(view)
    }

    override fun onBindViewHolder(holder: CardViewViewHolder, position: Int) {
        val hero = listHero[position]

        Glide.with(holder.itemView.context)
            .load(hero.photo)
            .apply(RequestOptions().override(200, 200))
            .into(holder.imgPhoto)

        holder.tvName.text = hero.name
        holder.tvDetail.text = hero.detail

        holder.btnFavorite.setOnClickListener {
            Toast.makeText(
                holder.itemView.context,
                "Favorite " + listHero[holder.adapterPosition].name,
                Toast.LENGTH_SHORT
            ).show()
        }

        holder.btnShare.setOnClickListener {
            val bitmap = BitmapFactory.decodeResource(resources, hero.photo);
            val path = MediaStore.Images.Media.insertImage(contentResolver, bitmap, "Title", null)
            val uri = Uri.parse(path)
            val shareIntent = Intent()
            shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
            shareIntent.putExtra(Intent.EXTRA_TITLE, hero.name)
            shareIntent.putExtra(Intent.EXTRA_TEXT, hero.detail)
            shareIntent.type = "image/*"
            holder.itemView.context.startActivity(Intent.createChooser(shareIntent, "Share Via..."))
            }

        holder.itemView.setOnClickListener {
            Toast.makeText(
                holder.itemView.context,
                "Kamu memilih " + listHero[holder.adapterPosition].name,
                Toast.LENGTH_SHORT
            ).show()
        }
    }
    
    override fun getItemCount(): Int {
        return listHero.size
    }

    inner class CardViewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var imgPhoto: ImageView = itemView.findViewById(R.id.img_item_photo)
        var tvName: TextView = itemView.findViewById(R.id.tv_item_name)
        var tvDetail: TextView = itemView.findViewById(R.id.tv_item_detail)
        var btnFavorite: Button = itemView.findViewById(R.id.btn_set_favorite)
        var btnShare: Button = itemView.findViewById(R.id.btn_set_share)
    }
}

paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path
        name="myimages"
        path="images/" />

    <cache-path
        name="shared_images"
        path="MyImagesShare/" />

    <external-path
        name="external_files"
        path="." />

    <external-files-path
        name="images"
        path="Pictures" />

</paths>

Manifest配置

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.satset.satsetapps.fileprovider"
android:grantUriPermissions="true"
android:exported="false">

<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

解决方案

问题分析

  • MediaStore插入图片的兼容性问题:MediaStore.Images.Media.insertImage在Android 10+(API 29)以上的存储权限规则下,无法保证生成的Uri能被第三方应用正常访问,且会将图片保存到公共相册,不符合私有分享的需求。
  • Intent配置错误:同时分享图片和文本时,设置type="image/*"会导致部分应用(如WhatsApp)无法识别额外文本;未添加临时Uri权限,第三方应用可能无法读取图片。
  • Bitmap处理风险:直接用BitmapFactory.decodeResource加载资源图片,易触发内存溢出(OOM),尤其针对大尺寸图片。

修复步骤

1. 替换Adapter的分享逻辑

将原btnShare.setOnClickListener代码替换为以下内容,用Glide处理图片保存并通过FileProvider生成安全Uri:

holder.btnShare.setOnClickListener {
    val context = holder.itemView.context
    // 用Glide加载图片并保存到缓存目录
    Glide.with(context)
        .asBitmap()
        .load(hero.photo)
        .into(object : com.bumptech.glide.request.target.SimpleTarget<Bitmap>() {
            override fun onResourceReady(resource: Bitmap, transition: com.bumptech.glide.request.transition.Transition<in Bitmap>?) {
                // 创建缓存子目录
                val cacheDir = File(context.cacheDir, "MyImagesShare")
                if (!cacheDir.exists()) {
                    cacheDir.mkdirs()
                }
                // 创建唯一命名的图片文件
                val imageFile = File(cacheDir, "${hero.name}_${System.currentTimeMillis()}.png")
                try {
                    // 将Bitmap写入文件
                    val outputStream = FileOutputStream(imageFile)
                    resource.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
                    outputStream.flush()
                    outputStream.close()

                    // 通过FileProvider生成可分享的Uri
                    val uri = FileProvider.getUriForFile(
                        context,
                        "com.satset.satsetapps.fileprovider",
                        imageFile
                    )

                    // 配置分享Intent
                    val shareIntent = Intent(Intent.ACTION_SEND)
                    shareIntent.type = "image/*"
                    shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
                    // 合并文本内容,适配多数应用的分享规则
                    shareIntent.putExtra(Intent.EXTRA_TEXT, "${hero.name}\n${hero.detail}")
                    // 授予第三方应用临时读取权限
                    shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

                    // 启动分享选择器
                    context.startActivity(Intent.createChooser(shareIntent, "分享至"))
                } catch (e: IOException) {
                    e.printStackTrace()
                    Toast.makeText(context, "分享失败:${e.message}", Toast.LENGTH_SHORT).show()
                }
            }
        })
}

2. 补充必要导入

在Adapter顶部添加以下导入语句:

import android.graphics.Bitmap
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import androidx.core.content.FileProvider

3. 验证FileProvider配置

现有paths.xml中的cache-path已对应代码中使用的MyImagesShare目录,无需修改。

4. Android 13+权限适配(可选)

若应用目标SDK为33及以上,需在Manifest中添加媒体读取权限(仅当后续扩展本地图片分享时需要):

<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

关键说明

  • 使用Glide处理图片加载与保存,避免OOM问题,同时兼容资源、网络等多种图片来源。
  • FileProvider生成的Uri符合Android存储权限规则,可安全将私有目录图片共享给第三方应用。
  • Intent.FLAG_GRANT_READ_URI_PERMISSION确保第三方应用能读取图片资源。
  • 合并文本到EXTRA_TEXT字段,适配WhatsApp等应用的图文分享逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 23:45:43