文章

场景设计:跨端SDK架构深度解析

场景设计:跨端SDK架构深度解析

跨端SDK是跨端开发中最具挑战性的工程模块之一——它需要在多个平台维持一致的API体验,又要最大化复用业务逻辑,同时保证各平台的Native能力不被阉割。本文围绕一个真实场景(跨端统计分析SDK)完整讲述其架构设计过程。

一、背景与意义

现实场景

假设你在一家拥有千万级日活App的公司,同时运营着iOS App、Android App、微信小程序、支付宝小程序、以及Web端H5。公司在所有端上都需要一套用户行为统计分析SDK,用于:

  • 用户行为的跨端统一归因
  • 漏斗转化的全链路追踪
  • A/B实验的跨端分层
  • 崩溃与性能监控的统一上报

一个直观但糟糕的方案:每个端单独开发一套SDK,暴露一致的外层API。这样虽然API看起来一致,但:

  • 三倍的Bug修复成本
  • 三倍的功能迭代成本
  • 三倍的代码审查成本
  • 各端行为不一致几乎是必然的

跨端SDK架构的核心矛盾:如何在一套代码基础上支持多端?

SDK与普通业务代码的区别

理解SDK的特殊性——它不像普通业务代码在可控环境中运行:

维度普通业务代码SDK
宿主环境自己控制不确定(第三方App)
初始化时机应用启动可能任意时刻
依赖管理可控制版本不能与宿主冲突
崩溃影响影响自家App可能导致宿主App崩溃
包体积不计较必须轻量(<100KB)
API稳定性可随时改必须向后兼容

二、概念与定义

2.1 跨端SDK的整体架构模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌────────────────────────────────────────────┐
│              业务接口层 (API)                │
├────────────────────────────────────────────┤
│           核心逻辑层 (Core)                  │
│  ┌──────┐  ┌──────┐  ┌────────┐           │
│  │事件路由│  │队列管理│  │ 缓存策略 │          │
│  └──────┘  └──────┘  └────────┘           │
├────────────────────────────────────────────┤
│           适配器层 (Adapters)               │
│ ┌────────┐ ┌────────┐ ┌──────┐ ┌────────┐ │
│ │iOS Adpt│ │Android │ │小程序│ │Web Adpt│ │
│ └────────┘ └────────┘ └──────┘ └────────┘ │
├────────────────────────────────────────────┤
│         平台层 (Platform Interface)         │
│  (网络请求 / 本地存储 / 设备信息 / 线程)     │
└────────────────────────────────────────────┘

分层职责

  • API层:SDK对外暴露的接口,确保各端API一致
  • 核心层:平台无关的业务逻辑——事件聚合、缓冲、压缩、重试
  • 适配器层:将平台接口转换为核心层需要的抽象接口
  • 平台层:各平台的基础能力(网络、存储、线程)

2.2 核心设计原则

1
2
3
4
5
6
7
8
9
10
11
原则1: 核心无关化 (Core Agnostic)
  Core层不引用任何平台特定代码(没有#import <UIKit>、没有wx.xxx)
  
原则2: API同构化 (API Isomorphism)  
  各端暴露的API签名保持一致
  
原则3: 插件可插拔 (Pluggable)
  SDK功能通过插件扩展,核心除外

原则4: 瘦核心,胖插件 (Thin Core, Fat Plugins)
  核心只做三件事:事件路由、队列管理、生命周期

三、最小示例

3.1 SDK接口定义 (API层)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 跨端SDK的公共TypeScript接口定义
// 这是"契约"——各平台的实现都必须遵循

export interface ITrackerSDK {
  // 初始化
  init(config: SDKConfig): Promise<void>;
  
  // 事件追踪
  track(event: TrackEvent): void;
  trackBatch(events: TrackEvent[]): void;
  
  // 用户标识
  setUserId(userId: string): void;
  setUserProperties(props: Record<string, any>): void;
  
  // 页面追踪
  trackPageView(pageName: string, properties?: Record<string, any>): void;
  
  // 计时器
  startTimer(key: string): void;
  stopTimer(key: string, properties?: Record<string, any>): void;
  
  // 生命周期
  flush(): Promise<void>;
  shutdown(): void;
  
  // 插件系统
  use(plugin: ITrackerPlugin): void;
  eject(pluginName: string): void;
}

export interface SDKConfig {
  appKey: string;
  serverUrl: string;
  uploadInterval?: number;   // 上报间隔(ms),默认30000
  batchSize?: number;       // 批量上报条数,默认50
  maxCacheSize?: number;    // 最大缓存条数,默认10000
  sampleRate?: number;      // 采样率,0-1,默认1.0
  autoTrack?: AutoTrackConfig;
  debug?: boolean;
}

export interface TrackEvent {
  event: string;
  properties?: Record<string, any>;
  timestamp?: number;
  uuid?: string;
  user?: UserInfo;
}

3.2 Core层的平台无关实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// TrackerCore.ts — 核心逻辑层,不依赖任何平台
// 这是SDK中唯一需要通过单元测试全面覆盖的模块

import { compress } from './compress';
import { TrackerQueue } from './queue';

export class TrackerCore {
  private queue: TrackerQueue;
  private config: SDKConfig;
  private uploadTimer: ReturnType<typeof setInterval> | null = null;
  private plugins: Map<string, ITrackerPlugin> = new Map();
  private userInfo: UserInfo = {};
  private timers: Map<string, number> = new Map();
  
  // 注入的是"平台接口"而非具体实现
  constructor(
    private platform: IPlatformAdapter,
    config: SDKConfig
  ) {
    this.config = config;
    this.queue = new TrackerQueue({
      maxSize: config.maxCacheSize || 10000,
      persist: platform.storage,  // 依赖注入
    });
  }
  
  // 初始化:加载缓存 + 启动定时器
  async init(): Promise<void> {
    await this.queue.loadFromStorage();
    
    // 定时上报
    this.uploadTimer = setInterval(
      () => this.flush(),
      this.config.uploadInterval || 30000
    );
    
    // 注册生命周期回调(各平台适配器注入)
    this.platform.lifecycle.onForeground(() => {
      // 从后台切到前台时立即上报已缓存事件
      this.flush();
    });
    
    this.platform.lifecycle.onBackground(() => {
      // 进入后台时立即上报
      this.flush();
    });
    
    // 自动采集(如适用)
    if (this.config.autoTrack?.appLaunch) {
      this.track({
        event: '$AppLaunch',
        properties: {
          timestamp: Date.now(),
          launchType: this.platform.device.getLaunchType(),
        },
      });
    }
  }
  
  track(event: TrackEvent): void {
    if (Math.random() > (this.config.sampleRate || 1.0)) return;
    
    const enrichedEvent = this.enrichEvent(event);
    
    // 插件拦截
    for (const plugin of this.plugins.values()) {
      const shouldProceed = plugin.beforeTrack?.(enrichedEvent);
      if (shouldProceed === false) return; // 插件决定放弃
    }
    
    this.queue.push(enrichedEvent);
    
    // 达到批量上报阈值立即触发
    if (this.queue.size() >= (this.config.batchSize || 50)) {
      this.flush();
    }
  }
  
  private enrichEvent(event: TrackEvent): TrackEvent {
    return {
      ...event,
      uuid: this.generateUUID(),
      timestamp: event.timestamp || Date.now(),
      user: {
        ...this.userInfo,
        ...event.user,
      },
      properties: {
        ...event.properties,
        '$platform': this.platform.device.getPlatform(),
        '$os_version': this.platform.device.getOSVersion(),
        '$network_type': this.platform.device.getNetworkType(),
        '$screen_width': this.platform.device.getScreenWidth(),
        '$screen_height': this.platform.device.getScreenHeight(),
        '$tz_offset': new Date().getTimezoneOffset(),
      },
    };
  }
  
  async flush(): Promise<void> {
    if (this.queue.isEmpty()) return;
    
    const batch = this.queue.drain(this.config.batchSize || 50);
    
    // 压缩
    const compressed = await compress(batch);
    
    // 使用平台网络接口发送
    const success = await this.platform.network.post(
      this.config.serverUrl,
      compressed
    );
    
    if (!success) {
      // 发送失败,重新入队
      this.queue.reEnqueue(batch);
    }
  }
  
  use(plugin: ITrackerPlugin): void {
    if (this.plugins.has(plugin.name)) {
      console.warn(`[TrackerCore] Plugin ${plugin.name} already registered`);
      return;
    }
    this.plugins.set(plugin.name, plugin);
    plugin.install?.(this);
  }
}

四、核心知识点拆解

4.1 平台抽象层 (Platform Abstraction)

平台抽象层是跨端SDK中最关键的代码。它定义了一组接口,各平台分别实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// IPlatformAdapter.ts — 平台适配器接口定义
// 这是Core层唯一能接触的"平台相关"抽象

export interface IPlatformAdapter {
  // 网络
  network: {
    post(url: string, data: any): Promise<boolean>;
    get(url: string): Promise<any>;
    isOnline(): boolean;
  };
  
  // 存储
  storage: {
    getItem(key: string): Promise<string | null>;
    setItem(key: string, value: string): Promise<void>;
    removeItem(key: string): Promise<void>;
    getSize(): Promise<number>;
  };
  
  // 设备信息
  device: {
    getPlatform(): string;
    getOSVersion(): string;
    getDeviceModel(): string;
    getNetworkType(): 'wifi' | '4g' | '5g' | '3g' | 'offline';
    getScreenWidth(): number;
    getScreenHeight(): number;
    getLaunchType(): 'cold' | 'warm';
  };
  
  // 生命周期
  lifecycle: {
    onForeground(callback: () => void): void;
    onBackground(callback: () => void): void;
    onLowMemory(callback: () => void): void;
  };
  
  // 线程
  thread: {
    runInBackground<T>(fn: () => Promise<T>): Promise<T>;
    runOnMainThread(fn: () => void): void;
  };
}

各平台的存储适配器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 小程序适配器
export class WeAppStorageAdapter implements IStorage {
  async getItem(key: string): Promise<string | null> {
    try {
      const { data } = await wx.getStorage({ key });
      return data as string;
    } catch {
      return null;
    }
  }
  
  async setItem(key: string, value: string): Promise<void> {
    await wx.setStorage({ key, data: value });
  }
  
  async removeItem(key: string): Promise<void> {
    await wx.removeStorage({ key });
  }
  
  async getSize(): Promise<number> {
    const info = await wx.getStorageInfo();
    return info.currentSize;
  }
}

// React Native适配器
export class RNStorageAdapter implements IStorage {
  async getItem(key: string): Promise<string | null> {
    return await AsyncStorage.getItem(key);
  }
  
  async setItem(key: string, value: string): Promise<void> {
    await AsyncStorage.setItem(key, value);
  }
  
  async removeItem(key: string): Promise<void> {
    await AsyncStorage.removeItem(key);
  }
  
  async getSize(): Promise<number> {
    const keys = await AsyncStorage.getAllKeys();
    let size = 0;
    for (const key of keys) {
      const val = await AsyncStorage.getItem(key);
      size += (key.length + (val?.length || 0)) * 2; // UTF-16
    }
    return Math.round(size / 1024); // 返回KB
  }
}

4.2 插件系统架构

插件系统是SDK扩展性的保障。一个好的插件设计应该是”三明治结构”:

graph LR
    A[宿主App] -->|track()| B[插件A.beforeTrack]
    B --> C[插件B.beforeTrack]
    C --> D[Core.track]
    D --> E[插件A.afterTrack]
    E --> F[插件C.afterTrack]
    F --> G[网络上传]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 插件接口
export interface ITrackerPlugin {
  readonly name: string;
  readonly version?: string;
  
  // 安装时调用
  install?(core: TrackerCore): void;
  
  // 事件追踪钩子——在Core处理前/后调用
  beforeTrack?(event: TrackEvent): boolean | void;
  afterTrack?(event: TrackEvent): void;
  
  // 上报钩子
  beforeUpload?(batch: TrackEvent[]): TrackEvent[] | void;
  afterUpload?(success: boolean, batch: TrackEvent[]): void;
}

// 插件示例:自动采集应用内购事件插件
export class InAppPurchasePlugin implements ITrackerPlugin {
  name = 'in_app_purchase';
  version = '1.0.0';
  
  private core: TrackerCore | null = null;
  
  install(core: TrackerCore): void {
    this.core = core;
    
    // 注册平台支付回调(通过platform adapter实现)
    // 这里的平台适配器已由Core初始化时注入了生命周期
    const platform = (core as any).platform;
    
    if (platform.purchase) {
      platform.purchase.onPurchaseComplete((product: any) => {
        core.track({
          event: '$Purchase',
          properties: {
            product_id: product.id,
            price: product.price,
            currency: product.currency,
            transaction_id: product.transactionId,
          },
        });
      });
    }
  }
  
  beforeTrack(event: TrackEvent): boolean | void {
    // 对购买事件做数据校验
    if (event.event === '$Purchase' && !event.properties?.transaction_id) {
      console.warn('[PurchasePlugin] Missing transaction_id');
      return false; // 拦截无效数据
    }
  }
}

4.3 事件队列管理

队列管理是SDK可靠性的核心——确保事件不会因为网络异常或App闪退而丢失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// TrackerQueue.ts — 线程安全的事件缓冲队列
export class TrackerQueue {
  private events: TrackEvent[] = [];
  private isDraining: boolean = false;
  private persistPromise: Promise<void> = Promise.resolve();
  
  constructor(
    private options: {
      maxSize: number;
      persist: IStorage;
    }
  ) {}
  
  push(event: TrackEvent): void {
    this.events.push(event);
    
    // 限制队列大小,防止内存溢出
    if (this.events.length > this.options.maxSize) {
      const removed = this.events.shift();
      console.warn(`[TrackerQueue] Queue full, dropped oldest event: ${removed?.event}`);
    }
    
    // 异步持久化(不阻塞主流程)
    this.persistPromise = this.persistQueue();
  }
  
  drain(count: number): TrackEvent[] {
    if (this.isDraining) return [];
    this.isDraining = true;
    
    const batch = this.events.splice(0, count);
    this.isDraining = false;
    
    return batch;
  }
  
  reEnqueue(events: TrackEvent[]): void {
    // 失败的事件放回队列前端(保证顺序)
    this.events.unshift(...events);
    
    // 更新持久化
    this.persistPromise = this.persistQueue();
  }
  
  size(): number {
    return this.events.length;
  }
  
  isEmpty(): boolean {
    return this.events.length === 0;
  }
  
  // 关键方法:App被杀死后恢复
  async loadFromStorage(): Promise<void> {
    try {
      const data = await this.options.persist.getItem('@tracker/queue');
      if (data) {
        const parsed = JSON.parse(data);
        if (Array.isArray(parsed)) {
          this.events = parsed;
        }
      }
    } catch {
      // 解析失败则丢弃
      this.events = [];
    }
  }
  
  private async persistQueue(): Promise<void> {
    if (this.events.length === 0) return;
    // 截取最近的500条持久化(避免写入过多)
    const toPersist = this.events.slice(-500);
    await this.options.persist.setItem(
      '@tracker/queue',
      JSON.stringify(toPersist)
    );
  }
}

4.4 数据压缩策略

在网络上传前做压缩,可以减少带宽消耗和电池损耗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// compress.ts — 多级压缩策略
export async function compress(events: TrackEvent[]): Promise<Uint8Array> {
  // 阶段1: 结构压缩(去除冗余字段名)
  const compacted = compactStructure(events);
  
  // 阶段2: JSON序列化
  const jsonStr = JSON.stringify(compacted);
  
  // 阶段3: 如果超过阈值,启用gzip压缩
  if (jsonStr.length > 1024) {
    return await gzipCompress(jsonStr);
  }
  
  return new TextEncoder().encode(jsonStr);
}

// 字段名短化——减少每个事件约40%的体积
function compactStructure(events: TrackEvent[]): any[] {
  const fieldMapping: Record<string, string> = {
    'event': 'e',
    'properties': 'p',
    'timestamp': 't',
    'uuid': 'u',
    'user': 'us',
    'userId': 'ui',
    'eventName': 'en',
    'duration': 'd',
    '$platform': 'plat',
    '$os_version': 'os',
    '$network_type': 'net',
  };
  
  return events.map(event => {
    const compacted: any = {};
    for (const [key, value] of Object.entries(event)) {
      if (fieldMapping[key]) {
        compacted[fieldMapping[key]] = value;
      } else {
        compacted[key] = value; // 不认识的key原样保留
      }
    }
    // 递归处理properties
    if (compacted.p) {
      compacted.p = compactProperties(compacted.p, fieldMapping);
    }
    return compacted;
  });
}

五、实战案例:构建完整的跨端统计SDK

5.1 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
tracker-sdk/
├── core/                          # 核心逻辑(TypeScript,所有平台共享)
│   ├── tracker-core.ts            # 核心类
│   ├── queue.ts                   # 事件队列
│   ├── compress.ts                # 压缩策略
│   ├── plugins/                   # 内置插件
│   │   ├── auto-track.ts          # 自动采集
│   │   ├── session.ts             # 会话管理
│   │   └── debug.ts               # 调试模式
│   └── types/                     # 类型定义
│       ├── sdk-config.ts
│       ├── track-event.ts
│       └── plugin.ts
│
├── adapters/                      # 各平台适配器
│   ├── ios/                       # iOS(Swift)
│   ├── android/                   # Android(Kotlin)
│   ├── weapp/                     # 小程序
│   │   ├── adapter.ts
│   │   ├── storage.ts
│   │   ├── network.ts
│   │   └── lifecycle.ts
│   └── web/                       # Web
│
├── packages/                      # 各平台SDK包
│   ├── tracker-ios/               # CocoaPods
│   ├── tracker-android/           # Maven
│   ├── tracker-weapp/             # npm / miniprogram
│   └── tracker-web/               # npm
│
└── scripts/
    ├── build-core.ts              # 构建核心层
    └── publish.ts                 # 发布脚本

5.2 在宿主App中集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// iOS端集成(Swift)
// AppDelegate.swift
import TrackerSDK

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        Tracker.shared.initialize(
            config: SDKConfig(
                appKey: "your_app_key",
                serverUrl: "https://tracker.example.com/v1/batch",
                autoTrack: AutoTrackConfig(
                    appLaunch: true,
                    pageViews: true,
                    clicks: true
                )
            )
        )
        
        // 注册插件
        Tracker.shared.use(InAppPurchasePlugin())
        
        return true
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 小程序端集成
// app.js
import { TrackerSDK } from '@company/tracker-weapp';

App({
  onLaunch() {
    const tracker = new TrackerSDK({
      appKey: 'your_app_key',
      serverUrl: 'https://tracker.example.com/v1/batch',
      autoTrack: {
        appLaunch: true,
        pageViews: true,
        clicks: true,
      },
    });
    
    tracker.use(new InAppPurchasePlugin());
    
    this.tracker = tracker;
  },
});

5.3 数据上报链路验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 端到端测试:验证事件从采集到上报的全链路
// 在CI中运行

async function testSDKDataFlow() {
  // 1. 模拟平台环境
  const mockAdapter = createMockPlatformAdapter();
  const core = new TrackerCore(mockAdapter, testConfig);
  await core.init();
  
  // 2. 触发事件
  core.track({
    event: 'test_event',
    properties: { value: 42, label: 'test' },
  });
  
  // 3. 触发上报
  await core.flush();
  
  // 4. 验证网络层收到数据
  expect(mockAdapter.network.lastPostUrl).toBe(
    'https://tracker.example.com/v1/batch'
  );
  
  const sentData = JSON.parse(
    new TextDecoder().decode(mockAdapter.network.lastPostBody)
  );
  
  // 5. 验证数据完整性
  expect(sentData.events).toHaveLength(1);
  expect(sentData.events[0].event).toBe('test_event');
  expect(sentData.events[0].properties.value).toBe(42);
  expect(sentData.events[0].user.userId).toBeDefined();
  
  // 6. 验证压缩
  expect(sentData.events[0].properties['$platform']).toBeDefined();
}

六、底层原理

6.1 依赖注入与IoC

跨端SDK的核心层使用控制反转实现平台无关性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 核心层不创建任何平台相关对象,全部由外部注入
class TrackerCore {
  // 通过构造函数注入平台适配器
  constructor(
    private platform: IPlatformAdapter,
    private config: SDKConfig
  ) {
    // 不会出现 new UserDefaults() / wx.getSystemInfo() 等平台代码
  }
}

// 各平台的SDK入口完成注入
// 小程序端
class WeAppTrackerSDK {
  constructor(config: SDKConfig) {
    const adapter = new WeAppPlatformAdapter();
    this.core = new TrackerCore(adapter, config);
  }
}

// iOS端  
class IOSTrackerSDK {
  constructor(config: SDKConfig) {
    const adapter = new iOSPlatformAdapter();
    this.core = new TrackerCore(adapter, config);
  }
}

这样做的好处:

  1. Core层所有代码都可以写单元测试(传入Mock适配器)
  2. 新增平台只需实现适配器接口,Core层零修改
  3. 适配器可以针对平台做性能优化(比如小程序用wx.request而非fetch)

6.2 线程模型的跨端抽象

不同平台的线程模型差异很大:

操作iOSAndroid小程序Flutter
UI操作主线程主线程渲染线程GPU Runner
网络请求任何线程任何线程逻辑线程IO Runner
文件读写不建议主线程不建议主线程逻辑线程IO Runner
大量计算后台线程AsyncTask逻辑线程Worker Isolate

SDK层需要抽象这一切:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ThreadAdapter.ts — 线程抽象
interface IThreadAdapter {
  background<T>(fn: () => T): Promise<T>;
  main(fn: () => void): void;
}

// 小程序线程适配器
class WeAppThreadAdapter implements IThreadAdapter {
  async background<T>(fn: () => T): Promise<T> {
    // 小程序中没有真正的多线程,但可以用Worker
    // 简单场景下直接在逻辑线程执行
    return fn();
  }
  
  main(fn: () => void): void {
    // setData等UI操作在逻辑线程完成,不用切线程
    fn();
  }
}

// Android线程适配器
class AndroidThreadAdapter implements IThreadAdapter {
  async background<T>(fn: () => T): Promise<T> {
    return new Promise((resolve, reject) => {
      threadPool.execute(() => {
        try { resolve(fn()); }
        catch (e) { reject(e); }
      });
    });
  }
  
  main(fn: () => void): void {
    new Handler(Looper.getMainLooper()).post(fn);
  }
}

七、高频面试题解析

Q1: 跨端SDK的版本策略如何设计?

A:采用语义化版本(SemVer)+ 平台后缀的模式:

  • Core层独立版本号(如 2.1.0)
  • 各平台包版本号 = Core版本 + 平台定制后缀(如 2.1.0.ios1)
  • Changelog必须标明每个版本对应的Core版本
  • 版本兼容性测试矩阵:3个Core版本 × 每个平台2个版本

Q2: SDK如何保证向后兼容?

A:1) 核心层接口(Core的公开方法)一旦发布永不改签名,新增用重载;2) 适配器接口禁止删除方法,新增方法给默认实现;3) 事件数据模型用Protobuf,字段新增而不是废弃;4) 版本号中用deprecated注解标注废弃方法,保留至少2个主版本。

Q3: SDK包体积如何优化?

A:1) 使用Tree-shaking(构建时移除未使用的插件);2) 压缩字段名(前面介绍的compact策略);3) 非核心功能拆为可选插件,按需加载;4) 小程序端利用分包,SDK放入独立分包;5) Flutter插件使用FFI减少Dart代码量。

Q4: 多个SDK共存时的冲突怎么解决?

A:建立SDK沙箱机制:1) 所有全局名称加前缀(ttTracker_);2) 使用单独的命名空间/包名;3) 网络请求使用独立的URLSession配置;4) 存储使用专属Key前缀;5) 依赖的第三方库必须shade/relocate包名。

八、总结与扩展

跨端SDK架构的设计本质是”在变化中寻找不变”——把平台差异封装到适配器层,把业务逻辑固化在核心层

架构演进的生命周期:

  1. 单体阶段:每个平台一套代码——迭代快但维护成本高
  2. 抽象阶段:提取Core层,多平台共享——效率提升但仍有重复
  3. 插件阶段:Core + 插件系统——扩展能力强
  4. 自动生成阶段:基于接口定义自动生成各平台适配器代码——完全消除重复

不要做的

  • ❌ Core层与平台代码混在一起(无法测试,无法迁移)
  • ❌ 适配器做成全量接口暴露出平台细节(破坏抽象)
  • ❌ 插件系统过于灵活导致Core层不稳定(插件不要能修改Core内部状态)
  • ❌ 追求100%的功能共享(有些平台特异的功能就该留给平台)

优秀的跨端SDK架构让开发者感觉”在不同平台上写同一份代码”,而不是”在不同平台上为同一件事反复打工”。

本文由作者按照 CC BY 4.0 进行授权

© 独行的风. 保留部分权利。

本站采用 Jekyll 主题 Chirpy

本站总访问量 本站访客数 本文阅读量