Bootstrap

HarmonyOS开发实战( Beta5版)小程序场景性能优化开发指导

简介

小程序是一种轻量级的应用,它不需要下载、安装即可使用,用户可以通过扫描二维码或者搜索直接打开使用。小程序运行在特定的平台上,平台提供了小程序的运行环境(运行容器)和一些基础服务(小程序API)。

小程序的架构设计使得它具有快速开发、易于部署、用户使用门槛低等特点,非常适用于实现一些轻量级的应用场景。

图1 经典小程序的双线程架构

经典小程序的双线程架构

小程序的架构如图1所示,其重要组成结构包括小程序API、JSVM Worker、Webview Render三个部分:

小程序API: 它定义了小程序与外部系统或服务交互的方式。小程序平台提供了丰富的API,包括但不限于网络请求、本地存储、设备信息、媒体处理等。开发者可以通过调用这些API来实现小程序的各种功能,如获取用户位置、播放音乐、访问网络资源等。

JSVM Worker: 简称Worker,指的是小程序中逻辑层,它允许开发者在不阻塞主线程的情况下执行耗时的操作,如数据的上传下载、大量数据处理等。比如小程序中的Worker是一个运行在JS线程中的 JavaScript 环境,它与主线程并行执行,互不干扰。

Webview Render: 简称Render, 通常指的是小程序的视图层或渲染层,即小程序的界面是如何被渲染到屏幕上的。小程序的渲染层由小程序框架提供,开发者通过编写WXML(类似 HTML 的标记语言)和WXSS(类似 CSS 的样式表)来定义界面的布局和样式。小程序的渲染引擎会将WXML和WXSS解析并渲染成用户可以看到的界面。

典型业务场景

典型场景

根据人因体验指标,总结当前小程序性能体验的场景如下:

场景分类场景名称简述
小程序页面加载页面加载从原生页面启动小程序,从抬手到小程序首页完全加载完成。
小程序页面中上下滑动浏览场景滑动帧率用户在小程序页面中上下滑动,浏览页面中信息。如用户在外卖小程序页面,上下滑动浏览商家信息。
点击跳转场景点击响应点击跳转,从抬手到页面变化第一帧

场景优化方案

从小程序的场景出发,通过剖析小程序运行时的性能瓶颈, 发现小程序的性能主要体现在以下几个方面:

  1. 组件启动性能:指小程序中各个组件(JSVM、ArkWeb)从初始化到可用状态所需的时间。
  2. 加载性能:涉及小程序从网络或本地加载资源(如页面、图片、脚本等)的速度。
  3. 执行性能:小程序代码执行的效率,包括脚本解析、执行和逻辑处理的速度。
  4. 调用性能:小程序API调用的响应速度和效率,包括与操作系统或硬件交互的性能。
  5. 渲染性能:小程序界面元素渲染到屏幕上的速度和流畅度,直接影响用户体验。

针对各个场景的特征和性能瓶颈,设计了高性能方案,小程序容器可以通过接入高性能方案来解决性能问题。

场景分类场景名称优化方案
小程序页面加载页面加载小程序容器预热
同步JSBridge&AsyncJSB
字节码CodeCache加速
小程序公共资源加载加速
JSVM性能优化
小程序API性能优化
小程序页面中上下滑动浏览场景滑动帧率同步JSBridge&AsyncJSB
JSVM性能优化
小程序API性能优化
点击跳转场景点击响应小程序容器预热
同步JSBridge&AsyncJSB
字节码CodeCache加速
小程序公共资源加载加速
JSVM性能优化
小程序API性能优化

小程序容器预热

原理介绍

小程序启动时,需要创建一个JavaScript引擎,用来执行小程序的逻辑代码,再创建一个WebView,用来渲染小程序的视图。

如果小程序启动时,每次都新创建小程序的运行时环境,其加载性能不可避免的会受到影响,造成用户体验下降。

小程序启动大致流程如图2所示:

图2 小程序启动时序示意图

小程序启动时序示意图

小程序在启动过程中,包含Worker的启动和Render的启动、小程序框架(render.js&service.js)的初始化等步骤,且每个小程序的启动过程都是重复的,因此可以将小程序容器提前创建好,并将公共资源注入到容器中,在小程序启动时,只需要加载小程序的业务代码即可。

场景优化关键点

  1. 预热WebView: 创建离线WebView,并注入小程序视图层的公共资源,并初始化小程序的视图框架。

  2. 预热JSVM: 创建JSVM,并注入小程序逻辑层的公共资源,并初始化小程序逻辑框架。

上述2个关键优化点能带来显著的性能提升。由于所有的关键点都是建立在预热的思路上,提前深度预热小程序运行时容器。下表对各关键点的效果、代价和建议场景进行对比。

关键点收益代价
预热Webview小程序启动性能提升建一个空WebView,多消耗约10M内存
预热JSVM小程序启动性能提升建一个空JSVM,多消耗约5M内存

JSVM的创建与预热较为简单,不涉及UI的复杂状态转换,详细案例可参考《使用JSVM-API接口创建多个引擎执行JS代码并销毁》

WebView预热方案

鸿蒙化实现方案

本方案适用于WebView预热场景。开发者创建一个新的ArkWeb组件,并在后台注入小程序的公共资源,组件状态为Hidden和InActive,开发者可以在后续使用中按需动态让WebView挂载到ArkUI树上,进行显示。详情请参考《Web组件开发性能提升指导-预渲染优化》

说明
Web组件数量限制:WebView创建会消耗更多的内存、算力,小程序创建离线WebView组件数量要求小于200个。

性能建议:WebView同样提供了CAPI,推荐在C++侧对WebView进行操作。

通过提前创建WebView控件,进行WebView组件的预热,还可以更进一步,结合后续章节中离线资源免拦截注入,可以实现对小程序框架的预热。

同步JSBridge与异步JSBridge

JSBridge优化解决方案

原理描述

本方案适用于ArkWeb应用侧通信场景,开发者可根据应用架构选择合适的业务通信机制(以下简称JSBridge):

  1. 应用使用ArkTS语言开发,推荐使用ArkWeb在ArkTS提供的runJavaScriptExt接口实现应用侧至前端页面的通信,同时使用registerJavaScriptProxy实现前端页面至应用侧的通信。

  2. 应用使用ArkTS、C++语言混合开发,或本身应用结构较贴近于小程序架构,自带C++侧环境,推荐使用ArkWeb在NDK侧提供的OH_NativeArkWeb_RunJavaScript及OH_NativeArkWeb_RegisterJavaScriptProxy接口实现JSBridge功能。

图3 小程序数据通信架构示意图

小程序数据通信架构示意图

图3为具有普适性的小程序一般架构,其中逻辑层需要应用自带JavaScript运行时,本身已存在C++环境,通过NDK接口可直接在C++环境中完成与视图层(ArkWeb作为渲染器)的通信,无需再返回ArkTS环境调用JSBridge相关接口。

图4 NDK JSBridge方案示意图 

NDK JSBridge方案示意图

NDK JSBridge方案可以解决ArkTS环境的冗余切换,同时允许回调在非UI线程上报,避免造成UI阻塞,如图4所示。

实践案例

【场景一】 使用ArkTS接口实现JSBridge通信

具体实践场景请参考《Web组件开发性能提升指导-JSBridge优化案例一》

【场景二】 使用NDK接口实现JSBridge通信

图5 使用NDK接口实现JSBridge通信核心流程图

使用NDK接口实现JSBridge通信核心流程图

具体实现场景请参考《Web组件开发性能提升指导-JSBridge优化案例二》

异步JSBridge调用

原理描述

本方案用于ArkWeb侧调用系统能力场景下,将开发者指定的JSBridge接口调用抛出后,不等待执行结果,以避免在应用主线程负载重时,JSBridge同步调用可能导致Web线程等待IPC时间过长,造成调用阻塞的问题。

优化前优化后

优化前: JSBCall调用应用主线程上运行的业务功能函数时,Web渲染线程需要停下来,等待应用主线程上执行完业务功能函数,才能返回Web渲染线程,Web渲染线程才能继续往下执行。当应用主线程繁忙时,JSB调用需要排队才能得到执行,这时Web渲染线程阻塞的时间变长,性能劣化非常严重。

优化后: JSBCall调用应用主线程上运行的业务功能函数时,Web渲染线程无需等待应用主线程执行业务功能函数,可以继续往下执行,当应用主线程繁忙时,性能提升非常明显。

实践案例

具体实践场景及方案关键点请参考《Web组件开发性能提升指导-异步JSBridge调用》

CodeCache加速

ArkWeb支持预编译JavaScript生成字节码缓存(Code Cache)

原理描述

本方案用于在页面加载之前提前将即将使用到的JavaScript文件编译成字节码并缓存到本地,在页面首次加载时节省编译时间。

图6 预编译JavaScript核心流程图

预编译JavaScript核心流程图

实践案例

具体实践场景及方案关键点请参考《Web组件开发性能提升指导-预编译JavaScript生成字节码缓存(Code Cache)》

ArkWeb支持自定义协议的JavaScript生成字节码缓存(Code Cache)

原理描述

本方案用于在页面加载时存在自定义协议的JavaScript,支持其生成字节码缓存到本地,在页面非首次加载时节省编译时间。

实践案例

具体实践场景及方案关键点请参考《Web组件开发性能提升指导-支持自定义协议的JavaScript生成字节码缓存(Code Cache)》

JSVM支持JavaScript生成字节码缓存(Code Cache)

原理描述

V8引擎在执行JavaScript代码之前需要将JavaScript代码先编译成ByteCode,再执行代码逻辑,而这个编译的耗时,一般占JS执行时间的20~30%,因此,提供JavaScript文件编译成ByteCode的接口,可以加速JSVM的执行速度。

API说明

接口优功能说明
OH_JSVM_CompileScript编译JavaScript代码并返回绑定到当前环境的编译脚本
OH_JSVM_CompileScriptWithOrigin编译JavaScript代码并返回绑定到当前环境的编译脚本,同时传入包括 sourceMapUrl 和源文件名在内的源代码信息,用于处理sourcemap信息
OH_JSVM_CreateCodeCache为编译脚本创建code cache
实践案例

编译及执行JavaScript代码(创建vm,注册function,执行JavaScript,销毁vm):

#include <cstring>
#include <fstream>
#include <string>
#include <vector>

// 依赖libjsvm.so
#include "ark_runtime/jsvm.h"

using namespace std;

static JSVM_Value Hello(JSVM_Env env, JSVM_CallbackInfo info) {
  JSVM_Value output;
  void* data = nullptr;
  OH_JSVM_GetCbInfo(env, info, nullptr, nullptr, nullptr, &data);
  OH_JSVM_CreateStringUtf8(env, (char*)data, strlen((char*)data), &output);
  return output;
}

static JSVM_CallbackStruct hello_cb = { Hello, (void*)"Hello" };

static string srcGlobal = R"JS(
const concat = (...args) => args.reduce((a, b) => a + b);
throw new Error("exception triggered")
)JS";

static void RunScript(JSVM_Env env, string& src,
                       bool withOrigin = false,
                       const uint8_t** dataPtr = nullptr,
                       size_t* lengthPtr = nullptr) {
  JSVM_HandleScope handleScope;
  OH_JSVM_OpenHandleScope(env, &handleScope);

  JSVM_Value jsSrc;
  OH_JSVM_CreateStringUtf8(env, src.c_str(), src.size(), &jsSrc);

  const uint8_t* data = dataPtr ? *dataPtr : nullptr;
  size_t length = lengthPtr ? *lengthPtr : 0;
  bool cacheRejected = true;
  JSVM_Script script;
  // 编译js代码
  if (withOrgin) {
    JSVM_ScriptOrigin origin {
      // 以包名 helloworld 为例, 假如存在对应的 sourcemap, source map 的的路径可以是 /data/app/el2/100/base/com.example.helloworld/files/index.js.map
      .sourceMapurl = "/data/app/el2/100/base/com.example.helloworld/files/index.js.map",
      // 源文件名字
      .resourceName = "index.js",
      // scirpt 在源文件中的起始行列号
      .resourceLineOffset = 0,
      .resourceColumnOffset = 0,
    }
    OH_JSVM_CompileScriptWithOrigin(env, jsSrc, data, length, true, &cacheRejected, origin, &script);
  } else {
    OH_JSVM_CompileScript(env, jsSrc, data, length, true, &cacheRejected, &script);
  }
  printf("Code cache is %s\n", cacheRejected ? "rejected" : "used");

  JSVM_Value result;
  // 执行js代码
  OH_JSVM_RunScript(env, script, &result);

  char resultStr[128];
  size_t size;
  OH_JSVM_GetValueStringUtf8(env, result, resultStr, 128, &size);
  printf("%s\n", resultStr);
  if (dataPtr && lengthPtr && *dataPtr == nullptr) {
    // 将js源码编译出的脚本保存到cache,可以避免重复编译,带来性能提升
    OH_JSVM_CreateCodeCache(env, script, dataPtr, lengthPtr);
    printf("Code cache created with length = %ld\n", *lengthPtr);
  }

  OH_JSVM_CloseHandleScope(env, handleScope);
}

小程序公共资源加载加速

ArkWeb离线资源免拦截注入

原理描述

本方案用于在页面加载之前提前将即将使用到的图片、样式表和脚本资源注入到WebView内存缓存中,在页面首次加载时节省网络请求时间。

图7 资源加速核心流程图

资源加速核心流程图

实践案例

具体实践场景及方案关键点请参考《Web组件开发性能提升指导-离线资源免拦截注入》

ArkWeb资源拦截替换加速

原理描述

本方案在原本的资源拦截替换接口基础上新增支持了ArrayBuffer格式的入参,开发者无需在应用侧进行ArrayBuffer到String格式的转换,可直接使用ArrayBuffer格式的数据进行拦截替换,避免格式转换和内存拷贝影响性能

实践案例

具体实践场景及方案关键点请参考《Web组件开发性能提升指导-资源拦截替换加速》

JSVM性能优化

Harmony JSVM-API是基于标准JavaScript引擎提供的一套稳定的ABI,为开发者提供了较为完整的JavaScript引擎能力,包括创建和销毁引擎,执行JavaScript代码,JavaScript/C++交互等关键能力。

开发者可以在应用运行期间直接执行一段动态加载的JavaScript代码。也可以选择将一些对性能、底层系统调用有较高要求的核心功能用C/C++实现并将C++方法注册到JavaScript侧,在JavaScript代码中直接调用,提高应用的执行效率。

创建JSVM Snapshot: 在JSVM预热完成之后,将JSVM的状态保存在磁盘上,第二次加载时,如果小程序的框架代码没有发生变化,直接加载Snapshot文件,无需再次执行小程序框架初始化的动作,提升性能的同时还有降低功耗的效果。

JSVM Snapshot

原理描述

Script虚拟机(JSVM)的快照创建功能,将当前运行时的JavaScript程序状态保存为一个快照文件,这个快照文件包含了当前的堆内存、执行上下文、函数闭包等信息。

动快照: 虚拟机在某个特定时间点的状态快照,包含了当前虚拟机的所有内部状态和数据。通过创建一个启动快照,可以在之后的时间点恢复虚拟机到相同的状态。

机启动快照可以简化一些复杂的编程任务,使得在JSVM中管理和维护虚拟机更加便捷,使程序更加灵活与稳定。

实践案例

API: JVM_CreateSnapshot

#include "napi/native_api.h"
#include "ark_runtime/jsvm.h"
#include <hilog/log.h>
#include <fstream>

// CreateAndUseSnapshot注册回调
static JSVM_CallbackStruct param[] = {
  {.data = nullptr, .callback = CreateAndUseSnapshot},
};
static JSVM_CallbackStruct *method = param;
// CreateAndUseSnapshot方法别名,供JS调用
static JSVM_PropertyDescriptor descriptor[] = {
  {"createAndUseSnapshot", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT},
};

static const int MAX_BUFFER_SIZE = 128;
// CreateHelloString()函数需绑定到JSVM虚拟机, 用于OH_JSVM_CreateSnapshot虚拟机快照的正常创建
static JSVM_Value CreateHelloString(JSVM_Env env, JSVM_CallbackInfo info)
{
  JSVM_Value outPut;
  OH_JSVM_CreateStringUtf8(env, "Hello world!", JSVM_AUTO_LENGTH, &outPut);
  return outPut;
}
// 提供外部引用的方式以便JavaScript环境可以调用绑定的函数
static JSVM_CallbackStruct helloCb = {CreateHelloString, nullptr};
// 外部引用
static intptr_t externals[] = {
  (intptr_t)&helloCb,
  0,
};

static JSVM_Value RunVMScript(JSVM_Env env, std::string &src)
{
  // 打开handleScope作用域
  JSVM_HandleScope handleScope;
  OH_JSVM_OpenHandleScope(env, &handleScope);
  JSVM_Value jsStr = nullptr;
  OH_JSVM_CreateStringUtf8(env, src.c_str(), src.size(), &jsStr);
  // 编译JavaScript代码
  JSVM_Script script;
  OH_JSVM_CompileScript(env, jsStr, nullptr, 0, true, nullptr, &script);
  // 执行JavaScript代码
  JSVM_Value result = nullptr;
  OH_JSVM_RunScript(env, script, &result);
  // 关闭handleScope作用域
  OH_JSVM_CloseHandleScope(env, handleScope);
  return result;
}
// OH_JSVM_CreateSnapshot的样例方法
static void CreateVMSnapshot() {
  // 创建JavaScript虚拟机实例,打开虚拟机作用域
  JSVM_VM vm;
  JSVM_CreateVMOptions vmOptions;
  memset(&vmOptions, 0, sizeof(vmOptions));
  // isForSnapshotting设置该虚拟机是否用于创建快照
  vmOptions.isForSnapshotting = true;
  OH_JSVM_CreateVM(&vmOptions, &vm);
  JSVM_VMScope vmScope;
  OH_JSVM_OpenVMScope(vm, &vmScope);
  // 创建JavaScript环境,打开环境作用域
  JSVM_Env env;
  // 将native函数注册成JavaScript可调用的方法
  JSVM_PropertyDescriptor descriptor[] = {
    {"createHelloString", nullptr, &helloCb, nullptr, nullptr, nullptr, JSVM_DEFAULT},
  };
  OH_JSVM_CreateEnv(vm, 1, descriptor, &env);
  JSVM_EnvScope envScope;
  OH_JSVM_OpenEnvScope(env, &envScope);
  // 使用OH_JSVM_CreateSnapshot创建虚拟机的启动快照
  const char *blobData = nullptr;
  size_t blobSize = 0;
  JSVM_Env envs[1] = {env};
  JSVM_Status status = OH_JSVM_CreateSnapshot(vm, 1, envs, &blobData, &blobSize);
  if (status == JSVM_OK) {
    OH_LOG_INFO(LOG_APP, "Test JSVM OH_JSVM_CreateSnapshot success, blobSize = : %{public}ld", blobSize);
  }
  // 将snapshot保存到文件中
  // 保存快照数据,/data/storage/el2/base/files/test_blob.bin为沙箱路径
  // 以包名为com.example.jsvm为例,实际文件会保存到/data/app/el2/100/base/com.example.jsvm/files/test_blob.bin
  std::ofstream file("/data/storage/el2/base/files/test_blob.bin",
                      std::ios::out | std::ios::binary | std::ios::trunc);
  file.write(blobData, blobSize);
  file.close();
  // 关闭并销毁环境和虚拟机
  OH_JSVM_CloseEnvScope(env, envScope);
  OH_JSVM_DestroyEnv(env);
  OH_JSVM_CloseVMScope(vm, vmScope);
  OH_JSVM_DestroyVM(vm);
}
static void RunVMSnapshot() {
  // blobData的生命周期不能短于vm的生命周期
  // 从文件中读取snapshot
  std::vector<char> blobData;
  std::ifstream file("/data/storage/el2/base/files/test_blob.bin", std::ios::in | std::ios::binary | std::ios::ate);
  size_t blobSize = file.tellg();
  blobData.resize(blobSize);
  file.seekg(0, std::ios::beg);
  file.read(blobData.data(), blobSize);
  file.close();
  OH_LOG_INFO(LOG_APP, "Test JSVM RunVMSnapshot read file blobSize = : %{public}ld", blobSize);
  // 使用快照数据创建虚拟机实例
  JSVM_VM vm;
  JSVM_CreateVMOptions vmOptions;
  memset(&vmOptions, 0, sizeof(vmOptions));
  vmOptions.snapshotBlobData = blobData.data();
  vmOptions.snapshotBlobSize = blobSize;
  OH_JSVM_CreateVM(&vmOptions, &vm);
  JSVM_VMScope vmScope;
  OH_JSVM_OpenVMScope(vm, &vmScope);
  // 从快照中创建环境env
  JSVM_Env env;
  OH_JSVM_CreateEnvFromSnapshot(vm, 0, &env);
  JSVM_EnvScope envScope;
  OH_JSVM_OpenEnvScope(env, &envScope);
  // 执行js脚本,快照记录的env中定义了createHelloString()
  std::string src = "createHelloString()";
  JSVM_Value result = RunVMScript(env, src);
  // 环境关闭前检查脚本运行结果
  if (result == nullptr) {
    OH_JSVM_ThrowError(env, nullptr, "Test JSVM RunVMSnapshot-RunVMScript result is nullptr");
    return;
  }
  char str[MAX_BUFFER_SIZE];
  OH_JSVM_GetValueStringUtf8(env, result, str, MAX_BUFFER_SIZE, nullptr);
  OH_LOG_INFO(LOG_APP, "Test JSVM RunVMSnapshot-RunVMScript result is: %{public}s", str);
  // 关闭并销毁环境和虚拟机
  OH_JSVM_CloseEnvScope(env, envScope);
  OH_JSVM_DestroyEnv(env);
  OH_JSVM_CloseVMScope(vm, vmScope);
  OH_JSVM_DestroyVM(vm);
  return;
}

static JSVM_Value CreateAndUseSnapshot(JSVM_Env env, JSVM_CallbackInfo info)
{
  // OH_JSVM_Init(&initOptions)需在开发流程中的RunJsVm()中第一次初始化(只能初始化一次)
  // JSVM_InitOptions initOptions 赋值是在开发流程中完成的
  // 创建虚拟机快照并将快照保存到文件中
  CreateVMSnapshot();
  // snapshot可以记录下特定的js执行环境,可以跨进程通过snapshot快速还原出js执行上下文环境
  RunVMSnapshot();
  JSVM_Value result = nullptr;
  OH_JSVM_CreateInt32(env, 0, &result);
  return result;
}

JSVM CodeCache

见JSVM支持JavaScript生成字节码缓存(Code Cache)章节

小程序API性能优化

小程序API背景介绍

小程序API是各家大厂小程序生态控制点,通过JS框架解耦对系统的依赖,按照小程序的约束,开发者只能在worker中调用小程序API。

小程序API性能trace点建议

指标类别缩略语全称中文名称解释
逻辑层JSSJS engine StartupJavaScript引擎启动耗时执行JavaScript引擎创建的耗时。
渲染层RSRender StartupWeb引擎启动耗时Webview实例创建的耗时。
逻辑层和渲染层PLTPkg Load Time包加载时间/资源准备耗时把小程序包下载下来,再注入到JavaScript引擎和渲染引擎中的耗时。
逻辑层FFCFirst Frame Calculate首帧计算耗时从开始执行小程序的逻辑到发送给webview第一帧数据之前的耗时。
渲染层FPFirst Paint首次绘制Webview页面首个像素点开始绘制的时刻,无论是否绘制了文本、图片。
渲染层FRFirst Render首帧渲染耗时从接收到第一帧渲染数据到渲染完成时间的耗时。
渲染层LCPLargest Contentful Paint最大有内容绘制Webview渲染面积最大的文本/图片的时刻。
渲染层FFSPFirst Full Screen Paint首次全屏完整绘制从小程序启动到首屏元素铺满的耗时,页面加载结束前最后一次渲染。
JSAPISIWSync Invoke Wait同步调用等待耗时同步调用等待,如getSystemInfoSync等待线程处理的时间。
JSAPISIESync Invoke Execute同步调用执行耗时同步调用执行耗时,如getSystemInfoSync API的执行时间。
JSAPIAIWAsync Invoke Wait异步调用等待耗时如request请求等待线程处理的时间。
JSAPIAICASync Invoke Complete异步调用完成耗时如request API执行时间,从发送到接收完网络数据的时间。
通信RJSRun JavaScript执行JS时间调用webview RunJavaScript接口的耗时。
通信JSBJSB InvokeJSBridge 调用耗时webview通过JS Bridge调用ArkTS/Native API耗时。

通过上述Trace点,可以非常方便的查找出小程序性能的逻辑层、小程序API调用、和渲染层瓶颈点。

小程序API架构建议

小程序API从功能上分类主要有媒体、设备、基础、界面、网络、画布等几百上千不等的API,每个小程序在启动、滑动过程中,Worker有大量并发的小程序API调用,如果将这些API都实现在应用主线程上,将给小程序的性能带来严重的影响,如图8所示。

图8 小程序API调用逻辑

小程序API调用逻辑

在鸿蒙上,将所有的小程序API转发给应用主线程之后,将会造成严重的API调用拥塞的情况。如图9所示:

图9 小程序API调用顺序

小程序API调用顺序

整理一下小程序API调用顺序:

  1. 定位getlocation#297异步API调用发起

  2. 网络Request#298请求异步API调用发起

  3. CanIUse#296同步API调用发起

正常情况下, CanIUse接口耗时1ms,但是在主线程拥塞的情况下,同步调用等待了127ms,等待getlocation执行完成才有机会执行。

因此,建议JSAPI的架构设计如图10所示:

图10 JSAPI调用架构优化示意图

JSAPI调用架构优化示意图

小程序API使用ArkTS编程的注意点

鸿蒙应用采用的声明式编程范式,采用数据驱动的方式改变应用的行为,与android和iOS上的命令式编程范式有很大的区别,鸿蒙上提供了on接口对系统属性进行监听,不必重复调用系统接口获取信息,采用on接口进行监听的方式,在提升性能的同时降低功耗。

如图11与图12所示,每次调用系统接口获取信息,性能劣化严重:

图11 API耗时占比图-函数trace数据

API耗时占比图-函数trace数据

图12 API耗时占比图-函数耗时数据

API耗时占比图-函数耗时数据

针对上图中的三个耗时最大的API:

  1. geoLocationManager.isLocationEnabled

  2. access.getState

  3. wifiManager.isWifiActive

通过查询官方文档,这三个接口都可以通过on接口监听系统变化,如果没有变化,小程序API直接用缓存结果即可,如果有变化,系统会回调应用的监听处理函数,修改缓存值。

因此,在小程序API实现时,应该关注小程序API实现中使用的系统API是否有对应的on接口函数,如果有对应的on接口函数,应该采用on函数优化小程序API性能。

高频小程序API实现方案推荐

异步API名称实现方向
request在C侧实现
getStorage在C侧实现
getStorageInfo在C侧实现
removeStorage在C侧实现
getScreenBrightnessArkTS主线程实现
getSettingon响应式改造
getNetworkTypeon响应式改造
getSystemInfoon响应式改造
getBatteryInfoon响应式改造
getLocationgetLocation
同步API名称实现方向
getSystemInfoSyncon响应式改造
getStorageSync在C侧实现
setStorageSync在C侧实现
getAccountInfoSyncArkTS主线程实现

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

;