跳转至

JS 应用开发与发布

基于Hello World 新建 JS 应用

本章用于说明如何基于 Hello World 示例新建 JS 应用,并完成环境准备、工程创建、图形工具配置、签名配置、编译打包、安装、卸载和预置流程。HelloWorld 示例只包含基础页面显示,组件和接口能力可在“工程代码的文件结构与代码走读”章节继续查阅。

IDE

目前支持OpenHarmony JS应用开发的IDE为DevEco Studio 5.0 Release。

  • 5.0版本,5.0版本支持API 10。
  • 不推荐:2.1版本为早期版本,支持API 5。

DevEco Studio 5.0 Release

DevEco Studio For OpenHarmony(获取工具请单击链接下载,以下简称DevEco Studio)是基于IntelliJ IDEA Community开源版本打造,用作开发穿戴JS应用。

说明: 当前产品使用的IDE版本为DevEco Studio 5.0 Release。

haptobin_tool.jar工具使用

背景:商用应用建议上架release版本安装包,本工具支持将release hap转换为bin。

工具介绍:将IDE编译出的release的hap转为bin的工具。

获取路径:解压sdk发布包中的jsapp.zip,在“jsapp\js_sample\helper”路径下的“fastjson-1.2.83.jar”、“haptobin_tool.jar”。

使用方法:

  1. 将“fastjson-1.2.83.jar”、“haptobin_tool.jar”放在同一目录下。
  2. 执行命令“java -jar "xxx/haptobin_tool.jar" --hap-path "xxx/xxx.hap" --bin-path "xxx/xxx.bin"”。

    --hap-path:hap包所在路径。

    --bin-path:所生成bin包所在路径。

说明: 当前章节所指hap包是DevEco Studio使用release模式编译的“*.hap”产物。如果使用IDE的debug模式,无需使用“haptobin_tool.jar”。

haptobintool.jar工具使用编译

工程创建

使用 DevEco Studio 5.0 Release 创建工程

  1. 选择“[Lite]Empty Ability(JS)”工程。

    选择“\【Lite\】Empty Ability\(JS\)”工程

  2. 配置项目,包括包名、工程路径、项目类型等。

    配置项目,包括包名、工程路径、项目类型等

  3. 在Hello World程序中打开Previewer预览器显示预览界面。

    DevEcoStudio预览器界面

    说明: 页面视图逻辑主要由hml、css、js组成,详情请参考“文件组织”。

图形工具使用

使用 DevEco Studio 5.0 Release 配置图形工具

图片压缩工具详见《图形工具使用指南》。将图片资源解码成bin文件后打包到应用包中可以优化图片性能,使用步骤如下:

  1. 设置图像转换工具环境变量,本示例中设置如下,路径变量名为ImageTool:

    图 1 设置环境变量

    设置环境变量

  2. 进入DevEco Studio 5.0 Release版本新建工程entry目录,使用以下脚本替换替换“hvigorfile.ts”:

    import { legacyHapTasks ,OhosPluginId ,OhosHapContext} from '@ohos/hvigor-ohos-plugin';
    import { hvigor, HvigorNode, HvigorPlugin} from '@ohos/hvigor';
    import {spawn,exec,execSync} from 'child_process'
    import * as fs from 'fs';
    import * as path from 'path';
    import { PythonShell } from 'python-shell';
    function containsPng(filename: string): boolean {
        return filename.toLowerCase().includes('png') || filename.toLowerCase().includes('bmp') || filename.toLowerCase().includes('jpg');
    }
    function copyDirectorySync(src: string, dest: string) {
        if (!fs.existsSync(dest)) {
            fs.mkdirSync(dest, { recursive: true });
        }
        const entries = fs.readdirSync(src, { withFileTypes: true });
        for (const entry of entries) {
            const srcPath = path.join(src, entry.name);
            const destPath = path.join(dest, entry.name);
            if (entry.isDirectory()) {
                copyDirectorySync(srcPath, destPath);
            } else {
                if (containsPng(srcPath)) {
                    fs.copyFileSync(srcPath, destPath);
                }
            }
        }
    }
    function createDirSync(dir: string): void {
        if (!fs.existsSync(dir)) {
            createDirSync(path.dirname(dir));
            fs.mkdirSync(dir);
        }
    }
    function delay(milliseconds: number) {
        return new Promise<void>(resolve => {
            setTimeout(() => {
                resolve();
            }, milliseconds);
        });
    }
    function deletePngBinFiles(folderPath: string): void {
        fs.readdirSync(folderPath).forEach((file) => {
            const filePath = path.join(folderPath, file);
            if (fs.lstatSync(filePath).isFile() && file.endsWith('.png.bin')) {
                console.log(filePath);
                fs.unlinkSync(filePath);
            } else if (fs.lstatSync(filePath).isDirectory()) {
                deletePngBinFiles(filePath);
            }
        });
    }
    async function executeCommand(dirPath: string): Promise<void> {
        return new Promise((resolve, reject) => {
            const myVar = process.env.ImageTool;
            let options = { cwd: myVar };
            console.log(myVar);
            const result = execSync('python ' + myVar + "\\images_tool.py -p file -c config.xml -i "+dirPath+" -o "+dirPath, options);
            console.log(result.toString());
        });
    }
    export function customPlugin(): HvigorPlugin {
        return {
            pluginId: 'customPlugin',
            context() {
                return {
                    data: 'customPlugin xxx'
                };
            },
            async apply(currentNode: HvigorNode): Promise<void> {
                currentNode.registerTask({
                    // 任务名称
                    name: `default@makepicbin`,
                    // 任务执行逻辑主体函数
                    async run() {
                        const hapContext = currentNode.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
                        hapContext?.targets((target: default) => {
                            const outputPath = target.getBuildTargetOutputPath();
                            console.log(outputPath);
                            const appName = 'MainAbility';
                            const srcDir = `${target.projectPath}/entry/src/main/js/` + appName;
                            const destDir = `${target.projectPath}/entry/build/default/intermediates/lite_source/default/assets/js/` + appName;
                            const destDir2 = `${target.projectPath}/entry/build/default/intermediates/loader_out_lite/default/js/` + appName;
                            const testDir = `${target.projectPath}/test`;
                            try {
                                createDirSync(destDir);
                                console.log('目录创建成功');
                                createDirSync(destDir2);
                                console.log('目录创建成功');
                                copyDirectorySync(srcDir, destDir);
                                console.log('文件夹拷贝完成');
                                copyDirectorySync(srcDir, destDir2);
                                console.log('文件夹拷贝完成');
                                console.log(`${target.projectPath}/entry/build/default/intermediates`);
                                executeCommand(`${target.projectPath}/entry/build/default/intermediates`);
                                console.log('Command executed successfully11');
                                deletePngBinFiles(`${target.projectPath}/entry/build/default/intermediates`)
                            } catch (error) {
                                console.error('创建过程中出错:', error, destDir);
                            }
                        });
                    },
                    // 配置前置任务依赖
                    dependencies: [
                        `default@LegacyCompileLiteJS`,
                        `default@LegacyCompileArkTS`,
                        `default@LegacyGenerateLoaderJson`,
                        `default@CacheNativeLibs`],
                    // 配置任务的后置任务依赖
                    postDependencies: [
                        `default@LegacyGenerateLiteCode`,
                        `default@LegacyBuildJS`,
                        `default@LegacyPackageHap`,
                        `default@SignHap`,
                        `assembleHap`
                    ]
                });
            }
        };
    }
    export default {
        system: legacyHapTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
        plugins:[customPlugin()]               /* Custom plugin to extend the functionality of Hvigor. */
    }
    
    • process.env.ImageTool:1设置的图片转换工具所在目录。

说明: 图形工具源码及安装请参考《图形工具使用指南》

签名介绍

说明: 应用签名密钥由产品厂商自行管理,解决方案不指定具体密钥。非商用调试场景,可采用调试模式关闭验签,或使用OpenHarmony社区签名。

商用产品默认开启验签,当使用厂商对应公钥和证书后,可验证所安装应用的完整性和真实性。

OpenHarmony 社区签名方法可参考 OpenHarmony hapsigntool 使用说明

DevEco Studio自动签名如下图所示,调试阶段可使用默认配置,即OH社区签名。

DevEco Studio自动签名如下图所示,调试阶段可使用默认配置,即OH社区签名

调试模式下关闭验签方法如下:

编译打包

按照下图中①标记的步骤构建应用包,并将在下图中②位置生成对应的应用包。

按照下图中①标记的步骤构建应用包,并将在下图中②位置生成对应的应用包

编译 Hello World JS 应用时,建议按以下顺序检查:

  1. 使用 DevEco Studio 打开 samples/js_samples/helloworld 工程。

  2. 确认 entry/src/main/config.jsonbundleNamedeviceType、Ability 名称和 pages 路径与当前工程一致。

  3. 使用 DevEco Studio 执行构建,生成 HAP 应用包。

  4. 如果需要安装 release 包或商用包,使用 haptobin_tool.jar 将 HAP 转换为 BIN;如果是 IDE debug 模式,可按调试流程直接使用 HAP 产物。

应用安装

  1. 将“编译打包”中生成的应用包文件上传到板端任意位置,如“/user/jsapp/sample.bin”。

    说明: 文件上传方式请参考《DebugKits 工具使用指南》“数据上传与下载”章节。

  2. 安装应用包:

    1. 如果调试非签名JSApp时,可以在串口上通过如下命令执行关闭验签。对已签名应用跳过此步骤。

      AT+OHOS=OHOSFWK_BM_SET,disable

    2. 在串口上执行应用安装,允许覆盖安装:

      AT+OHOS=OHOSFWK_BM_INSTALL,/user/jsapp/sample.bin

    3. 如果覆盖安装时,在命令末尾添加参数“rmdata”,则会删除应用沙箱历史数据:

      AT+OHOS=OHOSFWK_BM_INSTALL,/user/jsapp/sample.bin,rmdata

应用卸载

在串口上执行卸载:AT+OHOS=OHOSFWK_BM_UNINSTALL,XXX

说明: XXX为需要卸载应用的包名,例如:AT+OHOS=OHOSFWK_BM_UNINSTALL,com.example.test。更多OHOS命令及参数,请见“/src/middleware/utils/at/at_ohos_cmd/at/at_ohos.c”中的g_at_ohos_cmd中对应OHOS命令及参数定义。

预置应用

  1. 请参见《文件系统使用指南》“YAFFS 镜像开发指导”,制作文件系统镜像,将需要预置的应用放到“/user/app/system/ace/vendor/”目录下。
  2. 烧录镜像并重启设备,预置应用将会在设备启动时自动安装。

说明:

  • 卸载预置应用 当手动执行预置应用的卸载后,系统会在“/user/app/user/ace/etc/”目录下生成“uninstalled_delbundle.json”文件。这确保了在设备下次重启时,手动卸载的应用不会被自动重新安装。
  • 恢复预置应用 如果希望恢复之前卸载的预置应用,只需删除“/user/app/user/ace/etc/”目录下的“uninstalled_delbundle.json”文件。在删除该文件后,下次设备重启时预置应用将会重新安装。

运行验收

安装完成后,按以下项目确认 Hello World JS 应用已跑通:

  • 应用管理命令返回安装成功。
  • 设备应用列表中出现 HelloWorld JS 应用。
  • 点击应用后进入首页,页面显示 HelloWorld 或多语言资源中配置的文本。
  • 切换系统语言后,页面文本可读取 i18n 目录下对应语言资源。

如果应用未显示在列表中,优先检查安装包是否上传到正确路径、签名模式是否与安装包匹配,以及 config.json 中的 bundleName 是否与卸载或覆盖安装操作一致。

OH应用市场

穿戴设备配套的手机管理软件,在接入应用市场(https://www.openwearplay.com)后,可以安装管理OpenHarmony穿戴应用市场中的JsApp;应用安全安装需要使用根证书进行证书链的完整性校验。

穿戴设备端需上传证书链的根证书文件以及根证书文件的哈希列表、哈希列表签名文件到设备的“/user/app/user/certcfg”目录下,如下图所示:

穿戴设备端需上传证书链的根证书文件以及根证书文件的哈希列表、哈希列表签名文件到设备的“/user/app/user/certcfg”目录下,如下图所示

  • ohos_trusted_root_cert.bin:OpenHarmony社区根证书文件。
  • app_market_root_cert.pem:穿戴应用市场根证书文件。
  • root_cert_hash_list.bin:包含根证书文件名及根证书内容SHA256值的哈希列表文件。
  • hash_list_sign.bin:使用穿戴市场私钥对哈希列表进行签名的签名文件,验签的公钥已预置在镜像内。

须知: 根证书可以只传一个,也支持可扩展,如上是同时支持OpenHarmony社区签名的应用也支持穿戴应用市场签名的应用;如果不推上述文件,开启应用验签的情况下,设备只能安装OpenHarmony社区签名的应用。