openvela快应用开发实战

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 为例,获取实时天气数据:

  1. 注册和风天气开发控制台账号,创建应用并获取 API KEY
  2. 通过 @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 快应用开发的核心知识:

  1. AIoT-IDE 环境:项目创建、依赖安装、模拟器配置、打包发布
  2. UX 文件系统:单文件/多文件两种模式,template/style/script 三要素
  3. UI 开发:Flex 布局驱动的界面开发,天气页面完整示例
  4. 页面交互:路由跳转、滑动返回、事件绑定
  5. 模板语法:计算属性、Props、循环/条件指令、组件系统
  6. 进阶功能:多语言、多屏适配、动态组件、条件编译
  7. 性能优化:7 项内存优化 + 5 项体积优化 + list/Swiper 业务优化 + 9 项启动优化

下一篇将介绍综合实例------天气应用的完整开发流程。

相关推荐
程序员差不多先生7 天前
Openvela+ 瑞芯微+DeepSeek 桌面机器人实战评测
机器人·瑞芯微·deepseek·openvela·桌面机器人
开源武术1 个月前
蓝牙应用开发实战
openvela
开源武术4 个月前
使用 J-Link GDB 插件增强 openvela 线程调试
openvela
开源武术4 个月前
openvela 使用 _FORTIFY_SOURCE 增强 C 语言内存安全性
openvela
YaoYuan93235 个月前
openvela——动态管理日志输出通道及其实现原理
openvela
开源武术6 个月前
openvela 使用 VSCode 调试 SIM 环境
openvela
自由的晚风1 年前
基于小米Open-Vela开源系统的高级计算器实现 | 支持C++数学函数与 LVGL UI
经验分享·物联网·开源·嵌入式·小米·nuttx·openvela
byte轻骑兵1 年前
OpenVela——专为AIoT领域打造的开源操作系统
开源·openvela
一只搬砖的猹1 年前
小米vela系统(基于开源nuttx内核)——openvela开源项目
linux·开源·小米·rtos·nuttx·openvela·apache2.0