openvela快应用开发实战:从项目初始化到性能优化完全指南
标签:openvela | 嵌入式 | 快应用
本文是《从零开始学openvela》系列第二篇,完整讲解基于 AIoT-IDE 的 openvela 快应用开发流程,涵盖项目结构、UI开发、页面交互、模板语法、多屏适配与性能优化。
2.1 基于 AIoT-IDE 的 JS 应用开发流程
2.1.1 准备开发环境
下载 AIoT-IDE:
下载适配 Ubuntu 虚拟机的安装包 data.tar,传输到虚拟机后解压:
bash
tar -xf data.tar
解压后进入 usr/share/aiot-ide/ 目录,双击 aiot-ide 可执行文件即可启动。
2.1.2 项目初始化
1. 创建 Vela 项目
通过 AIoT-IDE 菜单栏 文件 → 新建项目,选择项目类型和模板,输入项目名称和保存路径后点击创建。
2. 环境依赖处理
项目创建后,右侧引导页会提示环境问题及对应解决按钮:
bash
# 升级 Node.js 版本
sudo npm install -g n --registry=https://registry.npmmirror.com
sudo n 18
# 安装项目依赖(换国内镜像避免网络问题)
npm install --registry=https://registry.npmmirror.com
# 安装 aiot-toolkit
npm install -g aiot-toolkit --registry=https://registry.npmmirror.com
模拟器可在右侧引导页点击 初始化模拟器运行环境 自动创建,也可在设备管理页手动新建。
3. 运行与调试项目
- 指定目标模拟器 → 点击 运行,IDE 自动打包并在模拟器中启动应用
- 点击 调试,底部弹出调试面板进行断点、日志等操作
4. 打包项目
开发模式:
bash
# 点击【打包】自动生成 dist/(含 .debug.rpk 调试包)和 build/(编译后 JS 文件)
生产模式:
bash
# 方式一:点击【发布】自动生成签名
# 方式二:手动生成签名文件
openssl req -newkey rsa:2048 -nodes -keyout private.pem \
-x509 -days 3650 -out certificate.pem
# 将 private.pem 和 certificate.pem 放入 sign/ 目录后再发布
2.2 快应用快速入门
2.2.1 项目结构
openvela 快应用的工程目录组织:
├── manifest.json # 项目配置(必选):应用信息、系统权限、路由规则
├── app.ux # 全局UI与应用生命周期(可选)
├── pages/ # 页面文件夹(必选)
│ ├── index/
│ │ └── index.ux
│ └── detail/
│ └── detail.ux
├── i18n/ # 多语言配置(可选)
│ ├── defaults.json
│ ├── zh-CN.json
│ └── en-US.json
└── common/ # 公共资源(可选)
├── style.css
├── utils.js
└── logo.png
各文件职责:
| 文件 | 职责 |
|---|---|
manifest.json |
声明应用基本信息、系统权限(蓝牙/网络等)、页面路由规则 |
app.ux |
管理应用生命周期、全局数据/方法、跨页面公共逻辑 |
pages/ |
每个子文件夹对应一个页面,含 UI/逻辑/样式 |
i18n/ |
多语言 JSON 文件,实现界面文案动态切换 |
common/ |
全局样式、工具函数、静态资源复用 |
2.2.2 UX 文件
UX 文件是 Vela 框架的页面描述文件,支持两种组织模式:
单文件模式 (结构+样式+逻辑在同一 .ux 文件中):
html
<template>
<div class="page">
<text class="title">欢迎打开 {{title}}</text>
<input class="btn" type="button" value="跳转到详情页" onclick="routeDetail">
</div>
</template>
<style>
.btn {
width: 400px;
height: 60px;
background-color: #09ba07;
color: #ffffff;
}
</style>
<script>
import router from '@system.router'
export default {
private: { title: '示例页面' },
routeDetail() {
router.push({ uri: '/pages/detail' })
}
}
</script>
三要素说明:
<template>:定义页面结构,支持{``{插值}}和事件绑定(onclick/@click)<style>:CSS 样式,通过 class 绑定<script>:页面逻辑,数据响应、事件处理、路由跳转
多文件模式(复杂页面拆分):
pages/detail/
├── detail.ux # 结构(不可含 <template> 标签)
├── detail.css # 样式
└── detail.js # 逻辑
拆分后
.ux文件不要再写<template>标签,框架会自动识别.ux/.css/.js的分工。
app.ux 全局入口:
javascript
// app.ux 的 script
import util from './util'
export default {
showMenu: util.showMenu,
createShortcut: util.createShortcut,
util
}
// 所有页面可通过 this.$app.$def.util.xxx 调用公共方法
2.2.3 编写页面 UI 示例
下面以天气页面为例,演示完整的 UI 开发:
template 部分:
html
<template>
<div class="root column center">
<!-- 城市与天气状态 -->
<div class="top row center">
<text class="city">{{ city }}</text>
<text class="status">{{ weather.text }}</text>
</div>
<!-- 天气图标与温度 -->
<div class="middle row center">
<image class="icon" src="/common/icons/101.png"></image>
<text class="temp">{{ weather.temp }}°</text>
</div>
<!-- 辅助信息 -->
<div class="bottom row center">
<div class="info-item column center">
<text class="value">{{ weather.feelsLike }}°</text>
<text class="label">体感温度</text>
</div>
<div class="info-item column center">
<text class="value">{{ weather.humidity }}%</text>
<text class="label">湿度</text>
</div>
<div class="info-item column center">
<text class="value">{{ weather.vis }}km</text>
<text class="label">能见度</text>
</div>
</div>
<!-- 更新时间 -->
<div class="update-time center">
<text>数据更新于 {{ weather.obsTime }}</text>
</div>
</div>
</template>
style 部分(Flex 布局驱动):
css
.root {
width: 466px; height: 466px;
background-color: #000;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
color: #fff;
padding: 20px;
}
.top { width: 100%; justify-content: space-around; }
.city { font-size: 28px; font-weight: bold; }
.status { font-size: 24px; color: #ddd; }
.middle { width: 100%; justify-content: center; }
.icon { width: 120px; height: 120px; margin-right: 20px; }
.temp { font-size: 48px; font-weight: bold; }
.bottom { width: 100%; justify-content: space-around; }
/* 辅助布局类 */
.row { display: flex; flex-direction: row; }
.column { display: flex; flex-direction: column; }
.center { align-items: center; justify-content: center; }
所有
row/column类基于 Flex 布局,通过flex-direction控制排列方向,justify-content控制主轴对齐。
2.2.4 添加页面交互
路由跳转(index.ux → detail.ux):
javascript
// index.ux 的 script
import router from "@system.router"
export default {
routeDetail() {
router.push({ uri: "/pages/detail" })
}
}
html
<!-- index.ux 的 template -->
<input class="btn" type="button" value="跳转到详情页" onclick="routeDetail" />
路由模块需在
manifest.json中声明:"features": [{ "name": "system.router" }]
滑动返回(detail.ux → index.ux):
html
<!-- 在根容器上绑定 swipe 事件 -->
<div class="root column center" @swipe="handleSwipe">
<!-- 页面内容 -->
</div>
javascript
import router from "@system.router"
export default {
handleSwipe(eve) {
if (eve.direction === 'right') {
router.back() // 向右滑动返回上一页
}
}
}
2.2.5 获取实时数据
以和风天气 API 为例,获取实时天气数据:
- 注册和风天气开发控制台账号,创建应用并获取 API KEY
- 通过
@system.fetch模块发起网络请求
javascript
import router from '@system.router'
import fetch from '@system.fetch'
export default {
private: {
city: '武汉市',
weather: {
text: '晴转多云',
temp: '6',
feelsLike: '10',
humidity: '67',
vis: '5',
obsTime: '12-20 09:15'
}
},
onShow() {
this.getWeatherData()
},
getWeatherData() {
fetch.fetch({
url: 'https://devapi.qweather.com/v7/weather/now?location=101200101&key=YOUR_API_KEY',
success: (res) => {
const data = JSON.parse(res.data)
this.weather = data.now
this.weather.obsTime = this.formatTime(data.updateTime)
},
fail: (err) => {
console.error('天气数据获取失败:', err)
}
})
}
}
动态数据使用
{``{ }}双大括号语法绑定,数据存储在页面数据对象中,支持响应式更新。
2.2.6 设置编译参数
在 manifest.json 中配置编译选项:
json
{
"config": {
"target": "vela",
"output": "dist",
"minify": true
}
}
2.3 基础功能
2.3.1 文件存储与资源访问
openvela 快应用提供了 @system.storage 和 @system.file 模块进行数据持久化。
2.3.2 模板语法
Vela 模板语法类似 Vue,支持以下核心特性:
计算属性:
javascript
export default {
private: { firstName: 'Zhang', lastName: 'San' },
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
}
Props(父子组件传值):
javascript
// 子组件
export default {
props: ['title', 'count']
}
html
<!-- 父组件中使用 -->
<child-component title="Hello" count="{{ num }}"></child-component>
事件绑定:
html
<input type="button" value="点击" @click="handleClick" />
<input type="button" value="长按" @longpress="handleLongPress" />
循环指令:
html
<list>
<list-item for="{{ items }}" type="item">
<text>{{ $item.name }} - {{ $idx }}</text>
</list-item>
</list>
条件指令:
html
<text if="{{ score >= 90 }}">优秀</text>
<text elif="{{ score >= 60 }}">及格</text>
<text else>不及格</text>
组件定义与使用:
javascript
// component.ux
export default {
private: { msg: '组件内容' }
}
html
<import name="my-component" src="./component.ux"></import>
<my-component></my-component>
2.3.3 样式语法
内联样式:
html
<text style="color: red; font-size: 20px;">内联样式</text>
class 绑定:
html
<text class="{{ isActive ? 'active' : 'inactive' }}">动态class</text>
页面布局核心:
- Flex 布局为主(
flex-direction: row/column+justify-content+align-items) - 不依赖外部 CSS 框架,所有样式自包含在
.ux或.css文件中
媒体查询(多分辨率适配):
css
@media (width > 400px) {
.container { padding: 20px; }
}
@media (width <= 400px) {
.container { padding: 10px; }
}
2.3.4 脚本语法
页面数据对象:
javascript
export default {
private: { count: 0 }, // 私有数据(页面内部可见)
protected: { title: '' }, // 受保护数据(子组件可访问)
public: { appName: '' } // 公开数据(全局可访问)
}
全局对象和方法:
javascript
// 通过 this.$app 访问 app.ux 暴露的全局数据
console.log(this.$app.$def.util.formatDate())
// 通过 global 访问系统全局对象
console.log(global.deviceInfo)
生命周期:
| 生命周期 | 触发时机 |
|---|---|
onInit() |
页面初始化 |
onReady() |
页面渲染完成 |
onShow() |
页面显示 |
onHide() |
页面隐藏 |
onDestroy() |
页面销毁 |
onBackPress() |
用户按返回键 |
javascript
export default {
onInit() { console.log('初始化') },
onReady() { console.log('渲染完成') },
onShow() { console.log('页面显示,可发起数据请求') },
onHide() { console.log('页面隐藏,可暂停动画') },
onDestroy() { console.log('页面销毁,清理资源') }
}
2.3.5 页面切换
javascript
import router from '@system.router'
// 跳转
router.push({ uri: '/pages/detail' })
// 替换当前页
router.replace({ uri: '/pages/detail' })
// 返回
router.back()
// 清除历史并跳转
router.clear({ uri: '/pages/home' })
2.4 进阶功能
2.4.1 多语言覆盖
json
// i18n/zh-CN.json
{ "hello": "你好", "weather": "天气" }
// i18n/en-US.json
{ "hello": "Hello", "weather": "Weather" }
html
<text>{{ $t('hello') }}</text>
2.4.2 后台运行
在 manifest.json 中申请后台权限,允许应用在退出后保持部分服务运行。
2.4.3 页面启动模式
支持单任务(默认)/ 多任务模式,在 manifest 中配置路由栈行为。
2.4.4 多屏适配
openvela 支持智能手表、带屏音箱等多种屏幕尺寸:
适配规范:
- 以 466×466(方屏手表基准)为设计稿尺寸
- 使用 Flex 弹性布局 + 百分比/相对单位
- 避免硬编码像素值
条件编译(按设备类型区分代码):
javascript
// deviceType 通过编译时注入
if (DEVICE_TYPE === 'watch') {
// 手表专属逻辑
} else if (DEVICE_TYPE === 'speaker') {
// 音箱专属逻辑
}
2.4.5 动态组件
允许在运行时根据数据动态切换渲染的组件:
html
<component is="{{ currentComponent }}"></component>
2.4.6 拓展组件
openvela 提供丰富的系统组件,包括 list、swiper、image、video、map 等,直接在 template 中声明即可使用。
2.5 优化策略与代码实践
2.5.1 内存优化
1. 数据绑定精简
不要把和 UI 无关的数据放到响应式数据中,框架会为每个响应式数据创建 observer 代理,额外占用内存:
javascript
const someObj = { a: 1 } // ✅ 普通变量,无额外开销
export default {
protected: {
name: 'aaa',
someObj: { a: 1 } // ❌ UI 无关却放响应式,浪费内存
}
}
2. 原地更新对象/数组
直接替换对象引用会导致旧对象无法被 GC 回收:
javascript
// ❌ 直接替换,旧数组无法回收
this.list = [{ name: 'bb', age: 21 }]
// ✅ 原地修改,不改变引用
this.list[0].name = 'bb'
this.list[0].age = 21
3. 避免全局缓存页面对象
javascript
// ❌ 页面数据挂载到全局,可能导致内存泄漏
this.$app.$def.someArray.push(this.foo)
// ✅ 仅在页面作用域内操作
this.list.push({ name: 'bb', age: 21 })
4. 定时器清理
javascript
export default {
onShow() {
this.timer = setTimeout(() => {}, 1000000)
},
onDestroy() {
if (this.timer) clearTimeout(this.timer) // 页面销毁时必须清理
}
}
5. 文件数据及时释放
javascript
let foo = await storage.getItem('key')
let fooObj = JSON.parse(foo)
// 使用完毕后
foo = null
fooObj = null
6. 主动触发垃圾回收
javascript
global.runGC() // 注意:不要频繁调用,会导致卡顿
7. static 属性优化
html
<!-- 节点级:标记整个节点为静态 -->
<text static>{{ name }}</text>
<image static src="/assets/icon/a.png"></image>
<!-- 属性级:只标记特定属性为静态 -->
<div if.static="{{ bool }}">
<text style.static="{{ styl }}" class.static="{{ cls }}" static>{{ name }}</text>
</div>
<!-- block级:整个区块内的所有节点标记为静态 -->
<block static>
<text class="{{ cls }}">标题:{{ title }}</text>
<list>
<list-item for="{{ list }}" type="item">
<text>{{ $item }}</text>
</list-item>
</list>
</block>
2.5.2 减少打包代码体积
| 策略 | 做法 |
|---|---|
| 减少依赖 | 删除未使用的 npm 包,用 dayjs 替代 moment |
| 全局方法 | 通用工具函数挂载到 global,避免重复 import |
| 压缩图片 | 降低分辨率 + 压缩工具优化 |
| 去除死代码 | 删除未使用的 CSS 样式和 JS 函数 |
| 合并页面 | 多个详情页整合为一个动态页面,通过参数区分内容 |
javascript
// global.js:统一挂载通用方法,所有页面从全局获取
import foo from './foo'
import bar from './bar'
global.foo = foo
global.bar = bar
// app.ux:入口文件引入
import './global'
// 任意页面直接使用,无需 import
const { foo, bar } = global
2.5.3 常用业务优化
list 与长文案分块渲染:
html
<scroll id="scroll" scroll-y="true" onscroll="handleScroll">
<div id="content">
<block if="{{ currentKey >= 0 }}">
<text>{{ contentArray[0].content }}</text>
</block>
<block if="{{ currentKey >= 1 }}">
<text>{{ contentArray[1].content }}</text>
</block>
<block if="{{ currentKey >= 2 }}">
<text>{{ contentArray[2].content }}</text>
</block>
</div>
</scroll>
javascript
export default {
data: {
contentArray: [
{ content: '文案一...' },
{ content: '文案二...' },
{ content: '文案三...' }
],
currentKey: 0,
currentTHEight: 0
},
handleScroll(e) {
// 触底时加载下一块文案
if (this.currentTHEight - e.scrollY < 40) {
this.currentKey = this.currentKey + 1
}
}
}
Swiper 多图懒加载:
利用 3 个子组件 + 动态更新内容实现无限张图片轮播,核心逻辑:监听 @change 事件,根据滑动方向动态替换非当前可见位置的图片数据。
2.5.4 启动时延优化
| 优化项 | 方法 |
|---|---|
| 避免 setTimeout 延迟 | 首页逻辑放在 onReady 中直接执行 |
| 首页数据缓存 | 将首页数据缓存到本地,第二次启动时先展示缓存再异步刷新 |
| Logo 页避免 HTTP | 把 Logo 图打包到本地资源,不依赖网络 |
| UI 先行 | 先渲染 UI 骨架,数据异步填充 |
| 减少 console 打印 | 生产模式移除 console.log |
| 图片缓存和裁剪 | 预加载 + 精确裁剪到需求尺寸 |
| 诊断连接状态 | 通信前先检查设备连接状态 |
小结
本文完整覆盖了 openvela 快应用开发的核心知识:
- AIoT-IDE 环境:项目创建、依赖安装、模拟器配置、打包发布
- UX 文件系统:单文件/多文件两种模式,template/style/script 三要素
- UI 开发:Flex 布局驱动的界面开发,天气页面完整示例
- 页面交互:路由跳转、滑动返回、事件绑定
- 模板语法:计算属性、Props、循环/条件指令、组件系统
- 进阶功能:多语言、多屏适配、动态组件、条件编译
- 性能优化:7 项内存优化 + 5 项体积优化 + list/Swiper 业务优化 + 9 项启动优化
下一篇将介绍综合实例------天气应用的完整开发流程。