我正在参加「掘金·启航计划」
操作流程:H5 中进行业务操作,会进行弹框预览 pdf 文件(react-pdf),进行签字;也会用弹框进行多次视频流截屏处理(canvas)。弹框组件用的是 antd-mobile 的 Modal 组件。
问题描述:在微信浏览器中,会直接强制刷新页面;在 ios 浏览器中,也会直接强制刷新页面;在谷歌模拟器中,浏览器直接崩溃了,看控制台之后,才知道是内存溢出了。
发现问题
在微信浏览器中,每次签字后,再截取视频流,几次操作后,点击“确定”按钮,会导致强制刷新页面。
在客户 IOS 手机的微信公众号中运行 H5 页面发现该问题,我们这边的测试机无法复现,复现步骤也是按他们的固定方法,增加了问题排查难度。只能让驻场测试人员帮忙验证、复现,但是中间的沟通交流啥的总会有差异,验证过程需要我们改了,发布到测试,他们再验证,导致效率很低。
最开始是在微信公众号中发现该问题的,发现微信会直接强制刷新页面。到微信公众号交流社区查了一下,有很多这种类似的问题,例如频繁切换摄像头进行拍照、使用微信的 chooseImage 等。具体文章,例如:webview 组件内调用拍照功能返回页面时造成页面刷新。但是没看见官方的解决回复。。。
- 问题原因:手机瞬时内存达到峰值,导致系统崩溃了,微信这边主动触发页面刷新重新加载了。如果不做刷新操作,那就会显示空白页了
- 解决尝试:
- 重启手机系统;
- 清除一点系统存储空间;
- 清除微信缓存;
- 用 canvas 截取视频流时,需要进行压缩处理;
复现问题
我们经过一段时间处理解决后,慢慢缩小问题范围,最终在我们手机复现出来了,而且简化了复现步骤。但是具体定位还不准确
在谷歌模拟器中复现了问题,但是浏览器的表现是直接崩溃了,然后排查浏览器的内存表现,发现“截屏”按钮的操作时间是 3-5s,存在明显卡顿,主要关注 canvas 截图导致的卡顿和崩溃问题
-
分析问题可能性:
- pdf 预览导致内存消耗
- canvas 截取视频流导致内存消耗
-
解决尝试
- 更改字体 font-family:解决 ios15 系统的手机使用 html2canvas 截图页面空白崩溃
- 释放 canvas 空间:复杂情况下 canvas 实践,性能问题和崩溃问题的定位与解决
以上处理均未解决
3. 定位问题
最终,用最简单的步骤复现出了问题,是 modal 预览 pdf 弹框的问题。
最开始以为是 pdf 预览的问题,最后才定位在 modal 弹框,虽然关闭销毁了,但是占据的内存空间没有释放,感觉是假的关闭。
最终改了一行代码,解决了问题,如下所示:{show ? children : <></>}
import React, { useState, useImperativeHandle, forwardRef, useRef, useEffect } from 'react'
import styles from './index.less'
import { Modal, Icon } from 'antd-mobile'
interface iprops {
title?: string
closable?: boolean
children: React.ReactElement | Array<React.ReactElement>
hiddenCloseButton?: boolean
footer?: React.ReactElement | Array<React.ReactElement>
openCallBack?: (arg1: any) => void
closeCallBack?: () => void
className?: string
}
interface iDetailRef {
current: {
dialogShowOrHide: (arg: boolean) => void
container: {
current: HTMLElement
}
}
}
function detailDialog(props: iprops, ref: iDetailRef) {
const { title, children, closable, hiddenCloseButton, footer, openCallBack, className, closeCallBack } = props
const [show, dialogShowOrHide] = useState(false)
const container = useRef(null)
// 暴露给外部的方法
useImperativeHandle(ref, () => ({
dialogShowOrHide,
container
}))
useEffect(() => {
// 打开弹框后的回调事件
if (show) {
openCallBack && openCallBack(container)
}
}, [show])
// 弹框默认关闭事件
function handleModalClose() {
if (closable) {
dialogShowOrHide(false)
}
}
// 自定义关闭按钮事件
function handleClose() {
closeCallBack && closeCallBack()
dialogShowOrHide(false)
}
return (
<>
<Modal
className={`${className} ${styles.detail_modal}`}
visible={show}
title={title}
transparent
onClose={handleModalClose}
closable={closable}
popup
animationType="slide-up"
>
<div className={styles.detail_modal_widget}>
{
// 关闭按钮
!hiddenCloseButton ? (
<div className={styles.deatil_modal_close} onClick={handleClose}>
<Icon type="cross" color="#999999" size="md" />
</div>
) : (
<></>
)
}
<div className={styles.detail_modal_content} ref={container}>
{/* 最终就是改了一行代码,不显示时,销毁内容 */}
{show ? children : <></>}
</div>
{/* footer内容 */}
{footer ? <div className={styles.detail_modal_footer}>{footer}</div> : <></>}
</div>
</Modal>
</>
)
}
export default forwardRef(detailDialog)