使用uniapp制作安卓app容器
1. 背景
项目需要做一个安卓app
,而且不需要上架应用市场,部门也没有安卓开发,想着就套个webview
就行了吧。没有选择react native
之类的是因为这些工具需要安装很多环境工具,我只是开发一个壳子没必要这么复杂。
用webview
也方便快速修复页面问题。
所以最后选择了uniapp
,但是uniapp
本身就是套在一个大的webview
下的, 所以再套一个webview
难免会有一些意想不到的问题,下面就是一些踩过的坑记录。
2. 项目初始化
新建项目就默认模板就行,我只需要壳子。
启动了之后可以看到有两个调试工具
第一个就是网页上常用的vue
调试工具,可以看到vue
组件属性啥的,第二个就是类似chrome
的控制台,但是无法查看元素
,还有就是必须让设备和电脑在同一个网段下才行,不然连接不上。
hbuilder
的控制台本身也有一些输出,比如页面的console
但是这里输出对象的时候不是很方便查看,如果你需要的话就打开上面说的第二个调试工具。
3. webview
使用
整个项目很简单,大概就这样一个页面
<template>
<web-view :src='PROJECT_PATH' @message="onMessage">web-view>
template>
<script>
// ...
script>
3.1 网页与app
通信
3.1.1. 网页 -> APP
首先要在项目中引入uni.webview.js,这个就相当于jsbridge
,可以让网页操作uniapp
。
初始化完成后会在window
上挂载一个uni
对象,通过uni.postMessage
就能往app
发送消息,app
中监听onMessage
就行。
这里有几个小坑:
- 发送的格式
window.uni.postMessage({ data: 数据 })
,必须要有个字段data
,这样app
才能收到数据。源码
2. 发送的数据不需要序列化成字符串,
uniapp
会转换json
。 3. app
在message
事件中接收到事件参数应该这样解构
function onMessage(e) {
const {
type,
data
} = e.detail.data[0]
}
3.1.2. APP -> 网页
app
向网页传输消息就直接调用网页的js
就行了。这里我统一封装了一个函数:
// app向网页发送消息
const deliverMessage = (msg) => {
// 调用webview中的deliverMessage函数
// 这个函数是我在网页挂载的一个全局函数,调用deliverMessage后会触发页面中的一些事件
currentWebview.evalJS(`deliverMessage(${JSON.stringify(msg)})`)
}
上面的代码例子中出现的currentWebview
需要我们自己去获取。
// vue2中
const rootWebview = this.$scope.$getAppWebview()
this.currentWebview = rootWebview.children()[0]
// vue3中
import {
getCurrentInstance,
ref,
} from "vue";
const currentWebview = ref(null)
const vueInstance = getCurrentInstance()
const rootWebview = vueInstance.proxy.$scope.$getAppWebview()
currentWebview.value = rootWebview.children()[0]
这里也有一个坑,rootWebview.children()
如果你一渲染就获取是无法获取到webview
实例的,具体原因没有深入研究,估计是异步的原因
这里提供两个思路:
- 加一个定时器,延迟获取
webview
,这个方法虽然听起来不保险,但是实际测试还是挺稳当的。关键是简单。
setTimeout(() => {
currentWebview.value = rootWebview.children()[0]
}, 1000)
- 你要是觉得定时器不保险,那就使用
plus
的api
手动创建webview
。但是消息处理这块比较麻烦。官网参考
<template>
template>
// 我这里vue3为例
onMounted(() => {
plus.globalEvent.addEventListener('plusMessage', ({data: {type, args}}) => {
// 是网页调用uni的api
if(type === 'WEB_INVOKE_APPSERVICE') {
const {data: {name, arg}} = args
// 是发送消息事件
if(name === 'postMessage') {
// arg就是传过来的数据
}
}
})
const wv = plus.webview.create("", "webview", {
'uni-app': 'none',
})
wv.loadURL(网页地址)
rootWebview.append(wv);
})
plus.globalEvent.addEventListener
这个是翻源码找到的,主要是我不想改uni.webview.js
的源码,所以只有找到正确的监听事件。
WEB_INVOKE_APPSERVICE
是uniapp
内部定义的一个名字,反正就是用来交互操作的命名空间。
这样基础的互操作就有了。
3.1.3. 整个流程
网页
调用window.uni.postMessage({ data })
=>app
监听(用组件的onMessage
或者自定义的globalEvent
)app
调用网页
定义的函数deliverMessage
并传递参数,网页
中的deliverMessage
内部处理监听
// 网页中的deliverMessage
window.deliverMessage = (msg) => {
// 触发网页注册的监听器
eventListeners.forEach((listener) => {
});
};
3.2. 返回拦截
默认情况下,手机按下返回键,app
会响应提示是否退出,但是实际我需要网页进入二级路由的时候,按下手机返回键是返回上一级路由而不是退出。当路由是一级路由时才提示是否退出app
import {
onBackPress,
onShow,
} from '@dcloudio/uni-app'
// 页面当前的路由信息
const pageRoute = shallowRef()
onBackPress(() => {
// tab页正常app返回逻辑
if (pageRoute.value?.isTab) {
return false
} else {
// 二级路由拦截app返回
return true
}
})
pageRoute
是页面当前路由信息,页面通过监听路由变化触发routeChange
事件,将路由信息传给app
。当按下返回键的时候,判断当前路由配置是不是tab
页,如果是就正常退出,不是就拦截返回。
4. 总结
有了通信功能,很多操作就可以实现了,比如获取设备safeArea
,获取设备联网状态等等。
来源:juejin.cn/post/7313740940773097482