Skip to content

Commit 5f5f8d2

Browse files
committed
feat: webview白屏的问题查找和修复
1 parent 23cde1c commit 5f5f8d2

File tree

4 files changed

+259
-0
lines changed

4 files changed

+259
-0
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# webview白屏的问题查找和修复
2+
3+
## 介绍
4+
有运营反应,有部分用户访问我们的小程序后,打开 `webview` 页面,显示白屏,删除小程序重新进入也未解决问题。
5+
6+
## 初步判断
7+
这里有俩个关键点
8+
9+
1. 仅部分用户出现白屏
10+
1. 以前是能正常访问
11+
12+
初步判断是代码执行的兼容性问题
13+
14+
## 顺藤摸瓜
15+
刚巧团队内部有个同事的 `iphone 7` 能复现这个问题, 于是我们首先进行了 `charles` 抓包, 分别拿到了正常手机和白屏手机的请求, 对比发现俩者从服务端拿到的资源请求包括文件名 `hash` 是一致的。
16+
17+
![webview_charles_1](/Images/Essay/webview白屏的问题查找和修复/webview_charles_1.png "webview_charles_1")
18+
19+
到了这里我们基本可以确定不同设备上拿到的代码是一致的,出问题的是兼容性,在不同机型上渲染执行可能出错导致的白屏。
20+
21+
前面提到用户以前是能正常访问 `webview` 的,也就是在某一段时间我们的代码编译后导致的白屏.
22+
23+
于是我接下来的重点放到了查找问题代码,通过切换月份分支,从 `5,6,7` 月分别调试缩小到7月范围,然后按照 `commit` 减半调试缩小范围,确定了包含问题代码的 `commit` 记录
24+
25+
![webview_commit_1](/Images/Essay/webview白屏的问题查找和修复/webview_commit_1.png "webview_commit_1")
26+
27+
在多次注释更新的包后,最终确定了问题定位出在 `package.json` 升级的新包 `@muyi086/var-type` 上.
28+
29+
![webview_commit_2](/Images/Essay/webview白屏的问题查找和修复/webview_commit_2.png "webview_commit_2")
30+
31+
## 出问题的代码分析
32+
1. 这是老版本的代码,使用 `js` 实现
33+
34+
::: code-group
35+
```js [index.js 源码]
36+
/**
37+
* @Description: js变量类型判断
38+
* @Author: MuYi086
39+
40+
* @Blog: https://github.com/MuYi086/blog
41+
* @Date: 2021/04/11 12:30
42+
*/
43+
class VarType {
44+
constructor () {
45+
this.typeList = ['Null', 'Undefined', 'Object', 'Array', 'ArrayBuffer', 'String', 'Number', 'Boolean', 'Function', 'RegExp', 'Date', 'FormData', 'File', 'Blob', 'URLSearchParams', 'Set', 'WeakSet', 'Map', 'WeakMap']
46+
this.init()
47+
}
48+
49+
/**
50+
* 判断变量类型
51+
* @param {string} value
52+
* @returns lowercase string
53+
*/
54+
type (value) {
55+
const s = Object.prototype.toString.call(value)
56+
return s.match(/\[object (.*?)\]/)[1].toLowerCase()
57+
}
58+
59+
/**
60+
* 增加判断类型数据方法
61+
*/
62+
init () {
63+
this.typeList.forEach((t) => {
64+
this['is' + t] = (o) => {
65+
return this.type(o) === t.toLowerCase()
66+
}
67+
})
68+
}
69+
70+
/**
71+
* isBuffer
72+
* @param {any} val
73+
* @returns boolean
74+
*/
75+
isBuffer (val) {
76+
return val !== null && !this.isUndefined(val) && val.constructor !== null && !this.isUndefined(val.constructor) && this.isFunction(val.constructor.isBuffer) && val.constructor.isBuffer(val)
77+
}
78+
79+
/**
80+
* isStream
81+
* @param {any} val
82+
* @returns boolean
83+
*/
84+
isStream (val) {
85+
return this.isObject(val) && this.isFunction(val.pipe)
86+
}
87+
}
88+
// 使用 varType["isNull"](null)等
89+
module.exports = new VarType()
90+
```
91+
```js [index.min.cjs 美化后]
92+
var e = class {
93+
constructor() {
94+
this.typeList = ["Null", "Undefined", "Object", "Array", "ArrayBuffer", "String", "Number", "Boolean", "Function", "RegExp", "Date", "FormData", "File", "Blob", "URLSearchParams", "Set", "WeakSet", "Map", "WeakMap"],
95+
this.init()
96+
}
97+
type(t) {
98+
return Object.prototype.toString.call(t).match(/\[object (.*?)\]/)[1].toLowerCase()
99+
}
100+
init() {
101+
this.typeList.forEach(t = >{
102+
this["is" + t] = r = >this.type(r) === t.toLowerCase()
103+
})
104+
}
105+
isBuffer(t) {
106+
return t !== null && !this.isUndefined(t) && t.constructor !== null && !this.isUndefined(t.constructor) && this.isFunction(t.constructor.isBuffer) && t.constructor.isBuffer(t)
107+
}
108+
isStream(t) {
109+
return this.isObject(t) && this.isFunction(t.pipe)
110+
}
111+
};
112+
module.exports = new e;
113+
```
114+
:::
115+
116+
1. 这是升级后新版本的代码,使用 `ts` 实现
117+
118+
::: code-group
119+
```ts [index.ts]
120+
/**
121+
* @Description: js变量类型判断
122+
* @Author: MuYi086
123+
124+
* @Blog: https://github.com/MuYi086/blog
125+
* @Date: 2021/04/11 12:30
126+
*/
127+
class VarType {
128+
private typeList: string[]
129+
private static _instance: VarType | null = null
130+
constructor() {
131+
this.typeList = ['Null', 'Undefined', 'Object', 'Array', 'ArrayBuffer', 'String', 'Number', 'Boolean', 'Function', 'RegExp', 'Date', 'FormData', 'File', 'Blob', 'URLSearchParams', 'Set', 'WeakSet', 'Map', 'WeakMap']
132+
this.init()
133+
}
134+
static get instance(): VarType {
135+
if (!VarType._instance) {
136+
VarType._instance = new VarType()
137+
}
138+
return VarType._instance
139+
}
140+
/**
141+
* 判断变量类型
142+
* @param {string} value
143+
* @returns lowercase string
144+
*/
145+
private type (value: any): string {
146+
const s = Object.prototype.toString.call(value)
147+
return s.match(/\[object (.*?)\]/)[1].toLowerCase()
148+
}
149+
150+
/**
151+
* 增加判断类型数据方法
152+
*/
153+
private init(): void {
154+
const self = this
155+
this.typeList.forEach((t: string) => {
156+
Object.defineProperty(VarType.prototype, `is${t}`, {
157+
value: function (o: any) {
158+
return self.type(o) === t.toLowerCase()
159+
},
160+
writable: true,
161+
configurable: true
162+
})
163+
})
164+
}
165+
/**
166+
* isBuffer
167+
* @param {any} val
168+
* @returns boolean
169+
*/
170+
static isBuffer(val: any): boolean {
171+
return val !== null && (VarType as any).isUndefined(val) && val.constructor !== null && (VarType as any).isUndefined(val.constructor) && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val)
172+
}
173+
174+
/**
175+
* isStream
176+
* @param {any} val
177+
* @returns boolean
178+
*/
179+
static isStream(val: any): boolean {
180+
return (VarType as any).isObject(val) && (VarType as any).isFunction(val.pipe)
181+
}
182+
}
183+
// 使用 varType["isNull"](null)等
184+
export const varType = VarType.instance
185+
```
186+
```js [inedex.min.cjs 美化后]
187+
var s = Object.defineProperty;
188+
var o = Object.getOwnPropertyDescriptor;
189+
var c = Object.getOwnPropertyNames;
190+
var u = Object.prototype.hasOwnProperty;
191+
var p = (e, t) = >{
192+
for (var n in t) s(e, n, {
193+
get: t[n],
194+
enumerable: !0
195+
})
196+
},
197+
y = (e, t, n, i) = >{
198+
if (t && typeof t == "object" || typeof t == "function") for (let r of c(t)) ! u.call(e, r) && r !== n && s(e, r, {
199+
get: () = >t[r],
200+
enumerable: !(i = o(t, r)) || i.enumerable
201+
});
202+
return e
203+
};
204+
var f = e = >y(s({},
205+
"__esModule", {
206+
value: !0
207+
}), e);
208+
var b = {};
209+
p(b, {
210+
varType: () = >l
211+
});
212+
module.exports = f(b);
213+
var a = class e {
214+
typeList;
215+
static _instance = null;
216+
constructor() {
217+
this.typeList = ["Null", "Undefined", "Object", "Array", "ArrayBuffer", "String", "Number", "Boolean", "Function", "RegExp", "Date", "FormData", "File", "Blob", "URLSearchParams", "Set", "WeakSet", "Map", "WeakMap"],
218+
this.init()
219+
}
220+
static get instance() {
221+
return e._instance || (e._instance = new e),
222+
e._instance
223+
}
224+
type(t) {
225+
return Object.prototype.toString.call(t).match(/\[object (.*?)\]/)[1].toLowerCase()
226+
}
227+
init() {
228+
let t = this;
229+
this.typeList.forEach(n = >{
230+
Object.defineProperty(e.prototype, `is$ {
231+
n
232+
}`, {
233+
value: function(i) {
234+
return t.type(i) === n.toLowerCase()
235+
},
236+
writable: !0,
237+
configurable: !0
238+
})
239+
})
240+
}
241+
static isBuffer(t) {
242+
return t !== null && e.isUndefined(t) && t.constructor !== null && e.isUndefined(t.constructor) && typeof t.constructor.isBuffer == "function" && t.constructor.isBuffer(t)
243+
}
244+
static isStream(t) {
245+
return e.isObject(t) && e.isFunction(t.pipe)
246+
}
247+
},
248+
l = a.instance;
249+
```
250+
:::
251+
252+
对比发现 `ts` 的代码实现使用了单例模式,并且内部使用了`defineProperty`, `getOwnPropertyDescriptor` 等属性,这也是导致某些机型上不执行代码导致白屏的原因, 因为在编译时没有提供 `polyfill` 支持旧版浏览器。
253+
254+
## 解决方案
255+
由于线上 `bug` 需要紧急修复,所以当前先简单处理,发布一个新的 `npm``js` 版本替换原先的 `ts` 版本, 然后紧急发版。以后有时间会优化代码,在 `ts` 版本上尝试修复编译导致的问题。
256+
257+
258+
259+
Loading
Loading
Loading

0 commit comments

Comments
 (0)