上周又有人在群里抛截图,面板弹出来了,点微信好友转圈两秒然后没下文,日志里干干净净。这种问题十有八九不是 SDK 坏了,是接入链路上某个环节对不上。做过 Android Studio 里集成 ShareSDK 做第三方分享的同学应该都懂,聚合分享这活儿看着是"引个依赖、调个 show",实际上从开放平台注册、签名指纹、清单文件、回调 Activity 到混淆规则,至少有五六个地方要同时对齐,任何一个错位,表现都是同一个——分享不出去。
这篇东西就是把这些环节拆开讲清楚。ShareSDK 是一套聚合分享组件,把微信、QQ、微博、系统分享这些渠道的登录、分享、回调收敛成一套统一 API,省掉你逐个接入原生 SDK 的重复劳动,适合需要"点多面广"分享能力的中大型 App,也适合刚接手老项目、被回调问题折磨的同学。下面从选型逻辑一路讲到线上收尾,能直接抄的部分我尽量写成可复制的代码和配置。
1. 先把需求拆干净:为什么选聚合分享 SDK 而不是逐个接原生
1.1 逐个接原生 SDK 的隐性成本远高于第一眼看到的
很多人做技术选型时算的账是"接三个平台的原生 SDK 大概两天",实际干下来往往是一周。原因不在核心代码量,而在那些看不到的协调成本。每个渠道都要单独去开放平台注册应用、填包名、填签名、等审核,每个渠道的回调 Activity 放在不同的包里、继承不同的父类、注册不同的 intent-filter,每个渠道的分享参数结构还各不相同——图片链接在 A 平台叫 imageUrl,在 B 平台叫 imageObject,压缩策略和尺寸上限也不一样。
更要命的是这些代码会随平台更新而失效。某个平台某次调整了缩略图上限,你的分享就静默失败;某个平台把回调类的包名规则收紧,你的分享成功但 UI 永远停在"分享中"。三套代码意味着三次回归测试,四套就是四次。如果产品需求只是"能让用户把内容发出去",这笔投入的性价比非常低。
我见过一个内容类项目,最初老老实实接了三个原生 SDK,后来产品要加两个新渠道,评估下来改动量比重新接一遍还大,最后整体换成了聚合方案。所以选型时真正该问的不是"聚合 SDK 是不是更轻",而是"我要维护的渠道数量会不会变"。只要答案是"会",聚合方案的优势就成立。
1.2 聚合方案帮你抹平了什么,又没有帮你抹平什么
ShareSDK 的核心价值是把"渠道差异"收进 SDK 内部,对外只暴露一层统一接口:统一初始化、统一平台标识、统一分享参数结构、统一回调协议、统一分享面板 UI。你在业务层写一份代码,切换渠道只是换一个字符串常量。这对业务层是实打实的减负,尤其是分享面板这种交互统一、业务逻辑薄的功能。
但它不是银弹,有几件事它替你做不了:开放平台的应用注册与审核是你自己的账号行为;包名与签名的绑定关系必须你手动填对,SDK 没法猜;目标 App 未安装时的降级体验需要你在业务层设计;图片资源合规(尺寸、格式、是否公网可访问)依然是调用方责任。把这四件事当成"SDK 应该自动处理"的,基本都会在调试阶段撞墙。
理解这一点很重要,因为它决定了你的调试顺序。当你遇到"分享没反应",第一反应不应该是去翻 SDK 源码,而应该按"账号绑定 → 清单文件 → 回调注册 → 参数合法"这条链路逐段卡点。后面第 4 章的排查表就是按这个顺序排的。
1.3 渠道能力矩阵:先想清楚哪些渠道真的需要
不同渠道的能力差异很大,不是所有渠道都值得接。接之前先拿一张表把需求框住,能省掉一半工作量。下表是基于常见实践的整理,具体以你接入时服务方文档为准:
| 渠道 | 是否需要开放平台审核 | 是否要求客户端安装 | 回调机制 | 常见坑点 |
|---|---|---|---|---|
| 微信好友 | 是,需应用审核通过 | 是 | 通过 wxapi 包下的回调类 | 包名/签名不匹配直接无回调 |
| 微信朋友圈 | 是 | 是 | 同上 | 仅支持图文/链接,纯文本受限 |
| QQ / Qzone | 是 | 是 | SDK 内部广播 + 回调类 | tencent+appId 的 scheme 未声明 |
| 微博 | 是 | 是 | 回调类 | 图片需先上传,链接需备案域名 |
| 系统分享 | 否 | 否 | 无回调 | 只能拿到"已唤起",拿不到结果 |
这张表最关键的信息其实是最后一列——只有系统分享是无回调的,其余渠道理论上都能拿到成功/失败/取消三态。如果你的产品经理要求"分享成功给积分",那就必须走前四类,系统分享只能算兜底入口。
另外还要注意一个产品侧的取舍:微信朋友圈对纯文本分享有限制,如果你要分享的是纯文字内容,朋友圈渠道实际上会失败,需要在 UI 上把该渠道置灰或者自动转成"生成图片再分享"。这类细节在需求评审阶段就该确认,别等联调时才发现。
1.4 版本矩阵:SDK、构建插件、AGP 三者必须对齐
这里单独拎出来讲,因为它是新手最容易忽略、也最容易导致"编译过了但运行崩"的原因。聚合 SDK 通常以 Gradle 插件形式接入(负责自动注入清单、自动生成渠道配置),这个插件对 Android Gradle Plugin 的版本是有要求的。AGP 升级到较新版本后,老插件可能无法解析变体信息,表现为构建期报找不到 task 或者清单合并失败。
我的建议是把三个版本号写进项目文档并锁死:AGP 版本、Gradle Wrapper 版本、分享 SDK 插件版本。升级 AGP 时把这三个一起评估,别单独动一个。如果确实要升级,先在独立分支上升,跑通"初始化 → 分享 → 回调"完整链路再合回主干。这个流程听起来啰嗦,但比线上发现问题强太多。
2. 动手之前的三件事:账号、签名、工程基线
2.1 包名与签名指纹是绕不过去的硬门槛
所有渠道的审核第一步都是"你是谁"。开放平台通过应用包名 + 签名指纹来识别调用方,两者任一不匹配,渠道侧会直接拒绝,而客户端往往拿不到明确错误,只表现为"点了没反应"。所以这一步必须先做,且要用正式发布用的签名文件去算指纹,不要用 debug 签名凑数。
取指纹有两条路。第一条是命令行,直接读 keystore:
keytool -list -v \ -keystore release.jks \ -alias your_alias \ -storepass 你的store密码 \ -keypass 你的key密码输出里会有一行MD5:和一串冒号分隔的十六进制。第二条是 Gradle 自带的报告任务,在 Android Studio 的 Terminal 里执行:
./gradlew signingReport它会把每个变体用的签名信息都打出来,包括 MD5 和 SHA1,很适合确认"当前构建到底签的哪个文件"。我用这条命令查出过一次事故:CI 上配置的 keystore 路径写错,回退到了默认 debug 签名,本地调试一切正常,出包就全渠道失效。
关于格式,这里有个特别容易被卡住的细节:不同渠道对指纹格式要求不一样。有的要小写无冒号,有的要大写带冒号,有的要 SHA1。填之前把平台页面的输入框提示看清楚,实在不确定就把大小写两种都试一遍。我习惯在文档里存一份对照表,包含每个渠道填的字段、格式、填写日期,换人接手时不用重新考古。
2.2 Gradle 仓库与插件配置的写法
工程基线部分分三步:加仓库、加插件依赖、加渠道配置。仓库地址会随服务方调整而变化,我不建议在文章里贴死链,按你接入时的官方文档填即可,通常形如:
// 项目根目录 build.gradle buildscript { repositories { google() mavenCentral() maven { url 'SDK服务方提供的Maven仓库地址' } // 以官方文档为准 } dependencies { classpath 'com.android.tools.build:gradle:8.x.x' classpath 'SDK服务方提供的构建插件坐标' // 与SDK大版本对应 } } allprojects { repositories { google() mavenCentral() maven { url 'SDK服务方提供的Maven仓库地址' } } }然后在 app 模块里应用插件并填写渠道信息:
// app/build.gradle apply plugin: 'com.android.application' apply plugin: 'SDK服务方提供的插件名' SDK配置块 { appKey "你的AppKey" appSecret "你的AppSecret" ShareSDK { devInfo { Wechat { appId "wx开头的AppID" appSecret "微信开放平台的应用密钥" withShareTicket true bypassApproval false } WechatMoments { appId "同一个AppID" appSecret "同一个密钥" bypassApproval false } QQ { appId "QQ互联的数字AppID" appKey "QQ互联的AppKey" } SinaWeibo { appKey "微博的AppKey" appSecret "微博的AppSecret" callbackUri "https://你的回调地址" shareByAppClient true } } } }这段配置的本质是在编译期生成渠道清单和初始化参数,所以改完必须重新构建,光改代码不重新 sync 是不生效的。我遇到过同事改完配置直接点运行,结果用的还是缓存里的旧清单,排查了半小时。养成习惯:改了这段就 Clean 一次。
2.3 清单文件:几个必须手写的声明
虽然插件会自动注入大部分内容,但有两处必须自己写清楚。第一处是微信回调 Activity,它必须放在你的包名.wxapi这个固定路径下,类名必须是WXEntryActivity,少一个字都收不到回调:
package com.example.demo.wxapi; import cn.sharesdk.wechat.utils.WXEntryActivity; public class WXEntryActivity extends WXEntryActivity { // 不需要写任何代码,父类已经处理了回调分发 }对应清单声明:
<activity android:name=".wxapi.WXEntryActivity" android:exported="true" android:launchMode="singleTop" android:taskAffinity="你的包名" android:theme="@android:style/Theme.Translucent.NoTitleBar" />exported="true"是必须的,因为回调是从外部应用唤起的;taskAffinity设成包名是为了避免多任务栈里出现重复页面。第二处是Android 11 及以上的包可见性声明,不加的话你连"微信是否安装"都判断不出来,渠道会一直显示为不可用:
<queries> <package android:name="com.tencent.mm" /> <package android:name="com.tencent.mobileqq" /> <intent> <action android:name="android.intent.action.VIEW" /> <data android:scheme="weixin" /> </intent> </queries>QQ 的回调还需要在清单里声明tencent加 AppID 的 scheme,这个通常由插件生成,但如果你手动裁剪过清单,记得检查一遍。我一般会在集成完成后做一次"清单体检":把合并后的清单导出来,搜索wxapi、tencent、queries三个关键字,确认都在。
2.4 混淆规则和隐私初始化时机
混淆这块的规则不复杂,但漏了就是线上崩溃,且往往在灰度阶段才发现:
-keep class cn.sharesdk.** { *; } -keep class com.mob.** { *; } -keep class com.sina.** { *; } -keep class **.R$* { *; } -keep class **.wxapi.** { *; } -dontwarn cn.sharesdk.** -dontwarn com.mob.**注意最后那条**.wxapi.**,很多人只保留了 SDK 包名,忘了自己的回调类也在反射调用链上,混淆后类名变了,回调自然分发不到。
另一个必须提的是隐私合规。现在所有 SDK 都要求在用户同意隐私政策之后才能初始化,提前初始化会被判定为违规收集设备信息。标准做法是在 Application 里先不初始化,等用户在首屏弹窗点"同意"之后,再调用授权提交接口并初始化:
// 用户点击"同意隐私政策"后调用 MobSDK.submitPolicyGrantResult(true, null);如果你用的是较新的版本,这个方法可能已经被整合进初始化流程,具体以文档为准。这个细节不难,但它是应用上架的硬门槛,别放到最后补。
3. 初始化、分享调用与回调的完整链路
3.1 初始化时序:什么时候初始化,初始化几次
理想的初始化时机是"用户同意隐私政策之后、第一次打开需要分享的页面之前"。也就是放在同意回调里,或者放在 Application 中但用标志位控制。初始化本身是幂等的,重复调用不会出问题,但为了代码清晰,我习惯用单例包装一层:
public class ShareManager { private static volatile boolean inited = false; public static void init(Context context) { if (inited) return; synchronized (ShareManager.class) { if (!inited) { MobSDK.submitPolicyGrantResult(true, null); ShareSDK.initSDK(context.getApplicationContext()); inited = true; } } } }这里有个反直觉的点:初始化失败不会抛异常。ShareSDK 的初始化是异步的,失败信息通过日志输出,客户端拿不到返回值。所以第一次接入时一定要开日志开关,把 SDK 内部日志打到 Logcat:
MobSDK.setDebugMode(true); // 仅调试包打开我见过的问题里,有相当一部分是"AppKey 填错"或者"AppSecret 少了一位",前端表现完全一样。打开日志后,初始化阶段的报错信息会非常明确,能省掉大量猜测。
3.2 平台对象与分享面板:两种调用姿势
SDK 提供两种分享入口,各有适用场景。平台对象直调适合"指定渠道、无 UI"的场景,比如从详情页的"分享到微信"按钮直接触发;分享面板适合"让用户自己选渠道",它自带一套底部弹出的网格 UI,你可以自定义标题、渠道顺序、是否显示"更多"。
平台直调的核心代码:
Platform platform = ShareSDK.getPlatform(Wechat.NAME); platform.setPlatformActionListener(new PlatformActionListener() { @Override public void onComplete(Platform platform, int action, HashMap<String, Object> res) { // 分享成功 } @Override public void onError(Platform platform, int action, int code, Throwable throwable) { // 分享失败,code 是渠道返回的错误码 } @Override public void onCancel(Platform platform, int action) { // 用户取消 } }); Platform.ShareParams params = new Platform.ShareParams(); params.setShareType(Platform.SHARE_WEBPAGE); params.setTitle("标题文案"); params.setText("正文描述"); params.setImageUrl("https://你的域名/thumb.png"); params.setUrl("https://你的域名/detail?id=123"); platform.share(params);注意setShareType这个参数,它决定了分享内容被如何解释。SHARE_WEBPAGE是链接卡片,SHARE_IMAGE是纯图片,SHARE_TEXT是纯文本。不同类型支持的字段不同,比如纯文本类型你设了 imageUrl 也不会生效,链接类型如果不给 url 在微信侧会直接被拒。这一点务必对着渠道能力表核对。
分享面板的写法更简单,配置一次 UI 就能复用:
OnekeyShare oks = new OnekeyShare(); oks.setTitle("分享标题"); oks.setText("分享正文"); oks.setTitleUrl("https://你的域名/detail?id=123"); oks.setUrl("https://你的域名/detail?id=123"); oks.setImageUrl("https://你的域名/thumb.png"); oks.setSite("你的应用名"); oks.setSiteUrl("https://你的域名"); oks.setCallback(new PlatformActionListener() { /* 同上 */ }); oks.show(context);如果只想显示部分渠道,用oks.setPlatform(...)或者在渠道配置里关掉不需要的项。定制外观的部分(比如只显示一行、去掉"更多")通过OnekeyShareTheme实现,一般项目用默认风格就够了,不必过度定制。
3.3 三种典型内容的完整参数与规格
纯文本分享最省事,但要注意微信好友对纯文本的支持仅限于会话,朋友圈不支持纯文本。文本里带链接时,部分渠道会自动抓取网页标题和缩略图,抓不到就显示纯文本,视觉上很朴素。
图文链接分享是内容类 App 的主力场景,参数上有几个硬性要求:链接必须是公网可访问的 HTTPS 地址,不能是内网或本地文件路径;缩略图必须是公网可访问的图片地址,不能传本地路径。我踩过的坑是测试环境用的是内网域名,本地调试一切正常,线上切了域名反而忘记同步改配置,导致缩略图 404,卡片变成灰块。
本地图片分享适合"生成海报再分享"的场景。用setImagePath传本地绝对路径,但要注意图片大小,多数渠道对分享图片有上限(常见是几 MB 级别),超过会被压缩甚至直接失败。稳妥做法是分享前先自己压缩一遍:
private String compressForShare(Bitmap src) { File out = new File(context.getCacheDir(), "share_" + System.currentTimeMillis() + ".jpg"); int maxSide = 1080; float scale = Math.min(1f, maxSide * 1f / Math.max(src.getWidth(), src.getHeight())); Bitmap scaled = Bitmap.createScaledBitmap(src, (int) (src.getWidth() * scale), (int) (src.getHeight() * scale), true); try (FileOutputStream fos = new FileOutputStream(out)) { scaled.compress(Bitmap.CompressFormat.JPEG, 85, fos); } catch (IOException e) { return null; } finally { if (scaled != src) scaled.recycle(); } return out.getAbsolutePath(); }缩略图规格是另一个高频坑点。微信侧的缩略图有比较严格的大小限制(经验值在 32KB 以内比较稳),超过这个体积可能导致分享卡片显示异常或者直接失败。我的做法是缩略图单独出一张 200x200 左右的小图,别直接复用列表页的大图。
3.4 回调里该做什么、不该做什么
回调是分享链路里业务价值最高的部分,但也是最容易被写坏的部分。onComplete里可以做的是:记录埋点、发放奖励、刷新状态。不该做的是:弹 Toast 说"分享成功"——因为onComplete只代表 SDK 层面认为分享流程走完了,并不等于用户真的发到了好友或者朋友圈。部分渠道存在"用户点了发送但最终取消"的情况,SDK 无法感知。
所以涉及利益的设计(比如分享得积分),我通常做两层:SDK 回调作为"触发",服务端做"校验"。校验方式是记录分享发起时间、渠道、用户 ID,配合频次限制和风控规则,而不是纯信任客户端回调。这套设计做下来,恶意刷分的成本会高很多。
onError里的 code 值值得关注,把它落到日志里,出问题时能快速定位是渠道拒绝还是网络问题。onCancel建议只做日志,不要弹任何提示,用户主动取消还弹个"分享取消"是很烦人的交互。
3.5 分享成功与否的判断边界
这里得说句实话:没有哪个聚合方案能 100% 确认用户真的完成了分享。渠道返回的成功只代表"内容已成功交给目标应用",用户可能发出去,也可能在最后一屏取消。能拿到的最强信号是微信分享后返回的 transaction 标识,但即便如此也存在边界情况。
实践中的做法是"不追求绝对准确,但要控制损失"。具体是:本地回调立即给一个轻量的 UI 反馈(比如按钮变成"已分享"),服务端按日限制奖励次数,同时对异常高频账号做冷却。这个组合能把薅羊毛的收益压到接近零,又不会误伤正常用户。
4. 踩坑排查实录:五类高频故障的定位路径
4.1 面板弹出来了但渠道是灰的,或者点了没反应
这是最高频的问题,原因基本集中在三个地方。第一是包可见性没声明,Android 11 之后应用默认看不到其他应用,SDK 判断"微信未安装",渠道就被置灰。第二是签名指纹不匹配,渠道侧拒绝但不给明确错误。第三是AppKey 与包名不对应,比如测试包和正式包用了同一份配置,但只有正式包在开放平台注册过。
排查顺序建议:先看 Logcat 里 SDK 的初始化日志,确认渠道配置是否加载成功;再用adb shell pm list packages | grep tencent确认设备上装了目标应用;然后打印当前签名的 MD5,和开放平台后台逐个字符对比;最后确认 AppKey 属于哪个包名。
4.2 分享出去了但回调永远不回来
回调链路涉及"你的应用 → 目标应用 → 你的回调 Activity",任何一环断了都会表现为"分享成功但没回调"。最常见的原因是WXEntryActivity 的包路径或类名写错,必须是包名.wxapi.WXEntryActivity,且必须继承 SDK 提供的父类。其次是exported属性没开,系统不允许外部唤起。第三是清单合并时被覆盖,比如主模块和某个库都声明了同名 Activity。
我建议在集成完成后立刻做一次"回调自测":随便分享一条文本,看 Logcat 里有没有进入回调类。如果没有任何日志,基本可以确定是清单或类路径问题,而不是代码逻辑问题。
4.3 Debug 正常,Release 包分享必失败
这类"只在线上出现"的问题,八成是混淆或者签名差异。混淆方面,除了前面提到的 keep 规则,还要注意某些渠道 SDK 用了反射调用自己的内部类,规则里统一用通配符保留整个包最稳。签名方面,很多团队本地调试用 debug 签名,CI 出包用 release 签名,但开放平台上只注册了其中一个。这时候的表现是:本地测试全绿,打包给别人装就废。
我的做法是在构建脚本里加一道检查,打包时打印当前签名指纹,和文档里记录的正式签名做人工核对。这个动作花不了一分钟,能挡住很多低级问题。
4.4 图片分享失败、内存溢出与白屏
图片相关的问题集中在体积和格式。体积上,超过渠道限制会被拒;格式上,部分渠道不接受 WebP,建议统一转 JPEG。内存上,从相册取原图直接分享,在低端机上很容易 OOM,尤其是 4000 万像素的照片。
处理思路是"进内存之前先降采样"。用BitmapFactory.Options的inJustDecodeBounds先读尺寸,算出采样率再真正解码,这一步能把内存占用降到原来的几分之一。分享用的图片不需要原图质量,1080 长边、85 压缩率在手机上看起来完全够用。
还有一类"白屏"问题容易被误判成崩溃:分享链接卡片时,如果页面需要登录才能访问,渠道抓取时会拿到登录页,卡片显示空白或者错误标题。解决办法是给分享链接加一个免登录的预览路径,或者在页面里补 Open Graph 标签(title、description、image),让抓取器能拿到正确信息。
4.5 常见问题速查表
| 现象 | 最可能的原因 | 快速验证方式 |
|---|---|---|
| 渠道置灰不可点 | 未声明包可见性 / 目标应用未安装 | 检查 queries 声明,adb 查包名 |
| 点击后无任何反应 | AppKey 与包名不匹配 | 打开 SDK 调试日志看初始化结果 |
| 分享成功但无回调 | wxapi 路径或 exported 属性错误 | 检查合并后的清单文件 |
| 仅 Release 包失败 | 混淆规则缺失 / 签名不一致 | 对比 debug 与 release 的签名指纹 |
| 缩略图不显示 | 图片地址非公网 / 体积超限 | 浏览器直接访问该 URL,看文件大小 |
| 链接卡片标题为空 | 页面缺少抓取标签 / 需登录 | 用链接抓取工具预览 |
这张表我一般直接贴在项目 wiki 上,新人遇到问题先自查一遍,能过滤掉大部分常见情况。
5. 上线前的收尾:包体、降级与数据回收
5.1 按需引入渠道,别把整套都打进去
聚合 SDK 的一个副作用是包体会变大,因为它把多个渠道的客户端代码都塞进来了。如果你的产品只需要微信和 QQ,就在配置里把微博相关的项去掉,插件会相应地不注入对应模块。这一步通常能省下几百 KB 到 1MB 不等,对包体敏感的项目值得做。
除了渠道裁剪,还可以关注资源配置的合并。分享面板的图标、多语言字符串都是资源,如果项目本身做了资源压缩,记得检查分享相关资源有没有被误删。我遇到过一次面板图标全变成方块,最后发现是资源压缩规则里把share*前缀的资源全排除了。
5.2 失败降级:让用户永远有出口
分享失败最差的体验是"什么都没发生"。我习惯在失败回调里做两级降级:第一级,如果是目标应用未安装,弹一个提示引导用户,同时把该渠道在面板里隐藏一段时间;第二级,如果连续失败,自动切到系统分享作为兜底。
系统分享虽然拿不到回调,但至少用户能把内容发出去,体验上是"部分成功"而不是"完全失败"。这个兜底逻辑用 Android 原生的Intent.ACTION_SEND实现即可,不需要额外依赖:
Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, "正文内容 https://你的域名/detail?id=123"); context.startActivity(Intent.createChooser(intent, "分享到"));记得判空resolveActivity,否则在没有可分享应用的设备上会抛异常。
5.3 埋点:把"分享"这件事量化出来
分享功能的业务价值需要通过数据说话。我一般埋三个点:面板曝光(用户看到了哪些渠道)、渠道点击(用户选了哪个)、结果回调(成功/失败/取消,带错误码)。这三个点串起来,能算出每个渠道的点击率、成功率、失败原因分布。
有了这组数据,很多决策就有依据了。比如某个渠道成功率长期低于 80%,先去查是不是版本兼容问题;某个渠道点击率高但成功率低,可能是参数配置有问题。没有数据的团队,只能靠用户投诉来发现问题,滞后性好几天。
埋点实现上有个细节:回调可能在非主线程,写数据库或者上报要注意线程切换。另外分享面板的曝光时机建议放在show()之后延时一小段,避免把"面板初始化失败"也算成曝光。
5.4 版本升级的回归清单
SDK 升级不是换个版本号就完事。我整理了一份自己的回归清单,每次升级按这个走一遍:初始化是否正常(看日志)、面板是否能弹出、每个渠道是否都能走通、回调是否正常触发、Release 包混淆后是否正常、低版本 Android(尤其是 7.0 和 11)是否正常、未安装目标应用时提示是否正确。
这七项跑完大概二十分钟,但能避免上线后回滚。我吃过一次亏:升级后功能全对,但忘了验证 Android 11 的包可见性,结果新系统用户全部无法分享,紧急发版修了两天才稳住。
最后分享一个我自己的习惯。每次集成或升级完分享功能,我会在真机上用"三无环境"测一遍——没有缓存、没有登录、刚装的包。这个环境最接近新用户,很多因为本地状态残留而被掩盖的问题会立刻暴露出来。踩过几次坑之后,这个动作已经变成我的肌肉记忆了。