uni-app x 正式支持鸿蒙,又一个原生级全平台框架落地

3,966 阅读8分钟

其实在很久之前的《浅谈 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 编译后的原生语言
AndroidKotlin
iOSSwift
鸿蒙ArkTS
Web / 小程序JavaScript

甚至极端一点说,uni-app x 可以不需要单独写插件去调用平台 API,你可以直接在 uts 代码里引用平台原生 API ,因为你的代码本质上也是会被编译成原生代码,所以 uts ≈ native code ,只是使用时需要配置上对应的条件编译(如 APP-ANDROIDAPP-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 ,在编译时会把内置的如ArrayDateJSONMapMathString 等内置对象转为 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 平台,部分 MathRegExp 的方法)在特定原生平台上的支持可能存在限制或行为差异
  • ····

当然,实际上需要面临的细节问题肯定很多,具体能支持到什么地步,还是需要 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)

详细可见:doc.dcloud.net.cn/uni-app-x/s…

那么,你觉得你会考虑试试 uni-app x 吗?