简介
开发者实现在应用中跳转显示网页需要分为两个方面:使用@ohos.web.webview提供Web控制能力;使用Web组件提供网页显示的能力。在实际应用中往往由于各种原因导致首次跳转Web网页或Web组件内跳转时出现白屏、卡顿等情况。本文介绍提升Web首页加载与Web网页间跳转速度的几种方法。
优化思路
用户在使用Web组件显示网页时往往会经历四个阶段:无反馈-->白屏-->网页渲染-->完全展示,系统会在各个阶段内分别进行WebView初始化、建立网络连接、接受数据与渲染页面等操作,如图一所示是WebView的启动阶段。
图一 Web组件显示页面的阶段
要优化Web组件的首页加载性能,可以从图例标记的三个阶段来进行优化:
- 在WebView的初始化阶段:应用打开WebView的第一步是启动浏览器内核,而这段时间由于WebView还不存在,所有后续的步骤是完全阻塞的。因此可以考虑在应用中预先完成初始化WebView,以及在初始化的同时通过预先加载组件内核、完成网络请求等方法,使得WebView初始化不是完全的阻塞后续步骤,从而减小耗时。
- 在建立连接阶段:当开发者提前知道访问的网页地址,我们可以预先建立连接,进行DNS预解析。
- 在接收资源数据阶段:当开发者预先知道用户下一页会点击什么页面的时候,可以合理使用缓存和预加载,将该页面的资源提前下载到缓存中。
综上所述,开发者可以通过方法1和2来提升Web首页加载速度,在应用创建Ability的时候,在OnCreate阶段预先初始化内核。随后在onAppear阶段进行预解析DNS、预连接要加载的首页。
在网页跳转的场景,开发者也可以通过方法3,在onPageEnd阶段预加载下一个要访问的页面,提升Web网页间的跳转和显示速度,如图二所示。
图二 Web组件的生命周期回调函数
优化方法
提前初始化内核
原理介绍
当应用首次打开时,默认不会初始化浏览器内核,只有当创建WebView实例的时候,才会开始初始化浏览器内核。
为了能提前初始化WebView实例,@ohos.web.webview提供了initializeWebEngine方法。该方法实现在Web组件初始化之前,通过接口加载Web引擎的动态库文件,从而提前进行Web组件动态库的加载和Web内核主进程的初始化,最终以提高启动性能,减少白屏时间。
实践案例
【反例】
在未初始化Web内核前提下,启动加载Web页面
- import web_webview from '@ohos.web.webview';
- import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
-
- @Entry
- @Component
- struct Index {
- controller: web_webview.WebviewController = new web_webview.WebviewController();
-
- build() {
- Column() {
- Web({ src: 'https://www.example.com/example.html', controller: this.controller })
- .fileAccess(true)
- }
- }
- }
性能打点数据如下,getMessageData进程中的Duration为加载页面开始到结束的耗时:
【正例】
在页面开始加载时,调用initializeWebEngine()接口初始化Web内核,具体步骤如下:
- 初始化Web内核
- // EntryAbility.ets
- import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
- import { webview } from '@kit.ArkWeb';
-
- export default class EntryAbility extends UIAbility {
- onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
- webview.WebviewController.initializeWebEngine();
- }
- }
- 加载Web组件
- // xxx.ets
- import web_webview from '@ohos.web.webview';
- import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
-
- @Entry
- @Component
- struct Index {
- controller: web_webview.WebviewController = new web_webview.WebviewController();
-
- build() {
- Column() {
- Web({ src: 'https://www.example.com/example.html', controller: this.controller })
- .fileAccess(true)
- }
- }
- }
性能打点数据如下,getMessageData进程中的Duration为加载页面开始到结束的耗时:
总结
页面加载方式 | 耗时(局限不同设备和场景,数据仅供参考) | 说明 |
---|---|---|
直接加载Web页面 | 1264ms | 在加载Web组件时才初始化Web内核,增加启动时间 |
提前初始化Web内核 | 1153ms | 加载页面时减少了Web内核初始化步骤,提高启动性能 |
预解析DNS、预连接
WebView在onAppear阶段进行预连接socket, 当Web内核真正发起请求的时候会直接复用预连接的socket,如果当前预解析还没完成,真正发起网络请求进行DNS解析的时候也会复用当前正在执行的DNS解析任务。同理即使预连接的socket还没有连接成功,Web内核也会复用当前正在连接中的socket,进而优化资源的加载过程。
@ohos.web.webview提供了prepareForPageLoad方法实现预连接url,在加载url之前调用此API,对url只进行DNS解析、socket建链操作,并不获取主资源子资源。
参数:
参数名 | 类型 | 说明 |
---|---|---|
url | string | 预连接的url。 |
preconnectable | boolean | 是否进行预连接。如果preconnectable为true,则对url进行dns解析,socket建链预连接;如果preconnectable为false,则不做任何预连接操作。 |
numSockets | number | 要预连接的socket数。socket数目连接需要大于0,最多允许6个连接。 |
使用方法如下:
// 开启预连接需要先使用上述方法预加载WebView内核。 webview.WebviewController.initializeWebEngine(); // 启动预连接,连接地址为即将打开的网址。 webview.WebviewController.prepareForPageLoad("https://www.example.com", true, 2);
预加载下一页
开发者可以在onPageEnd阶段进行预加载,当真正去加载下一个页面的时候,如果预加载已经成功,则相当于直接从缓存中加载页面资源,速度更快。一般来说能够准确预测到用户下一步要访问的页面的时候,可以进行预加载将要访问的页面,比如小说下一页, 浏览器在地址栏输入过程中识别到用户将要访问的页面等。
@ohos.web.webview提供prefetchPage方法实现在预测到将要加载的页面之前调用,提前下载页面所需的资源,包括主资源子资源,但不会执行网页JavaScript代码或呈现网页,以加快加载速度。
参数:
参数名 | 类型 | 说明 |
---|---|---|
url | string | 预加载的url。 |
additionalHeaders | Array | url的附加HTTP请求头。 |
使用方法如下:
- // ../src/main/ets/pages/WebBrowser.ets
-
- import webview from '@ohos.web.webview';
- // ...
-
- controller: webview.WebviewController = new webview.WebviewController();
- // ...
- Web({ src: 'https://www.example.com', controller: this.controller })
- .onPageEnd((event) => {
- // ...
- // 在确定即将跳转的页面时开启预加载
- this.controller.prefetchPage('https://www.example.com/nextpage');
- })
- Button('下一页')
- .onClick(() => {
- // ...
- // 跳转下一页
- this.controller.loadUrl('https://www.example.com/nextpage');
- })
预渲染优化
原理介绍
预渲染优化适用于Web页面启动和跳转场景,例如,进入首页后,跳转到其他子页。与预连接、预下载不同的是,预渲染需要开发者额外创建一个新的ArkWeb组件,并在后台对其进行预渲染,此时该组件并不会立刻挂载到组件树上,即不会对用户呈现(组件状态为Hidden和InActive),开发者可以在后续使用中按需动态挂载。
具体原理如下图所示,首先需要定义一个自定义组件封装ArkWeb组件,该ArkWeb组件被离线创建,被包含在一个无状态的节点NodeContainer中,并与相应的NodeController绑定。该ArkWeb组件在后台完成预渲染后,在需要展示该ArkWeb组件时,再通过NodeController将其挂载到ViewTree的NodeContainer中,即通过NodeController绑定到对应的NodeContainer组件。预渲染通用实现的步骤如下:
创建自定义ArkWeb组件:开发者需要根据实际场景创建封装一个自定义的ArkWeb组件,该ArkWeb组件被离线创建。 创建并绑定NodeController:实现NodeController接口,用于自定义节点的创建、显示、更新等操作的管理。并将对应的NodeController对象放入到容器中,等待调用。 绑定NodeContainer组件:将NodeContainer与NodeController进行绑定,实现动态组件页面显示。
图三 预渲染优化原理图
说明
预渲染相比于预下载、预连接方案,会消耗更多的内存、算力,仅建议针对高频页面使用,单应用后台创建的ArkWeb组件要求小于200个。
实践案例
-
创建载体,并创建ArkWeb组件
- // 载体Ability
- // EntryAbility.ets
- import {createNWeb} from "../pages/common"
- onWindowStageCreate(windowStage: window.WindowStage): void {
- windowStage.loadContent('pages/Index', (err, data) => {
- // 创建ArkWeb动态组件(需传入UIContext),loadContent之后的任意时机均可创建
- createNWeb("https://www.example.com", windowStage.getMainWindowSync().getUIContext());
- if (err.code) {
- return;
- }
- });
- }
-
创建NodeContainer和对应的NodeController,渲染后台ArkWeb组件
- // 创建NodeController
- // common.ets
- import { UIContext } from '@kit.ArkUI';
- import { webview } from '@kit.ArkWeb';
- import { NodeController, BuilderNode, Size, FrameNode } from '@kit.ArkUI';
- // @Builder中为动态组件的具体组件内容
- // Data为入参封装类
- // 调用onActive,开启渲染
- @Builder
- function WebBuilder(data:Data) {
- Column() {
- Web({ src: data.url, controller: data.controller })
- .onPageBegin(() => {
- data.controller.onActive();
- })
- .width("100%")
- .height("100%")
- }
- }
- let wrap = wrapBuilder<Data[]>(WebBuilder);
- // 用于控制和反馈对应的NodeContianer上的节点的行为,需要与NodeContainer一起使用
- export class myNodeController extends NodeController {
- private rootnode: BuilderNode<Data[]> | null = null;
- // 必须要重写的方法,用于构建节点数、返回节点挂载在对应NodeContianer中
- // 在对应NodeContianer创建的时候调用、或者通过rebuild方法调用刷新
- makeNode(uiContext: UIContext): FrameNode | null {
- console.info(" uicontext is undifined : "+ (uiContext === undefined));
- if (this.rootnode != null) {
- // 返回FrameNode节点
- return this.rootnode.getFrameNode();
- }
- // 返回null控制动态组件脱离绑定节点
- return null;
- }
- // 当布局大小发生变化时进行回调
- aboutToResize(size: Size) {
- console.info("aboutToResize width : " + size.width + " height : " + size.height )
- }
- // 当controller对应的NodeContainer在Appear的时候进行回调
- aboutToAppear() {
- console.info("aboutToAppear")
- }
- // 当controller对应的NodeContainer在Disappear的时候进行回调
- aboutToDisappear() {
- console.info("aboutToDisappear")
- }
- // 此函数为自定义函数,可作为初始化函数使用
- // 通过UIContext初始化BuilderNode,再通过BuilderNode中的build接口初始化@Builder中的内容
- initWeb(url:string, uiContext:UIContext, control:WebviewController) {
- if(this.rootnode != null)
- {
- return;
- }
- // 创建节点,需要uiContext
- this.rootnode = new BuilderNode(uiContext)
- // 创建动态Web组件
- this.rootnode.build(wrap, { url:url, controller:control })
- }
- }
- // 创建Map保存所需要的NodeController
- let NodeMap:Map<string, myNodeController | undefined> = new Map();
- // 创建Map保存所需要的WebViewController
- let controllerMap:Map<string, WebviewController | undefined> = new Map();
- // 初始化需要UIContext 需在Ability获取
- export const createNWeb = (url: string, uiContext: UIContext) => {
- // 创建NodeController
- let baseNode = new myNodeController();
- let controller = new webview.WebviewController() ;
- // 初始化自定义Web组件
- baseNode.initWeb(url, uiContext, controller);
- controllerMap.set(url, controller)
- NodeMap.set(url, baseNode);
- }
- // 自定义获取NodeController接口
- export const getNWeb = (url : string) : myNodeController | undefined => {
- return NodeMap.get(url);
- }
-
通过NodeContainer使用已经预渲染的页面
- // 使用NodeController的Page页
- // Index.ets
- import {createNWeb, getNWeb} from "./common"
- @Entry
- @Component
- struct Index {
- build() {
- Row() {
- Column() {
- // NodeContainer用于与NodeController节点绑定,rebuild会触发makeNode
- // Page页通过NodeContainer接口绑定NodeController,实现动态组件页面显示
- NodeContainer(getNWeb("https://www.example.com"))
- .height("90%")
- .width("100%")
- }
- .width('100%')
- }
- .height('100%')
- }
- }
预取POST请求优化
原理介绍
预取POST请求适用于Web页面启动和跳转场景,当即将加载的Web页面中存在POST请求且POST请求耗时较长时,会导致页面加载时间增加,可以选择不同时机对POST请求进行预取,消除等待POST请求数据下载完成的耗时,具体有以下两种场景可供参考:
- 如果是应用首页,推荐在ArkWeb组件创建后或者提前初始化web内核后,对首页的POST请求进行预取,如onCreate、aboutToAppear。
- 当前页面完成加载后,可以对用户下一步可能点击页面的POST请求进行预取,推荐在Web组件的生命周期函数onPageEnd及后继时机进行。
注意事项:
- 本方案能消除POST请求下载耗时,预计收益可能在百毫秒(依赖POST请求的数据内容和当前网络环境)。
- 预取POST请求行为包括连接和资源下载,连接和资源加载耗时可能达到百毫秒(依赖POST请求的数据内容和当前网络环境),建议开发者为预下载留出足够的时间。
- 预取POST请求行为相比于预连接会消耗额外的流量、内存,建议针对高频页面使用。
- POST请求具有一定的即时性,预取POST请求需要指定恰当的有效期。
- 目前仅支持预取Context-Type为application/x-www-form-urlencoded的POST请求。最多可以预获取6个POST请求。如果要预获取第7个,会自动清除最早预获取的POST缓存。开发者也可以通过clearPrefetchedResource()接口主动清除后续不再使用的预获取资源缓存。
- 如果要使用预获取的资源缓存,开发者需要在正式发起的POST请求的请求头中增加键值“ArkWebPostCacheKey”,其内容为对应缓存的cacheKey。
案例实践
场景一:加载包含POST请求的首页
【不推荐用法】
当首页中包含POST请求,且POST请求耗时较长时,不推荐直接加载Web页面
- // xxx.ets
- import { webview } from '@kit.ArkWeb';
-
- @Entry
- @Component
- struct WebComponent {
- webviewController: webview.WebviewController = new webview.WebviewController();
-
- build() {
- Column() {
- Web({ src: 'https://www.example.com/', controller: this.webviewController })
- }
- }
- }
【推荐用法】
通过预取POST加载包含POST请求的首页,具体步骤如下:
- 通过initializeWebEngine()来提前初始化Web组件的内核,然后在初始化内核后调用prefetchResource()预获取将要加载页面中的POST请求。
- // EntryAbility.ets
- import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
- import { webview } from '@kit.ArkWeb';
-
- export default class EntryAbility extends UIAbility {
- onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- console.info('EntryAbility onCreate.');
- webview.WebviewController.initializeWebEngine();
- // 预获取时,需要将"https://www.example1.com/POST?e=f&g=h"替换成为真实要访问的网站地址
- webview.WebviewController.prefetchResource(
- {
- url: 'https://www.example.com/POST?e=f&g=h',
- method: 'POST',
- formData: 'a=x&b=y'
- },
- [{
- headerKey: 'c',
- headerValue: 'z'
- }],
- 'KeyX', 500
- );
- AppStorage.setOrCreate('abilityWant', want);
- console.info('EntryAbility onCreate done.');
- }
- }
- 通过Web组件,加载包含POST请求的Web页面
- // xxx.ets
- import { webview } from '@kit.ArkWeb';
-
- @Entry
- @Component
- struct WebComponent {
- webviewController: webview.WebviewController = new webview.WebviewController();
-
- build() {
- Column() {
- Web({ src: 'https://www.example.com/', controller: this.webviewController })
- .onPageEnd(() => {
- // 清除后续不再使用的预获取资源缓存
- webview.WebviewController.clearPrefetchedResource(['KeyX']);
- })
- }
- }
- }
- 在页面将要加载的JavaScript文件中,发起POST请求,设置请求响应头ArkWebPostCacheKey为对应预取时设置的cachekey值'KeyX'
- const xhr = new XMLHttpRequest();
- xhr.open('POST', 'https://www.example.com/POST?e=f&g=h', true);
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
- xhr.setRequestHeader('ArkWebPostCacheKey', 'KeyX');
- xhr.onload = function () {
- if (xhr.status >= 200 && xhr.status < 300) {
- console.info('成功', xhr.responseText);
- } else {
- console.error('请求失败');
- }
- }
- const formData = new FormData();
- formData.append('a', 'x');
- formData.append('b', 'y');
- xhr.send(formData);
场景二:加载包含POST请求的下一页
【不推荐用法】
当即将加载的Web页面中包含POST请求,且POST请求耗时较长时,不推荐直接加载Web页面
- // xxx.ets
- import { webview } from '@kit.ArkWeb';
-
- @Entry
- @Component
- struct WebComponent {
- webviewController: webview.WebviewController = new webview.WebviewController();
-
- build() {
- Column() {
- // 在适当的时机加载业务用Web组件,本例以Button点击触发为例
- Button('加载页面')
- .onClick(() => {
- // url请替换为真实地址
- this.controller.loadUrl('https://www.example1.com/');
- })
- Web({ src: 'https://www.example.com/', controller: this.webviewController })
- }
- }
- }
【推荐用法】
通过预取POST加载包含POST请求的下一个跳转页面,具体步骤如下:
- 当前页面完成显示后,使用onPageEnd()对即将要加载页面中的POST请求进行预获取。
- // xxx.ets
- import { webview } from '@kit.ArkWeb';
-
- @Entry
- @Component
- struct WebComponent {
- webviewController: webview.WebviewController = new webview.WebviewController();
-
- build() {
- Column() {
- // 在适当的时机加载业务用Web组件,本例以Button点击触发为例
- Button('加载页面')
- .onClick(() => {
- // url请替换为真实地址
- this.controller.loadUrl('https://www.example1.com/');
- })
- Web({ src: 'https://www.example.com/', controller: this.webviewController })
- .onPageEnd(() => {
- // 预获取时,需要将"https://www.example1.com/POST?e=f&g=h"替换成为真实要访问的网站地址
- webview.WebviewController.prefetchResource(
- {
- url: 'https://www.example1.com/POST?e=f&g=h',
- method: 'POST',
- formData: 'a=x&b=y'
- },
- [{
- headerKey: 'c',
- headerValue: 'z'
- }],
- 'KeyX', 500
- );
- })
- }
- }
- }
- 将要加载的页面中,js正式发起POST请求,设置请求响应头ArkWebPostCacheKey为对应预取时设置的cachekey值'KeyX'
- const xhr = new XMLHttpRequest();
- xhr.open('POST', 'https://www.example1.com/POST?e=f&g=h', true);
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
- xhr.setRequestHeader('ArkWebPostCacheKey', 'KeyX');
- xhr.onload = function () {
- if (xhr.status >= 200 && xhr.status < 300) {
- console.info('成功', xhr.responseText);
- } else {
- console.error('请求失败');
- }
- }
- const formData = new FormData();
- formData.append('a', 'x');
- formData.append('b', 'y');
- xhr.send(formData);
JSBridge优化
适用场景
应用使用ArkTS、C++语言混合开发,或本身应用架构较贴近于小程序架构,自带C++侧环境, 推荐使用ArkWeb在native侧提供的ArkWeb_ControllerAPI、ArkWeb_ComponentAPI实现JSBridge功能。
上图为具有普适性的小程序一般架构,其中逻辑层需要应用自带JavaScript运行时,本身已存在C++环境,通过native接口可直接在C++环境中完成与视图层(ArkWeb作为渲染器)的通信,无需再返回ArkTS环境调用JSBridge相关接口。
native JSBridge方案可以解决ArkTS环境的冗余切换,同时允许回调在非UI线程上报,避免造成UI阻塞。
案例实践
【反例】
使用ArkTS接口实现JSBridge通信。
应用侧代码:
- import { webview } from '@kit.ArkWeb';
-
- @Entry
- @Component
- struct WebComponent {
- webviewController: webview.WebviewController = new webview.WebviewController();
-
- aboutToAppear() {
- // 配置Web开启调试模式
- webview.WebviewController.setWebDebuggingAccess(true);
- }
-
- build() {
- Column() {
- Button('runJavaScript')
- .onClick(() => {
- console.info('现在时间是:'+new Date().getTime())
- // 前端页面函数无参时,将param删除。
- this.webviewController.runJavaScript('htmlTest(param)');
- })
- Button('runJavaScriptCodePassed')
- .onClick(() => {
- // 传递runJavaScript侧代码方法。
- this.webviewController.runJavaScript(`function changeColor(){document.getElementById('text').style.color = 'red'}`);
- })
- Web({ src: $rawfile('index.html'), controller: this.webviewController })
- }
- }
- }
前端页面代码:
- <!DOCTYPE html>
- <html>
- <body>
- <button type="button" onclick="callArkTS()">Click Me!</button>
- <h1 id="text">这是一个测试信息,默认字体为黑色,调用runJavaScript方法后字体为绿色,调用runJavaScriptCodePassed方法后字体为红色</h1>
- <script>
- // 调用有参函数时实现。
- var param = "param: JavaScript Hello World!";
- function htmlTest(param) {
- document.getElementById('text').style.color = 'green';
- document.getElementById('text').innerHTML = '现在时间:'+new Date().getTime()
- console.info(param);
- }
- // 调用无参函数时实现。
- function htmlTest() {
- document.getElementById('text').style.color = 'green';
- }
- // Click Me!触发前端页面callArkTS()函数执行JavaScript传递的代码。
- function callArkTS() {
- changeColor();
- }
- </script>
- </body>
- </html>
点击runJavaScript按钮后触发h5页面htmlTest方法,使得页面内容变更为当前时间戳,如下图所示:
经过多轮测试,可以得出从点击原生button到h5触发htmlTest方法,耗时约7ms~9ms。
【正例】
使用NDK接口实现JSBridge通信。
应用侧代码:
- import testNapi from 'libentry.so';
- import { webview } from '@kit.ArkWeb';
-
- class testObj {
- test(): string {
- console.info('ArkUI Web Component');
- return "ArkUI Web Component";
- }
-
- toString(): void {
- console.info('Web Component toString');
- }
- }
-
- @Entry
- @Component
- struct Index {
- webTag: string = 'ArkWeb1';
- controller: webview.WebviewController = new webview.WebviewController(this.webTag);
- @State testObjtest: testObj = new testObj();
-
- aboutToAppear() {
- console.info("aboutToAppear")
- //初始化web ndk
- testNapi.nativeWebInit(this.webTag);
- }
-
- build() {
- Column() {
- Row() {
- Button('runJS hello')
- .fontSize(12)
- .onClick(() => {
- console.info('start:---->'+new Date().getTime());
- testNapi.runJavaScript(this.webTag, "runJSRetStr(\"" + "hello" + "\")");
- })
- }.height('20%')
-
- Row() {
- Web({ src: $rawfile('runJS.html'), controller: this.controller })
- .javaScriptAccess(true)
- .fileAccess(true)
- .onControllerAttached(() => {
- console.info(this.controller.getWebId());
- })
- }.height('80%')
- }
- }
- }
hello.cpp作为应用C++侧业务逻辑代码:
//注册对象及方法,发送脚本到H5执行后的回调,解析存储应用侧传过来的实例等代码逻辑这里不进行展示,开发者根据自身业务场景自行实现。
- // 发送JS脚本到H5侧执行
- static napi_value RunJavaScript(napi_env env, napi_callback_info info) {
- size_t argc = 2;
- napi_value args[2] = {nullptr};
- napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
-
- // 获取第一个参数 webTag
- size_t webTagSize = 0;
- napi_get_value_string_utf8(env, args[0], nullptr, 0, &webTagSize);
- char *webTagValue = new (std::nothrow) char[webTagSize + 1];
- size_t webTagLength = 0;
- napi_get_value_string_utf8(env, args[0], webTagValue, webTagSize + 1, &webTagLength);
- OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ArkWeb", "ndk OH_NativeArkWeb_RunJavaScript webTag:%{public}s",
- webTagValue);
-
- // 获取第二个参数 jsCode
- size_t bufferSize = 0;
- napi_get_value_string_utf8(env, args[1], nullptr, 0, &bufferSize);
- char *jsCode = new (std::nothrow) char[bufferSize + 1];
- size_t byteLength = 0;
- napi_get_value_string_utf8(env, args[1], jsCode, bufferSize + 1, &byteLength);
-
- OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ArkWeb",
- "ndk OH_NativeArkWeb_RunJavaScript jsCode len:%{public}zu", strlen(jsCode));
-
- // 构造runJS执行的结构体
- ArkWeb_JavaScriptObject object = {(uint8_t *)jsCode, bufferSize, &JSBridgeObject::StaticRunJavaScriptCallback,
- static_cast<void *>(jsbridge_object_ptr->GetWeakPtr())};
- controller->runJavaScript(webTagValue, &object);
- return nullptr;
- }
-
- EXTERN_C_START
- static napi_value Init(napi_env env, napi_value exports) {
- napi_property_descriptor desc[] = {
- {"nativeWebInit", nullptr, NativeWebInit, nullptr, nullptr, nullptr, napi_default, nullptr},
- {"runJavaScript", nullptr, RunJavaScript, nullptr, nullptr, nullptr, napi_default, nullptr},
- };
- napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
- return exports;
- }
- EXTERN_C_END
-
- static napi_module demoModule = {
- .nm_version = 1,
- .nm_flags = 0,
- .nm_filename = nullptr,
- .nm_register_func = Init,
- .nm_modname = "entry",
- .nm_priv = ((void *)0),
- .reserved = {0},
- };
-
- extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
Native侧业务代码entry/src/main/cpp/jsbridge_object.h、entry/src/main/cpp/jsbridge_object.cpp 详见应用侧与前端页面的相互调用(C/C++)
runJS.html作为应用前端页面:
- <!DOCTYPE html>
- <html lang="en-gb">
- <head>
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>run javascript demo</title>
- </head>
- <body>
- <h1>run JavaScript Ext demo</h1>
- <p id="webDemo"></p>
- <br>
- <button type="button" style="height:30px;width:200px" onclick="testNdkProxyObjMethod1()">test ndk method1 ! </button>
- <br>
- <br>
- <button type="button" style="height:30px;width:200px" onclick="testNdkProxyObjMethod2()">test ndk method2 ! </button>
- <br>
-
- </body>
- <script type="text/javascript">
-
- function testNdkProxyObjMethod1() {
-
- //校验ndk方法是否已经注册到window
- if (window.ndkProxy == undefined) {
- document.getElementById("webDemo").innerHTML = "ndkProxy undefined"
- return "objName undefined"
- }
-
- if (window.ndkProxy.method1 == undefined) {
- document.getElementById("webDemo").innerHTML = "ndkProxy method1 undefined"
- return "objName test undefined"
- }
-
- if (window.ndkProxy.method2 == undefined) {
- document.getElementById("webDemo").innerHTML = "ndkProxy method2 undefined"
- return "objName test undefined"
- }
-
- //调用ndk注册到window的method1方法,并将结果回显到p标签
- var retStr = window.ndkProxy.method1("hello", "world", [1.2, -3.4, 123.456], ["Saab", "Volvo", "BMW", undefined], 1.23456, 123789, true, false, 0, undefined);
- document.getElementById("webDemo").innerHTML = "ndkProxy and method1 is ok, " + retStr;
- }
-
- function testNdkProxyObjMethod2() {
-
- //校验ndk方法是否已经注册到window
- if (window.ndkProxy == undefined) {
- document.getElementById("webDemo").innerHTML = "ndkProxy undefined"
- return "objName undefined"
- }
-
- if (window.ndkProxy.method1 == undefined) {
- document.getElementById("webDemo").innerHTML = "ndkProxy method1 undefined"
- return "objName test undefined"
- }
-
- if (window.ndkProxy.method2 == undefined) {
- document.getElementById("webDemo").innerHTML = "ndkProxy method2 undefined"
- return "objName test undefined"
- }
-
- var student = {
- name:"zhang",
- sex:"man",
- age:25
- };
- var cars = [student, 456, false, 4.567];
- let params = "[\"{\\\"scope\\\"]";
- //调用ndk注册到window的method2方法,并将结果回显到p标签
- var retStr = window.ndkProxy.method2("hello", "world", false, cars, params);
- document.getElementById("webDemo").innerHTML = "ndkProxy and method2 is ok, " + retStr;
- }
-
- function runJSRetStr(data) {
- const d = new Date();
- let time = d.getTime();
- document.getElementById("webDemo").innerHTML = new Date().getTime()
- return JSON.stringify(time)
- }