跨平台应用开发进阶(五十)uni-app ios web-view嵌套H5项目白屏问题分析及解决

一、前言

应用uni-app框架开发好APP上架使用过程中,发现应用经过长时间由后台切换至前台时,通过webview方式嵌套的H5页面发生白屏现象。

二、问题分析

任何手机设备上,当手机内存不足时,os都会回收资源。一般是先回收后台打开的资源。如果当前应用占用的资源过高,当前应用也有可能崩溃。尤其是在调用摄像头点击拍照时,手机内存占用会达到一个峰值,此时较容易出问题。

有关内存管理,详参博文《安全生产:内存溢出和内存泄漏》。

在iOS上,当内存不足时,根据uiwebviewwkwebview的不同,它自身有不同的回收策略。

如果是uiwebview的app(常见于5+app),内存不足时整个app会崩溃,即闪退。

如果是wkwebview的app(uni-app和wap2app在iOS上默认就是wkwebview),内存不足时,单个wkwebview会崩溃。也就是所谓的应用还在,而页面白屏。

这个问题在所有使用wkwebview的应用都会出现,比如微信的公众号网页里也存在。在微信小程序里,它做了一个自动恢复手段,可以让jscore存储数据状态,崩溃的wkwebview自动恢复。所以在遇到问题时,会白一下然后恢复渲染。

三、解决方案

  • uni-app因为引入了独立的jscore处理数据状态,jscore不会崩溃,所以官方采用了和微信小程序一致的策略,补充自动的白屏恢复能力。亲测使用 HBuilder 3.6.4.20220922 并无白屏自动恢复功能,怀疑是HBuilder版本问题!

  • uni-app中也可以使用nvue来避免这个问题,nvue页面不会出现内存不足引发的白屏崩溃。

3.1 nvue 页面替代 vue 页面

nvue文件webview使用方式如下:

javascript 复制代码
//nvue 中的webview需要自行设置宽高否则无法展示<template>
    <view>
        <web-view ref="webview" src="/hybrid/html/local.html" style="width: 500px;height: 600px;" @onPostMessage="getMessage"></web-view>
    </view>
</template>

<script>
    export default {
        data(){
            return {
            }
        },
        onLoad() {
            setTimeout(()=>{
                this.handlePostMessage('测试传参')
            },200)
        },
        methods: {
            handlePostMessage(res) {
                this.$refs.webview.evalJs(`handleMessage()`);  
            },  
            getMessage(e) {
                uni.showModal({
                    content: JSON.stringify(e.detail),
                    showCancel: false
                })
            }
        }
    }
</script>

h5页面内容如下:

javascript 复制代码
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>本地网页</title>
        <style type="text/css">
            .btn {
                display: block;
                margin: 20px auto;
                padding: 5px;
                background-color: #007aff;
                border: 0;
                color: #ffffff;
                height: 40px;
                width: 200px;
            }

            .btn-red {
                background-color: #dd524d;
            }

            .btn-yellow {
                background-color: #f0ad4e;
            }

            .desc {
                padding: 10px;
                color: #999999;
            }
        </style>
    </head>
    <body>
        <p class="desc">web-view 组件加载本地 html 示例,仅在 App 环境下生效。点击下列按钮,跳转至其它页面。</p>
        <div class="btn-list">
            <button class="btn" type="button" data-action="navigateTo">navigateTo</button>
            <button class="btn" type="button" data-action="redirectTo">redirectTo</button>
            <button class="btn" type="button" data-action="navigateBack">navigateBack</button>
            <button class="btn" type="button" data-action="reLaunch">reLaunch</button>
            <button class="btn" type="button" data-action="switchTab">switchTab</button>
        </div>
        <p class="desc" id="lizhao">网页向应用发送消息。注意:小程序端应用会在此页面后退时接收到消息。</p>
        <div class="btn-list">
            <button class="btn btn-red" type="button" id="postMessage">postMessage</button>
        </div>
        <!-- uni 的 SDK -->
        <script type="text/javascript" src="https://unpkg.com/@dcloudio/uni-webview-js@0.0.1/index.js"></script>
        <script type="text/javascript">
            
            window.handleMessage=function(data){
                alert('传来的参数'+data)
            }    
            document.addEventListener('UniAppJSBridgeReady', function() {
                
                document.querySelector('.btn-list').addEventListener('click', function(evt) {
                    var target = evt.target;
                    if (target.tagName === 'BUTTON') {
                        var action = target.getAttribute('data-action');
                        switch (action) {
                            case 'switchTab':
                                uni.switchTab({
                                    url: '/pages/tabBar/API/API'
                                });
                                break;
                            case 'reLaunch':
                                uni.reLaunch({
                                    url: '/pages/tabBar/API/API'
                                });
                                break;
                            case 'navigateBack':
                                uni.navigateBack({
                                    delta: 1
                                });
                                break;
                            default:
                                uni[action]({
                                    url: '/pages/component/button/button'
                                });
                                break;
                        }
                    }
                });
                document.querySelector("#postMessage").addEventListener('click', function() {
                    uni.postMessage({
                        data: {
                            action: 'message888888'
                        }
                    });
                })
            });
        </script>
    </body>
</html>

注意⚠️:uni-app 中的 nvue 页面问题 nvue 页面不使用 webview 渲染,但其中的web-view组件说明如下:

  • nvueweex 组件模式weex模式下的web-view组件是weex自己实现的,它目前仍然使用UIWebview。官方会追踪weex的升级。

  • nvueuni-app组件模式web-view组件使用WKWebview,不可修改为uiWebview

3.2 白屏检测刷新

3.2.1 自动刷新

  1. 需要一个全局挂载的工具类,Vue.prototype.$utils = utils
  2. 在需要使用的页面(一般为tab页)最外层需要设置为同一个class名称;
  3. onshow方法调用;
javascript 复制代码
let pageList = {};
const utils = {
    reloadCurrentPage: function(_self, isTab = true) {
        // #ifdef APP-PLUS
        // 获取当前路由及传参,以备页面刷新之用
        var route = _self.$scope.route;
        var data = _self.$scope.options && _self.$scope.options.data;
        var url = '/' + route;
        if (data) {
            url = '/' + route + '?data=' + data;
        }
        var isRecovery = true; // 页面刷新标识
        let newTime = Date.now();
        if (pageList[url]) {
            const query = uni.createSelectorQuery().in(_self);
            //这里select()中替换为自己的样式class名称
            query.select('.container').fields({size:true}, data => {
                isRecovery = false; // 重置页面刷新标识
            }).exec();
            setTimeout(() => {
            	// 页面白屏,需触发刷新机制
                if (isRecovery) {
                    //如果获取不到节点
                    //确保只刷新一次
                    if (newTime - pageList[url] > 3000) {
                        //超过3秒才重新刷新,这里设置几秒就行,目的是防止无限刷新
                        //因为刷新后页面肯定会出来,但是立马再次调用该方法不一定能获取节点(DOM树未必构建完毕)
                        pageList[url] = newTime;
                        // 若为tab标签栏位
                        if (isTab) {
                            uni.reLaunch({
                                url
                            })
                        } else {
                        	// 若为页面
                            uni.redirectTo({
                                url
                            })
                        }
                    }
                }
            }, 600)
        } else {
        	// 页面正常,记录当前时间
            pageList[url] = newTime;
        }
        // if (plus.os.name === 'iOS') {

        // }
        // #endif
    }
}

3.2.2 手动刷新

webview页面提供按钮以支持用户手动刷新,

手动刷新实现逻辑如下:

手动刷新按钮在检测到白屏事件发生时显示:

javascript 复制代码
query.select('.container').fields({size:true}, data => {
     isShow = true; // 展示刷新按钮
 }).exec();

应用3.2.1小节提供的刷新方法,当点击刷新按钮调用刷新方式。

3.3 总结

在前端减少内存使用,最重要的就是图片渲染,尤其是大图片。

在页面上不要渲染多张大图,比如从摄像头或相册选择多张图,并缩放尺寸渲染在页面上,虽然肉眼看起来手机屏幕上是几张小图,但实际上是多张大图只是被缩小,这种情况非常耗费内存。一张图片3m,9张这样的大图同时渲染到屏幕上,什么手机都受不了。

一个缩略图控制在几k或十几k,才是合理的。

详情页面展现多张大图并不受影响。如果图片滚动在屏幕外,os内存不足时也会自动收回这些屏幕外图片占用的渲染资源,最吃资源的就是同屏渲染多张大图。

四、拓展阅读

相关推荐
y先森2 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy2 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189112 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿3 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡4 小时前
commitlint校验git提交信息
前端
虾球xz5 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇5 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒5 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员5 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐5 小时前
前端图像处理(一)
前端