基于 ohos-rs 适配 ada-url 到鸿蒙系统

187 阅读8分钟

本文我们将基于 ohos-rs 来实现一个完整的鸿蒙原生模块开发,并且将其发布到 ohpm 中心仓。

本文涉及代码仓库地址在:github.com/ohos-rs/ada…

背景

在开始之前,我们先简单讲解下 ada-url

简单来说就是一个使用 C++ 实现的高性能符合规范的 URL 解析工具,从 Node.js18 版本开始引入,让内置的 url 模块提升了四倍的性能。

相关阅读可以参考:blog.rafaelgss.dev/state-of-no…

因为其优秀的性能和完备的能力,开源之后逐渐在各个语言或者开发领域有了对应的适配版本,因此我们现在尝试将其适配到鸿蒙平台上面。

得益于官方的 Rust 实现,我们可以基于 ohos-rs 非常快速的将其适配到鸿蒙生态。

Let's go~

开发

得益于 ohos-rs 的发展,我们能够通过几行命令完成一个基于原生模块实现的 Har 包的初始化、开发、发布等流程。

ohos-rs 关于环境初始化等操作,这里不再过多讲解,具体可以参考官网文档

创建项目

现在我们创建一个项目名为ada-ohos,并且在创建项目的时候我们需要定义我们的 har 包名为@ohos-rs/ada,其命令如下所示:

ohrs init ada-ohos --package=@ohos-rs/ada

17214429245762.jpg

可以看到,除去生成了最基本的 Rust 相关开发代码之外,我们还额外生成了一个鸿蒙 har 包所需的最基本的项目结构与相关文件。

⚠️ 注意:现阶段生成的oh-package.json5文件中缺少了 dependencies 字段,该字段缺少会导致最终发布的审核不过,该问题将在下一个版本的脚手架工具中解决,现阶段可以自行补齐即可。

最终的项目结构如下所示:

17214431484753.jpg

开发

完成项目的创建之后,我们就可以开始正常的开发了,首先就是需要将 ada 依赖加入到项目依赖中。

cargo add ada-url

在开始开发前,我们先思考下如果提供给上层的 ArkTS 使用应当是以什么样的形式或者接口,这里我们可以参考系统内置的@kit.ArkTS中提供的 url 模块来设计。

系统内置模块的使用示例

import { url } from "@kit.ArkTS";

const urlInstance = url.URL.parseURL("https://www.baidu.com");
const h = urlInstance.host;

从示例中我们可以很清晰的看到,URL 解析的流程如下所示:

  1. 有一个对象,对象上面带有一个静态方法用于解析 URL。
  2. 解析返回的是 Url 对象,该对象可以直接获取诸如 host、port 等信息。

那么我们就可以定义两个结构体分别用于表示最外层的带有静态方法的对象和解析的结果,如下所示:

/// ada-url wrapper support parse and canParse method.
#[napi]
pub struct Ada();

/// url instance wrapper, can directly get host,protocol,etc.
#[napi]
pub struct Url {
    // 持有 ada 解析的 url 实例
    inner: AUrl,
}

Ada

对于 Ada 来说,我们需要让其暴露静态方法用于解析 url 对象等操作,我们可以直接写出如下代码:

use ada_url::Url as AUrl;
use napi_derive_ohos::napi;
use napi_ohos::{Error, Result, Status};

#[napi]
impl Ada {
    /// parse url
    #[napi]
    pub fn parse(url: String) -> Result<Url> {
        let ada = AUrl::parse(url, None)
            .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;
        Ok(Url { inner: ada })
    }
}

这里有几个核心开发点需要注意:

  1. 函数的返回值一般为基础类型(支持转为上层 ArkTS 数据结构的)
  2. 如果需要使用 Result 来处理错误等逻辑,需要使用 napi_ohos 暴露的 Result
  3. 对于错误必须是有impl From<x> for napi_ohos::Error的错误才可以被正常接收,否则应该使用map_err等操作将其转化为可识别的错误
  4. 对于结构体来说,需要定义静态方法则只需要将对应的结构体实现方法不添加self关联即可

这样我们就完成了一个最简单的 Native 对象定义,其带有一个静态方法parse,parse 方法接收一个字符串,并且返回一个 Url 的 native 对象。

注意:返回 Result 就意味着我们需要额外处理这里可能的错误。

现在我们继续实现 Url 对象,用于获取解析之后的数据。

Url

在上面的例子中,我们可以看到,获取数据时是直接通过.操作符进行操作的。在 JS/ArkTS 中,我们知道除去对象本身的属性之外,还可以通过getter方法来定义其通过该操作符获取值,出于方便和可读性来看,我们可以为每个属性都设置对应的getter方法,避免了在构建 Url 对象时全部获取其属性。

那么我们这里的代码就可以实现如下:

#[napi]
impl Url {
    #[napi(getter)]
    pub fn host(&self) -> &str {
        self.inner.host()
    }
}

因为 ada-url 当我们去获取参数的时候已经明确不会报错了,那么这里我们就不再需要使用 Result 来处理错误相关的内容了。

定义getter方法也非常简单,如代码所示即可。

在这之后,我们将整个项目进行构建来查看是否符合预期。

ohrs build

执行完毕之后,我们可以看到在项目中生成了dist以及对应的类型声明文件等内容。

17214451483200.jpg

测试

现在我们先简单测试下解析是否符合预期,具体的使用操作可以参考之前的文章:juejin.cn/post/737246…

17214453839204.jpg

已经正常的解析出了数据并且是符合预期的。

接下来我们简单的测试下性能数据,看下使用 ada-url 实现的能力和系统内置的是否有一定的优化。

这里的测试方案,我们使用官方的HiTraceMeter来实现,具体的使用示例可参考 developer.huawei.com/consumer/cn…

测试代码如下所示:

  parseWithBuiltin = () => {
    hiTraceMeter.startTrace("builtin-url", 1234);
    const u = url.URL.parseURL('http://nodejs.org:89/docs/latest/api/foo/bar/qua/13949281/0f28b/' +
      '/5d49/b3020/url.html#test?payload1=true&payload2=false&test=1' +
      '&benchmark=3&foo=38.38.011.293&bar=1234834910480&test=19299&3992&' +
      'key=f5c65e1e98fe07e648249ad41e1cfdb0');
    const h = u.host;
    hiTraceMeter.finishTrace("builtin-url", 1234);
  }

  parseWithAda() {
    hiTraceMeter.startTrace("ada-url", 1235);
    const u = Ada.parse('http://nodejs.org:89/docs/latest/api/foo/bar/qua/13949281/0f28b/' +
      '/5d49/b3020/url.html#test?payload1=true&payload2=false&test=1' +
      '&benchmark=3&foo=38.38.011.293&bar=1234834910480&test=19299&3992&' +
      'key=f5c65e1e98fe07e648249ad41e1cfdb0');
    const h = u.host;
    hiTraceMeter.finishTrace("ada-url", 1235);
  }

我们分别使用内置解析和 ada-url 解析来多次调用取平均值获得最终的性能数据。

Note: 这种测试方式相当粗暴,不过可以在一定程度上说明问题。另外性能测试以及测试数据本身就很容易有争议,这里只做参考,不认为是官方或者严格意义下的最终测试结果。

数据怎么采集呢?

使用 IDE 自带的 Profiler 工具采集即可,具体的操作如下:

  1. 点击最下方的 Profiler
  2. 连接设备和安装应用
  3. 选择设备和应用
  4. 选择 Time
  5. create session
  6. 开始记录
  7. 执行操作

17214459703756.jpg

当开始记录的时候,点击应用内的函数执行,几秒结束后,等待工具处理完成即可。

然后我们可以参考我们自定义的点最终数据如下所示:

17214462267768.jpg

17214462348294.jpg

在分别调用 50 次的情况下,ada-url 的平均耗时为 50us 而内建解析的耗时为 196us 接近 200us。

在一定程度上说明了使用 ada-url 提供的解析能力比内建解析性能更好。

发布

到以上,我们就完成了整体的开发逻辑,现在我们需要将这个包发布到 ohpm 的中心仓库。

具体的登录注册相关流程这里不多赘述,参考官方文档进行配置即可。

其中认证管理部分生成公私钥的时候一定要注意一个细节:

  • 生成的时候要求输入密码,务必要输入密码并且妥善保管。在发布阶段需要输入该密钥。
  • -f参数使用绝对路径

17214473765499.jpg

构建包

在我们的项目目录中,我们只需要简单的使用如下命令即可:

ohrs artifact

执行完毕之后,我们可以看到在当前目录下生成了一个package.har的文件,该文件就是最终用于发布的包文件。

17214467612381.jpg

这里构建的时候需要注意几个点:

  1. 建议使用release模式构建
  2. 每次更新版本号的时候,务必手动CHANGELOG.md文件中的版本更新信息
  3. 授权许可默认是 MIT 授权,请根据自行需要修改

发布

完成之后,调用 ohpm 命令进行发布。

ohpm publish ./package.har

17214474451877.jpg

成功之后在你的中心仓个人信息中会有如下提示:

17214474745920.jpg

到这里从 0 开始开发一个 native 模块到最终发布的全部流程就完成了,整个过程中我们可以看到很多东西:

  1. 使用内建的能力并不一定就是最好的,通过一些其他手段或许能够有更好的收益。
  2. 一个完整的包开发的工作是很多的,除去本身的开发还会有诸如文档、变更日志、维护、测试等等一系列工作,开发的时间可能只是其中很小的一部分。

目前 ada for harmony 的包已提审,不过实现相对简单还有很多功能并没有实现,也欢迎社区一起共建~

希望本文对你有所帮助~