ArkWeb简介

ArkWeb (方舟Web) 提供了Web组件,用于在应用程序中显示Web页面内容。常见使用场景包括应用集成Web页面、浏览器网页浏览场景、小程序等。

ArkWeb主要包含网页显示能力和Web控制能力,其中Web组件提供网页显示能力,支持多种属性、事件,可以通过链式调用的方法进行设置。Web组件通过关联WebviewController,提供web页面的控制能力,包含控制web页面前进/后退、执行JavaScript代码、控制页面放大缩小等。

说明:

一个WebviewController对象只能控制一个Web组件,且必须在Web组件和WebviewController绑定后,才能调用WebviewController上的方法(静态方法除外)。

常用的web组件属性如下

常用的web组件事件如下

常用的WebviewController接口如下

Web页面加载

加载Web页面是Web组件的基本功能,常用的场景有加载本地页面和加载网络页面。Web页面的加载依赖Web组件接口, Web接口及参数说明如下所示。

接口:Web(value: WebOptions)

WebOptions主要参数说明:

说明:

RenderMode.ASYNC_RENDER表示Web组件自渲染,即web组件作为图形surface节点独立送显;

RenderMode.SYNC_RENDER表示支持Web组件统一渲染能力,即Web渲染跟随系统组件一起送显;

加载本地网页

当加载本地地址时,src的入参为 src:$rawfile(index.html),其中,index.html是工程中resources/rawfile目录下的文件名称。

加载网络网页

当加载网络页面时,src需要设置成对应的网络地址,同时需要在module.json5文件中添加网络权限。module.json5文件添加网络权限如下所示。

变更页面

开发者可以在Web组件创建时,指定默认加载的网络页面。在默认页面加载完成后,如果开发者需要变更此Web组件显示的网络页面,可以通过调用loadUrl()接口加载指定的网页。

Web组件生命周期

概述

Web组件提供了丰富的组件生命周期回调接口,通过这些回调接口,开发者可以感知Web组件的生命周期状态变化,进行相关的业务处理

Web组件的生命周期主要包括:onControllerAttached、onLoadIntercept、onlnterceptRequest、onPageBegin、onPageEnds等,如下图所示。

自定义组件生命周期和组件通用生命周期

自定义组件生命周期是用@Component装饰的自定义组件的生命周期,常见的自定义组件生命周期有aboutToAppear、aboutToDisappear等。组件通用生命周期是ArkTS组件通用的生命周期,包括onAppear、onDisAppear等

Web组件生命周期与他们(自定义组件生命周期和组件通用生命周期)之间的关系是怎么样的呢?下面说明。

Web组件生命周期回调顺序

其中,Web组件在加载html、css、js、图片等资源时,onlnterceptRequest均会进行回调。

自定义组件析构销毁时执行aboutToDisappear函数,Web组件会被销毁,Web组件与WebviewController解绑,js运行环境也会一并销毁

  • aboutToAppear函数:在创建自定义组件的新实例后,在执行其build函数前执行。建议在此设置WebDebug调试模式、自定义协议URL的权限、Cookie等

  • onControllerAttached事件:当Controller成功绑定到Web组件时触发该回调,且禁止在该事件回调前调用Web组件相关的接口,否则会抛出js-error异常。建议在此事件中注入JS对象、设置自定义用户代理,使用操作网页不相关的接口。但因该回调调用时网页还未加载,因此无法在回调中使用有关操作网页的接口,例如zoomInzoomOut

  • onLoadIntercept事件:当Web组件加载url之前触发该回调,用于判断是否阻止此次访问。默认允许加载。

  • onInterceptRequest事件:当Web组件加载url之前触发该回调,用于拦截url并返回响应数据

  • onPageBegin事件:网页开始加载时触发该回调,且只在主frame(表示一个用于展示HTML页面的元素)触发。如果是iframe或者frameset(用于包含frame的HTML标签)的内容加载时则不会触发此回调。多frame页面可能同时加载,主frame加载结束时子frame可能仍在加载。同一页面导航或失败的导航不会触发该回调。

  • onProgressChange事件:告知开发者当前页面加载的进度。多frame页面或者子frame可能还在继续加载而主frame已经加载结束,所以在onPageEnd事件后仍可能收到该事件。

  • onPageEnd事件:网页加载完成时触发该回调,且只在主frame触发。多frame页面有可能同时开始加载,即使主frame已经加载结束,子frame也有可能才开始或者继续加载中。同一页面导航或失败的导航不会触发该回调。建议在此回调中执行JavaScript脚本。注意,收到该回调不能保证下一帧反映DOM状态。

Web组件网页异常加载过程所涉及的状态说明

  • onOverrideUrlLoading事件:当URL将要加载到当前Web中时,让宿主应用程序有机会获得控制权,回调函数返回true将导致当前Web中止加载URL,而返回false则会导致Web继续照常加载URL。onLoadIntercept接口和onOverrideUrlLoading接口行为不一致,触发时机也不同,所以在应用场景上存在一定区别。onLoadIntercept事件在LoadUrl和iframe加载时触发,但onOverrideUrlLoading事件在LoadUrl和特定iframe加载时不会触发。

  • onPageVisible事件:Web回调事件。渲染流程中当HTTP响应的主体开始加载,新页面即将可见时触发该回调。此时文档加载还处于早期,因此链接的资源比如在线CSS、在线图片等可能尚不可用。

  • onRenderExited事件:应用渲染进程异常退出时触发该回调,可以在此回调中进行系统资源的释放、数据的保存等操作。如果应用希望异常恢复,需要调用loadUrl接口重新加载页面。详细用法参考应用如何避免Web组件渲染子进程异常退出导致的页面卡死问题

  • onDisAppear事件:组件卸载消失时触发此回调。该事件在组件卸载时触发。

应用侧代码举例参考Web组件的生命周期

应用如何避免Web组件渲染子进程异常退出导致的页面卡死问题

ArkWeb(方舟Web)是一个Web组件平台,旨在为应用程序提供展示Web页面内容的功能,并向开发者提供一系列的能力,如页面加载、交互和调试等功能。使用ArkWeb相关应用时,可能因各种原因(例如前端偶现异常导致ArkWeb渲染子进程崩溃,或是打开的应用较多,系统资源紧张导致后台ArkWeb渲染子进程被终止)而出现页面卡死的问题,这时需要重新打开页面或重启应用来解决。

在ArkWeb渲染子进程异常退出导致页面卡死后,应用可通过监听onRenderExited事件来获取具体的退出原因RenderExitReason,并在异常回调中根据退出的具体原因,执行相应的异常处理。

开发实践案例参考Web组件的生命周期

应用侧与前端页面的相互调用

ArkWeb提供了JSBridge API, JSBridge 是一种在JavaScript 和应用之间进行通信的桥梁机制。JSBridge允许H5前端页面与应用代码相互调用方法、传递数据,从而为用户提供更加丰富的功能体验。

应用侧调用前端页面函数

应用侧调用前端页面函数

应用侧可以通过WebviewController 的runJavaScript() 方法调用前端页面的JavaScript相关函数。

接口:runJavaScript(script: string): Promise<string>

该方法异步执行JavaScript脚本,并通过Promise方式返回脚本执行的结果。runJavaScript需要在loadUrI完成后,比如onPageEnd中调用

    应用侧还可以通过webview的runJavaScriptExt()方法调用前端页面的JavaScript相关函数。

    runJavaScript()runJavaScriptExt()在参数类型上有以下差异:runJavaScriptExt()支持string和ArrayBuffer类型参数,而runJavaScript()仅支持string类型参数。

    在下面的示例中,点击应用侧的“runJavaScript”按钮时,触发前端页面的htmlTest()方法。

    • 前端页面代码。

      <!-- index.html -->
      <!DOCTYPE html>
      <html>
      <head>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
      </head>
      <body>
      <button type="button" onclick="callArkTS()">Click Me!</button>
      <h1 id="text">这是一个测试信息,默认字体为黑色,调用runJavaScript方法后字体为黄色、调用runJavaScriptParam方法后字体为绿色、调用runJavaScriptCodePassed方法后字体为红色</h1>
      <script>
          // 有参函数。
          var param = "param: JavaScript Hello World!";
          function htmlTestParam(param) {
              document.getElementById('text').style.color = 'green';
              console.info(param);
          }
          // 无参函数。
          function htmlTest() {
              document.getElementById('text').style.color = 'yellow';
          }
          // 点击“Click Me!”按钮,触发前端页面callArkTS()函数执行JavaScript传递的代码。
          function callArkTS() {
              changeColor();
          }
      </script>
      </body>
      </html>
      • 应用侧代码。
      import { webview } from '@kit.ArkWeb';
      
      @Entry
      @Component
      struct WebComponent {
        webviewController: webview.WebviewController = new webview.WebviewController();
      
        aboutToAppear() {
          // 配置Web开启调试模式
          webview.WebviewController.setWebDebuggingAccess(true);
        }
      
        build() {
          Column() {
            Button('runJavaScriptParam')
              .onClick(() => {
                // 调用前端页面有参函数。
                this.webviewController.runJavaScript('htmlTestParam(param)');
              })
            Button('runJavaScript')
              .onClick(() => {
                // 调用前端页面无参函数。
                this.webviewController.runJavaScript('htmlTest()');
              })
            Button('runJavaScriptCodePassed')
              .onClick(() => {
                // 传递runJavaScript侧代码方法。
                this.webviewController.runJavaScript(
                  `function changeColor(){document.getElementById('text').style.color = 'red'}`);
              })
            Web({ src: $rawfile('index.html'), controller: this.webviewController })
          }
        }
      }

      前端页面调用应用侧函数

      开发者使用Web组件将应用侧代码注册到前端页面中,注册完成之后,前端页面中使用注册的对象名称就可以调用应用侧的方法,实现在前端页面中调用应用侧方法。

      如何建立应用侧与H5侧的交互通道

      将应用侧代码注册到前端页面中有两种方式,一种在Web组件初始化时通过javaScriptProxy()接口注册。另外一种在Web组件初始化完成后通过registerJavaScriptProxy()接口注册。两种方式都需要和deleteJavaScriptRegister接口配合使用,防止内存泄漏

      接口:javaScriptProxy(javaScriptProxy: JavaScriptProxy)

      接口作用:注入JavaScript对象到window对象中,web页面中可以通过window对象调用该对象的方法

      JavaScriptProxy参数说明如下:

      ArkTS侧注册代码示例如下图

      接下来介绍前端页面调用应用侧函数的第二种方式,使用webviewController的registerJavaScriptProxy方法注册。

      接口:registerJavaScriptProxy(object: object, name: string, methodList: Array<string>,asyncMethodList?: Array<string>, permission?: string): void

       接口作用:注入JavaScript对象到window对象中,并在window对象中调用该对象的方法

      参数说明:

      在下面的示例中,将test()方法注册在前端页面中, 该函数可以在前端页面触发运行。

      应用侧调用javaScriptProxy()接口注册

      import { webview } from '@kit.ArkWeb';
      import { BusinessError } from '@kit.BasicServicesKit';
      
      class TestClass {
        test(): string {
          return 'ArkTS Hello World!';
        }
      }
      
      @Entry
      @Component
      struct WebComponent {
        webviewController: webview.WebviewController = new webview.WebviewController();
        // 声明需要注册的对象
        @State testObj: TestClass = new TestClass();
      
        build() {
          Column() {
            // Web组件加载本地index.html页面
            Web({ src: $rawfile('index1.html'), controller: this.webviewController})
              // 将对象注入到web端
              .javaScriptProxy({
                object: this.testObj,
                name: 'testObjName',
                methodList: ['test'],
                controller: this.webviewController,
                // 可选参数
                asyncMethodList: [],
                permission: '{"javascriptProxyPermission":{"urlPermissionList":' +
                  '[{"scheme":"resource","host":"rawfile","port":"","path":""},' +
                  '{"scheme":"e","host":"f","port":"g","path":"h"}],"methodList":' +
                  '[{"methodName":"test","urlPermissionList":' +
                  '[{"scheme":"https","host":"xxx.com","port":"","path":""},' +
                  '{"scheme":"resource","host":"rawfile","port":"","path":""}]},' +
                  '{"methodName":"test11","urlPermissionList":' +
                  '[{"scheme":"q","host":"r","port":"","path":"t"},' +
                  '{"scheme":"u","host":"v","port":"","path":""}]}]}}'
              })
          }
        }
      }

        应用侧调用registerJavaScriptProxy()接口注册

        说明

            test(): string {
                return 'ArkUI Web Component';
            }
        
            toString(): void {
                console.info('Web Component toString');
            }
        
            Web({ src: $rawfile('index1.html'), controller: this.webviewController })
                .onControllerAttached(()=>{//onControllerAttached可替换为onPageEnd,但要注意 onPageEnd中注册方法后,需重新加载后生效
                      this.webviewController.refresh();
                   try {
                    this.webviewController.registerJavaScriptProxy(this.testObj, 'testObjName', ['test', 'toString'],
                            // 可选参数, asyncMethodList
                            [],
                            // 可选参数, permission
                            '{"javascriptProxyPermission":{"urlPermissionList":[{"scheme":"resource","host":"rawfile","port":"","path":""},' +
                            '{"scheme":"e","host":"f","port":"g","path":"h"}],"methodList":[{"methodName":"test","urlPermissionList":' +
                            '[{"scheme":"https","host":"xxx.com","port":"","path":""},{"scheme":"resource","host":"rawfile","port":"","path":""}]},' +
                            '{"methodName":"test11","urlPermissionList":[{"scheme":"q","host":"r","port":"","path":"t"},' +
                            '{"scheme":"u","host":"v","port":"","path":""}]}]}}'
                    );
                  } catch (error) {
                    console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
                  }
                })

        Web页面调用应用侧方法

        let str = testObjName.test();

        传递复杂参数/返回值

        传递Array

        Array可以作为注册对象方法的参数或返回值,在应用侧和前端页面之间传递。

        作为返回值/参数传递
        class TestClass {
          test(): Array<number> {
            return [1, 2, 3, 4]
          }
        
          toString(param: Array<number>): void {
            console.info('Web Component toString' + param);
          }
        }
        @State testObj: TestClass = new TestClass();
        try {
            this.webviewController.registerJavaScriptProxy(this.testObj, "testObjName", ["test", "toString"]);
        } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
        }
        
        Web页面使用
        testObjName.toString(testObjName.test());

        传递对象

        非Function等复杂类型作为注册对象方法的参数或返回值,在应用侧和前端页面之间传递。

        // xxx.ets
        class Student {
          name: string = '';
          age: string = '';
        }
        
        class TestClass {
          // 传递的基础类型name:"jeck", age:"12"。
          test(): Student {
            let st: Student = { name: "jeck", age: "12" };
            return st;
          }
        
          toString(param: ESObject): void {
            console.info('Web Component toString' + param["name"]);
          }
        }
        
        webviewController: webview.WebviewController = new webview.WebviewController();
        @State testObj: TestClass = new TestClass();
        try {
            this.webviewController.registerJavaScriptProxy(this.testObj, "testObjName", ["test", "toString"]);
        } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
        }
        Web页面使用
        testObjName.toString(testObjName.test());

        传递Function:web页面向应用侧传Callback,供应用侧调用

        Callback可以作为注册对象方法的参数或返回值,在应用侧和前端页面之间传递。

        class TestClass {
          test(param: Function): void {
            param("call callback");
          }
        
          toString(param: String): void {
            console.info('Web Component toString' + param);
          }
        }
        
        webviewController: webview.WebviewController = new webview.WebviewController();
        @State testObj: TestClass = new TestClass();
        try {
            this.webviewController.registerJavaScriptProxy(this.testObj, "testObjName", ["test", "toString"]);
        } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
        }
        Web页面使用
        testObjName.test(function(param){testObjName.toString(param)});

        应用侧调用前端页面Object里的Function

        前端页面Object里的Function可以作为注册对象方法的参数或返回值,在应用侧和前端页面之间传递。

        class TestClass {
          test(param: ESObject): void {
            param.hello("call obj func");
          }
        
          toString(param: string): void {
            console.info('Web Component toString' + param);
          }
        }
        
        webviewController: webview.WebviewController = new webview.WebviewController();
        @State testObj: TestClass = new TestClass();
        this.webviewController.registerJavaScriptProxy(this.testObj, "testObjName", ["test", "toString"]);
        
        //web使用代码
        function Obj1(){
            this.methodNameListForJsProxy=["hello"];
            this.hello=function(param){
                testObjName.toString(param)
            };
        }
        var st1 = new Obj1();
        function callArkTS() {
            testObjName.test(st1);
        }

          前端页面调用应用侧Object里的Function

          应用侧Object里的Function可以作为注册对象方法的参数或返回值,在应用侧和前端页面之间传递。

          class ObjOther {
            methodNameListForJsProxy: string[]
          
            constructor(list: string[]) {
              this.methodNameListForJsProxy = list
            }
          
            testOther(json: string): void {
              console.info(json)
            }
          }
          
          class TestClass {
            ObjReturn: ObjOther
          
            constructor() {
              this.ObjReturn = new ObjOther(["testOther"]);
            }
          
            test(): ESObject {
              return this.ObjReturn
            }
          
            toString(param: string): void {
              console.info('Web Component toString' + param);
            }
          }
          
          webviewController: webview.WebviewController = new webview.WebviewController();
          @State testObj: TestClass = new TestClass();
          this.webviewController.registerJavaScriptProxy(this.testObj, "testObjName", ["test", "toString"]);
          
          //web页面调用
          testObjName.test().testOther("call other object func");

          Promise场景

          第一种使用方法,在应用侧new Promise,将Promise作为对象方法的参数或返回值,向前端页面传递。

          class TestClass {
            test(): Promise<string> {
              let p: Promise<string> = new Promise((resolve, reject) => {
                setTimeout(() => {
                  console.info('执行完成');
                  reject('fail');
                }, 10000);
              });
              return p;
            }
          
            toString(param: string): void {
              console.info(" " + param);
            }
          }
          
          this.webviewController.registerJavaScriptProxy(this.testObj, "testObjName", ["test", "toString"]);
          
          //web使用
          testObjName.test().then((param)=>{testObjName.toString(param)}).catch((param)=>{testObjName.toString(param)})

          第二种使用方法,在前端页面new Promise,将Promise作为对象方法的参数或返回值,向应用侧传递。===代码没理解到位,随后整理

          验证通道是否建立成功

          1. 打开web调试。

            开启web调试请参考使用DevTools工具调试前端页面

          2. 举例说明通道是否建立成功。

            使用复杂类型使用方法中应用侧和前端页面之间传递Array作为示例,调试结果如下图所示:

          视频课程中开发案例

          页面介绍:中间的高亮部分为Web页面,其他为原生页面。

          Web页面拉起通讯录

          方式一:使用registerJavaScriptProxy

          步骤一:在ArkTS侧实现拉起通讯录的方法,并返回所选联系人的信息。

          import { contact } from '@kit.ContactsKit';
          
            chooseContact(): Promise<string> {
              let phone:string = '';
              let name:string = '';
              return new Promise((resolve) => {
                if (deviceInfo.productModel === 'emulator') {
                  return resolve('13800000000_test');
                }
                let promise = contact.selectContacts();
                promise.then((info: Array<contact.Contact>) => {
                  info.forEach((item: contact.Contact) => {
                    phone = item?.phoneNumbers ? item?.phoneNumbers[0].phoneNumber : '';
                    name = item?.name ? item?.name?.fullName : '';
                  })
                  resolve(phone + '_' + name);
                }).catch((err: object | string) => {
                  hilog.error(0xFF00, 'SelectContact', '%{public}s', `selectContact fail: err->${JSON.stringify(err)}`);
                });
              })
            }

          步骤二:使用registerJavaScriptProxy注册JavaScript对象

          在Web组件的onControllerAttached回调方法中调用webviewController的registerJavaScriptProxy()注册JavaScript对象方法。

          注意:在aboutToDisappear中添加webviewController的deleteJavaScriptRegister,防止内存泄漏。

            aboutToDisappear() {
              this.webviewController.deleteJavaScriptRegister('jsbridgeHandle');
              hilog.info(0xFF00, 'SelectContact', '%{public}s', '[LifeCycle] aboutToDisappear');
            }
          
            build() {
              Column() {
                Web({
                  src: $rawfile('index.html'),
                  controller: this.webviewController
                })
                  .height(500)
                  .javaScriptAccess(true)
                  .onAppear(() => {
                    hilog.info(0xFF00, 'SelectContact', '%{public}s', '[LifeCycle] onAppear');
                  })
                  .onDisAppear(() => {
                    hilog.info(0xFF00, 'SelectContact', '%{public}s', '[LifeCycle] onDisAppear');
                  })
                  .onControllerAttached(() => {
                    this.webviewController.registerJavaScriptProxy({ call: this.chooseContact }, 'jsbridgeHandle', ['call']);
                    hilog.info(0xFF00, 'SelectContact', '%{public}s', '[LifeCycle] onControllerAttached');
                  })
              }
              .width('100%')
              .height('100%')
              .padding({
                left: 24,
                right: 24
              })
            }

          视频中说使用registerJavaScriptProxy()接口时需要配合deleteJavaScriptRegister接口使用,防止内存泄漏。但是视频中使用javaScriptProxy()接口时没有配合deleteJavaScriptRegister接口使用

          步骤三:在H5前端页面通过JavaScript对象调用ArkTS方法

          在H5前端页面中定义js方法chooseContact,点击通讯录图标时会调用chooseContact方法。在chooseContact中,通过注册的jsbridgeHandle对象调用ArkTS方法。

          function chooseContact() {
            jsbridgeHandle.call()
              .then((data) => {
                const phone = data.split('_')[0];
                const name = data.split('_')[1];
                const result = splitPhone(phone);
                document.getElementById('phone_tip').innerHTML = name;
                document.getElementById('phone').value = result;
              });
          }

          方式二:使用Web组件的javaScriptProxy

          在Web组件中添加javaScriptAccess属性,将javaScriptAccess设置为true。然后在Web组件中添加javaScriptProxy方法,注册JavaScript对象方法。

                Web({
                  src: $rawfile('index.html'),
                  controller: this.webviewController
                })
                  .height(500)
                  .javaScriptAccess(true)
                  .javaScriptProxy({
                    object: {
                      call: this.chooseContact
                    },
                    name: 'jsbridgeHandle',
                    methodList: ['call'],
                    controller: this.webviewController
                  })

          应用侧调用JS方法充值

          在H5前端页面js中实现recharge方法模拟充值,并返回充值的结果。

          function recharge() {
            if(amount === null) {
              return '请选择充值金额';
            }
            return document.getElementById('phone').value.length === 13 ? '充值成功' : '请输入正确的手机号';
          }

          在ArkTS应用侧,通过runJavaScript调用JS方法,并处理结果。在Button组件的onClick回调方法中调用webviewController的runJavaScript方法用于执行JavaScript方法recharge,并根据结果进行弹窗提醒。

          import { promptAction } from '@kit.ArkUI';
          
                Button('充值')
                  .width('100%')
                  .height(40)
                  .margin({ bottom: 16 })
                  .onClick(() => {
                    this.webviewController.runJavaScript('recharge()')
                      .then((result) => {
                        promptAction.showToast({ message: result.replaceAll('"', '') });
                      });
                  })

          常见问题

          Web组件提供具有网页显示能力,@ohos.web.webview提供web控制能力。通过webview 的WebviewController可以控制Web组件各种行为,包含控制web页面前进/后退、执行JavaScript代码、控制页面放大缩小等。

          下面说法都是正确的:

          A. fileAccess设置是否开启应用中文件系统的访问。

          B. imageAccess设置是否允许自动加载图片资源,默认允许。

          C. javaScriptAccess表示是否允许执行JavaScript脚本,默认允许执行。

          D. zoomAccess设置是否支持手势缩放,默认允许执行缩放。

          A. 访问在线网页时需添加网络权限ohos.permission.INTERNET。

          B. @ohos.web.webview提供web控制能力,web组件提供网页显示的能力。

          C. onAppear是组件通用生命周期事件,onPageBegin、onPageEnd是Web组件独有的生命周期事件。

          D. WebviewController可以控制Web组件各种行为。

          A. 使用registerJavaScriptProxy()接口,需要和deleteJavaScriptRegister接口配合使用,防止内存泄漏。

          B. 在registerJavaScriptProxy中,同步函数列表和异步函数列表不可同时为空,否则此次调用接口注册失败。

          C. 在registerJavaScriptProxy中,如果同一方法在同步与异步列表中重复注册,将默认异步调用。

          D. runJavaScript需要在loadUrl完成后,比如onPageEnd中调用。

          Logo

          讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

          更多推荐