Flutter分享功能深度解析:从share到share_plus的跨平台实践
1. 项目概述:Flutter Share Plugin 不是“点一下就发”,而是跨平台内容分发的精密协调器
在 Flutter 开发中,“分享”这件事,远比表面看起来复杂得多。你可能刚写完一个漂亮的卡片页面,想让用户一键把订单截图、商品链接或活动文案发到微信、QQ、钉钉甚至系统短信里——结果发现 share: ^2.0.4 插件调用后,iOS 上弹出的是原生 UIActivityViewController,Android 上却卡在 Intent.createChooser() 的空白界面;或者更糟:在 Android 12+ 设备上直接抛出 SecurityException: Permission Denial ;又或者,你传入的图片路径是 File('/data/user/0/com.example.app/cache/share.jpg') ,结果微信收不到图,而 Telegram 却能正常显示。这些不是 Bug,而是 Flutter Share Plugin 在底层与各平台原生分享机制对接时,必须直面的权限、路径、MIME 类型、URI 权限授予、沙盒隔离等真实战场。它不是一个“万能转发按钮”,而是一套需要开发者深度理解平台差异、文件生命周期和 Intent/Activity/ViewController 行为逻辑的跨平台分发协调器。核心关键词 Flutter 、 Share Plugin 、 share 、 Flutter app 、 share function ,每一个都指向一个具体的技术断点: Flutter 决定了我们用 Dart 编写逻辑但无法绕过原生层; Share Plugin 是官方维护的桥梁,但桥墩打在哪、桥面怎么铺,得由你决定; share 是用户动作,但背后是 ACTION_SEND 、 UIActivityType 、 NSExtensionActivationRule 的精密匹配; Flutter app 意味着你的 APK/IPA 包里必须预埋好 FileProvider 配置或 Info.plist 声明; share function 则要求你必须区分“纯文本分享”、“带附件分享”、“多类型混合分享”三种模式,每种的参数构造、错误处理、降级策略都完全不同。这篇文章不讲“如何安装插件”,而是带你拆开 share_plus (当前主流替代方案)的源码包,看它如何在 AndroidManifest.xml 里动态注入 <provider> 标签,在 AppDelegate.swift 中拦截 application(_:open:options:) 回调,在 ShareDelegate 里做 MIME 类型自动推导,并告诉你为什么 shareFiles([file]) 在 iOS 上必须配合 UIActivityViewController.excludedActivityTypes = [.airDrop, .postToTwitter] 才能避免崩溃。适合所有已能跑通 flutter run 、但一碰分享就掉坑里的中阶 Flutter 开发者——尤其是那些正在交付电商、教育、工具类 App,且被测试同学反复追问“为什么微信收不到图”的人。
2. 核心设计思路与方案选型:为什么放弃 share 2.x,坚定选择 share_plus 3.x?
当你在 pubspec.yaml 里敲下 share: ^2.0.4 并执行 flutter pub get ,你很可能立刻遭遇标题里提到的网络热词:“flutter项目pub get卡在resolving dependencies”。这不是网络问题,而是 share 2.x 插件早已停止维护,其依赖的 path_provider 、 permission_handler 等子包版本锁死在旧版,与当前 Flutter SDK(尤其是 3.10+)的 null-safety 和 platform interface 规范严重冲突。我实测过,在 Flutter 3.22 环境下, share: ^2.0.4 会导致 pub get 卡在 Resolving dependencies... 超过 8 分钟,最终报错 Because no versions of share match >2.0.4 <3.0.0, version solving failed. 。这本质上是一个信号:官方已将维护重心完全转向 share_plus 。但选 share_plus 不是拍脑袋决定,而是基于三重硬性约束的理性选择。
第一重约束是 平台兼容性覆盖 。 share_plus 3.x 明确支持 Android 5.0(API 21)至 Android 14(API 34),iOS 11.0 至 iOS 17,同时提供 Web 端基础分享能力(通过 navigator.share() API)。而 share 2.x 在 Android 12+ 上因 Intent.FLAG_GRANT_READ_URI_PERMISSION 权限模型变更,会直接触发 SecurityException ;在 iOS 16+ 上因 UIActivityViewController 的 completionWithItemsHandler 回调时机变化,导致分享成功状态无法准确捕获。 share_plus 的源码里, android/src/main/kotlin/dev/fluttercommunity/plus/share/SharePlugin.kt 第 187 行明确做了 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 的分支处理,为 Android 13+ 新增了 Intent.FLAG_ACTIVITY_NEW_TASK 的强制设置,这是 share 2.x 完全缺失的关键补丁。
第二重约束是 文件分享的健壮性 。原始 share 插件对 File 对象的处理极其脆弱。它默认将 File 路径直接转为 content:// URI,但未校验该路径是否属于应用私有目录( getCacheDir() )、是否已通过 FileProvider 授权、是否在 Android 10+ 的 Scoped Storage 下被限制访问。 share_plus 则在 ShareFile.kt 中封装了完整的 FileProvider URI 生成逻辑:它会自动检测 File 所在目录,若为 getCacheDir() 或 getFilesDir() ,则调用 FileProvider.getUriForFile() 生成合法 content:// URI;若为外部存储路径(如 getExternalStoragePublicDirectory() ),则先检查 MANAGE_EXTERNAL_STORAGE 权限(Android 11+),再尝试 MediaStore 插入,最后返回 content://media/... URI。这个过程涉及至少 5 个 Android 系统 API 调用和 3 种异常捕获, share 2.x 全部省略,导致“图片发不出去”成为高频问题。
第三重约束是 错误反馈的可调试性 。 share 2.x 的 share() 方法返回 Future<void> ,失败时仅抛出泛化 PlatformException ,错误码如 "ERROR" 、 "UNKNOWN" ,无任何上下文。而 share_plus 的 Share.shareXxx() 方法统一返回 Future<ShareResult> ,其中 ShareResult 包含 status: ShareStatus (枚举值: completed 、 cancelled 、 failed 、 notAvailable )和 error: String? 字段。更重要的是,它在 Android 端会捕获 ActivityNotFoundException 并返回 notAvailable ,在 iOS 端会监听 UIActivityViewController.completionWithItemsHandler 的 completed 参数,精准区分“用户点了取消”和“系统无可用分享目标”。这种粒度的反馈,是定位“为什么微信没出现在分享列表里”的唯一依据。
因此,我的实操建议是: 立即删除 share: ^2.0.4 ,改用 share_plus: ^6.3.0 (截至 2024 年 7 月最新稳定版) 。它的 pubspec.yaml 依赖声明如下:
dependencies:
flutter:
sdk: flutter
share_plus: ^6.3.0
执行 flutter pub get 后,你会看到控制台快速输出 Running "flutter pub get" in my_app... ,不再卡顿。这不是版本数字的简单升级,而是从“能用就行”到“生产可用”的质变。后续所有实操步骤、配置、避坑技巧,均基于 share_plus 6.3.0 展开。如果你的项目仍卡在 share 2.x,第一步不是查文档,而是执行 flutter pub upgrade --major-versions ,让 pub 自动帮你迁移依赖树——这是解决 flutter项目pub get卡在resolving dependencies 的根治方案。
3. 核心细节解析与实操要点:从 Share.shareFiles() 到 content:// URI 的完整链路
当你调用 Share.shareFiles([file]) 时,Dart 层代码只是一行指令,但背后是 Flutter Engine、Platform Channel、原生 Java/Kotlin/Swift 三层的精密协作。理解这条链路,是解决“图片发不出去”、“文字带乱码”、“分享后 App 崩溃”等问题的前提。我们以 Android 端为例,逐层拆解 file 如何变成系统能识别的 content:// URI。
3.1 Dart 层: ShareFile 对象的构造与校验
在 Dart 代码中,你通常这样写:
final file = File('${(await getTemporaryDirectory()).path}/share_image.jpg');
await file.writeAsBytes(imageBytes);
await Share.shareFiles([file], text: '这是一张分享图片');
这里 file 是一个 File 实例,其 path 属性指向 /data/user/0/com.example.myapp/cache/share_image.jpg 。 share_plus 的 shareFiles() 方法首先会对每个 file 执行严格校验:
- 检查
file.existsSync()是否为true,若为false,直接抛出ArgumentError,错误信息为"File does not exist: ${file.path}"; - 检查
file.lengthSync() > 0,若文件大小为 0,同样抛出ArgumentError,因为空文件无法被系统分享; - 检查
file.path是否属于应用私有目录(即是否以getCacheDir()或getFilesDir()的绝对路径开头)。这是关键一步:share_plus会调用path_provider的getCacheDir()获取当前缓存目录路径,然后用file.path.startsWith(cacheDirPath)进行字符串匹配。如果匹配成功,说明该文件位于沙盒内,必须通过FileProvider授权;如果匹配失败(例如路径是/sdcard/Download/test.jpg),则进入外部存储处理流程。
提示:
getTemporaryDirectory()返回的路径在 Android 上就是getCacheDir(),所以你的file100% 会进入FileProvider流程。这是绝大多数“图片发不出去”问题的根源——你没配FileProvider。
3.2 Platform Channel 层:从 Dart 到 Java 的数据序列化
校验通过后, share_plus 会将 file.path 、 text 、 subject 等参数打包成一个 Map<String, dynamic> ,通过 MethodChannel 发送给 Android 原生层。这个 Map 的结构如下:
{
"files": ["/data/user/0/com.example.myapp/cache/share_image.jpg"],
"text": "这是一张分享图片",
"subject": null,
"mimeTypes": ["image/jpeg"]
}
注意 mimeTypes 字段: share_plus 会根据文件扩展名自动推导 MIME 类型。 .jpg → "image/jpeg" , .png → "image/png" , .pdf → "application/pdf" 。这个推导逻辑在 ShareFile.kt 的 getMimeType() 函数中实现,它使用 URLConnection.guessContentTypeFromName() ,比手动写 if (path.endsWith('.jpg')) return 'image/jpeg' 更可靠。但推导并非万能:如果文件没有扩展名(如 /cache/share_123 ), guessContentTypeFromName() 会返回 null ,此时 share_plus 会 fallback 到 "application/octet-stream" ,这可能导致微信拒绝接收(微信对 octet-stream 类型有严格过滤)。
3.3 Android 原生层: FileProvider 的 URI 生成与 Intent 构建
Java/Kotlin 层接收到 Map 后,核心任务是将 file.path 转换为系统可识别的 content:// URI。 share_plus 的 SharePlugin.kt 第 221 行开始执行此逻辑:
val uri = if (isInAppPrivateDir(file)) {
// 步骤1:获取 FileProvider 的 authority
val authority = "${context.packageName}.flutter.share_provider"
// 步骤2:调用 FileProvider.getUriForFile()
FileProvider.getUriForFile(context, authority, file)
} else {
// 外部存储处理逻辑(此处略)
}
这里 authority 的值是硬编码的 ${context.packageName}.flutter.share_provider ,例如你的包名是 com.example.myapp ,那么 authority 就是 com.example.myapp.flutter.share_provider 。这个 authority 必须与你在 AndroidManifest.xml 中声明的 <provider> 标签的 android:authorities 属性完全一致,否则 getUriForFile() 会抛出 IllegalArgumentException: Failed to find configured root that contains /data/user/0/... 。
生成 uri 后, share_plus 构建 Intent :
val intent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_STREAM, uri) // 关键!传入 content:// URI
putExtra(Intent.EXTRA_TEXT, text)
type = mimeTypes.firstOrNull() ?: "text/plain" // 设置 MIME 类型
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
Intent.FLAG_GRANT_READ_URI_PERMISSION 是 Android 7.0+ 强制要求的标志,它临时授予目标 Activity(如微信)读取该 content:// URI 的权限。没有它,微信进程无法打开你的文件,直接报 SecurityException 。
3.4 AndroidManifest.xml 配置: FileProvider 的生死线
FileProvider 的配置是整个链路中最容易出错、也最常被忽略的一环。你必须在 android/app/src/main/AndroidManifest.xml 的 <application> 标签下,添加以下 <provider> 声明:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.flutter.share_provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
注意三点:
android:authorities必须与 Kotlin 代码中的authority变量值完全一致,${applicationId}会被 Gradle 自动替换为你的实际包名;android:exported="false"是安全要求,禁止其他 App 直接访问你的FileProvider;<meta-data>指向@xml/file_paths,这意味着你必须在android/app/src/main/res/xml/目录下创建file_paths.xml文件。
file_paths.xml 的内容必须精确匹配你的文件存储位置。如果你只用 getTemporaryDirectory() (即 getCacheDir() ),那么 file_paths.xml 应为:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache_files/" path="."/>
</paths>
<cache-path> 标签告诉 FileProvider :“所有 getCacheDir() 下的文件,都允许通过此 provider 访问”。 path="." 表示根目录。如果你还用了 getFilesDir() 存储文件,则需添加 <files-path name="files_files/" path="."/> 。 切记:不能写 <external-path name="external_files/" path="."/> ,这会暴露整个外部存储,违反 Google Play 政策。
注意:
share_plus的文档有时会建议使用<external-cache-path>,这是错误的。<external-cache-path>对应getExternalCacheDir(),而getTemporaryDirectory()返回的是内部缓存目录,必须用<cache-path>。混淆这两者,是FileProvider配置失败的头号原因。
4. 实操过程与核心环节实现:从零配置到真机验证的完整流水线
现在,我们把前面所有理论知识,落地为一条可执行、可复现、可 debug 的完整操作流水线。整个过程分为 5 个阶段:环境准备、插件集成、Android 配置、iOS 配置、真机验证。每个阶段都包含精确命令、配置代码、预期输出和常见陷阱。请严格按顺序操作,跳步会导致后续步骤失败。
4.1 环境准备:确保 Flutter SDK 和工具链处于健康状态
在开始任何分享功能开发前,必须确认你的本地环境没有被标题中提到的热词所困扰。运行以下命令,逐一排查:
# 1. 检查 Flutter SDK 状态,解决 "initializing the flutter sdk. this could take a few minutes."
flutter doctor -v
预期输出中, [✓] Flutter 、 [✓] Android toolchain 、 [✓] Xcode (macOS)或 [✓] Chrome (Windows/Linux)应全部为绿色对勾。如果出现 [!] 或 [✗] ,重点看 Android SDK Manager 是否已安装 Android SDK Build-Tools 34.0.0 (或你项目指定的版本),以及 ANDROID_HOME 环境变量是否正确指向 SDK 根目录。常见错误是 flutter配置了环境变量为啥还cmd提示找不到命令 ,这通常是因为 PATH 中 flutter/bin 的路径写错了,或未重启终端。解决方案:在终端中执行 echo $PATH (macOS/Linux)或 echo %PATH% (Windows),确认 flutter/bin 在其中;若不在,重新配置并 source ~/.zshrc (macOS)或重启 CMD。
# 2. 清理可能的锁文件,解决 "waiting for another flutter command to release the startup lock..."
rm -f .flutter-plugins .flutter-plugins-dependencies
rm -rf build/
flutter clean
startup lock 通常是上一次 flutter run 或 flutter pub get 异常中断留下的 .lock 文件。 flutter clean 会清除 build/ 目录和所有中间产物,是最彻底的解锁方式。
# 3. 升级依赖,根治 "flutter项目pub get卡在resolving dependencies"
flutter pub upgrade --major-versions
此命令会强制将所有依赖升级到最新主版本。对于 share 插件,它会自动将 share: ^2.0.4 替换为 share_plus: ^6.3.0 ,并同步更新其依赖的 path_provider 、 permission_handler 等。执行后,控制台应快速输出 Changed 5 dependencies! 类似信息,而非卡在 Resolving dependencies... 。
4.2 插件集成: pubspec.yaml 与 Dart 代码的最小可行实现
完成环境清理后,编辑 pubspec.yaml ,添加 share_plus 依赖:
dependencies:
flutter:
sdk: flutter
share_plus: ^6.3.0 # 确保是 6.3.0,不是 6.x.x 的模糊版本
保存后,在项目根目录执行:
flutter pub get
预期输出: Running "flutter pub get" in my_app... 1,234ms ,耗时应在 2 秒内。如果超过 5 秒,说明仍有依赖冲突,需回退到 4.1 步骤重新执行 flutter pub upgrade --major-versions 。
接着,在你的 Dart 页面(如 lib/main.dart 的 MyHomePage 中),添加一个按钮和分享逻辑:
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
// ... 在 _MyHomePageState 类中添加方法
Future<void> _shareImage() async {
try {
// 步骤1:创建临时文件
final directory = await getTemporaryDirectory();
final file = File('${directory.path}/share_test.jpg');
// 步骤2:写入测试图片(这里用纯色图模拟)
final imageBytes = await _generateTestImageBytes(); // 此函数见下方
await file.writeAsBytes(imageBytes);
// 步骤3:调用分享
await Share.shareFiles(
[file],
text: '来自 Flutter App 的测试分享',
subject: 'Flutter Share Test',
);
} on PlatformException catch (e) {
print('分享失败: ${e.code} - ${e.message}');
}
}
// 生成 100x100 红色 PNG 图片的字节数据(用于测试)
Future<Uint8List> _generateTestImageBytes() async {
final pngData = await decodeImageFromList(Uint8List.fromList([
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG header
// ... 此处省略 100x100 红色像素的完整 PNG 数据(实际项目中用真实图片)
]));
return encodePng(pngData!);
}
注意: _generateTestImageBytes() 是简化版,实际项目中应使用 image_picker 或网络下载的真实图片。关键点在于 file 必须是 File 对象,且路径必须在 getTemporaryDirectory() 下。
4.3 Android 配置: AndroidManifest.xml 与 file_paths.xml 的精确手术
这是最容易出错的环节。请严格按以下路径和内容操作:
步骤1:创建 file_paths.xml
- 在
android/app/src/main/res/下,创建xml目录(如果不存在)。 - 在
xml目录下,新建file_paths.xml文件,内容为:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache_files/" path="."/>
</paths>
步骤2:修改 AndroidManifest.xml
- 打开
android/app/src/main/AndroidManifest.xml。 - 在
<application>标签内(任意位置,但必须在<application>内),添加<provider>声明:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.flutter.share_provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
关键检查点:
android:authorities中的${applicationId}会自动替换,无需手动填写包名;<meta-data>的android:resource必须是@xml/file_paths,路径和文件名必须完全一致;- 确保
android/app/src/main/res/xml/file_paths.xml文件真实存在,且文件编码为 UTF-8。
步骤3:验证配置 运行 flutter build apk --debug ,如果配置正确,构建会成功。如果报错 Failed to find configured root that contains /data/user/0/... ,说明 file_paths.xml 路径或 <cache-path> 标签有误,请回退检查。
4.4 iOS 配置: Info.plist 的三项关键声明
iOS 端配置相对简单,但有三项声明缺一不可。打开 ios/Runner/Info.plist ,在 <dict> 标签内,添加以下三个 key-value 对:
<!-- 1. 允许分享到社交平台 -->
<key>LSApplicationQueriesSchemes</key>
<array>
<string>wechat</string>
<string>weixin</string>
<string>qq</string>
<string>mqq</string>
<string>sinaweibo</string>
</array>
<!-- 2. 添加分享所需权限描述(iOS 10+ 要求) -->
<key>NSPhotoLibraryUsageDescription</key>
<string>此App需要访问相册,以便分享图片</string>
<!-- 3. 如果分享文本,添加剪贴板权限(iOS 14+ 要求) -->
<key>NSPasteboardUsageDescription</key>
<string>此App需要访问剪贴板,以便复制分享链接</string>
解释:
LSApplicationQueriesSchemes告诉 iOS:“我的 App 想要查询并打开微信(wechat)、QQ(qq)等 App”。没有它,UIActivityViewController会过滤掉这些 App,导致它们不出现在分享列表中;NSPhotoLibraryUsageDescription是访问相册的权限描述,即使你只是分享内存中的图片,share_plus在 iOS 端也会尝试将其保存到临时相册再分享,因此需要此权限;NSPasteboardUsageDescription是 iOS 14+ 新增的剪贴板访问权限,用于分享文本时的后台复制操作。
4.5 真机验证:从模拟器到真机的全流程测试
配置完成后,不要急于 flutter run ,先进行真机验证。模拟器(尤其是 iOS Simulator)对 UIActivityViewController 的支持不完整,很多分享目标(如微信)根本不会出现。
Android 真机测试:
- 用 USB 连接 Android 手机,开启 USB 调试;
- 运行
flutter run -d <device-id>(<device-id>可通过flutter devices查看); - 点击分享按钮,观察:
- 预期:弹出系统分享面板,显示微信、QQ、短信等图标;
- 若无图标:检查
LSApplicationQueriesSchemes是否遗漏wechat; - 若点击微信后无反应:检查
file_paths.xml和AndroidManifest.xml配置; - 若微信提示“文件损坏”:检查
mimeTypes是否正确,.jpg文件必须是image/jpeg,不能是image/jpg。
iOS 真机测试:
- 将手机连接 Mac,Xcode 中选择设备,点击运行;
- 点击分享按钮,观察:
- 预期:弹出
UIActivityViewController,顶部显示“分享到”及多个 App 图标; - 若无微信图标:检查
Info.plist中LSApplicationQueriesSchemes的wechat和weixin是否都存在; - 若分享后微信闪退:检查
NSPhotoLibraryUsageDescription是否已添加; - 若分享文本时提示“无法访问剪贴板”:检查
NSPasteboardUsageDescription。
- 预期:弹出
实操心得:我曾在一个电商项目中,因
Info.plist漏写了weixin(只写了
5. 常见问题与排查技巧实录:一份来自 12 个 Flutter 项目的血泪清单
在过去的两年里,我主导或参与了 12 个不同行业的 Flutter 项目(电商、教育、政务、医疗、工具类),每一个都集成了分享功能。以下是我在这些项目中,亲手踩过、亲手修复、并记录下来的 7 个最高频、最隐蔽、最让人抓狂的问题,附带精准的排查路径和一招毙命的解决方案。这份清单,比任何官方文档都更贴近真实战场。
5.1 问题:Android 真机上分享图片,微信显示“文件已损坏”,但 QQ 和短信能正常接收
现象还原: 用户点击分享,系统面板弹出,选择微信,微信打开后显示一张灰色占位图,下方文字“文件已损坏”。但选择 QQ,图片能正常显示;选择短信,图片也能作为彩信发送。
排查路径: 这是典型的 MIME 类型不匹配问题。微信对 content:// URI 的 MIME 类型校验极其严格,它要求 Intent.EXTRA_STREAM 对应的 URI,其 Content-Type 必须是 image/jpeg 或 image/png ,且不能有任何偏差。而 share_plus 的 MIME 推导逻辑,有时会因文件扩展名不规范而失败。
解决方案: 强制指定 MIME 类型,绕过自动推导。修改 Dart 代码:
await Share.shareFiles(
[file],
text: '分享文字',
mimeTypes: ['image/jpeg'], // 关键!强制指定
);
原理: share_plus 的 shareFiles() 方法接受 mimeTypes 参数,它会直接将此数组的第一个元素作为 Intent.setType() 的参数。这样,无论 file.path 是 share.jpg 还是 share_123 , Intent 的 type 都会被设为 image/jpeg ,微信就能正确识别。
注意:
mimeTypes数组长度必须为 1,且必须与文件实际格式一致。image/jpeg对应.jpg/.jpeg,image/png对应.png。用错会导致所有 App 都无法打开。
5.2 问题:iOS 真机上, UIActivityViewController 面板弹出后,微信、QQ 图标全部消失,只剩“拷贝”、“保存图像”
现象还原: 在 iPhone 上,分享面板只显示系统自带的几个选项,第三方 App 图标一个都不见。用户反馈“分享不了微信”。
排查路径: 这 100% 是 Info.plist 中 LSApplicationQueriesSchemes 配置错误。iOS 11+ 对此有严格限制,必须精确声明你要调用的 App 的 URL Scheme。
解决方案: 检查并修正 Info.plist 。微信必须同时声明 wechat 和 weixin :
<key>LSApplicationQueriesSchemes</key>
<array>
<string>wechat</string>
<string>weixin</string>
<!-- QQ 同理 -->
<string>qq</string>
<string>mqq</string>
</array>
原理: 微信有两个官方 Scheme: wechat:// 用于旧版, weixin:// 用于新版。只声明一个,iOS 就会过滤掉另一个。 share_plus 的源码中, SharePlugin.m 的 canOpenURL: 方法会依次尝试 wechat:// 和 weixin:// ,只有两者都存在,微信图标才会显示。
5.3 问题: shareFiles() 调用后,Android 12+ 设备直接崩溃,Logcat 报 java.lang.SecurityException: Permission Denial
现象还原: 在 Pixel 5(Android 12)或三星 S22(Android 13)上,点击分享按钮,App 瞬间闪退,Logcat 显示 SecurityException: Permission Denial: starting Intent { act=android.intent.action.SEND ... } from ProcessRecord{...} (pid=..., uid=...) not exported from uid ... 。
排查路径: 这是 Android 12+ 的 Intent.FLAG_ACTIVITY_NEW_TASK 强制要求未满足。 share_plus 6.3.0 已内置此 flag,但如果你的 minSdkVersion 低于 21,或 targetSdkVersion 未升级到 31+, FLAG_ACTIVITY_NEW_TASK 可能被忽略。
解决方案: 确保 android/app/build.gradle 中的 targetSdkVersion 为 31 或更高:
android {
compileSdkVersion 34
defaultConfig {
applicationId "com.example.myapp"
minSdkVersion 21
targetSdkVersion 34 // 关键!必须 >= 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
}
原理: Android 12(API 31)引入了 Activity 启动的严格模式,要求 Intent 必须显式设置 FLAG_ACTIVITY_NEW_TASK ,否则视为非法启动。 share_plus 的 Kotlin 代码中已添加此 flag,但它只在 targetSdkVersion >= 31 时才生效。
5.4 问题:分享纯文本时,iOS 上 UIActivityViewController 面板不显示“微信”、“QQ”,只显示“邮件”、“信息”
现象还原: 当 Share.share(text: 'hello') 时,iOS 面板只有系统 App,第三方 App 全部消失。但分享图片时,微信又能正常显示。
排查路径: 这是 UIActivityViewController 的 excludedActivityTypes 默认行为。当只分享文本时, share_plus 会排除一些不支持纯文本的 Activity,但微信的 UIActivityTypePostToWeChat 有时会被误排除。
解决方案: 手动指定 excludedActivityTypes ,保留微信。在 Dart 中:
await Share.share(
'hello',
excludedShareOptions: [
ShareOption.email,
ShareOption.message,
], // 排除邮件和短信,保留微信、QQ
);
原理: share_plus 的 excludedShareOptions 参数会映射到 iOS 的 excludedActivityTypes 。 ShareOption.wechat 是默认包含的,我们只需排除不需要的,就能确保微信图标出现。
5.5 问题: flutter run 后,Android Studio 控制台疯狂刷屏 D/FlutterSharePlugin: Sharing files... ,但分享面板就是不弹出
现象还原: 控制台日志显示 Sharing files... ,但 UI 无任何响应,仿佛点击无效。
排查路径: 这是 MethodChannel 调用未被原生层正确注册。 share_plus 的 registerWith() 方法可能未被调用。
解决方案: 检查 android/app/src/main/kotlin/.../MainActivity.kt (或 MainActivity.java )。确保 GeneratedPluginRegistrant.registerWith(flutterEngine) 被调用:
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine) // 关键!必须存在
}
}
原理: GeneratedPluginRegistrant 是 Flutter 自动生成的注册器,它会调用 SharePlugin.registerWith() ,从而将 MethodChannel 的 handler 注册到引擎。如果此行被注释或删除,Dart 层的 shareFiles() 调用就石沉大海。
5.6 问题:分享大文件(>10MB)时,Android
更多推荐
所有评论(0)