鸿蒙开发5.0案例分析:Web场景性能优化(二)

06-01 1162阅读

📝往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)

🚩 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

🚩 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

🚩 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

🚩 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

🚩 记录一场鸿蒙开发岗位面试经历~

📃 持续更新中……


JSBridge

JSBridge优化解决方案

适用场景

应用使用ArkTS、C++语言混合开发,或本身应用架构较贴近于小程序架构,自带C++侧环境, 推荐使用ArkWeb在Native侧提供的ArkWeb_ControllerAPI、ArkWeb_ComponentAPI实现JSBridge功能。

鸿蒙开发5.0案例分析:Web场景性能优化(二)

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

鸿蒙开发5.0案例分析:Web场景性能优化(二)

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 })
    }
  }
}

前端页面代码:



Click Me!

这是一个测试信息,默认字体为黑色,调用runJavaScript方法后字体为绿色,调用runJavaScriptCodePassed方法后字体为红色

// 调用有参函数时实现。 var param = "param: JavaScript Hello World!"; function htmlTest(param) { document.getElementById('text').style.color = 'green'; document.getElementById('text').innerHTML = '现在时间:'+new Date().getTime() console.log(param); } // 调用无参函数时实现。 function htmlTest() { document.getElementById('text').style.color = 'green'; document.getElementById('text').innerHTML = '现在时间:'+new Date().getTime(); } // Click Me!触发前端页面callArkTS()函数执行JavaScript传递的代码。 function callArkTS() { changeColor(); }

点击runJavaScript按钮后触发h5页面htmlTest方法,使得页面内容变更为当前时间戳,如下图所示:

鸿蒙开发5.0案例分析:Web场景性能优化(二)

经过多轮测试,可以得出从点击原生button到h5触发htmlTest方法,耗时约7ms~9ms。

案例二:使用NDK接口实现JSBridge通信。

应用侧代码:

import testNapi from 'libentry.so';
import { webview } from '@kit.ArkWeb';
class testObj {
  constructor() {
  }
  test(): string {
    console.log('ArkUI Web Component');
    return "ArkUI Web Component";
  }
  toString(): void {
    console.log('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.log('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.error("ndk onControllerAttached webId: " + 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(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作为应用前端页面:



  
  run javascript demo


run JavaScript Ext demo


test ndk method1 !

test ndk method2 !
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); }

点击runJS hello按钮后触发h5页面runJSRetStr方法,使得页面内容变更为当前时间戳。

鸿蒙开发5.0案例分析:Web场景性能优化(二)

经过多轮测试,可以得出从点击原生button到h5触发runJSRetStr方法,耗时约2ms~6ms。

总结

通信方式耗时(局限不同设备和场景,数据仅供参考)说明
ArkWeb实现与前端页面通信7ms~9msArkTS环境冗余切换,耗时较长
ArkWeb、c++实现与前端页面通信2ms~6ms避免ArkTS环境冗余切换,耗时短

JSBridge优化方案适用于ArkWeb应用侧与前端网页通信场景,开发者可根据应用架构选择合适的业务通信机制:

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

说明

开发者需根据当前业务区分是否存在C++侧环境(较为显著标志点为当前应用是否使用了Node API技术进行开发,若是则该应用具备C++侧环境)。 具备C++侧环境的应用开发,可使用ArkWeb提供的NDK侧JSBridge接口。 不具备C++侧环境的应用开发,可使用ArkWeb侧JSBridge接口。

异步JSBridge调用

原理介绍

异步JSBridge调用适用于H5侧调用原生或C++侧注册的JSBridge函数场景下,将用户指定的JSBridge接口的调用抛出后,不等待执行结果, 以避免在ArkUI主线程负载重时JSBridge同步调用可能导致Web线程等待IPC时间过长,从而造成阻塞的问题。

实践案例

案例一:使用ArkTS接口实现JSBridge通信,具体步骤如下:

  1. 只注册同步函数
import { webview } from '@kit.ArkWeb';
// 定义ETS侧对象及函数
class TestObj {
  constructor() {}
  test(testStr:string): string {
    let start = Date.now();
    // 模拟耗时操作
    for(let i = 0; i  {
      let start = Date.now();
      // 模拟耗时操作(异步)
      setTimeout(() => {
        for(let i = 0; i  {
      let start = Date.now();
      // 模拟耗时操作(异步)
      setTimeout(() => {
        for (let i = 0; i  {
      let start = Date.now();
      // 模拟耗时操作(异步)
      setTimeout(() => {
        for (let i = 0; i {
          try{
            this.controller.refresh();
          } catch (error) {
            console.error(`ErrorCode:${(error as BusinessError).code},Message:${(error as BusinessError).message}`);
          }
        })
      Button('Register JavaScript To Window')
        .onClick(()=>{
          try {
            //只注册同步函数
            this.controller.registerJavaScriptProxy(this.webTestObj,"objTestName",["webTest","webString"]);
          } catch (error) {
            console.error(`ErrorCode:${(error as BusinessError).code},Message:${(error as BusinessError).message}`);
          }
        })
      Web({src: $rawfile('index.html'),controller: this.controller}).javaScriptAccess(true)
    }
  }
}
  1. H5侧调用JSBridge函数


  
  
  Document


 Click Me!

async function htmlTest() { document.getElementById("demo").innerHTML = '测试开始:' + new Date().getTime() + '\n'; const time1 = new Date().getTime(); objTestName.webString(); const time2 = new Date().getTime(); objAsyncName.asyncString(); const time3 = new Date().getTime(); objName.asyncTestBool(); const time4 = new Date().getTime(); objName.test(); const time5 = new Date().getTime(); objTestName.webTest(); const time6 = new Date().getTime(); objAsyncName.asyncTest(); const time7 = new Date().getTime(); const result = [ 'objTestName.webString()耗时:'+ (time2 - time1), 'objAsyncName.asyncString()耗时:'+ (time3 - time2), 'objName.asyncTestBool()耗时:'+ (time4 - time3), 'objName.test()耗时:'+ (time5 - time4), 'objTestName.webTest()耗时:'+ (time6 - time5), 'objAsyncName.asyncTest()耗时:'+ (time7 - time6) ] document.getElementById("demo").innerHTML = document.getElementById("demo").innerHTML + '\n' + result.join('\n'); }

案例二:使用registerJavaScriptProxy或javaScriptProxy注册异步函数或异步同步共存,H5侧调用JSBridge函数与不推荐用法一致。

// registerJavaScriptProxy方式注册
Button('refresh')
  .onClick(()=>{
    try{
      this.controller.refresh();
    } catch (error) {
      console.error(`ErrorCode:${(error as BusinessError).code},Message:${(error as BusinessError).message}`)
    }
  })
Button('Register JavaScript To Window')
  .onClick(()=>{
    try {
      // 调用注册接口对象及成员函数,其中同步函数列表必填,空白则需要用[]占位;异步函数列表非必填
      // 同步、异步函数都注册
      this.controller.registerJavaScriptProxy(this.testObjtest,"objName",["test"],["asyncTestBool"]);
      // 只注册异步函数,同步函数列表处留空
      this.controller.registerJavaScriptProxy(this.asyncTestObj,"objAsyncName",[],["asyncTest","asyncString"]);
    } catch (error) {
      console.error(`ErrorCode:${(error as BusinessError).code},Message:${(error as BusinessError).message}`);
    }
  })
Web({src: $rawfile('index.html'),controller: this.controller}).javaScriptAccess(true)
// javaScriptProxy方式注册
// javaScriptProxy只支持注册一个对象,若需要注册多个对象请使用registerJavaScriptProxy
Web({src: $rawfile('index.html'),controller: this.controller})
  .javaScriptAccess(true)
  .javaScriptProxy({
    object: this.testObjtest,
    name:"objName",
    methodList: ["test","toString"],
    //指定异步函数列表
    asyncMethodList: ["test","toString"],
    controller: this.controller
  })

总结

数据运行结果如下:

注册方法类型耗时(局限不同设备和场景,数据仅供参考)说明
同步方法1398ms,2707ms,2705ms同步函数调用会阻塞JavaScript线程
异步方法2ms,2ms,4ms异步函数调用不阻塞JavaScript线程

通过运行数据可看到async的异步方法不需要等待结果,所以在JavaScript单线程任务队列中不会长时间占用,同步任务需要等待原生主线程同步执行后返回结果。

说明

JSBridge接口在注册时,即会根据注册调用的接口决定其调用方式(同步/异步)。开发者需根据当前业务区分, 是否将其注册为异步函数。

  • 同步函数调用将会阻塞JavaScript的执行,等待调用的JSBridge函数执行结束,适用于需要返回值,或者有时序问题等场景。
  • 异步函数调用时不会等待JSBridge函数执行结束,后续JavaScript可在短时间后继续执行。但JSBridge函数无法直接返回值。
  • 注册在ETS侧的JSBridge函数调用时需要在主线程上执行;NDK侧注册的函数将在其他线程中执行。
  • 异步JSBridge接口与同步接口在JavaScript侧的调用方式一致,仅注册方式不同,本部分调用方式仅作简要示范。

    附NDK接口实现JSBridge通信(C++侧注册异步函数):

    // 定义JSBridge函数
    static void ProxyMethod1(const char* webTag, void* userData) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ArkWeb", "Method1 webTag :%{public}s",webTag);
    }
    static void ProxyMethod2(const char* webTag, void* userData) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ArkWeb", "Method2 webTag :%{public}s",webTag);
    }
    static void ProxyMethod3(const char* webTag, void* userData) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ArkWeb", "Method3 webTag :%{public}s",webTag);
    }
    void RegisterCallback(const char *webTag) {
        int myUserData = 100;
        //创建函数方法结构体
        ArkWeb_ProxyMethod m1 = {
            .methodName = "method1",
            .callback = ProxyMethod1,
            .userData = (void *)&myUserData
        };
        ArkWeb_ProxyMethod m2 = {
            .methodName = "method2",
            .callback = ProxyMethod2,
            .userData = (void *)&myUserData
        };
        ArkWeb_ProxyMethod m3 = {
            .methodName = "method3",
            .callback = ProxyMethod3,
            .userData = (void *)&myUserData
        };
        ArkWeb_ProxyMethod methodList[2] = {m1,m2};
        //创建JSBridge对象结构体
        ArkWeb_ProxyObject obj = {
            .objName = "ndkProxy",
            .methodList = methodList,
            .size = 2
        };
        // 获取ArkWeb_Controller API结构体
        ArkWeb_AnyNativeAPI* apis = OH_ArkWeb_GetNativeAPI(ArkWeb_NativeAPIVariantKind::ARKWEB_NATIVE_CONTROLLER);
        ArkWeb_ControllerAPI* ctrlApi = reinterpret_cast(apis);
            // 调用注册接口,注册函数
            ctrlApi->registerJavaScriptProxy(webTag, &obj);
        ArkWeb_ProxyMethod asyncMethodList[1] = {m3};
        ArkWeb_ProxyObject obj2 = {
            .objName = "ndkProxy",
        .methodList = asyncMethodList,
        .size = 1
        };
        ctrlApi->registerAsyncJavaScriptProxy(webTag, &obj2);
    }
    

    同层渲染

    同层渲染是一种优化技术,用于提高Web页面的渲染性能。同层渲染会将位于同一个图层的元素一起渲染,以减少重绘和重排的次数,从而提高页面的渲染效率。

    总结

    本文深入探讨了Web页面加载的原理和优化方法,为开发者提供了重要的指导和思路。在当今互联网时代,用户对网页加载速度和体验要求越来越高,因此页面加载优化成为开发者必须重视的一环。通过理解Web页面加载的原理,开发者可以更好地处理页面加载与优化的相关问题,提升应用的整体质量。

    文中提供了预连接、预下载、预渲染、预取POST、预编译等多种常见的优化方法,指导开发者优化Web页面的加载速度。这些方法可以有效提高应用流畅度、提升用户体验。但是,这几种方法都是基于预处理的方式进行优化的,所以存在一定的优化代价。

    在实际的开发场景中,开发者应该根据实际的情况进行权衡利弊,决定对应的方案与策略。此外,还提供了JSBridge与资源加速的优化方案,帮助开发者进一步提高Web加载性能。除了以上提到的优化方法,开发者还可以通过其他方式进一步优化页面加载速度。例如,压缩资源可以减小文件大小,减少加载时间;减少HTTP请求可以减少网络延迟,加快页面加载速度,提升用户体验。

    综上所述,Web页面加载优化对于提升用户体验、提高网站性能、增加页面浏览量和提高转化率具有重要意义。开发者应该重视页面加载优化,不断探索和实践各种优化方法,以提升用户体验,实现商业目标。通过文章介绍的几种优化方法,开发者可以改善页面加载速度,提升用户体验,增加页面浏览量,提高应用的活跃度和用户粘性。只有不断优化页面加载速度,才能更好地满足用户需求,提升应用价值。

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码