简介:面向需要在 Unity 项目中接入华为 HMS SDK 的开发者,这份 Demo 工程围绕华为账号登录、游戏服务、消息推送与权限适配等常见场景,提供了可参考的接入范例与脚本组织方式。压缩包共 2000 个文件、约 28.82MB:其中 1604 个 bin 和 136 个 info 对应 Android 平台及构建产物,127 个 class 和 83 个 java 体现华为 SDK 的 Java 层封装,25 个 jar 与 10 个 dll 为依赖库,13 个 cs 和 88 个 meta 是 Unity 侧调用脚本与资源标记,40 个 xml 则涉及清单与权限声明,另有 apk 与构建日志可供对照。目前已有 1845 人学习下载。工程内含 IL2CPP 打包设置、SDK 初始化与登录接口调用示例和 AndroidManifest 权限配置,可作为接入流程的实机对照样本,帮助读者理清 Unity 与原生 SDK 的交互边界,降低版本兼容与打包环节的试错成本。
1. 一个登录回调等了三小时:这个 Unity 渠道 SDK demo 能解决什么
一个登录按钮点下去,Unity 里的转圈转到黑屏,Logcat 打得漂漂亮亮,C# 侧却一个字都没收到——这是 Unity 接入渠道 SDK 时最典型的一幕。这份 unity 接入渠道 sdk demo 就是干这件事的最小可跑样本:一个 Unity 工程配一个 Android 原生 AAR,把初始化、账号登录、支付下单、生命周期回调四条链路打通,C# 侧只留一层薄封装,原生逻辑全部关在 Java 里。它适合两类人:手里接了安卓渠道包、必须在 Unity 里跑通登录支付的同学;以及想搞明白 AndroidJavaProxy、AAR 该放哪、Manifest 怎么合并这类老问题的开发者。代码量不大,但它把「Unity 与原生 SDK 之间的那条边界」划得非常清楚,照着换一套实现就能移植到别的渠道。
我见过太多团队卡在这一步:业务逻辑半天写完,结果三天全耗在 AAR 放进去了但类找不到、回调收到了但线程不对、Debug 包正常 Release 包闪退这类琐事上。这个 demo 的价值不在功能多少,而在于它把每个坑都走了一遍,并且给了能直接抄的写法。
2. 工程骨架拆解:Unity 侧、原生侧和 AAR 是怎么拼起来的
这个 demo 不是一个工程,而是两个半工程:一个纯 Android 的 wrapper 工程负责把渠道 SDK 包一层统一接口,一个 Unity 工程负责业务和表现层,中间靠一个 AAR 文件做物理隔离。理解这个边界,后面所有问题都好定位。
2.1 目录结构与三个工程边界
拿到包先看目录,结构决定了你改哪一层。我这边拆出来的典型布局是这样:
unity-channel-demo/ ├── native-wrapper/ # 原生侧,Android Studio 工程 │ ├── channel-wrapper/ # 产出 AAR 的 library module │ │ ├── src/main/java/com/demo/channel/ │ │ │ ├── ChannelBridge.java # 单例入口,Unity 唯一接触的类 │ │ │ ├── ILoginCallback.java # 登录回调接口,C# 侧要镜像实现 │ │ │ ├── IPayCallback.java # 支付回调接口 │ │ │ └── ChannelSdk.java # 渠道 SDK 的真实调用点 │ │ ├── src/main/AndroidManifest.xml │ │ └── build.gradle │ └── settings.gradle └── unity-client/ # Unity 工程 └── Assets/ ├── Plugins/Android/ │ ├── channel-wrapper-release.aar # 原生侧产物 │ ├── AndroidManifest.xml # 主 Manifest,会被合并 │ └── mainTemplate.gradle # 自定义 Gradle 模板 └── Scripts/ ├── ChannelSdk.cs # C# 门面(Facade) ├── LoginCallbackProxy.cs # AndroidJavaProxy 实现 └── GameEntry.cs # 业务调用方三个边界要记住:ChannelBridge.java以上是渠道 SDK 的地盘,改动要谨慎;ChannelBridge到ChannelSdk.cs之间是胶水层,出问题九成在这里;ChannelSdk.cs往上才是业务,业务代码里不应该出现任何AndroidJavaClass。
这种分层不是为了好看。真实项目里渠道方升级 SDK 是常态,如果业务层直接调AndroidJavaObject,每次升级都得全工程搜一遍改一遍,血泪经验。
2.2 C# 调用层:AndroidJavaClass 与 AndroidJavaProxy 的选型
Unity 调 Java 只有两条路:主动调用用AndroidJavaClass/AndroidJavaObject,被动接收回调必须用AndroidJavaProxy去实现 Java 接口。demo 里两者都用上了,用法很典型:
using UnityEngine; public class ChannelSdk : MonoBehaviour { private static ChannelSdk _instance; private AndroidJavaObject _bridge; // 对应 Java 侧 ChannelBridge 单例 void Awake() { if (_instance != null) { Destroy(gameObject); return; } _instance = this; DontDestroyOnLoad(gameObject); // 回调随时可能回来,必须跨场景保活 InitNative(); } private void InitNative() { // 1. 拿到 Java 单例,CallStatic 对应 Java 的 static 方法 using (var cls = new AndroidJavaClass("com.demo.channel.ChannelBridge")) { _bridge = cls.CallStatic<AndroidJavaObject>("getInstance"); } // 2. 取 Unity 的当前 Activity,几乎所有渠道 SDK 的 init 都要传它 using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) { // 参数:Activity、appId、channelId,后两个由渠道后台分配 _bridge.Call("init", activity, "app_10086", "channel_demo"); } } }几个参数必须说清楚。new AndroidJavaClass("com.demo.channel.ChannelBridge")里的字符串是 Java 类的全限定名,少一个包名段就是ClassNotFoundException,这是排查时第一个要看的东西。currentActivity拿到的是UnityPlayerActivity,如果你的工程用了自定义 Activity 继承它,这里拿到的仍然是子类实例,不影响。init的第三个参数渠道号建议从配置读,别硬编码,出多渠道包时会感谢自己。
using不是装饰。AndroidJavaClass/AndroidJavaObject在 Java 侧持有全局引用,不释放会一直堆积,长时间挂机后触发 GC 抖动。常见做法是短期对象一律using,长期持有的单例才放在字段里,在OnDestroy里Dispose()。
2.3 原生侧入口与 AndroidManifest 合并规则
原生侧的ChannelBridge只做三件事:持有 Activity、转发调用、把回调切回主线程。它刻意写得很薄,因为这一层是升级时最常被重写的地方。
package com.demo.channel; import android.app.Activity; import android.os.Handler; import android.os.Looper; public class ChannelBridge { private static volatile ChannelBridge sInstance; private Activity activity; private final Handler mainHandler = new Handler(Looper.getMainLooper()); public static ChannelBridge getInstance() { if (sInstance == null) { synchronized (ChannelBridge.class) { if (sInstance == null) sInstance = new ChannelBridge(); } } return sInstance; } public void init(Activity act, String appId, String channelId) { this.activity = act; // 渠道 SDK 初始化,回调里再通知 Unity 初始化完成 ChannelSdk.init(act, appId, channelId); } public void login(final ILoginCallback cb) { ChannelSdk.login(activity, new ChannelSdk.LoginResult() { @Override public void onSuccess(final String token, final String uid) { // 关键:渠道回调线程不可控,统一切回主线程再交给 Unity mainHandler.post(() -> cb.onSuccess(token, uid)); } @Override public void onFail(final int code, final String msg) { mainHandler.post(() -> cb.onFail(code, msg)); } }); } }mainHandler.post这一行是整个胶水层最重要的代码,原因在第 3 章展开。volatile加双重检查锁不是炫技,Unity 的主线程和 SDK 的工作线程都可能触发getInstance,少了它偶发拿到半初始化对象。
Manifest 合并是另一个高频翻车点。Unity 打包时会把Assets/Plugins/Android/AndroidManifest.xml、AAR 自带的 Manifest、以及 Unity 自动生成的unityLibraryManifest 三者合并,规则是「以主 Manifest 为准,冲突项报错」。demo 的主 Manifest 只需要声明权限和渠道要求的meta-data:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application> <!-- 渠道要求的 appId,值必须与后台一致 --> <meta-data android:name="com.demo.channel.APP_ID" android:value="app_10086" /> </application> </manifest>注意:不要在主 Manifest 里重复声明 AAR 里已有的
activity或provider,字段不完全一致就会触发Manifest merger failed,这类报错在合并日志里会明确写「Attribute application@xxx value=(a) from ... is also present at ... value=(b)」,照着日志改就行。
3. 从 Unity 到 Java 的一条完整调用链:初始化、登录、支付怎么串
看懂结构只是开始,真正上手改要顺着一条调用链走完。这一章按初始化、登录、支付三段拆,每段都给出能跑的代码和容易忽略的细节。
3.1 初始化与生命周期挂载
渠道 SDK 几乎都要求把宿主 Activity 的onResume/onPause/onDestroy转发给它,否则会出现「切后台回来支付丢单」「退出后统计不到」这类玄学问题。Unity 侧不需要改 Activity,用OnApplicationPause转发就够了:
void OnApplicationPause(bool paused) { if (_bridge == null) return; // 部分渠道把「回到前台」当作支付结果确认的时机,必须转发 _bridge.Call(paused ? "onPause" : "onResume"); } void OnDestroy() { if (_bridge != null) { _bridge.Call("onDestroy"); _bridge.Dispose(); // 释放 Java 侧全局引用 _bridge = null; } }对应 Java 侧:
public void onResume() { if (activity != null) ChannelSdk.onResume(activity); } public void onPause() { if (activity != null) ChannelSdk.onPause(activity); } public void onDestroy() { ChannelSdk.destroy(); activity = null; }参数说明:paused为 true 表示进入后台,false 表示回到前台。有些渠道在onResume里回调支付结果,顺序不能颠倒——ChannelSdk.onResume必须在base.onResume之前调用,这一点文档里通常只写一行小字。
初始化完成的回调别用SendMessage。SendMessage是字符串匹配,改个方法名就静默失效,编译器完全不报错,调试时就像面对黑匣子。demo 里用的是委托回调,第 6 章会把它进一步改成事件。
3.2 登录回调的代理实现与线程切换
登录是整条链路里最容易出问题的一环,因为它涉及双向通信:C# 发起、Java 回调。AndroidJavaProxy是唯一正解,但它的坑集中在「方法签名必须一字不差」。
using System; using UnityEngine; public class LoginCallbackProxy : AndroidJavaProxy { private readonly Action<string, string> _onSuccess; private readonly Action<int, string> _onFail; // 参数是 Java 接口的全限定名,必须与 AAR 里的包名+接口名完全一致 public LoginCallbackProxy(Action<string, string> onSuccess, Action<int, string> onFail) : base("com.demo.channel.ILoginCallback") { _onSuccess = onSuccess; _onFail = onFail; } // 方法名、参数类型、参数个数必须与 Java 接口一一对应 // 返回值是 void,C# 侧就得写 void,写成 string 会直接崩 void onSuccess(string token, string uid) { _onSuccess?.Invoke(token, uid); } void onFail(int code, string msg) { Debug.LogWarning($"[ChannelSdk] login failed code={code} msg={msg}"); _onFail?.Invoke(code, msg); } }对应 Java 接口只声明两个方法:
package com.demo.channel; public interface ILoginCallback { void onSuccess(String token, String uid); void onFail(int code, String msg); }调用处:
public void Login(Action<string, string> onSuccess, Action<int, string> onFail) { // 代理对象必须被字段或闭包持有,否则会被 GC 回收,回调直接消失 _loginProxy = new LoginCallbackProxy(onSuccess, onFail); _bridge.Call("login", _loginProxy); } private AndroidJavaProxy _loginProxy; // 关键:持有引用这里有两个必须记住的点。第一,base("包名.接口名")里的字符串错了不会在编译期报错,运行时报java.lang.NoClassDefFoundError或者代理方法永远不被调用。第二,代理对象如果只作为局部变量传进去,方法返回后可能被 GC 回收,表现是「第一次登录能成功,第二次没反应」,这个坑我踩过一次,查了整整一个下午。
线程问题在这层解决。第 2 章的mainHandler.post保证了 C# 回调发生在 Unity 主线程,所以_onSuccess里可以直接操作GameObject、改 UI。如果省掉那行,Android 7 以上大概率抛IllegalStateException,Android 5/6 上则表现为偶发卡死——这也是很多人觉得「真机玄学」的根源。
3.3 支付下单与结果校验
支付比登录多一步:客户端拿到的是「支付结果」,不是「发货凭证」。demo 里演示了正确姿势——客户端只负责把订单号和结果回传,真正的发货走服务端校验。
public void Pay(string orderId, int amountFen, string productId, Action<string> onSuccess, Action<int, string> onFail) { _payProxy = new PayCallbackProxy(onSuccess, onFail); // 金额单位是分,别传元,渠道侧一般按分处理,传错就是 100 倍差价 _bridge.Call("pay", orderId, amountFen, productId, _payProxy); }Java 侧:
public void pay(String orderId, int amountFen, String productId, final IPayCallback cb) { ChannelSdk.pay(activity, orderId, amountFen, productId, new ChannelSdk.PayResult() { @Override public void onSuccess(final String channelOrderId, final String extra) { mainHandler.post(() -> cb.onSuccess(channelOrderId, extra)); } @Override public void onFail(final int code, final String msg) { mainHandler.post(() -> cb.onFail(code, msg)); } }); }参数说明:orderId是自家服务端生成的订单号,必须全局唯一,重复下单渠道会拒;amountFen是整数分,用int在极端大额时有溢出风险,长期项目建议换long;productId是渠道后台配置的商品编号,写错会直接回调「商品不存在」。
回调成功后不要直接发道具。正确做法是把channelOrderId和orderId一起发给自家服务端,由服务端向渠道查单确认,再决定是否发货。demo 里在PayCallbackProxy.onSuccess中断言了这一点:
void onSuccess(string channelOrderId, string extra) { // 只做上报,不做发货。发货必须等服务端查单结果 Debug.Log($"[ChannelSdk] pay done, verify on server: {channelOrderId}"); _onSuccess?.Invoke(channelOrderId); }提示:测试阶段可以用渠道提供的沙箱账号走一遍「支付成功但服务端查单失败」的分支,这类异常路径不测,上线后就是客诉。
4. 打包链路:Gradle、AAR、签名和 IL2CPP 的实操步骤
代码写对了,包出不来一样白搭。这一章是从零到真机跑通的完整操作序列,按顺序执行基本不会卡。
4.1 AAR 产出与 Plugins/Android 目录放置
先在原生工程里产出 AAR:
cd native-wrapper # 只编 wrapper 模块的 release 变体,比整个工程 assembleRelease 快很多 ./gradlew :channel-wrapper:assembleRelease # 产物路径固定,直接拷进 Unity cp channel-wrapper/build/outputs/aar/channel-wrapper-release.aar \ ../unity-client/Assets/Plugins/Android/wrapper 模块的build.gradle有两个关键配置:
apply plugin: 'com.android.library' android { compileSdkVersion 34 defaultConfig { minSdkVersion 23 // 必须 >= 渠道 SDK 要求,低于它合并期直接失败 targetSdkVersion 34 consumerProguardFiles 'consumer-rules.pro' // 混淆规则随 AAR 一起走 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { // 渠道 SDK 通常以 aar/jar 形式提供,放在 libs 下 implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) }参数说明:minSdkVersion是合并时最硬的约束,Unity 默认可能是 22,渠道要 23,不改就报uses-sdk:minSdkVersion 22 cannot be smaller than version 23 declared in library。consumerProguardFiles指向的规则文件会被打进 AAR,Unity 出 Release 包时自动生效,比在 Unity 侧手写 keep 规则可靠。
拷进 Unity 后,在 Inspector 里确认这个 AAR 的Platform勾选是Android且CPU为ARM64/ARMv7全选。漏勾的表现是编辑器里一切正常,出包后ClassNotFoundException。
4.2 Player Settings 与 Gradle 模板
Unity 侧配置项比较分散,列成表更好对照:
| 配置项 | 路径 | 建议值 | 说明 |
|---|---|---|---|
| Scripting Backend | Player Settings > Other Settings | IL2CPP | Mono 无法出 ARM64,渠道基本不支持 |
| Target Architectures | 同上 | ARM64 + ARMv7 | 只出 ARM64 会丢掉老设备 |
| Minimum API Level | 同上 | 23 或更高 | 与 AAR 的 minSdkVersion 保持一致 |
| Target API Level | 同上 | 34 | 低于渠道要求会被应用市场拒 |
| Custom Main Gradle Template | Player Settings > Publishing Settings | 勾选 | 否则改不了依赖和打包参数 |
勾选Custom Main Gradle Template后,工程里会生成Assets/Plugins/Android/mainTemplate.gradle,在里面补依赖和冲突处理:
android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 } packagingOptions { // 多个 AAR 带同名 so 是合并期最常见报错,pickFirst 表示保留第一个 pickFirst 'lib/arm64-v8a/libc++_shared.so' pickFirst 'lib/armeabi-v7a/libc++_shared.so' } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation 'androidx.appcompat:appcompat:1.6.1' }pickFirst看着像掩盖问题,实际是标准做法:渠道 SDK 和 Unity 内置的libc++_shared.so版本不同,全保留会重复打包导致安装包体积翻倍甚至崩溃。真遇到Duplicate class报错,才需要加exclude group:排查是不是依赖冲突。
4.3 出包与真机验证
签名和出包:
# 命令行出包便于 CI 复用;-quit -batchmode 保证不弹窗 /Applications/Unity/Hub/Editor/2022.3.20f1/Unity.app/Contents/MacOS/Unity \ -quit -batchmode -projectPath ./unity-client \ -executeMethod BuildScript.BuildAndroid \ -logFile ./build.logBuildScript.BuildAndroid里设定EditorUserBuildSettings.buildAppBundle = false先出 APK 便于真机安装,上线再切 AAB。签名用PlayerSettings.Android.keystoreName指定,别把 keystore 和密码提交进仓库。
真机验证按这个顺序走,能快速定位问题在哪一层:
adb install -r app.apk装包,adb logcat -s Unity:V ChannelSdk:V AndroidRuntime:E起日志;- 冷启动看初始化日志,确认
ChannelSdk.init有回调; - 点登录,确认 Activity 被拉起(能看到渠道的登录页);
- 登录返回后看 Unity 日志里有没有
login success,没有就是代理层没接上; - 走一笔沙箱支付,重点看服务端能不能查到单。
注意:
adb logcat一定要加AndroidRuntime:E,很多原生崩溃不会进 Unity 日志,只在这条 tag 下能看到完整堆栈。
5. 踩坑排查清单:五个让人怀疑人生的报错
这一章的每一条都是我在类似工程里真实遇到过的,按「现象 → 原因 → 解决」写,遇到时可以直接对号入座。
5.1 回调收到了,Unity 报 can only be called from main thread
现象:登录成功后 C# 的回调方法体第一行就抛异常,提示can only be called from the main thread,或者操作 UI 时界面直接卡住不动。
原因:渠道 SDK 的网络回调跑在自己的线程池里,mainHandler.post那一行被省掉了,或者写的new Handler()没传Looper.getMainLooper(),在子线程构造 Handler 拿到的是当前线程的 Looper。
解决:所有跨语言回调统一走 Java 侧的mainHandler.post,不要在 C# 侧用SynchronizationContext或自己写 dispatcher 兜底——C# 侧的AndroidJavaProxy方法由 JNI 调用,此时线程上下文已经不可控了。检查方法是断点打在onSuccess里看Thread.currentThread().getName(),应该是main。
5.2 ClassNotFoundException: com.demo.channel.ChannelBridge
现象:编辑器里正常,真机上启动即闪退,日志里AndroidRuntime打出ClassNotFoundException,类名看着完全正确。
原因:三种可能,AAR 没被真正打进 APK(最常见)、AAR 的 Platform 勾选没开 Android、或者 Gradle 模板里的implementation被写成了compileOnly。
解决:先解压 APK 看classes.dex里有没有你的包名,unzip -l app.apk | grep dex之后用dexdump或者 Android Studio 的 APK Analyzer 查。没有就是打包配置问题;有的话检查类名大小写,Java 包名全小写是惯例但类名大小写敏感。另外Assets/Plugins/Android下如果有多个同名 AAR 版本,Unity 只会取一个,删掉旧的。
5.3 Manifest merger failed: minSdkVersion 冲突
现象:出包时构建直接失败,报uses-sdk:minSdkVersion 21 cannot be smaller than version 23 declared in library [channel-wrapper]。
原因:Unity 工程的Minimum API Level还停在默认的 21 或 22,而 AAR 里声明了 23。
解决:改mainTemplate.gradle的defaultConfig.minSdkVersion为 23,同时把 Player Settings 里的Minimum API Level也调到 23 以上。两处都要改,只改一处会在下一轮构建被覆盖回去。改完如果还报,重点是tools:overrideLibrary不要乱加,那是掩盖症状,运行时会崩。
5.4 Release 包闪退,Debug 包完全正常
现象:Development Build 打出来的包跑得好好的,换成 Release 一进游戏就闪退,堆栈里全是a.a.a这种混淆后的名字。
原因:渠道 SDK 内部用了反射、序列化或者 JNI 回调,被 R8/ProGuard 把类名和方法名混淆掉了。IL2CPP 打包默认开启代码裁剪和混淆。
解决:在 AAR 的consumer-rules.pro里加 keep 规则,最省事的是整包保留:
# 渠道 SDK 全部保留,反射和回调都依赖原始类名 -keep class com.demo.channel.** { *; } -keep interface com.demo.channel.** { *; } # 如果 SDK 内部有 native 方法,方法名不能被改 -keepclasseswithmembernames class * { native <methods>; }改完重出 AAR,再出 Release 包验证。别用-dontobfuscate一刀切关掉混淆,安装包体积和安全性都会受影响。
5.5 支付成功但服务端查不到单
现象:客户端明确收到了onSuccess,玩家说扣钱了,服务端查询订单状态一直是「未支付」。
原因:orderId在客户端被重新生成了一次。渠道侧的订单是绑定到orderId的,客户端如果拿时间戳或者 UUID 另起一个号,服务端按自己记录的号去查自然查不到。
解决:orderId只能由服务端下发,客户端全程透传,不做任何拼接或改写。同时把channelOrderId一起回传,服务端查单时优先用渠道订单号,orderId作为幂等键。排查时对比客户端日志里的orderId和服务端下单记录,一眼就能看出来是不是两个号。
6. 二次开发:把 demo 改成能长期维护的封装层
demo 能跑之后,下一步几乎所有人都会做同一件事:把它塞进正式项目。直接塞进去会很快乐,然后很快痛苦——业务代码里到处都是AndroidJavaObject,加个新渠道要改二十个文件。我在这种工程上栽过,后来固定了一套改法。
第一步,抽接口。在 C# 侧定义一个与平台无关的接口,Unity 工程里所有业务只认这个接口:
public interface IChannelSdk { void Init(string appId, string channelId, Action<bool> onDone); void Login(Action<LoginResult> onDone); void Pay(string orderId, long amountFen, string productId, Action<PayResult> onDone); void Logout(); void Report(string eventId, string json); }第二步,做两个实现。AndroidChannelSdk是真实实现,内部就是第 2、3 章那些代码;MockChannelSdk是编辑器实现,登录直接返回成功、支付延迟一秒返回成功。这一步的收益极大:UI 联调、策划验收、自动化跑流程都可以在编辑器里完成,不用每次打真机包等五分钟。
| 接口方法 | Android 实现 | Mock 实现 |
|---|---|---|
| Init | 调ChannelBridge.init,等待原生回调 | 立即回调 true |
| Login | AndroidJavaProxy接收ILoginCallback | 延迟 0.5s 返回固定 token |
| Pay | AndroidJavaProxy接收IPayCallback | 延迟 1s 返回成功,打印订单号 |
| Logout | 调原生清理登录态 | 清空内存字段 |
第三步,把回调统一成事件。demo 里用的是委托参数,改成事件后调用方更干净,也方便埋点统一打日志。第四步也是最后一步,加一层错误码映射:把渠道返回的上百个数字错误码翻译成五六种业务能处理的状态(用户取消、网络异常、参数错误、风控拦截、未知),业务层只判断这几种。这一步做完,换渠道时业务代码几乎不用动。
验证方法很简单:在编辑器里跑一遍完整流程,切到真机跑一遍,再断网跑一遍,最后用沙箱账号走一遍支付失败分支。四遍都过了才算接完。
从那以后我每接一个渠道 SDK,第一件事就是把它的回调线程和错误码表确认清楚,再动业务代码;demo 只是起点,封装层才是能陪你走完整个项目周期的部分。希望帮到你。
本文还有配套的精品资源,点击获取