游戏SDK的"三端统一"之路:从三倍痛苦到一套代码

一套SDK,三个平台,无限可能。本文聊聊游戏行业如何优雅地解决"三端分裂"的痛点。


写在前面

如果你是游戏SDK开发者,一定经历过这样的噩梦:

需求来了,iOS改完改Android,Android改完改H5, 三套代码三份苦,一个bug修三次…… 😭

这不是个例,而是行业通病。

今天我们就来聊聊:如何让三端SDK"统一思想",实现真正的跨平台复用。


一、三端分裂的"三倍痛苦"

1.1 现状:三套代码,三个世界

游戏SDK需要覆盖三个主流平台:

看起来只是"换个语言",实际上:

[配图建议:三个平台各自为战的示意图,中间隔着"语言壁垒"]

1.2 更可怕的是:行为不一致

代码重复还不是最痛苦的,行为不一致才是真正的噩梦:

"哦,这是Android那边的bug,我们iOS是好的。"

这种话说多了,运营和开发的友谊小船就翻了。🚣‍♀️

1.3 根本问题:没有统一抽象

三端分裂的本质是:

登录流程的核心逻辑是一样的:

获取授权 → 验证身份 → 返回用户信息

但因为三端API不同,这套逻辑被"复制粘贴"了三次, 每次都掺杂了平台特定的代码,最后谁也不认识谁。


二、统一的核心思路:三层分离

2.1 把"做什么"和"怎么做"分开

这里有一个关键的思维转换:

举个生活中的例子:

你用手机点外卖(业务逻辑),但支付时可以用微信、支付宝、银行卡(平台实现)。 点外卖的流程是统一的,但支付的细节可以不同。

SDK也是同理:

这就是三层架构的核心思想。🎯

[配图建议:三层架构的汉堡包示意图——上层是业务接口,中间是桥接翻译,底层是平台实现]

2.2 为什么是三层而不是两层?

你可能会问:为什么不能只有接口层和实现层?

因为中间需要一个"翻译官"

接口层说的是"业务语言":

"我要登录,用户名是xxx"

平台层说的是"技术语言":

iOS: "调用系统账号框架,传入凭证" Android: "启动Google登录Intent" H5: "跳转OAuth页面"

这就是关注点分离的威力。


三、架构设计详解:三层如何各司其职

3.1 第一层:接口层(统一门面)

这一层的代码是跨平台共享的,用一种语言写,三端都能用。

3.1.1 技术选型:用什么语言写接口层?

- iOS:通过JavaScriptCore引擎

- Android:通过V8或QuickJS引擎

3.1.2 接口设计原则

// ❌ 不好的设计
doAuth(type, callback)
processPayment(data)

// ✅ 好的设计
login(type: LoginType, callback: LoginCallback)
pay(productId: string, options: PayOptions)
// 登录参数(三端完全一致)
interface LoginOptions {
  type: 'wechat' | 'qq' | 'phone' | 'guest';
  timeout?: number;  // 超时时间(毫秒)
  extra?: Record<string, any>;  // 扩展参数
}

// 支付参数(三端完全一致)
interface PayOptions {
  productId: string;
  amount: number;
  currency: 'CNY' | 'USD';
  callbackUrl?: string;
}
// 统一的回调结构
interface Result<T> {
  code: number;      // 0=成功,非0=失败
  message: string;   // 错误信息
  data?: T;          // 成功数据
}

// 登录回调
interface LoginResult extends Result<{
  userId: string;
  token: string;
  nickname?: string;
}> {}

// 支付回调
interface PayResult extends Result<{
  orderId: string;
  transactionId?: string;
}> {}

错误码是跨平台开发中最容易出问题的地方。三端的底层错误码不同,但必须统一暴露给游戏层。

// 统一错误码定义
enum ErrorCode {
  // 成功
  SUCCESS = 0,
  
  // 通用错误(1-999)
  UNKNOWN_ERROR = -1,
  NETWORK_ERROR = -2,
  TIMEOUT = -3,
  CANCELLED = -4,
  
  // 登录相关(1000-1999)
  LOGIN_FAILED = -1000,
  LOGIN_CANCELLED = -1001,
  LOGIN_NETWORK_ERROR = -1002,
  LOGIN_INVALID_TOKEN = -1003,
  
  // 支付相关(2000-2999)
  PAY_FAILED = -2000,
  PAY_CANCELLED = -2001,
  PAY_NETWORK_ERROR = -2002,
  PAY_INVALID_PRODUCT = -2003,
  PAY_INSUFFICIENT_BALANCE = -2004,
  
  // SDK内部错误(9000-9999)
  SDK_NOT_INITIALIZED = -9000,
  SDK_VERSION_TOO_OLD = -9001,
  SDK_PLATFORM_NOT_SUPPORTED = -9002
}

// 错误码转换表(桥接层使用)
const ErrorCodeMapping = {
  // 微信错误码 → 统一错误码
  wechat: {
    '-4': ErrorCode.LOGIN_CANCELLED,      // 用户拒绝授权
    '-2': ErrorCode.LOGIN_CANCELLED,      // 用户取消
    '-1': ErrorCode.NETWORK_ERROR,        // 网络错误
  },
  
  // QQ错误码 → 统一错误码
  qq: {
    '100030': ErrorCode.LOGIN_CANCELLED,  // 用户拒绝授权
    '100031': ErrorCode.LOGIN_CANCELLED,  // 用户取消
  },
  
  // iOS Store错误码 → 统一错误码
  iosStore: {
    'SKErrorPaymentCancelled': ErrorCode.PAY_CANCELLED,
    'SKErrorPaymentInvalid': ErrorCode.PAY_INVALID_PRODUCT,
    'SKErrorNetworkError': ErrorCode.PAY_NETWORK_ERROR,
  }
};

3.2 第二层:桥接层(翻译中枢)

这是整个架构的"大脑",也是最考验设计的部分。

3.2.1 桥接层的核心职责

桥接层要做的事:

3.2.2 接口映射表的设计

桥接层的核心是一个映射表,定义了统一接口如何翻译到平台实现:

// 接口映射表
const InterfaceMapping = {
  login: {
    wechat: {
      ios: 'WechatSDK.sendAuthRequest',
      android: 'com.tencent.mm.opensdk.openapi.IWXAPI.sendReq',
      h5: 'window.location.href = "https://open.weixin.qq.com/connect/qrconnect"'
    },
    qq: {
      ios: 'TencentOAuth.authorize',
      android: 'com.tencent.tauth.Tencent.login',
      h5: 'QC.Login.showPopup'
    }
  },
  
  pay: {
    wechat: {
      ios: 'WXApi.sendReq',
      android: 'IWXAPI.sendReq',
      h5: 'WeixinJSBridge.invoke'
    },
    alipay: {
      ios: 'AlipaySDK.defaultService()',
      android: 'com.alipay.sdk.app.PayTask',
      h5: 'window.location.href = alipayUrl'
    }
  }
};

这个映射表是配置化的,可以动态更新,无需重新编译。

3.2.3 数据转换层

平台之间的数据格式差异很大,桥接层需要统一转换:

// 统一的用户信息格式
interface UserInfo {
  userId: string;
  nickname: string;
  avatar: string;
  gender: 'male' | 'female' | 'unknown';
  extra?: Record<string, any>;
}

// iOS微信返回的数据
interface IOSWechatUserInfo {
  openid: string;
  nickname: string;
  headimgurl: string;
  sex: number;  // 1=男, 2=女, 0=未知
}

// Android微信返回的数据
interface AndroidWechatUserInfo {
  openId: string;
  nickName: string;
  headImgUrl: string;
  sex: number;
}

// 桥接层的转换逻辑
function convertUserInfo(
  platform: 'ios' | 'android' | 'h5',
  rawData: any
): UserInfo {
  switch (platform) {
    case 'ios':
      return {
        userId: rawData.openid,
        nickname: rawData.nickname,
        avatar: rawData.headimgurl,
        gender: rawData.sex === 1 ? 'male' : rawData.sex === 2 ? 'female' : 'unknown'
      };
    
    case 'android':
      return {
        userId: rawData.openId,
        nickname: rawData.nickName,
        avatar: rawData.headImgUrl,
        gender: rawData.sex === 1 ? 'male' : rawData.sex === 2 ? 'female' : 'unknown'
      };
    
    case 'h5':
      // H5的数据可能又是另一种格式
      return {
        userId: rawData.unionid || rawData.openid,
        nickname: rawData.name,
        avatar: rawData.picture,
        gender: rawData.gender === 'M' ? 'male' : rawData.gender === 'F' ? 'female' : 'unknown'
      };
  }
}

3.2.4 异步处理机制

三端的异步机制完全不同:

平台 异步机制 示例
iOS Delegate/Block回调 [WXApi sendReq:req completion:^(...) {}]
Android Callback/Listener IWXAPI.sendReq(req, callback)
H5 Promise/Callback WeixinJSBridge.invoke(...).then(...)

桥接层需要统一这些差异:

// 统一的异步接口(使用Promise)
async function login(type: string): Promise<LoginResult> {
  return new Promise((resolve, reject) => {
    // 注册平台回调
    const callbackId = registerCallback((result) => {
      if (result.success) {
        resolve(result);
      } else {
        reject(new Error(result.message));
      }
    });
    
    // 调用平台实现
    callPlatformMethod('login', { type, callbackId });
  });
}

[配图建议:桥接层作为翻译官的卡通形象,左边是统一接口,右边是三个平台的API]

3.3 第三层:平台实现层(本地干活)

这一层的代码是平台特定的,每端各写各的。

但因为有桥接层"罩着",实现层只需要:

3.3.1 实现层的接口契约

桥接层和实现层之间有一个严格的契约

// 契约定义:实现层必须遵守的接口
interface PlatformImpl {
  // 登录
  login(params: LoginParams, callback: (result: LoginResult) => void): void;
  
  // 支付
  pay(params: PayParams, callback: (result: PayResult) => void): void;
  
  // 埋点
  track(event: string, params: Record<string, any>): void;
  
  // 推送
  registerPush(callback: (token: string) => void): void;
}

每个平台只需要实现这个接口:

// iOS实现(Swift)
class IOSPlatformImpl: PlatformImpl {
  func login(_ params: LoginParams, _ callback: @escaping (LoginResult) -> Void) {
    // 调用iOS原生SDK
    WechatSDK.sendAuthRequest { result in
      // 转换成统一格式
      let loginResult = LoginResult(
        code: result.errCode == 0 ? 0 : -1,
        message: result.errStr,
        data: result.userID
      )
      callback(loginResult)
    }
  }
  
  // ... 其他方法实现
}
// Android实现(Kotlin)
class AndroidPlatformImpl : PlatformImpl {
  override fun login(params: LoginParams, callback: (LoginResult) -> Unit) {
    // 调用Android原生SDK
    IWXAPI.sendReq(SendAuth.Req().apply {
      scope = "snsapi_userinfo"
      state = params.state
    })
    
    // 等待回调(通过BroadcastReceiver或EventBus)
    pendingCallbacks[params.callbackId] = callback
  }
  
  // ... 其他方法实现
}
// H5实现(TypeScript)
class H5PlatformImpl implements PlatformImpl {
  login(params: LoginParams, callback: (result: LoginResult) => void): void {
    // 调用H5的OAuth
    const url = `https://open.weixin.qq.com/connect/qrconnect?appid=xxx&redirect_uri=xxx`;
    window.location.href = url;
    
    // 等待重定向回调
    window.addEventListener('message', (event) => {
      if (event.data.type === 'login_success') {
        callback({
          code: 0,
          message: 'success',
          data: event.data.userInfo
        });
      }
    });
  }
  
  // ... 其他方法实现
}

3.3.2 实现层的自由度

虽然实现层要遵守契约,但在实现细节上有很大自由度:


四、统一的功能模块:标准化的力量

4.1 哪些功能适合统一?

游戏SDK通常包含以下模块,都可以用三层架构统一:

模块 统一接口示例 平台差异点 统一价值
🔐 登录 login(type) 账号体系不同 ⭐⭐⭐⭐⭐ 高
💳 支付 pay(productId) 支付渠道不同 ⭐⭐⭐⭐⭐ 高
📊 埋点 track(event) 采集方式不同 ⭐⭐⭐⭐⭐ 高
🔔 推送 registerPush() 推送服务不同 ⭐⭐⭐⭐ 中
📤 分享 share(platform) SDK集成不同 ⭐⭐⭐⭐ 中
📂 存储 save(data) 存储机制不同 ⭐⭐⭐ 低
🎵 音频 play(sound) 音频引擎不同 ⭐⭐ 低

4.2 以登录模块为例:完整的三层设计

4.2.1 接口层设计

// 登录类型枚举
enum LoginType {
  WECHAT = 'wechat',
  QQ = 'qq',
  PHONE = 'phone',
  GUEST = 'guest',
  APPLE = 'apple'  // iOS专用,其他平台降级处理
}

// 登录参数
interface LoginParams {
  type: LoginType;
  timeout?: number;  // 默认30秒
  extra?: {
    phoneNumber?: string;  // 手机号登录时需要
    countryCode?: string;  // 国家代码,默认+86
  };
}

// 登录结果
interface LoginResult {
  code: number;
  message: string;
  data?: {
    userId: string;
    token: string;
    refreshToken: string;
    expiresIn: number;  // token过期时间(秒)
    userInfo: {
      nickname: string;
      avatar: string;
      gender: 'male' | 'female' | 'unknown';
    };
  };
}

// 登录接口
interface LoginAPI {
  login(params: LoginParams): Promise<LoginResult>;
  logout(): Promise<void>;
  refreshToken(token: string): Promise<LoginResult>;
  getUserInfo(): Promise<UserInfo>;
}

4.2.2 桥接层实现

class LoginBridge implements LoginAPI {
  private platformImpl: PlatformImpl;
  
  constructor(platform: 'ios' | 'android' | 'h5') {
    // 根据平台创建对应的实现
    this.platformImpl = createPlatformImpl(platform);
  }
  
  async login(params: LoginParams): Promise<LoginResult> {
    // 1. 参数验证
    if (!params.type) {
      return { code: -1, message: 'login type is required' };
    }
    
    // 2. 平台兼容性检查
    if (params.type === LoginType.APPLE && !isIOS()) {
      // Apple登录只在iOS上可用,其他平台降级到游客登录
      console.warn('Apple Sign-In is only available on iOS, fallback to guest');
      params.type = LoginType.GUEST;
    }
    
    // 3. 调用平台实现
    try {
      const result = await this.platformImpl.login(params);
      
      // 4. 数据转换(统一格式)
      if (result.code === 0 && result.data) {
        result.data = this.convertLoginData(result.data);
      }
      
      // 5. 埋点上报(登录成功)
      this.track('login_success', {
        type: params.type,
        platform: getPlatform()
      });
      
      return result;
      
    } catch (error) {
      // 6. 错误处理
      const errorCode = this.translateError(error);
      
      // 埋点上报(登录失败)
      this.track('login_failed', {
        type: params.type,
        error: errorCode,
        platform: getPlatform()
      });
      
      return {
        code: errorCode,
        message: error.message
      };
    }
  }
  
  // 数据转换
  private convertLoginData(rawData: any): any {
    return {
      userId: rawData.openid || rawData.unionid || rawData.userId,
      token: rawData.access_token || rawData.token,
      refreshToken: rawData.refresh_token || rawData.refreshToken,
      expiresIn: rawData.expires_in || rawData.expiresIn,
      userInfo: {
        nickname: rawData.nickname || rawData.nickName || rawData.name,
        avatar: rawData.headimgurl || rawData.avatar || rawData.picture,
        gender: this.convertGender(rawData.sex || rawData.gender)
      }
    };
  }
  
  // 性别转换
  private convertGender(rawGender: any): string {
    if (typeof rawGender === 'number') {
      return rawGender === 1 ? 'male' : rawGender === 2 ? 'female' : 'unknown';
    }
    if (typeof rawGender === 'string') {
      return rawGender === 'M' ? 'male' : rawGender === 'F' ? 'female' : 'unknown';
    }
    return 'unknown';
  }
  
  // 错误码转换
  private translateError(error: any): number {
    const errorMap = {
      // 微信错误码
      '-4': -1001,  // 用户拒绝授权
      '-2': -1002,  // 用户取消
      '-1': -1003,  // 网络错误
      
      // QQ错误码
      '100030': -1001,  // 用户拒绝授权
      '100031': -1002,  // 用户取消
      
      // 通用错误
      'NETWORK_ERROR': -1003,
      'TIMEOUT': -1004,
      'UNKNOWN': -9999
    };
    
    return errorMap[error.code] || errorMap[error.message] || -9999;
  }
}

4.2.3 实现层示例(iOS)

// iOS登录实现
class IOSLoginImpl: PlatformImpl {
  private var wechatSDK: WechatSDK?
  private var qqSDK: TencentOAuth?
  
  func login(_ params: LoginParams, _ callback: @escaping (LoginResult) -> Void) {
    switch params.type {
    case .wechat:
      loginWithWechat(params, callback)
    case .qq:
      loginWithQQ(params, callback)
    case .phone:
      loginWithPhone(params, callback)
    case .guest:
      loginAsGuest(params, callback)
    case .apple:
      loginWithApple(params, callback)
    }
  }
  
  private func loginWithWechat(_ params: LoginParams, _ callback: @escaping (LoginResult) -> Void) {
    let req = SendAuthReq()
    req.scope = "snsapi_userinfo"
    req.state = UUID().uuidString
    
    WXApi.send(req) { success in
      if !success {
        callback(LoginResult(
          code: -1003,
          message: "WeChat SDK not installed or not configured"
        ))
      }
      // 等待回调(在AppDelegate中处理)
      WechatCallbackManager.shared.pendingCallback = callback
    }
  }
  
  private func loginWithApple(_ params: LoginParams, _ callback: @escaping (LoginResult) -> Void) {
    let provider = ASAuthorizationAppleIDProvider()
    let request = provider.createRequest()
    request.requestedScopes = [.fullName, .email]
    
    let controller = ASAuthorizationController(authorizationRequests: [request])
    controller.delegate = AppleAuthDelegate(callback)
    controller.performRequests()
  }
}

// Apple登录的代理
class AppleAuthDelegate: NSObject, ASAuthorizationControllerDelegate {
  private let callback: (LoginResult) -> Void
  
  init(_ callback: @escaping (LoginResult) -> Void) {
    self.callback = callback
  }
  
  func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
    if let credential = authorization.credential as? ASAuthorizationAppleIDCredential {
      let result = LoginResult(
        code: 0,
        message: "success",
        data: LoginData(
          userId: credential.user,
          token: credential.identityToken?.base64EncodedString() ?? "",
          // ... 其他字段
        )
      )
      callback(result)
    }
  }
  
  func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
    let errorCode: Int
    if let authError = error as? ASAuthorizationError {
      switch authError.code {
      case .canceled:
        errorCode = -1002  // 用户取消
      case .failed:
        errorCode = -1003  // 授权失败
      default:
        errorCode = -9999
      }
    } else {
      errorCode = -9999
    }
    
    callback(LoginResult(code: errorCode, message: error.localizedDescription))
  }
}

4.3 以埋点模块为例:数据一致性的保障

埋点的统一更有价值,因为数据一致性至关重要。

4.3.1 接口层设计

// 埋点事件定义
interface TrackEvent {
  name: string;  // 事件名
  properties?: Record<string, any>;  // 事件属性
  timestamp?: number;  // 事件时间(默认当前时间)
}

// 埋点API
interface TrackAPI {
  // 手动埋点
  track(event: TrackEvent): void;
  
  // 批量埋点
  trackBatch(events: TrackEvent[]): void;
  
  // 设置公共属性(所有事件都会带上)
  setCommonProperties(props: Record<string, any>): void;
  
  // 设置用户ID(用于用户关联)
  setUserId(userId: string): void;
  
  // 页面浏览自动埋点
  trackPageView(pageName: string): void;
}

4.3.2 桥接层:保证数据一致性

class TrackBridge implements TrackAPI {
  private platformImpl: PlatformImpl;
  private commonProperties: Record<string, any> = {};
  private userId: string = '';
  
  // 事件名标准化映射
  private static EVENT_NAME_MAPPING = {
    // 统一事件名 → 各平台可能的事件名
    'click_button': ['click_button', 'button_click', 'btn_click'],
    'page_view': ['page_view', 'view_page', 'pv'],
    'login_success': ['login_success', 'login', 'user_login'],
  };
  
  track(event: TrackEvent): void {
    // 1. 标准化事件名
    const normalizedName = this.normalizeEventName(event.name);
    
    // 2. 合并公共属性
    const properties = {
      ...this.commonProperties,
      ...event.properties,
      _platform: getPlatform(),
      _sdkVersion: SDK_VERSION,
      _timestamp: event.timestamp || Date.now(),
      _userId: this.userId
    };
    
    // 3. 参数校验
    if (!this.validateEvent(normalizedName, properties)) {
      console.error(`Invalid event: ${normalizedName}`, properties);
      return;
    }
    
    // 4. 调用平台实现
    this.platformImpl.track({
      name: normalizedName,
      properties: properties
    });
  }
  
  // 标准化事件名
  private normalizeEventName(name: string): string {
    // 检查是否需要标准化
    for (const [standard, variants] of Object.entries(TrackBridge.EVENT_NAME_MAPPING)) {
      if (variants.includes(name.toLowerCase())) {
        return standard;
      }
    }
    return name.toLowerCase();
  }
  
  // 参数校验
  private validateEvent(name: string, properties: Record<string, any>): boolean {
    // 事件名不能为空
    if (!name) return false;
    
    // 事件名只能是字母、数字、下划线
    if (!/^[a-z0-9_]+$/.test(name)) {
      console.warn(`Event name should only contain lowercase letters, numbers, and underscores: ${name}`);
      // 不阻止,只是警告
    }
    
    // 属性值不能包含敏感信息
    const sensitiveKeys = ['password', 'token', 'secret'];
    for (const key of Object.keys(properties)) {
      if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
        console.warn(`Sensitive data detected in event: ${key}`);
        delete properties[key];
      }
    }
    
    return true;
  }
  
  setCommonProperties(props: Record<string, any>): void {
    this.commonProperties = {
      ...this.commonProperties,
      ...props
    };
  }
}

"为什么iOS有 click_button 事件,Android却是 button_click?"

这种"低级错误"在三层架构下天然避免。📊

[配图建议:埋点数据流的示意图,三端数据汇聚到统一的数据仓库]


五、平台差异处理:优雅的兼容之道

5.1 平台差异的类型

平台差异可以分为三类:

差异类型 示例 处理策略
功能差异 iOS有Apple登录,Android没有 降级策略
性能差异 iOS渲染快,H5渲染慢 性能适配
行为差异 iOS权限弹窗,Android权限申请 封装统一

5.2 功能差异的降级策略

当某个功能在某平台不可用时,需要优雅降级:

// 功能可用性检查
const FeatureAvailability = {
  // Apple登录只在iOS 13+可用
  appleSignIn: (): boolean => {
    return isIOS() && getIOSVersion() >= 13;
  },
  
  // 微信登录需要安装微信客户端
  wechatLogin: (): boolean => {
    if (isIOS()) {
      return UIApplication.shared.canOpenURL(URL(string: "weixin://")!);
    } else if (isAndroid()) {
      return isPackageInstalled('com.tencent.mm');
    } else {
      return true;  // H5总是可用
    }
  },
  
  // Google Play服务
  googlePlay: (): boolean => {
    return isAndroid() && isGooglePlayAvailable();
  }
};

// 登录时的降级处理
async function login(type: LoginType): Promise<LoginResult> {
  // 检查功能可用性
  switch (type) {
    case LoginType.APPLE:
      if (!FeatureAvailability.appleSignIn()) {
        // 降级到游客登录
        console.warn('Apple Sign-In not available, fallback to guest');
        return login(LoginType.GUEST);
      }
      break;
      
    case LoginType.WECHAT:
      if (!FeatureAvailability.wechatLogin()) {
        // 提示用户安装微信
        return {
          code: -2001,
          message: 'Please install WeChat to use this login method'
        };
      }
      break;
  }
  
  // 正常登录
  return bridge.login({ type });
}

5.3 性能差异的适配策略

三端的性能差异很大,需要针对性优化:

// 根据平台性能调整加载策略
const LoadingStrategy = {
  ios: {
    // iOS性能好,一次性加载
    loadMode: 'all-at-once',
    timeout: 5000
  },
  android: {
    // Android性能中等,分批加载
    loadMode: 'batch',
    batchSize: 10,
    timeout: 8000
  },
  h5: {
    // H5性能最弱,懒加载
    loadMode: 'lazy',
    threshold: 500,  // 延迟500ms
    timeout: 10000
  }
};

function loadSDKModules() {
  const strategy = LoadingStrategy[getPlatform()];
  
  switch (strategy.loadMode) {
    case 'all-at-once':
      // 一次性加载所有模块
      return loadAllModules();
    
    case 'batch':
      // 分批加载
      return loadModulesInBatch(strategy.batchSize);
    
    case 'lazy':
      // 懒加载,需要时再加载
      return setupLazyLoading(strategy.threshold);
  }
}
// 不同平台的缓存策略
const CacheStrategy = {
  ios: {
    // iOS内存充足,多用内存缓存
    memoryCache: true,
    memoryTTL: 3600,  // 1小时
    diskCache: true,
    diskTTL: 86400    // 1天
  },
  android: {
    // Android内存管理严格,平衡使用
    memoryCache: true,
    memoryTTL: 1800,  // 30分钟
    diskCache: true,
    diskTTL: 86400
  },
  h5: {
    // H5依赖浏览器缓存
    memoryCache: false,
    localStorage: true,
    localStorageTTL: 86400
  }
};

5.4 行为差异的统一封装

平台间的行为差异需要封装,让上层无感知:

// 统一的权限接口
interface PermissionAPI {
  request(permission: Permission): Promise<boolean>;
  check(permission: Permission): Promise<boolean>;
}

enum Permission {
  CAMERA = 'camera',
  MICROPHONE = 'microphone',
  LOCATION = 'location',
  NOTIFICATION = 'notification'
}

// iOS实现
class IOSPermissionImpl implements PermissionAPI {
  async request(permission: Permission): Promise<boolean> {
    switch (permission) {
      case Permission.CAMERA:
        return new Promise((resolve) => {
          AVCaptureDevice.requestAccess(for: .video) { granted in
            resolve(granted)
          }
        });
      
      case Permission.NOTIFICATION:
        return new Promise((resolve) => {
          UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .sound, .badge]
          ) { granted, _ in
            resolve(granted)
          }
        });
    }
  }
}

// Android实现
class AndroidPermissionImpl implements PermissionAPI {
  async request(permission: Permission): Promise<boolean> {
    const androidPermission = this.mapPermission(permission);
    
    if (ContextCompat.checkSelfPermission(context, androidPermission) == PackageManager.PERMISSION_GRANTED) {
      return true;
    }
    
    return new Promise((resolve) => {
      ActivityCompat.requestPermissions(
        activity,
        [androidPermission],
        REQUEST_CODE
      );
      
      // 等待回调
      permissionCallback = resolve;
    });
  }
  
  private mapPermission(permission: Permission): string {
    const mapping = {
      [Permission.CAMERA]: 'android.permission.CAMERA',
      [Permission.MICROPHONE]: 'android.permission.RECORD_AUDIO',
      [Permission.LOCATION]: 'android.permission.ACCESS_FINE_LOCATION',
      [Permission.NOTIFICATION]: 'android.permission.POST_NOTIFICATIONS'
    };
    return mapping[permission];
  }
}

// H5实现
class H5PermissionImpl implements PermissionAPI {
  async request(permission: Permission): Promise<boolean> {
    switch (permission) {
      case Permission.CAMERA:
      case Permission.MICROPHONE:
        try {
          const stream = await navigator.mediaDevices.getUserMedia({
            video: permission === Permission.CAMERA,
            audio: permission === Permission.MICROPHONE
          });
          stream.getTracks().forEach(track => track.stop());
          return true;
        } catch {
          return false;
        }
      
      case Permission.NOTIFICATION:
        const result = await Notification.requestPermission();
        return result === 'granted';
    }
  }
}
const granted = await permission.request(Permission.CAMERA);
if (granted) {
  // 打开摄像头
}

六、游戏行业的实践经验

6.1 SDK版本管理的挑战

游戏SDK有个特殊问题:版本碎片化

6.1.1 三段式版本号设计

版本号格式:X.Y.Z

X - 接口层大版本(不兼容升级)
  - 改了接口定义,游戏需要重新接入
  - 例如:1.x → 2.x

Y - 桥接层功能更新(兼容升级)
  - 增加了新功能,但旧接口不变
  - 例如:1.1.x → 1.2.x

Z - 实现层Bug修复(补丁升级)
  - 修复了平台特定Bug
  - 例如:1.1.0 → 1.1.1

游戏接入时只需要关注 X,减少心智负担。

SDK 1.0.0 → 1.0.1(修复Android支付Bug)→ 游戏无需改动
SDK 1.0.1 → 1.1.0(新增分享功能)→ 游戏可选择升级
SDK 1.1.0 → 2.0.0(重构登录接口)→ 游戏需要适配新接口

6.1.2 兼容性保证

桥接层的一个重要职责是保证向后兼容

// 兼容性适配器
class CompatibilityAdapter {
  // 新版接口
  loginV2(params: LoginParamsV2): Promise<LoginResult> {
    // 内部逻辑...
  }
  
  // 旧版接口(适配到新版)
  loginV1(type: string, callback: Function): void {
    // 转换旧参数到新格式
    const params: LoginParamsV2 = {
      type: type as LoginType,
      timeout: 30000
    };
    
    // 调用新版接口
    this.loginV2(params)
      .then(result => callback(result))
      .catch(error => callback({ code: -1, message: error.message }));
  }
}

这样即使接口层升级了,桥接层也能保证旧游戏的兼容性。

6.2 热更新的实践

游戏行业对热更新有强烈需求,因为:

层次 热更新支持 策略
接口层 ❌ 不能热更 改了就要重新接入
桥接层 ✅ 可以热更 配置下发、逻辑调整
实现层 ⚠️ 平台限制 iOS禁止热更代码,Android可以,H5天然支持

6.2.1 配置化设计

把可能变化的逻辑提取成配置:

// 配置文件(可以从服务器下发)
const SDKConfig = {
  // 登录配置
  login: {
    timeout: 30000,  // 超时时间
    retryCount: 3,   // 重试次数
    enableLog: true  // 是否开启日志
  },
  
  // 支付配置
  pay: {
    channels: ['wechat', 'alipay', 'apple'],  // 支付渠道
    defaultChannel: 'wechat',  // 默认渠道
    callbackUrl: 'https://api.example.com/pay/callback'
  },
  
  // 埋点配置
  track: {
    batchSize: 10,  // 批量上报大小
    flushInterval: 5000,  // 上报间隔(毫秒)
    enable: true  // 是否开启埋点
  }
};

// 桥接层使用配置
class ConfigurableBridge {
  async login(params: LoginParams): Promise<LoginResult> {
    // 使用配置的超时时间
    const timeout = SDKConfig.login.timeout;
    
    // 使用配置的重试次数
    for (let i = 0; i < SDKConfig.login.retryCount; i++) {
      try {
        return await this.doLogin(params, timeout);
      } catch (error) {
        if (i === SDKConfig.login.retryCount - 1) {
          throw error;
        }
        await sleep(1000 * (i + 1));  // 递增重试间隔
      }
    }
  }
}
  1. 服务器下发新配置
  2. 桥接层读取新配置
  3. 行为立即生效,无需重新编译

6.2.2 脚本化逻辑

更高级的热更新是脚本化逻辑

// Lua脚本(可以从服务器下发)
const loginScript = `
  function login(params)
    if params.type == "wechat" then
      return callWechatLogin(params)
    elseif params.type == "qq" then
      return callQQLogin(params)
    else
      return error("unsupported login type")
    end
  end
`;

// 桥接层执行脚本
class ScriptableBridge {
  private luaVM: LuaVM;
  
  async login(params: LoginParams): Promise<LoginResult> {
    // 执行脚本
    return this.luaVM.execute(loginScript, 'login', params);
  }
}

这样连逻辑都可以热更了(iOS的JavaScriptCore也支持类似方案)。

6.3 兼容性处理的技巧

三端统一的另一个挑战:平台兼容性

6.3.1 兼容性矩阵

建立兼容性矩阵,明确支持范围:

平台 最低版本 推荐版本 特殊说明
iOS iOS 11.0 iOS 15.0+ 部分功能需要iOS 13+
Android Android 5.0 Android 10.0+ 需要适配各厂商ROM
H5 Chrome 60+ Chrome 90+ 不支持IE

6.3.2 兼容性检测

在SDK初始化时进行兼容性检测:

class CompatibilityChecker {
  static check(): CompatibilityReport {
    const report: CompatibilityReport = {
      platform: getPlatform(),
      version: getOSVersion(),
      features: {},
      warnings: [],
      errors: []
    };
    
    // 检测关键特性
    report.features = {
      webGL: this.checkWebGL(),
      webSocket: this.checkWebSocket(),
      localStorage: this.checkLocalStorage(),
      notifications: this.checkNotifications()
    };
    
    // 生成警告和错误
    if (getPlatform() === 'ios' && getIOSVersion() < 13) {
      report.warnings.push('Apple Sign-In requires iOS 13+');
    }
    
    if (getPlatform() === 'android' && getAndroidVersion() < 21) {
      report.errors.push('Android 5.0+ is required');
    }
    
    return report;
  }
}

// SDK初始化时调用
const report = CompatibilityChecker.check();
if (report.errors.length > 0) {
  console.error('SDK initialization failed:', report.errors);
  return;
}
if (report.warnings.length > 0) {
  console.warn('SDK warnings:', report.warnings);
}

6.3.3 优雅降级

遇到不支持的特性时,优雅降级:

// 降级策略表
const DegradationStrategy = {
  // Apple登录不可用 → 降级到其他登录方式
  'apple-signin': {
    fallback: 'guest',
    message: 'Apple Sign-In is not available on this device'
  },
  
  // WebGL不可用 → 降级到Canvas 2D
  'webgl': {
    fallback: 'canvas2d',
    message: 'WebGL is not supported, using Canvas 2D'
  },
  
  // 推送不可用 → 降级到轮询
  'push-notification': {
    fallback: 'polling',
    message: 'Push notification is not available, using polling'
  }
};

function applyFallback(feature: string): string {
  const strategy = DegradationStrategy[feature];
  if (strategy) {
    console.warn(strategy.message);
    return strategy.fallback;
  }
  return null;
}

七、实战案例:某游戏SDK的三端统一改造

7.1 改造前的痛点

某中型游戏公司的SDK改造前的情况:

7.2 改造过程

阶段一:架构设计(2周)

  1. 定义统一接口层
  2. 设计桥接层映射
  3. 制定平台实现规范

阶段二:核心模块迁移(4周)

  1. 登录模块重构(1周)
  2. 支付模块重构(1.5周)
  3. 埋点模块重构(1周)
  4. 推送模块重构(0.5周)

阶段三:全面重构(4周)

  1. 剩余模块迁移
  2. 统一测试
  3. 文档完善
  4. 灰度发布

7.3 改造后的收益

指标 改造前 改造后 提升
代码行数 45,000 31,000 -31%
团队人数 12人 5人 -58%
新功能周期 3-4周 1.5周 -60%
Bug修复周期 1-2周 3天 -70%
数据准确率 95% 99.9% +5%

八、测试策略:如何保证三端一致性

三端统一架构的一个巨大优势是:可以共享测试用例。但同时也带来了新的挑战:如何确保三端行为真正一致?

8.1 三层测试策略

8.1.1 接口层测试(共享)

接口层的测试用例可以完全共享,因为这是平台无关的业务逻辑。

// 共享的登录测试用例
describe('Login API', () => {
  test('should login with wechat successfully', async () => {
    const result = await sdk.login({ type: 'wechat' });
    expect(result.code).toBe(0);
    expect(result.data.userId).toBeDefined();
    expect(result.data.token).toBeDefined();
  });
  
  test('should return error when login cancelled', async () => {
    // Mock用户取消
    mockUserCancel();
    
    const result = await sdk.login({ type: 'wechat' });
    expect(result.code).toBe(-1001);  // LOGIN_CANCELLED
    expect(result.message).toContain('cancel');
  });
  
  test('should return error when network failed', async () => {
    // Mock网络错误
    mockNetworkError();
    
    const result = await sdk.login({ type: 'wechat' });
    expect(result.code).toBe(-2);  // NETWORK_ERROR
  });
});

8.1.2 桥接层测试(共享 + 平台特定)

桥接层的测试分为两部分:

// 桥接层共享测试
describe('LoginBridge', () => {
  test('should convert user info correctly', () => {
    const bridge = new LoginBridge('ios');
    
    // 测试iOS数据转换
    const iosData = {
      openid: '12345',
      nickname: '张三',
      headimgurl: 'http://example.com/avatar.jpg',
      sex: 1
    };
    
    const result = bridge.convertUserInfo(iosData);
    expect(result.userId).toBe('12345');
    expect(result.gender).toBe('male');
  });
  
  test('should translate error codes correctly', () => {
    const bridge = new LoginBridge('android');
    
    // 测试错误码翻译
    const errorCode = bridge.translateError({ code: '-4' });
    expect(errorCode).toBe(-1001);  // LOGIN_CANCELLED
  });
});

// 平台特定测试
describe('LoginBridge - iOS specific', () => {
  test('should map wechat login to correct iOS API', () => {
    const bridge = new LoginBridge('ios');
    const mapping = bridge.getInterfaceMapping('login', 'wechat');
    
    expect(mapping).toBe('WechatSDK.sendAuthRequest');
  });
});

8.1.3 实现层测试(平台特定)

实现层的测试需要使用各平台的测试框架:

8.2 集成测试:端到端验证

单元测试只能验证单个模块,集成测试才能验证整个链路。

8.2.1 自动化测试流程

# CI/CD配置示例(GitHub Actions)
name: SDK Integration Test

on: [push, pull_request]

jobs:
  test:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      
      # 接口层测试(共享)
      - name: Test Interface Layer
        run: npm run test:interface
      
      # iOS测试
      - name: Test iOS Implementation
        run: |
          cd ios
          xcodebuild test -scheme SDKTests -destination 'platform=iOS Simulator,name=iPhone 14'
      
      # Android测试
      - name: Test Android Implementation
        run: |
          cd android
          ./gradlew test
          ./gradlew connectedAndroidTest
      
      # H5测试
      - name: Test H5 Implementation
        run: |
          cd h5
          npm run test:unit
          npm run test:e2e

8.2.2 跨平台一致性测试

// 跨平台一致性测试
describe('Cross-platform Consistency', () => {
  const platforms = ['ios', 'android', 'h5'];
  
  platforms.forEach(platform => {
    describe(`Platform: ${platform}`, () => {
      let sdk: SDK;
      
      beforeAll(() => {
        sdk = createSDK(platform);
      });
      
      test('login should return same data structure', async () => {
        const result = await sdk.login({ type: 'guest' });
        
        // 验证数据结构一致
        expect(result).toHaveProperty('code');
        expect(result).toHaveProperty('message');
        expect(result).toHaveProperty('data.userId');
        expect(result).toHaveProperty('data.token');
      });
      
      test('error codes should be consistent', async () => {
        // Mock错误场景
        mockError(platform, 'network_failure');
        
        const result = await sdk.login({ type: 'wechat' });
        
        // 所有平台的网络错误都应该返回-2
        expect(result.code).toBe(-2);
        expect(result.message).toBeDefined();
      });
    });
  });
});

8.3 Mock策略:隔离平台依赖

在测试时,需要Mock平台依赖,让测试可以脱离真实环境运行。

8.3.1 平台Mock设计

// 平台Mock接口
interface PlatformMock {
  // Mock登录成功
  mockLoginSuccess(userInfo: UserInfo): void;
  
  // Mock登录失败
  mockLoginError(errorCode: number, message: string): void;
  
  // Mock用户取消
  mockUserCancel(): void;
  
  // Mock网络错误
  mockNetworkError(): void;
}

// iOS Mock实现
class IOSPlatformMock implements PlatformMock {
  mockLoginSuccess(userInfo: UserInfo): void {
    // Mock微信SDK的回调
    WechatSDK.mockCallback({
      errCode: 0,
      openid: userInfo.userId,
      nickname: userInfo.nickname
    });
  }
  
  mockLoginError(errorCode: number, message: string): void {
    WechatSDK.mockCallback({
      errCode: errorCode,
      errStr: message
    });
  }
}

// Android Mock实现
class AndroidPlatformMock implements PlatformMock {
  mockLoginSuccess(userInfo: UserInfo): void {
    // 发送Mock的Intent回调
    sendBroadcast(new Intent('com.example.LOGIN_SUCCESS')
      .putExtra('userId', userInfo.userId)
      .putExtra('nickname', userInfo.nickname));
  }
}

// H5 Mock实现
class H5PlatformMock implements PlatformMock {
  mockLoginSuccess(userInfo: UserInfo): void {
    // Mock OAuth回调
    window.postMessage({
      type: 'login_success',
      userInfo: userInfo
    }, '*');
  }
}

8.3.2 测试中使用Mock

describe('Login with Mock', () => {
  let sdk: SDK;
  let mock: PlatformMock;
  
  beforeEach(() => {
    sdk = createSDK('ios');
    mock = new IOSPlatformMock();
  });
  
  test('should handle login success', async () => {
    // 设置Mock
    mock.mockLoginSuccess({
      userId: '12345',
      nickname: '张三',
      avatar: 'http://example.com/avatar.jpg'
    });
    
    // 执行测试
    const result = await sdk.login({ type: 'wechat' });
    
    // 验证结果
    expect(result.code).toBe(0);
    expect(result.data.userId).toBe('12345');
    expect(result.data.userInfo.nickname).toBe('张三');
  });
  
  test('should handle network error', async () => {
    // 设置Mock
    mock.mockNetworkError();
    
    // 执行测试
    const result = await sdk.login({ type: 'wechat' });
    
    // 验证结果
    expect(result.code).toBe(-2);  // NETWORK_ERROR
    expect(result.message).toContain('network');
  });
});

8.4 测试覆盖率要求

为了保证质量,需要设定测试覆盖率目标:

层次 单元测试覆盖率 集成测试覆盖 说明
接口层 ≥90% 100% 业务逻辑,必须全覆盖
桥接层 ≥80% 100% 转换逻辑,关键路径全覆盖
实现层 ≥70% 100% 平台代码,核心功能全覆盖

九、常见问题与解决方案

在实施三端统一架构的过程中,团队经常会遇到一些典型问题。这里总结最常见的几个问题及其解决方案。

9.1 问题一:性能开销

桥接层的性能开销主要有两个来源:

  1. 接口调用开销:从接口层到实现层的跳转
  2. 数据转换开销:统一数据格式与平台格式的转换
// 策略1:缓存转换结果
class CachedBridge {
  private conversionCache = new Map<string, any>();
  
  convertUserInfo(rawData: any): UserInfo {
    const cacheKey = JSON.stringify(rawData);
    
    // 检查缓存
    if (this.conversionCache.has(cacheKey)) {
      return this.conversionCache.get(cacheKey);
    }
    
    // 转换并缓存
    const result = this.doConvert(rawData);
    this.conversionCache.set(cacheKey, result);
    
    return result;
  }
}

// 策略2:懒加载桥接层
class LazyBridge {
  private impl: PlatformImpl | null = null;
  
  async login(params: LoginParams): Promise<LoginResult> {
    // 第一次调用时才初始化实现层
    if (!this.impl) {
      this.impl = await this.loadPlatformImpl();
    }
    
    return this.impl.login(params);
  }
}

// 策略3:批量操作减少调用次数
class BatchBridge {
  private pendingTracks: TrackEvent[] = [];
  
  track(event: TrackEvent): void {
    this.pendingTracks.push(event);
    
    // 达到批量阈值时才真正调用
    if (this.pendingTracks.length >= 10) {
      this.flush();
    }
  }
  
  flush(): void {
    if (this.pendingTracks.length === 0) return;
    
    const events = [...this.pendingTracks];
    this.pendingTracks = [];
    
    // 一次性上报
    this.impl.trackBatch(events);
  }
}

9.2 问题二:平台特定功能

// 检测功能可用性
const FeatureSupport = {
  appleSignIn: () => isIOS() && getIOSVersion() >= 13,
  googlePlay: () => isAndroid() && isGooglePlayAvailable(),
  wechatH5: () => !isInWechatBrowser()  // H5微信登录需要特殊处理
};

// 使用时自动降级
async function login(type: LoginType): Promise<LoginResult> {
  // 检查功能是否支持
  if (type === LoginType.APPLE && !FeatureSupport.appleSignIn()) {
    console.warn('Apple Sign-In not available, using guest login');
    type = LoginType.GUEST;
  }
  
  return bridge.login({ type });
}
// 从服务器获取功能开关
const FeatureFlags = await fetchFeatureFlags();

// 根据开关决定是否启用功能
if (FeatureFlags.enableAppleSignIn && FeatureSupport.appleSignIn()) {
  // 显示Apple登录按钮
  showAppleSignInButton();
}
// 通用接口
interface CommonAPI {
  login(type: LoginType): Promise<LoginResult>;
}

// iOS扩展接口
interface IOSAPI extends CommonAPI {
  loginWithApple(): Promise<LoginResult>;
  evaluateAppStoreReview(): Promise<void>;
}

// Android扩展接口
interface AndroidAPI extends CommonAPI {
  loginWithGoogle(): Promise<LoginResult>;
  requestGooglePlayReview(): Promise<void>;
}

// 使用时判断平台
const api = createAPI();
if (isIOS() && 'loginWithApple' in api) {
  await (api as IOSAPI).loginWithApple();
}

9.3 问题三:调试困难

// 统一的日志系统
class SDKLogger {
  private static instance: SDKLogger;
  private logs: LogEntry[] = [];
  
  log(layer: 'interface' | 'bridge' | 'impl', action: string, data: any): void {
    const entry: LogEntry = {
      timestamp: Date.now(),
      layer,
      action,
      data,
      platform: getPlatform()
    };
    
    this.logs.push(entry);
    
    // 开发模式下打印
    if (__DEV__) {
      console.log(`[${layer}] ${action}`, data);
    }
  }
  
  // 导出日志用于调试
  exportLogs(): LogEntry[] {
    return [...this.logs];
  }
  
  // 上传日志到服务器
  async uploadLogs(): Promise<void> {
    const logs = this.exportLogs();
    await fetch('https://api.example.com/logs', {
      method: 'POST',
      body: JSON.stringify(logs)
    });
  }
}

// 在各层使用
class LoginBridge {
  async login(params: LoginParams): Promise<LoginResult> {
    SDKLogger.getInstance().log('bridge', 'login_start', params);
    
    try {
      const result = await this.impl.login(params);
      SDKLogger.getInstance().log('bridge', 'login_success', result);
      return result;
    } catch (error) {
      SDKLogger.getInstance().log('bridge', 'login_error', error);
      throw error;
    }
  }
}
// 调试面板(仅开发模式)
class DebugPanel {
  show(): void {
    if (!__DEV__) return;
    
    const panel = document.createElement('div');
    panel.innerHTML = `
      <div class="debug-panel">
        <h3>SDK Debug Panel</h3>
        <div class="actions">
          <button onclick="window.SDKDebug.testLogin()">Test Login</button>
          <button onclick="window.SDKDebug.testPay()">Test Pay</button>
          <button onclick="window.SDKDebug.exportLogs()">Export Logs</button>
        </div>
        <div class="logs"></div>
      </div>
    `;
    
    document.body.appendChild(panel);
  }
  
  testLogin(): void {
    console.log('Testing login...');
    sdk.login({ type: 'guest' })
      .then(result => console.log('Login result:', result))
      .catch(error => console.error('Login error:', error));
  }
}

// 全局暴露调试接口
if (__DEV__) {
  window.SDKDebug = new DebugPanel();
}

9.4 问题四:团队协作

角色 负责层次 职责
架构师 接口层 + 桥接层 定义接口、设计转换逻辑
iOS开发 iOS实现层 iOS平台具体实现
Android开发 Android实现层 Android平台具体实现
前端开发 H5实现层 H5平台具体实现
1. 架构师定义接口契约(IDL)
2. 生成三端的接口代码
3. 各端开发实现层代码
4. 桥接层集成测试
## Code Review清单

### 接口层代码
- [ ] 接口定义是否清晰易懂?
- [ ] 参数是否跨平台统一?
- [ ] 是否有平台特定的逻辑?(应该移到桥接层)

### 桥接层代码
- [ ] 数据转换是否正确?
- [ ] 错误码映射是否完整?
- [ ] 是否有平台特定的逻辑?(应该移到实现层)

### 实现层代码
- [ ] 是否遵守了接口契约?
- [ ] 是否返回了统一格式的数据?
- [ ] 是否有业务逻辑?(应该移到桥接层)

十、总结:三端统一的三个关键

走完这条"三端统一"之路,我们发现成功的关键在于:

1️⃣ 抽象要精准

不是所有代码都要统一,只抽象真正稳定的业务逻辑

登录流程是稳定的,但具体用什么第三方SDK,让它变化吧。

2️⃣ 分层要清晰

三层架构不是"为了分层而分层",而是让每层只关心自己的事

3️⃣ 契约要严格

三层之间的"契约"(接口定义)要严格定义,一旦发布就尽量稳定。


十一、写在最后

三端统一不是"银弹",不能解决所有问题。

但它提供了一种清晰的思维框架

当你面临跨平台问题时,先问自己—— 这个问题属于哪一层?

答案找到了,解决方案也就清晰了。

9.1 什么时候应该做三端统一?

9.2 实施建议

  1. 从小做起:先统一一个模块,验证架构
  2. 逐步迁移:不要一次性重构,风险太大
  3. 保持兼容:旧接口要保留,给游戏迁移时间
  4. 文档先行:先写接口定义,再写实现

愿每一位SDK开发者都能告别"三倍痛苦",享受"一次编写,三端运行"的优雅。✨



📌 要点总结

  1. 三端分裂的本质:业务逻辑被平台实现绑架,需要通过抽象来解绑
  2. 三层架构:接口层定义业务、桥接层负责翻译、实现层处理平台差异
  3. 接口设计原则:语义清晰、参数统一、回调规范
  4. 桥接层职责:接口映射、数据转换、异步处理、错误翻译
  5. 平台差异处理:功能降级、性能适配、行为统一封装
  6. 版本管理:三段式版本号,保证向后兼容
  7. 热更新策略:配置化、脚本化,把可变逻辑放在桥接层
  8. 实战收益:代码量减少31%,人力成本降低60%,开发效率提升60%

💬 评论 (0)

0/500
排序: