tencentcloud-webar 1.0.29-2 → 1.0.29-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-esm/450.js +1 -1
- package/build-esm/682.js +1 -1
- package/build-esm/avatar.js +1 -1
- package/build-esm/hand3d.js +1 -1
- package/build-esm/index.js +1 -1
- package/build-esm/sticker3d.js +1 -1
- package/build-umd/avatar.umd.js +1 -1
- package/build-umd/hand-3d.umd.js +1 -1
- package/build-umd/stickers-3d.umd.js +4 -4
- package/build-umd/webar-sdk.umd.js +2 -2
- package/doc/EventBus /345/256/236/344/276/213/347/272/247/346/224/271/351/200/240/346/214/207/345/215/227.md" +177 -0
- package/doc//345/244/232SDK/345/256/236/344/276/213/344/272/222/347/233/270/345/275/261/345/223/215/347/232/204/345/270/270/350/247/201/351/227/256/351/242/230/345/217/212/350/247/243/345/206/263/346/226/271/346/241/210.md +625 -0
- package/miniprogram_dist/core.js +1 -1
- package/miniprogram_dist/index.js +1 -1
- package/miniprogram_dist/plugin-3d.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
# 多SDK实例互相影响的常见问题及解决方案
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
在SDK开发中,支持单页面多实例是一个常见但复杂的需求。除了EventBus之外,还有许多常见的代码写法会导致多SDK实例互相影响。本文档总结了这些问题及其解决方案。
|
|
6
|
+
|
|
7
|
+
## 1. 单例模式(Singleton Pattern)
|
|
8
|
+
|
|
9
|
+
### 问题描述
|
|
10
|
+
单例模式是最常见的多实例问题来源,因为单例模式天然只允许一个实例存在。
|
|
11
|
+
|
|
12
|
+
### 常见形式
|
|
13
|
+
```typescript
|
|
14
|
+
// 静态实例属性
|
|
15
|
+
class AROptionsManager extends EventObj {
|
|
16
|
+
static instance: AROptionsManager = new AROptionsManager();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 静态获取方法
|
|
20
|
+
class QualityManager {
|
|
21
|
+
static instance: QualityManager = new QualityManager();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 静态属性
|
|
25
|
+
class GesturePipe extends EventObj {
|
|
26
|
+
static instance: GesturePipe = new GesturePipe();
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 问题场景
|
|
31
|
+
当页面创建多个SDK实例时,所有实例都会共享同一个单例对象,导致配置冲突、状态混乱等问题。
|
|
32
|
+
|
|
33
|
+
### 解决方案
|
|
34
|
+
1. **实例化单例**:将单例改为实例属性
|
|
35
|
+
```typescript
|
|
36
|
+
class ArSdk {
|
|
37
|
+
private optionsManager: AROptionsManager;
|
|
38
|
+
private qualityManager: QualityManager;
|
|
39
|
+
|
|
40
|
+
constructor(config: Config) {
|
|
41
|
+
this.optionsManager = new AROptionsManager();
|
|
42
|
+
this.qualityManager = new QualityManager();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **依赖注入**:通过构造函数传递
|
|
48
|
+
```typescript
|
|
49
|
+
class ArMainImpl {
|
|
50
|
+
private optionsManager: AROptionsManager;
|
|
51
|
+
|
|
52
|
+
constructor(optionsManager: AROptionsManager) {
|
|
53
|
+
this.optionsManager = optionsManager;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 2. 全局状态存储
|
|
61
|
+
|
|
62
|
+
### 问题描述
|
|
63
|
+
使用全局变量或对象存储状态,导致多实例共享状态。
|
|
64
|
+
|
|
65
|
+
### 常见形式
|
|
66
|
+
```typescript
|
|
67
|
+
// 全局变量
|
|
68
|
+
let resolution = 1;
|
|
69
|
+
|
|
70
|
+
// 全局对象
|
|
71
|
+
const globalData = {
|
|
72
|
+
defaultRoot: '',
|
|
73
|
+
version: '1.0.0'
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// window对象属性
|
|
77
|
+
window._arCustomReport = reportor.report.bind(reportor);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 问题场景
|
|
81
|
+
多个SDK实例修改全局状态,导致实例间状态互相影响。
|
|
82
|
+
|
|
83
|
+
### 解决方案
|
|
84
|
+
1. **实例级状态**:将全局状态移到SDK实例中
|
|
85
|
+
```typescript
|
|
86
|
+
class ArSdk {
|
|
87
|
+
private resolution: number = 1;
|
|
88
|
+
private customReport: Function;
|
|
89
|
+
|
|
90
|
+
constructor(config: Config) {
|
|
91
|
+
this.customReport = reportor.report.bind(reportor);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
2. **上下文对象**:创建上下文对象管理状态
|
|
97
|
+
```typescript
|
|
98
|
+
interface SDKContext {
|
|
99
|
+
resolution: number;
|
|
100
|
+
customReport: Function;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class ArSdk {
|
|
104
|
+
private context: SDKContext;
|
|
105
|
+
|
|
106
|
+
constructor(config: Config) {
|
|
107
|
+
this.context = {
|
|
108
|
+
resolution: 1,
|
|
109
|
+
customReport: reportor.report.bind(reportor)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 3. 全局事件监听
|
|
118
|
+
|
|
119
|
+
### 问题描述
|
|
120
|
+
直接在全局对象(如window、document)上添加事件监听器,导致多实例事件处理混乱。
|
|
121
|
+
|
|
122
|
+
### 常见形式
|
|
123
|
+
```typescript
|
|
124
|
+
// document事件监听
|
|
125
|
+
document.addEventListener("visibilitychange", this._visibilityChangeFunc);
|
|
126
|
+
|
|
127
|
+
// window事件监听
|
|
128
|
+
window.addEventListener('resize', this._handleResize);
|
|
129
|
+
|
|
130
|
+
// 全局对象事件监听
|
|
131
|
+
AROptionsManager.instance.on('mirrorChange', this._handleMirrorChange);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 问题场景
|
|
135
|
+
多个SDK实例都监听同一个全局事件,导致事件被多次处理,或者一个实例的事件处理影响其他实例。
|
|
136
|
+
|
|
137
|
+
### 解决方案
|
|
138
|
+
1. **实例级事件处理**:在事件处理函数中检查实例关联
|
|
139
|
+
```typescript
|
|
140
|
+
class ArSdk {
|
|
141
|
+
private _visibilityChangeFunc: (e: Event) => any;
|
|
142
|
+
|
|
143
|
+
constructor(config: Config) {
|
|
144
|
+
this._visibilityChangeFunc = (e) => {
|
|
145
|
+
// 检查是否是当前实例的事件
|
|
146
|
+
if (this.paused) return;
|
|
147
|
+
// 处理逻辑
|
|
148
|
+
};
|
|
149
|
+
document.addEventListener("visibilitychange", this._visibilityChangeFunc);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
destroy() {
|
|
153
|
+
document.removeEventListener("visibilitychange", this._visibilityChangeFunc);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
2. **事件命名空间**:为每个实例添加唯一标识
|
|
159
|
+
```typescript
|
|
160
|
+
class ArSdk {
|
|
161
|
+
private instanceId: string;
|
|
162
|
+
|
|
163
|
+
constructor(config: Config) {
|
|
164
|
+
this.instanceId = `sdk_${Date.now()}_${Math.random()}`;
|
|
165
|
+
|
|
166
|
+
// 使用命名空间事件
|
|
167
|
+
AROptionsManager.instance.on(`mirrorChange.${this.instanceId}`, this._handleMirrorChange);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
destroy() {
|
|
171
|
+
AROptionsManager.instance.off(`mirrorChange.${this.instanceId}`, this._handleMirrorChange);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 4. DOM操作冲突
|
|
179
|
+
|
|
180
|
+
### 问题描述
|
|
181
|
+
多个SDK实例操作同一个DOM元素,或者创建具有相同ID的DOM元素。
|
|
182
|
+
|
|
183
|
+
### 常见形式
|
|
184
|
+
```typescript
|
|
185
|
+
// 创建固定ID的元素
|
|
186
|
+
this.canvas = document.createElement("canvas");
|
|
187
|
+
this.canvas.id = "ar-renderer-canvas";
|
|
188
|
+
|
|
189
|
+
// 添加到body
|
|
190
|
+
document.body.appendChild(this.canvas);
|
|
191
|
+
|
|
192
|
+
// 查找固定ID的元素
|
|
193
|
+
const container = document.getElementById(containerId);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 问题场景
|
|
197
|
+
多个SDK实例创建相同ID的元素,导致DOM查询返回错误的元素,或者样式、事件绑定混乱。
|
|
198
|
+
|
|
199
|
+
### 解决方案
|
|
200
|
+
1. **唯一ID生成**:为每个实例生成唯一ID
|
|
201
|
+
```typescript
|
|
202
|
+
class ArSdk {
|
|
203
|
+
private instanceId: string;
|
|
204
|
+
|
|
205
|
+
constructor(config: Config) {
|
|
206
|
+
this.instanceId = `sdk_${Date.now()}_${Math.random()}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
createCanvas() {
|
|
210
|
+
const canvas = document.createElement("canvas");
|
|
211
|
+
canvas.id = `ar-renderer-canvas-${this.instanceId}`;
|
|
212
|
+
return canvas;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
2. **容器隔离**:每个SDK实例使用独立的容器
|
|
218
|
+
```typescript
|
|
219
|
+
class ArSdk {
|
|
220
|
+
private container: HTMLElement;
|
|
221
|
+
|
|
222
|
+
constructor(config: Config, containerId: string) {
|
|
223
|
+
this.container = document.getElementById(containerId);
|
|
224
|
+
if (!this.container) {
|
|
225
|
+
throw new Error(`Container with id ${containerId} not found`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
createCanvas() {
|
|
230
|
+
const canvas = document.createElement("canvas");
|
|
231
|
+
this.container.appendChild(canvas);
|
|
232
|
+
return canvas;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 5. Web Worker共享
|
|
240
|
+
|
|
241
|
+
### 问题描述
|
|
242
|
+
多个SDK实例共享同一个Web Worker,导致消息处理混乱。
|
|
243
|
+
|
|
244
|
+
### 常见形式
|
|
245
|
+
```typescript
|
|
246
|
+
// 全局Worker实例
|
|
247
|
+
let detectWorker: Worker;
|
|
248
|
+
|
|
249
|
+
// 多个实例使用同一个Worker
|
|
250
|
+
if (!detectWorker) {
|
|
251
|
+
detectWorker = new Worker('/detect.worker.js');
|
|
252
|
+
}
|
|
253
|
+
detectWorker.postMessage({ command: "init", options });
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 问题场景
|
|
257
|
+
多个SDK实例向同一个Worker发送消息,Worker无法区分消息来源,导致响应发送给错误的实例。
|
|
258
|
+
|
|
259
|
+
### 解决方案
|
|
260
|
+
1. **实例级Worker**:每个SDK实例创建自己的Worker
|
|
261
|
+
```typescript
|
|
262
|
+
class ArSdk {
|
|
263
|
+
private worker: Worker;
|
|
264
|
+
|
|
265
|
+
constructor(config: Config) {
|
|
266
|
+
this.worker = new Worker('/detect.worker.js');
|
|
267
|
+
this.worker.onmessage = this._handleWorkerMessage.bind(this);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
destroy() {
|
|
271
|
+
this.worker.terminate();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
2. **消息路由**:在Worker中添加消息路由机制
|
|
277
|
+
```typescript
|
|
278
|
+
// 在Worker中
|
|
279
|
+
const messageHandlers = new Map();
|
|
280
|
+
|
|
281
|
+
self.onmessage = function(e) {
|
|
282
|
+
const { instanceId, command, data } = e.data;
|
|
283
|
+
const handler = messageHandlers.get(instanceId);
|
|
284
|
+
if (handler) {
|
|
285
|
+
handler({ command, data });
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// 在SDK实例中
|
|
290
|
+
class ArSdk {
|
|
291
|
+
private instanceId: string;
|
|
292
|
+
|
|
293
|
+
constructor(config: Config) {
|
|
294
|
+
this.instanceId = `sdk_${Date.now()}_${Math.random()}`;
|
|
295
|
+
|
|
296
|
+
// 注册消息处理器
|
|
297
|
+
detectWorker.postMessage({
|
|
298
|
+
command: "register",
|
|
299
|
+
instanceId: this.instanceId
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 6. 资源加载冲突
|
|
308
|
+
|
|
309
|
+
### 问题描述
|
|
310
|
+
多个SDK实例同时加载相同资源,导致资源冲突或重复加载。
|
|
311
|
+
|
|
312
|
+
### 常见形式
|
|
313
|
+
```typescript
|
|
314
|
+
// 全局资源缓存
|
|
315
|
+
const resourceCache = new Map();
|
|
316
|
+
|
|
317
|
+
// 全局加载状态
|
|
318
|
+
let isLoading = false;
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 问题场景
|
|
322
|
+
多个SDK实例同时加载相同资源,可能导致资源被多次加载,或者加载状态混乱。
|
|
323
|
+
|
|
324
|
+
### 解决方案
|
|
325
|
+
1. **实例级资源管理**:每个SDK实例管理自己的资源
|
|
326
|
+
```typescript
|
|
327
|
+
class ArSdk {
|
|
328
|
+
private resourceCache: Map<string, any> = new Map();
|
|
329
|
+
|
|
330
|
+
async loadResource(url: string) {
|
|
331
|
+
if (this.resourceCache.has(url)) {
|
|
332
|
+
return this.resourceCache.get(url);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const resource = await fetch(url);
|
|
336
|
+
this.resourceCache.set(url, resource);
|
|
337
|
+
return resource;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
2. **共享资源池**:使用引用计数管理共享资源
|
|
343
|
+
```typescript
|
|
344
|
+
class ResourcePool {
|
|
345
|
+
private static instance: ResourcePool;
|
|
346
|
+
private resources = new Map<string, { data: any, refCount: number }>();
|
|
347
|
+
|
|
348
|
+
static getInstance(): ResourcePool {
|
|
349
|
+
if (!ResourcePool.instance) {
|
|
350
|
+
ResourcePool.instance = new ResourcePool();
|
|
351
|
+
}
|
|
352
|
+
return ResourcePool.instance;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async getResource(url: string): Promise<any> {
|
|
356
|
+
if (this.resources.has(url)) {
|
|
357
|
+
const resource = this.resources.get(url);
|
|
358
|
+
resource.refCount++;
|
|
359
|
+
return resource.data;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const data = await fetch(url);
|
|
363
|
+
this.resources.set(url, { data, refCount: 1 });
|
|
364
|
+
return data;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
releaseResource(url: string): void {
|
|
368
|
+
if (this.resources.has(url)) {
|
|
369
|
+
const resource = this.resources.get(url);
|
|
370
|
+
resource.refCount--;
|
|
371
|
+
if (resource.refCount <= 0) {
|
|
372
|
+
this.resources.delete(url);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## 7. 异步操作竞态条件
|
|
382
|
+
|
|
383
|
+
### 问题描述
|
|
384
|
+
多个SDK实例同时执行异步操作,导致操作结果互相覆盖。
|
|
385
|
+
|
|
386
|
+
### 常见形式
|
|
387
|
+
```typescript
|
|
388
|
+
// 全局异步操作状态
|
|
389
|
+
let isInitializing = false;
|
|
390
|
+
|
|
391
|
+
// 全局异步操作结果
|
|
392
|
+
let initResult: any;
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 问题场景
|
|
396
|
+
多个SDK实例同时初始化,可能导致初始化状态混乱,或者一个实例的初始化结果覆盖另一个实例的结果。
|
|
397
|
+
|
|
398
|
+
### 解决方案
|
|
399
|
+
1. **实例级异步状态**:每个SDK实例管理自己的异步状态
|
|
400
|
+
```typescript
|
|
401
|
+
class ArSdk {
|
|
402
|
+
private isInitializing = false;
|
|
403
|
+
private initPromise: Promise<void> | null = null;
|
|
404
|
+
|
|
405
|
+
async init(): Promise<void> {
|
|
406
|
+
if (this.isInitializing) {
|
|
407
|
+
return this.initPromise;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
this.isInitializing = true;
|
|
411
|
+
this.initPromise = this._doInit();
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
await this.initPromise;
|
|
415
|
+
} finally {
|
|
416
|
+
this.isInitializing = false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private async _doInit(): Promise<void> {
|
|
421
|
+
// 初始化逻辑
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 8. 定时器和动画帧冲突
|
|
429
|
+
|
|
430
|
+
### 问题描述
|
|
431
|
+
多个SDK实例使用全局定时器或动画帧,导致性能问题或逻辑混乱。
|
|
432
|
+
|
|
433
|
+
### 常见形式
|
|
434
|
+
```typescript
|
|
435
|
+
// 全局定时器ID
|
|
436
|
+
let tickerId: number;
|
|
437
|
+
|
|
438
|
+
// 全局动画帧ID
|
|
439
|
+
let animationFrameId: number;
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### 问题场景
|
|
443
|
+
多个SDK实例都启动自己的动画循环,可能导致性能问题,或者一个实例停止动画影响其他实例。
|
|
444
|
+
|
|
445
|
+
### 解决方案
|
|
446
|
+
1. **实例级定时器**:每个SDK实例管理自己的定时器
|
|
447
|
+
```typescript
|
|
448
|
+
class ArSdk {
|
|
449
|
+
private animationFrameId: number | null = null;
|
|
450
|
+
|
|
451
|
+
startAnimation() {
|
|
452
|
+
if (this.animationFrameId) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const animate = () => {
|
|
457
|
+
// 动画逻辑
|
|
458
|
+
this.animationFrameId = requestAnimationFrame(animate);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
this.animationFrameId = requestAnimationFrame(animate);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
stopAnimation() {
|
|
465
|
+
if (this.animationFrameId) {
|
|
466
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
467
|
+
this.animationFrameId = null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## 9. 配置覆盖
|
|
476
|
+
|
|
477
|
+
### 问题描述
|
|
478
|
+
多个SDK实例修改全局配置,导致配置互相覆盖。
|
|
479
|
+
|
|
480
|
+
### 常见形式
|
|
481
|
+
```typescript
|
|
482
|
+
// 全局配置对象
|
|
483
|
+
const globalConfig = {
|
|
484
|
+
width: 640,
|
|
485
|
+
height: 480,
|
|
486
|
+
mirror: false
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// 直接修改全局配置
|
|
490
|
+
globalConfig.mirror = true;
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### 问题场景
|
|
494
|
+
一个SDK实例修改全局配置,影响其他实例的行为。
|
|
495
|
+
|
|
496
|
+
### 解决方案
|
|
497
|
+
1. **实例级配置**:每个SDK实例维护自己的配置
|
|
498
|
+
```typescript
|
|
499
|
+
class ArSdk {
|
|
500
|
+
private config: SDKConfig;
|
|
501
|
+
|
|
502
|
+
constructor(config: SDKConfig) {
|
|
503
|
+
this.config = { ...defaultConfig, ...config };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
updateConfig(newConfig: Partial<SDKConfig>): void {
|
|
507
|
+
this.config = { ...this.config, ...newConfig };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## 10. 日志和上报冲突
|
|
515
|
+
|
|
516
|
+
### 问题描述
|
|
517
|
+
多个SDK实例使用全局日志或上报系统,导致日志混乱或上报数据错误。
|
|
518
|
+
|
|
519
|
+
### 常见形式
|
|
520
|
+
```typescript
|
|
521
|
+
// 全局日志配置
|
|
522
|
+
let logLevel = 'info';
|
|
523
|
+
|
|
524
|
+
// 全局上报函数
|
|
525
|
+
window._arCustomReport = reportor.report.bind(reportor);
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### 问题场景
|
|
529
|
+
多个SDK实例设置不同的日志级别,或者上报数据时无法区分来源实例。
|
|
530
|
+
|
|
531
|
+
### 解决方案
|
|
532
|
+
1. **实例级日志系统**:每个SDK实例使用自己的日志配置
|
|
533
|
+
```typescript
|
|
534
|
+
class ArSdk {
|
|
535
|
+
private logger: Logger;
|
|
536
|
+
private instanceId: string;
|
|
537
|
+
|
|
538
|
+
constructor(config: SDKConfig) {
|
|
539
|
+
this.instanceId = `sdk_${Date.now()}_${Math.random()}`;
|
|
540
|
+
this.logger = new Logger({
|
|
541
|
+
level: config.logLevel || 'info',
|
|
542
|
+
prefix: `[${this.instanceId}]`
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## 最佳实践总结
|
|
551
|
+
|
|
552
|
+
### 1. 避免全局状态
|
|
553
|
+
- 将所有状态封装在SDK实例中
|
|
554
|
+
- 避免使用全局变量和单例模式
|
|
555
|
+
- 使用依赖注入而非全局访问
|
|
556
|
+
|
|
557
|
+
### 2. 资源隔离
|
|
558
|
+
- 每个SDK实例使用独立的资源
|
|
559
|
+
- 为DOM元素、Worker等资源添加唯一标识
|
|
560
|
+
- 实现正确的资源清理机制
|
|
561
|
+
|
|
562
|
+
### 3. 事件隔离
|
|
563
|
+
- 使用命名空间区分不同实例的事件
|
|
564
|
+
- 在事件处理函数中检查实例关联
|
|
565
|
+
- 正确移除事件监听器
|
|
566
|
+
|
|
567
|
+
### 4. 异步操作管理
|
|
568
|
+
- 每个SDK实例管理自己的异步状态
|
|
569
|
+
- 避免全局异步操作状态
|
|
570
|
+
- 正确处理异步操作的竞态条件
|
|
571
|
+
|
|
572
|
+
### 5. 配置隔离
|
|
573
|
+
- 每个SDK实例维护自己的配置
|
|
574
|
+
- 避免修改全局配置
|
|
575
|
+
- 提供配置合并和更新机制
|
|
576
|
+
|
|
577
|
+
### 6. 生命周期管理
|
|
578
|
+
- 实现正确的初始化和销毁流程
|
|
579
|
+
- 确保所有资源在销毁时被正确释放
|
|
580
|
+
- 避免内存泄漏
|
|
581
|
+
|
|
582
|
+
## 检查清单
|
|
583
|
+
|
|
584
|
+
在开发支持多实例的SDK时,请检查以下问题:
|
|
585
|
+
|
|
586
|
+
- [ ] 是否使用了单例模式?
|
|
587
|
+
- [ ] 是否有全局变量或全局状态?
|
|
588
|
+
- [ ] 是否直接在全局对象上添加事件监听?
|
|
589
|
+
- [ ] 是否创建具有相同ID的DOM元素?
|
|
590
|
+
- [ ] 是否共享Web Worker?
|
|
591
|
+
- [ ] 是否有全局资源缓存?
|
|
592
|
+
- [ ] 是否有全局异步操作状态?
|
|
593
|
+
- [ ] 是否使用全局定时器或动画帧?
|
|
594
|
+
- [ ] 是否修改全局配置?
|
|
595
|
+
- [ ] 是否使用全局日志或上报系统?
|
|
596
|
+
|
|
597
|
+
通过解决这些问题,可以确保SDK支持多实例而不会出现互相影响的情况。
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## 当前SDK中发现的问题
|
|
602
|
+
|
|
603
|
+
基于对当前代码的分析,发现以下可能导致多实例互相影响的问题:
|
|
604
|
+
|
|
605
|
+
1. **单例模式**:
|
|
606
|
+
- `AROptionsManager.instance`
|
|
607
|
+
- `QualityManager.instance`
|
|
608
|
+
- `GesturePipe.instance`
|
|
609
|
+
- `TextureRefManager.instance`
|
|
610
|
+
|
|
611
|
+
2. **全局状态**:
|
|
612
|
+
- `resolution` 变量
|
|
613
|
+
- `window._arCustomReport` 全局函数
|
|
614
|
+
|
|
615
|
+
3. **全局事件监听**:
|
|
616
|
+
- `document.addEventListener("visibilitychange")`
|
|
617
|
+
- `AROptionsManager.instance.on("mirrorChange")`
|
|
618
|
+
|
|
619
|
+
4. **DOM操作冲突**:
|
|
620
|
+
- 固定ID的canvas元素:`"ar-renderer-canvas"`
|
|
621
|
+
|
|
622
|
+
5. **Worker共享**:
|
|
623
|
+
- 全局Worker实例
|
|
624
|
+
|
|
625
|
+
这些问题需要按照上述解决方案进行修复,以确保SDK支持多实例而不会出现互相影响的情况。
|