其实在很久之前的《浅谈 uts + uvue 下的 uni-app x 是什么》我们就聊过 uni-app x ,相信在此之前大家对于 uni-app 的印象应该都是在小程序居多,虽然 uni-app 也可以打包客户端 app,甚至有基于 weex 的 nvue 支持,但是其效果只能说是“一言难尽”,而这里要聊的 uni-app x ,其实就是 DCloud 在跨平台这两年的新尝试。
说起 uni-app x 其实已经被我遗忘很久了,虽然在去年的鸿蒙 Next 的发布会时 uni-app 有被提及,只是当时也没看到 uni-app x 的身影,而今天恰哈在朋友圈看到了 DCloud 开发分享的文章,才发现 uni-app x 已经完成了它全平台的最后一环:
从 uni-app x 自己的定位看,官方表示:uni-app x 的目标并非简单地改进跨平台框架的性能,而是为原生应用开发提供一种统一的、跨平台的编码范式 。
具体来说,就是 uni-app 不再是运行在 jscore 的跨平台框架,它是“基于 Web 技术栈开发,运行时编译为原生代码”的模式,相信这种模式大家应该也不陌生了,简单说就是:js(uts) 代码在打包时会直接编译成原生代码:
目标平台 | uts 编译后的原生语言 |
---|---|
Android | Kotlin |
iOS | Swift |
鸿蒙 | ArkTS |
Web / 小程序 | JavaScript |
甚至极端一点说,uni-app x 可以不需要单独写插件去调用平台 API,你可以直接在 uts 代码里引用平台原生 API ,因为你的代码本质上也是会被编译成原生代码,所以 uts ≈ native code ,只是使用时需要配置上对应的条件编译(如 APP-ANDROID
、APP-IOS
)支持:
import Context from "android.content.Context";
import BatteryManager from "android.os.BatteryManager";
import { GetBatteryInfo, GetBatteryInfoOptions, GetBatteryInfoSuccess, GetBatteryInfoResult, GetBatteryInfoSync } from '../interface.uts'
import IntentFilter from 'android.content.IntentFilter';
import Intent from 'android.content.Intent';
import { GetBatteryInfoFailImpl } from '../unierror';
/**
* 获取电量
*/
export const getBatteryInfo : GetBatteryInfo = function (options : GetBatteryInfoOptions) {
const context = UTSAndroid.getAppContext();
if (context != null) {
const manager = context.getSystemService(
Context.BATTERY_SERVICE
) as BatteryManager;
const level = manager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
);
let ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
let batteryStatus = context.registerReceiver(null, ifilter);
let status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
let isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
const res : GetBatteryInfoSuccess = {
errMsg: 'getBatteryInfo:ok',
level,
isCharging: isCharging
}
options.success?.(res)
options.complete?.(res)
} else {
let res = new GetBatteryInfoFailImpl(1001);
options.fail?.(res)
options.complete?.(res)
}
}
比如上方代码,通过
import BatteryManager from "android.os.BatteryManager"
可以直接导入使用 Android 的BatteryManager
对象。
甚至你可以直接在 uts 里直接实现 OnClickListener
接口:
import OnClickListener from 'android.view.View.OnClickListener';
// 实现 OnClickListener 接口
class User {
name:string = "name"
}
class StartBroadcastListener extends User implements OnClickListener{
override onClick(v?: View):void{
let myReceiver = new ScreenReceiver();
let filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
UTSAndroid.getUniActivity()!.registerReceiver(myReceiver, filter);
// 提示屏幕状态监听已经注册
Toast.makeText(UTSAndroid.getAppContext(),"屏幕状态监听已注册,注意观察控制台日志",Toast.LENGTH_LONG).show();
}
}
// 使用
let btn_start_screen_listen = this.findViewById<Button>(R.id.btn_start_screen_listen);
btn_start_screen_listen.setOnClickListener(new StartBroadcastListener());
或者直接在 iOS 平台直接获取当前 app 显示的 UIViewController ,并打开 alert 弹窗:
import { UTSiOS } from "DCloudUTSFoundation"
export function showAlert(title: string|null, message: string|null, result: (index: Number) => void) {
// uts方法默认会在子线程中执行,涉及 UI 操作必须在主线程中运行,通过 DispatchQueue.main.async 方法可将代码在主线程中运行
DispatchQueue.main.async(execute=():void => {
// 初始化 UIAlertController 实例对象 alert
let alert = new UIAlertController(title=title,message=message,preferredStyle=UIAlertController.Style.alert)
// 创建 UIAlertAction 按钮
let okAction = new UIAlertAction(title="确认", style=UIAlertAction.Style.default, handler=(action: UIAlertAction):void => {
// 点击按钮的回调方法
result(0)
})
// 创建 UIAlertAction 按钮
let cancelAction = new UIAlertAction(title="取消", style=UIAlertAction.Style.cancel, handler=(action: UIAlertAction):void => {
// 点击按钮的回调方法
result(1)
})
// 将 UIAlertAction 添加到 alert 上
alert.addAction(okAction)
alert.addAction(cancelAction)
// 打开 alert 弹窗
UTSiOS.getCurrentViewController().present(alert, animated= true)
})
}
可以看到,在 uni-app x 你是可以“代码混写”的,所以与传统的 uni-app 不同,uni-app 依赖于定制 TypeScript 的 uts 和 uvue 编译器:
- uts 和 ts 有相同的语法规范,并支持绝大部分 ES6 API ,在编译时会把内置的如
Array
、Date
、JSON
、Map
、Math
、String
等内置对象转为 Kotlin、Swift、ArkTS 的对象等,所以也不需要有 uts 之类的虚拟机,另外 uts 编译器在处理特定平台时,还会调用相应平台的原生编译器,例如 Kotlin 编译器和 Swift 编译器 - uvue 编译器基于 Vite 构建,并对它进行了扩展,大部分特性(如条件编译)和配置项(如环境变量)与 uni-app 的 Vue3 编译器保持一致,并且支持 less、sass、ccss 等 CSS 预处理器,例如 uvue 的核心会将开发者使用 Vue 语法和 CSS 编写的页面,编译并渲染为 ArkUI
而在 UI 上,目前除了编译为 ArkUI 之外,Android 和 iOS 其实都是编译成原生体系,目前看在 Android 应该是编译为传统 View 体系而不是 Compose ,而在 iOS 应该也是 UIKit ,按照官方的说法,就是性能和原生相当。
另外,uni-app x 在 iOS 平台还做了一些骚操作,由于 wift 编译 iOS 应用必须依赖 Xcode,而 DCloud 的开发者中 Xindows 占比高于 Mac 电脑,所以 uni-app x 在 iOS 上提供 js 和 swift 双选逻辑层:
也就是 uts 原生插件作者必须得有 mac 电脑,普通的 app 开发者可以没有 mac 电脑,使用插件也不需要 mac 电脑,通过云打包即可。
使用 js 逻辑层,你就可以不需要 mac 电脑,官方表示,js 逻辑层和原生渲染层的通信经过特殊处理,大幅提升通信效率问题,不再需要 bindingX 这类技术,而 UI 渲染则是 jscore + 原生渲染,从这个角度看应该还是优化过的 Weex 模式:
官方表示 js 模式可以大幅降低插件生态的建设难度, 插件作者只需要特殊适配 Android 版本,在iOS和Web端仍使用 ts/js 库,可以快速把 uni-app/web 的生态迁移到 uni-app x 。
而回到平台视角,现在 uni-app x 同样支持了微信小程序,所以从这个节点看,uni-app x 确实可以开始成为 DCloud 的下一代主力框架,如果后续推进顺利,uni-app 也许就成为历史了。
当然,前面展示的随意混编原生代码的写法其实并不规范,正常 uni-app x 还是需要统一成插件形式,官方表示目前插件市场已经有数千款 uni-app x 的插件,其中不少插件已支持鸿蒙next ,不过需要注意的是,uni-app x 不再支持旧有的原生语言插件,所有原生能力扩展都必须通过 uts 插件实现。
另外,它的局限性问题也很明显,因为它的优势在于编译器转译得到原生性能,但是它的劣势也是在于转译,和使用类 skia 独立绘制的场景不同, uni-app x 需要考虑 uts 在不同平台和不同语言之间的同步和约束。
其实在之前我们聊《用 Swift 写 Android App ?来了解下 Skip 原生级跨平台框架》 的时候就讲过,Skip 也是将 Swift 直接翻译成 Kotlin 原生去适配 Android,不同的是它是直接通过 Swift / SwiftUI 去转移为 Kotlin / Compose,所以在语法和兼容成本会更低一点点,但是就算这样,也存在需要需要妥协的地方,例如:
- 并不支持完整的语言,阉割是必须的,为了支持 Kotlin 和 Compose ,API 必然需要为了转译器而做删减
- Skip 转译器不能执行 Swift 类型推理以及完整的 Swift 编译器
- 语言差异:例如 Swift 和 Kotlin 处理泛型方式不同,又或者 Kotlin 缺乏 static protocol 要求
所以,回到 uni-app x ,Skip 的问题在它这里同样存在,甚至因为支持的平台更多,它需要做的兼容和 if else
场景会更复杂,这对于 uni-app x 的后续推进和细节优化会是最大的挑战,uts 确实也做了一些约束,比如:
- 在编译到 Kotlin 和 Swift 时,不支持
undefined
类型 - 不允许以
$
开头的变量名 - 使用
var
声明变量可能需要考虑平台差异 - 一些 uts 内置 API(如
Array.sort()
在 Swift 平台,部分Math
和RegExp
的方法)在特定原生平台上的支持可能存在限制或行为差异 - ····
当然,实际上需要面临的细节问题肯定很多,具体能支持到什么地步,还是需要 DCloud 的后续打磨。
最后,在性能方面,官方也提供了一些对比(具体我也没验证),场景是在华为 Mate 30 5G(麒麟990芯片)上进行的 100 个滑块同步滑动的测试,对比了 uni-app x (Kotlin)、Compose、Flutter 和 ArkUI-x:
- UI 流畅度: uni-app x 在此测试中未出现掉帧现象,而 Flutter 、Compose 和 ArkUI-x 则表现出卡顿
- 内存占用: uni-app x 的内存占用约为 105MB,低于 Flutter (141MB) 和 ArkUI-x (133MB),高于 Compose ( 98M)
那么,你觉得你会考虑试试 uni-app x 吗?