uni-app跨端开发之疑难杂症(持续更新~)

5,197 阅读5分钟

前言

今年,公司决定解决各个团队移动端开发的混战局面,由架构部出一套移动端框架,规范化开发标准。经过一段时间的调研,考虑到跨端以及公司主要技术栈为vue,最终选择了uni-app作为移动端框架,在大家都“很忙”的情况下,我成为了移动端框架的主要开发。以前就总听同事说,uni-app有很多坑,我对其也只是有些许了解,这回的全身心投入,才知道一入深坑愁似海

这段时间也做了一些成效,头大如斗的路由拦截、必不可少的http请求封装、提高成效的组件库、仿照微信的oAuth 2.0登录、复杂逻辑的离线存储、用户需要的增量更新包

有成效也踩了一些坑,百思不得解的console.log、烦到吐血的网络调试、爬坑许久的APP与h5通讯、性能极差的微信小程序端uni.canvasToTempFilePath

今天就要聊聊一些疑难杂症,有些忘记了,有些还没碰到,后续持续更新吧!

百思不得解的console.log

移动端框架是采用npm包的方式提供给业务部门使用,其中包含oAuth2.0登录方式,这其中涉及到了h5通过scheme协议唤醒app并且带回code等参数,相应的参数会存放在plus.runtime.arguments,其他情况下,plus.runtime.arguments的值为空。在给同事排查问题时我就简单操作,在node_modules对应的npm包里面写了不是很严谨的如下代码:

const args = plus.runtime.arguments;
// 这个是业务部门出错时,我添加的调试代码
console.log('>>>>>>', args)
if (args) {
  const isLogout = args.includes('logout');
  if (isLogout) {
    await this.handleSession();
  } else {
    await this.handleAuthorization(args);
  }
}

我测试是正常的,args是空值,所以是不会执行if内的逻辑的,但是他这边会执行if内的逻辑的,初步判断args由于某个原因导致存在值了,为了简单明了的查看输出内容,然后我就写了毁一生的console.log('>>>>>>', args),这行调试代码的输出内容如下,我一直以为args是空值,但是判断依旧为true,有点颠覆了我的人生观,后来灵机一动,删掉了第一个修饰参数,发现args原来是有值的,经过排查,是因为添加了微信小程序打开指定页面,导致记录当前页面数据。

image.png

烦到吐血的网络调试

网络调试对于我们的日常开发是很重要的,有助于快速判断资源请求问题,但uni-app在这方面有很大的缺陷,在讨论这个问题时,先来看一下uni-app的真机调试方式。

终端调试工具

当项目运行时,点击终端上的调试按钮,会弹出一个调试界面。

image.png

从调试面板中,可以看到仅有ConsoleElementsSources三个选项,期待许久的Network并没有出现,这种调试方式没办法实现网络请求调试。

image.png

webview调试控制台

点击工具栏的 运行 -> 运行到手机或模拟器 -> 显示webview调试控制台 会出现一个跟谷歌浏览器一样的调试界面,虽然这里有Network,但是很可惜,这个功能存在问题,没办法监听到网络请求。

image.png

Fiddler 抓取网络请求

在走投无路之下,只能另辟蹊径,借助工具,抓取真机的网络请求,接下来阐述一下怎么使用Fiddler抓取真机的网络请求,配置完需要重启才生效。

下载Fiddler

这是一个免费工具,自行在网络上下载即可。

Fiddler 基础配置

点击工具栏的tools,选择options就会弹出一个配置界面

image.png

image.png

HTTPS 配置

选择HTTPS选项,勾选选矿中的Capture HTTPS CONNECTsDecrypt HTTPs trfficIgnore server certificate errors

image.png

Connections 配置

这边配置的端口号后面配置代理的时候需要使用到。

image.png

手机配置代理

注意需要和电脑连接同一网络,点击进入手机WIFI详情界面,有个代理,选择手动模式,输入电脑的IP地址和Fiddler的监听端口,即可拦截到真机的所有网络请求,包含我们app对应的网络请求。

image.png

过滤

这边可以选择过滤对应的ip或域名,多个的话通过分号隔开即可。

image.png

爬坑许久的APP与h5通讯

谈论这个问题时,先描述一下uni-app实现的app怎么和h5通讯

app端

对于app端的通讯,.vue.nvue有两点区别,1. 获取webView实例不一致,2. 监听方法不一致。app向h5传递数据时,需要借助webview.evalJS执行h5的全局方法,而h5向app传递参数时,类似于h5发送postMessage,可以在webview的message/onPostMessage监听函数获取数据。

vue

获取webView示例

webView实例的获取,对于vue文件不是特别友好,需要借助于this.$scope.$getAppWebview(),如果是在组件中需要使用this.$parent.$scope.$getAppWebview(),添加延时的原因是,h5页面可能未加载完成,无法获取到对应的全局函数,会提示xxx函数undefined;

<template>
    <web-view src="http://www.juejin.com"></web-view>
</template>
<script>
    export default {
        onReady() {
            const currentWebview = this.$scope.$getAppWebview();
            const account = '清欢bx'
            setTimeout(() => {
                const webView = currentWebview.children()[0];
                webView.evalJS(`setAccountInfo(${account})`);
            }, 1000);
       }
    }
</script>

监听方法

vue文件采用@message触发监听函数

<template>
    <web-view @message="handleMessage" src="http://www.juejin.com"></web-view>
</template>
<script>
    export default {
        methods: {
            handleMessage(data) {
                console.log(data)
           }
       }
    }
</script>

nvue

获取webView示例

在nvue获取webView实例就很流畅了,直接通过this.$refs.webview就能获取到。

<template>
    <web-view ref="webview" src="http://www.juejin.com"></web-view>
</template>
<script>
    export default {
        onReady() {
            const account = '清欢bx'
            this.$refs.webview.evalJs(`setAccountInfo(${account})`);
       }
    }
</script>

监听方法

nvue文件采用@onPostMessage触发监听函数

<template>
    <web-view @onPostMessage="handleMessage" src="http://www.juejin.com"></web-view>
</template>
<script>
    export default {
        methods: {
            handleMessage(data) {
                console.log(data)
           }
        }
    }
</script>

h5 端

发送数据

需要引入一个uni-app的sdk,uni.webview.1.5.4.js,最低版本需要1.5.4,可以在index.html引入,也可以在main.js引入,注意点是传递的参数必须写在data里面,也就是维持这样的数据结构。

uni.postMessage({
    data: {
      xxx: xxx,
      xxx: xxx
    }
});

如果是页面加载完成时就需要发送数据,需要等待UniAppJSBridgeReady钩子结束后触发postMessage;

<script>
    export default {
        mounted() {
            document.addEventListener('UniAppJSBridgeReady', function() {
                uni.webView.getEnv(function(res) {
                    console.log('当前环境:' + JSON.stringify(res));
                });
                uni.postMessage({
                    data: {
                      action: 'message'
                    }
                });
           });
       }
    }
</script>

如果是通过事件点击发送数据,因为这时候页面已经加载完成,不需要再去监听UniAppJSBridgeReady钩子,直接触发uni.postMessage即可。

<template>
    <view>
        <button @click="handlePostMessage">发送数据</button>
    </view>
</template>
<script>
    export default {
        methods: {
            handlePostMessage() {
                uni.postMessage({
                    data: {
                      action: 'message'
                    }
                });
            }
       }
    }
</script>

获取数据

获取数据的函数,需要挂载到window上,可以直接写在main.js里面,数据需要共享到具体页面内,可以使用本地村存储localStorage、事件总线eventBusvuex,根据自己的需求选择。

window.setAccountInfo = function(data) {
    console.log(data)
}

踩坑点

uni is not defined

app需要涉及到离线或者内网,索引uni.webview.js下载到本地进行引入,因为uni.webview.js已经被编译成了umd格式,在vue项目中在进行一次打包后,导致this指向不是window,所以没有把uni挂在到全局上,将this指向改为window即可。

未改造之前的代码

image.png

改造后

image.png

或者

image.png

app向h5传递参数时,无法传递对象,并且传递的参数需要字符串序列化

在传递参数时,对象传递过去没办法识别,同时传递的参数需要执行JSON.stringify(),多个参数时,可以多个参数传递,也可以把多个参数进行字符串拼接,然后再h5端进行拆分处理。

const { account, password } = accountInfo;
const _account = JSON.stringify(account);
const _password = JSON.stringify(password);
setTimeout(() => {
    const webView = currentWebview.children()[0];
    webView.evalJS(`setAccountInfo(${_account}, ${_password})`);
}, 1000);

四、性能极差的canvas转图片

自定义组件库里包含手写签名组件,需要用到uni.canvasToTempFilePathcanvas转成图片,这个方法的生成基础图片大小是根据当前屏幕分辨率,在模拟器上运行感觉性能还可以,但是在真机上的性能不高,如果笔画多的话,有时需要十几秒时间,这是没办法接受的,不过也有解决方式,可以通过设置destWidthdestHeight来自定义图片生成的大小,牺牲一些图片清晰度,来提高性能。

uni.canvasToTempFilePath(
    {
      canvasId: this.canvaId,
      destWidth: this.imgWidth,
      destHeight: this.imgHeight,
      success: (res) => {
        console.log('success')
      },
      fail(e) {
        console.error(e);
      },
    },
    this,
);

小结

我目前主要负责公司uni-app移动端框架的开发,包含组件库相应的生态工具多端适配离线存储hybrid,如果你也正在做相同的事,或者在使用uni-app开发,或者在学习uni-app都可以相互探讨,在这踩坑的过程中,我会持续完善此系类文章,帮助大家和自己更好的使用uni-app开发项目,fighting~