一次uniapp问题排查

最近在开发uniapp,虽然第一次写,但是有vue开发经验,也没觉得有啥不一样的,上手还是比较简单的,但是明显我还是低估了uniapp的复杂度,这东西难点不在于业务开发,而是各个不同机型的适配,相比于传统的web开发,还是繁琐了很多。

业务开发中遇到了一个蛮有趣的问题,这里总结下。

背景与环境

首先简单的描述下背景:就是在app登出的时候,发现了苹果6Plus在退出登录时表现不一样的地方,H5和安卓以及苹果16、ipad都表现正常,就是点击退出登录按钮,弹出一个弹窗,点击退出登录,跳转到到登录页,很常规的一个操作了,但是在苹果6Plus机型上,出现了异常,弹窗点击不动,页面出现了卡死,只能杀死应用重新进入。

这里贴下相关代码:

php 复制代码
message.comform({
    msg:"确定退出登录嘛",
    title:"退出登录"
}).then(res => {
    await userStore.logout()
    uni.redirectTo({
        url:'/pages/login/index'
    })
})

这里就是清除下userStore中的状态,然后就跳转路由到登录页。

这里总结下uniapp路由跳转的方式

  1. uni.navigateTo 保留当掐你页面,跳转到某个应用内的页面
php 复制代码
// 带参数跳转
uni.navigateTo({
  url: '/pages/detail/detail?id=123&name=test'
})
​
// 对象参数(需要编码)
uni.navigateTo({
  url: '/pages/detail/detail?data=' + encodeURIComponent(JSON.stringify({
    id: 123,
    name: 'test'
  }))
})
  1. uni.redirectTo 关闭当前页面 跳转到应用内的某个页面
css 复制代码
uni.redirectTo({
  url: '/pages/home/home'
})
  1. uni.reLaunch 关闭所有页面,打开到应用内的某个页面
css 复制代码
uni.reLaunch({
  url: '/pages/index/index'
})
  1. uni.switchTab 跳转到tabBar页面,并关闭其他所有非tabBar页面
css 复制代码
uni.switchTab({
  url: '/pages/home/home'
})
  1. uni.navigateBack 返回上一级或者多级页面
scss 复制代码
// 返回上一页
uni.navigateBack()
​
// 返回多级页面
uni.navigateBack({
  delta: 2  // 返回2级
})

其中logout也是比较简单了,代码如下:

scss 复制代码
const layout = async () => { 
    removeToken()
    resetState()
}

也就是登录前移除token和store中的一些状态,看代码没有任何问题,但是就是在苹果6Plus中出现卡顿,很费解,光看代码看不出任何问题,就带着问题问了下gpt,知道了WKWebView 导航队列锁这个东西。

WKWebView 导航队列锁

WKWebView 导航队列锁定是 WebKit 内核中的一个线程安全机制,旨在防止在页面加载过程中的竞态条件。但在老版本中,这个机制实现得过于保守,导致并发导航操作容易被阻塞。

主要有这么几个场景会触发

  1. 快速连续导航调用

    javascript 复制代码
    // 微观时序问题 - 导航竞争
    const startTime = performance.now();
    ​
    // 导航请求1 - 第0ms
    uni.navigateTo({ url: '/pageA' });
    ​
    // 在导航1还未完成状态转换时...
    setTimeout(() => {
        // 导航请求2 - 第5ms (此时导航1可能还在 WKNavigationStateScheduled)
        uni.navigateTo({ url: '/pageB' });
    }, 5);
  2. 资源加载与导航竞争

    objectivec 复制代码
    // WebKit 内部资源加载时序
    - (void)startNavigation:(WKNavigation *)navigation {
        [self acquireNavigationLock]; // 获取导航锁
        
        // 开始加载主文档
        [self loadMainDocument];
        
        // 此时如果主文档中有同步资源请求
        // <script src="sync-script.js"></script>
        // 资源加载会阻塞导航锁释放
        
        [self releaseNavigationLock]; // 延迟释放!
    }
  3. js桥接与导航交互

    javascript 复制代码
    // uni-app 框架层可能的问题
    // 1. 页面生命周期钩子与导航竞争
    export default {
        onLoad() {
            // 在 onLoad 中执行耗时操作
            this.loadHeavyData(); // 阻塞导航完成
        },
        
        onShow() {
            // 触发 UI 更新,需要渲染锁
            this.startAnimation(); 
        }
    }
    ​

    这个实例中,onLoad的触发时机是页面首次创建时 , onShow触发是页面显示的时候会触发,当onLoad中执行loadHeavyData耗时操作时,等页面显示执行onShow中的startAnimation就会导致竞争资源,就会触发队列锁,导致页面卡死,比较好的方法就是让他们再一个方法中执行,然后将他们的步骤拆分下,比如这样:

    kotlin 复制代码
    onLoad() {
        // 阶段1: 立即执行 (导航锁持有期间)
        this.initUIState();
    ​
        // 阶段2: 延迟执行 (导航锁释放后)
        this.deferHeavyTask();
    ​
        // 阶段3: 空闲时执行
        this.idleNonCriticalTask();
    },
  4. css、js动画与js冲突

    ini 复制代码
    function startAnimation() {
        const element = document.querySelector('.animated');
        element.style.transform = 'translateX(0)';
        
        // 触发 CSS 动画
        requestAnimationFrame(() => {
            element.style.transform = 'translateX(100px)';
            
            // 在同一帧内触发导航
            uni.navigateTo({ url: '/next' }); // 危险!
        });
    }

然后回到项目相关代码,似乎也没有很大的计算量导致资源竞争,触发队列锁,看下gpt5优化后的代码:

javascript 复制代码
const clicking = ref(false)
const handleLogout = async () => {
    if(clicking.value) return
    click.value = false
    await message.confirm({msg:'确定退出登录?',title:'退出登录'})
    useStore.logOut()
    setTimeout(() => clicking.value=false,300)
}
javascript 复制代码
const delay = ms => new Promise(r => setTimeout(r,ms))
const loggingOut = ref(false)
const LogOut = async () => {
    if(loggingOut.value) return
    loggingOut.value = true
    removeToken()
    resetState()
    uni.$emit?.('router:unlock')
    uni.$emit?.('ui:closs-popups')
    
    await delay(220)
    
    try {uni.reLaunch({url:'/pages/login/index'}) } catch {}
    setTimeout(() => try{uni.relaunch({url:'/pages/login/index'}) catch {}, 700)
    
    loggingOut.value = false
}

看了下代码,似乎也没有啥变更,只是都加了个开关,然后加了个延时操作,但是问题确实解决了。确实非常奇怪的问题。

相关推荐
Bacon1 小时前
Vitest 一个基于 Vite 的快速单元测试框架
前端
学不动学不明白1 小时前
AES-GCM 解密失败解决方案
前端
一水鉴天1 小时前
整体设计 定稿 之16 三层智能合约体系实现设计和开发的实时融合
前端·人工智能·架构·智能合约
小菜今天没吃饱1 小时前
DVWA-XSS(Reflected)
前端·xss·dvwa
孟祥_成都1 小时前
前端下午茶!看看炫酷的动画,轻松一下!
前端·动效
lxh01131 小时前
合并K个升序链表题解
前端·数据结构·链表
小周码代码1 小时前
js 数字金额转为大写 js 金额转大写
开发语言·前端·javascript·js工具
航Hang*2 小时前
WEBSTORM前端——第1章:HTML——第2节:列表,表格,下拉菜单,文本框与按钮
前端·html·css3·webstorm
云计算DevOps-韩老师2 小时前
HTML盒子模型详解
前端·html