如何在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
相关产品推荐
相关产品推荐

