HarmonyOS NEXT - ArkWeb管理网页加载与浏览记录

使用Web组件加载页面
 

页面加载是Web组件的基本功能。根据页面加载数据来源可以分为三种常用场景,包括加载网络页面、加载本地页面、加载HTML格式的富文本数据。

页面加载过程中,若涉及网络资源获取,需要配置ohos.permission.INTERNET网络访问权限。

加载网络页面

开发者可以在Web组件创建时,指定默认加载的网络页面 。在默认页面加载完成后,如果开发者需要变更此Web组件显示的网络页面,可以通过调用loadUrl()接口加载指定的网页。Web组件的第一个参数变量src不能通过状态变量(例如:@State)动态更改地址,如需更改,请通过loadUrl()重新加载。

在下面的示例中,在Web组件加载完“www.example.com”页面后,开发者可通过loadUrl接口将此Web组件显示页面变更为“www.example1.com”。

 

// xxx.ets
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct WebComponent {
  controller: webview.WebviewController = new webview.WebviewController();

  build() {
    Column() {
      Button('loadUrl')
        .onClick(() => {
          try {
            // 点击按钮时,通过loadUrl,跳转到www.example1.com
            this.controller.loadUrl('www.example1.com');
          } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
          }
        })
      // 组件创建时,加载www.example.com
      Web({ src: 'www.example.com', controller: this.controller })
    }
  }
}

加载本地页面

将本地页面文件放在应用的rawfile目录下,开发者可以在Web组件创建的时候指定默认加载的本地页面 ,并且加载完成后可通过调用loadUrl()接口变更当前Web组件的页面。

在下面的示例中展示加载本地页面文件的方法:

  • 将资源文件放置在应用的resources/rawfile目录下。

    图1 资源文件路径

     

应用侧代码。
 

// xxx.ets
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct WebComponent {
  controller: webview.WebviewController = new webview.WebviewController();

  build() {
    Column() {
      Button('loadUrl')
        .onClick(() => {
          try {
            // 点击按钮时,通过loadUrl,跳转到local1.html
            this.controller.loadUrl($rawfile("local1.html"));
          } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
          }
        })
      // 组件创建时,通过$rawfile加载本地文件local.html
      Web({ src: $rawfile("local.html"), controller: this.controller })
    }
  }
}

 local.html页面代码。
 

<!-- local.html -->
<!DOCTYPE html>
<html>
  <body>
    <p>Hello World</p>
  </body>
</html>

local1.html页面代码。

 

<!-- local1.html -->
<!DOCTYPE html>
<html>
  <body>
    <p>This is local1 page</p>
  </body>
</html>

加载HTML格式的文本数据

Web组件可以通过loadData()接口实现加载HTML格式的文本数据。当开发者不需要加载整个页面,只需要显示一些页面片段时,可通过此功能来快速加载页面。

 

// xxx.ets
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct WebComponent {
  controller: webview.WebviewController = new webview.WebviewController();

  build() {
    Column() {
      Button('loadData')
        .onClick(() => {
          try {
            // 点击按钮时,通过loadData,加载HTML格式的文本数据
            this.controller.loadData(
              "<html><body bgcolor=\"white\">Source:<pre>source</pre></body></html>",
              "text/html",
              "UTF-8"
            );
          } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
          }
        })
      // 组件创建时,加载www.example.com
      Web({ src: 'www.example.com', controller: this.controller })
    }
  }
}

动态创建Web组件

支持命令式创建Web组件,这种方式创建的组件不会立即挂载到组件树,即不会对用户呈现(组件状态为Hidden和InActive),开发者可以在后续使用中按需动态挂载。后台启动的Web实例不建议超过200个。

 

// 载体Ability
// EntryAbility.ets
import { createNWeb } from "../pages/common"
onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', (err, data) => {
    // 创建Web动态组件(需传入UIContext),loadContent之后的任意时机均可创建
    createNWeb("https://www.example.com", windowStage.getMainWindowSync().getUIContext());
    if (err.code) {
      return;
    }
  });
}
// 创建NodeController
// common.ets
import { UIContext, NodeController, BuilderNode, Size, FrameNode } from '@kit.ArkUI';
import { webview } from '@kit.ArkWeb';

// @Builder中为动态组件的具体组件内容
// Data为入参封装类
class Data{
  url: string = "https://www.example.com";
  controller: WebviewController = new webview.WebviewController();
}

@Builder
function WebBuilder(data:Data) {
  Column() {
    Web({ src: data.url, controller: data.controller })
      .width("100%")
      .height("100%")
  }
}

let wrap = wrapBuilder<Data[]>(WebBuilder);

// 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用
export class myNodeController extends NodeController {
  private rootnode: BuilderNode<Data[]> | null = null;
  // 必须要重写的方法,用于构建节点数、返回节点挂载在对应NodeContainer中
  // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新
  makeNode(uiContext: UIContext): FrameNode | null {
    console.log(" uicontext is undefined : "+ (uiContext === undefined));
    if (this.rootnode != null) {
      // 返回FrameNode节点
      return this.rootnode.getFrameNode();
    }
    // 返回null控制动态组件脱离绑定节点
    return null;
  }
  // 当布局大小发生变化时进行回调
  aboutToResize(size: Size) {
    console.log("aboutToResize width : " + size.width  +  " height : " + size.height );
  }

  // 当controller对应的NodeContainer在Appear的时候进行回调
  aboutToAppear() {
    console.log("aboutToAppear");
  }

  // 当controller对应的NodeContainer在Disappear的时候进行回调
  aboutToDisappear() {
    console.log("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);
}
// 使用NodeController的Page页
// Index.ets
import { 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%')
  }
}

管理页面跳转及浏览记录导航

 

历史记录导航

在前端页面点击网页中的链接时,Web组件默认会自动打开并加载目标网址。当前端页面替换为新的加载链接时,会自动记录已经访问的网页地址。可以通过forward()backward()接口向前/向后浏览上一个/下一个历史记录。

页面加载过程中,若涉及网络资源获取,需要配置ohos.permission.INTERNET网络访问权限。

在下面的示例中,点击应用的按钮来触发前端页面的后退操作。

 

// xxx.ets
import { webview } from '@kit.ArkWeb';

@Entry
@Component
struct WebComponent {
  webviewController: webview.WebviewController = new webview.WebviewController();
  
  build() {
    Column() {
      Button('loadData')
        .onClick(() => {
          if (this.webviewController.accessBackward()) {
            this.webviewController.backward();
          }
        })
      Web({ src: 'https://www.example.com/cn/', controller: this.webviewController })
    }
  }
}

如果存在历史记录,accessBackward()接口会返回true。同样,您可以使用accessForward()接口检查是否存在前进的历史记录。如果您不执行检查,那么当用户浏览到历史记录的末尾时,调用forward()backward()接口时将不执行任何操作。

页面跳转

当点击网页中的链接需要跳转到应用内其他页面时,可以通过使用Web组件的onLoadIntercept()接口来实现。

在下面的示例中,应用首页Index.ets加载前端页面route.html,在前端route.html页面点击超链接,可跳转到应用的ProfilePage.ets页面。

  • 应用首页Index.ets页面代码。
     

    // index.ets
    import { webview } from '@kit.ArkWeb';
    import { router } from '@kit.ArkUI';
    
    @Entry
    @Component
    struct WebComponent {
      webviewController: webview.WebviewController = new webview.WebviewController();
    
      build() {
        Column() {
          // 资源文件route.html存放路径src/main/resources/rawfile
          Web({ src: $rawfile('route.html'), controller: this.webviewController })
            .onLoadIntercept((event) => {
              if (event) {
                let url: string = event.data.getRequestUrl();
                if (url.indexOf('native://') === 0) {
                  // 跳转其他界面
                  router.pushUrl({ url: url.substring(9) });
                  return true;
                }
              }
              return false;
            })
        }
      }
    }
    

    route.html前端页面代码。

     

    <!-- route.html -->
    <!DOCTYPE html>
    <html>
    <body>
      <div>
          <a href="native://pages/ProfilePage">个人中心</a>
       </div>
    </body>
    </html>
    

    跳转页面ProfilePage.ets代码。
     

    @Entry
    @Component
    struct ProfilePage {
      @State message: string = 'Hello World';
    
      build() {
        Column() {
          Text(this.message)
            .fontSize(20)
        }
      }
    }
    

    跨应用跳转

    Web组件可以实现点击前端页面超链接跳转到其他应用。

    在下面的示例中,点击call.html前端页面中的超链接,跳转到电话应用的拨号界面。

  • 应用侧代码。

     

    // xxx.ets
    import { webview } from '@kit.ArkWeb';
    import { call } from '@kit.TelephonyKit';
    
    @Entry
    @Component
    struct WebComponent {
      webviewController: webview.WebviewController = new webview.WebviewController();
    
      build() {
        Column() {
          Web({ src: $rawfile('call.html'), controller: this.webviewController })
            .onLoadIntercept((event) => {
              if (event) {
                let url: string = event.data.getRequestUrl();
                // 判断链接是否为拨号链接
                if (url.indexOf('tel://') === 0) {
                  // 跳转拨号界面
                  call.makeCall(url.substring(6), (err) => {
                    if (!err) {
                      console.info('make call succeeded.');
                    } else {
                      console.info('make call fail, err is:' + JSON.stringify(err));
                    }
                  });
                  return true;
                }
              }
              return false;
            })
        }
      }
    }
    

    前端页面call.html代码。

     

    <!-- call.html -->
    <!DOCTYPE html>
    <html>
    <body>
      <div>
        <a href="tel://xxx xxxx xxx">拨打电话</a>
      </div>
    </body>
    </html>
    

    拦截Web组件发起的网络请求

     

    通过网络拦截接口对Web组件发出的请求进行拦截,并可以为被拦截的请求提供自定义的响应头以及响应体。

    为Web组件设置网络拦截器

    为指定的Web组件或者ServiceWorker设置ArkWeb_SchemeHandler,当Web内核发出相应scheme请求的时候,会触发ArkWeb_SchemeHandler的回调。需要在Web组件初始化之后设置网络拦截器。

    当请求开始的时候会回调ArkWeb_OnRequestStart,请求结束的时候会回调ArkWeb_OnRequestStop。

    如果想要拦截Web组件发出的第一个请求,可以通过initializeWebEngine对Web组件提前进行初始化,然后设置拦截器进行拦截。

     

      // 创建一个ArkWeb_SchemeHandler对象。
      ArkWeb_SchemeHandler *schemeHandler;
      OH_ArkWeb_CreateSchemeHandler(&schemeHandler);
    
      // 为ArkWeb_SchemeHandler设置ArkWeb_OnRequestStart与ArkWeb_OnRequestStop回调。
      OH_ArkWebSchemeHandler_SetOnRequestStart(schemeHandler, OnURLRequestStart);
      OH_ArkWebSchemeHandler_SetOnRequestStop(schemeHandler, OnURLRequestStop);
    
      // 拦截webTag为“scheme-handler”的Web组件发出的scheme为“https”的请求。
      OH_ArkWeb_SetSchemeHandler("https", "scheme-handler", schemeHandler);
      OH_ArkWebServiceWorker_SetSchemeHandler("https", schemeHandler);
    

    也可以拦截非Web组件内置scheme的请求。

     

      // 创建一个ArkWeb_SchemeHandler对象。
      ArkWeb_SchemeHandler *schemeHandler;
      OH_ArkWeb_CreateSchemeHandler(&schemeHandler);
    
      // 为ArkWeb_SchemeHandler设置ArkWeb_OnRequestStart与ArkWeb_OnRequestStop回调。
      OH_ArkWebSchemeHandler_SetOnRequestStart(schemeHandler, OnURLRequestStart);
      OH_ArkWebSchemeHandler_SetOnRequestStop(schemeHandler, OnURLRequestStop);
    
      // 拦截webTag为“scheme-handler”的Web组件发出的scheme为“custom”的请求。
      OH_ArkWeb_SetSchemeHandler("custom", "scheme-handler", schemeHandler);
      OH_ArkWebServiceWorker_SetSchemeHandler("custom", schemeHandler);
    

    设置自定义scheme需要遵循的规则

    如果要拦截自定义scheme的请求,需要提前将自定义scheme注册到Web内核。需要在Web组件初始化之前进行注册,Web组件初始化后再注册会失败。

     

      // 注册“custom“ scheme到Web组件,并指定该scheme需要遵循标准的scheme规则,允许该scheme发出跨域请求。
      OH_ArkWeb_RegisterCustomSchemes("custom", ARKWEB_SCHEME_OPTION_STANDARD | ARKWEB_SCHEME_OPTION_CORS_ENABLED);
      // 注册“custom-local” scheme到Web组件,并指定该scheme需要遵循与“file” scheme一样的规则。
      OH_ArkWeb_RegisterCustomSchemes("custom-local", ARKWEB_SCHEME_OPTION_LOCAL);
      // 注册“custom-csp-bypassing”到Web组件,并指定该scheme需要遵循标准的scheme规则,允许忽略CSP检查。
      OH_ArkWeb_RegisterCustomSchemes("custom-csp-bypassing", ARKWEB_SCHEME_OPTION_CSP_BYPASSING | ARKWEB_SCHEME_OPTION_STANDARD);
      // 注册“custom-isolated”到Web组件,并指定该scheme的请求必须从相同scheme加载的网页中发起。
      OH_ArkWeb_RegisterCustomSchemes("custom-isolated", ARKWEB_SCHEME_OPTION_DISPLAY_ISOLATED);
    

    由于注册scheme需要在Web组件初始化之前进行注册,而网络拦截器需要在Web组件初始化之后设置,建议在EntryAbility的onCreate中调用c++接口注册scheme。

    scheme注册完毕后,通过initializeWebEngine对Web组件进行初始化,初始化完成后再设置网络拦截器。

     

      export default class EntryAbility extends UIAbility {
          onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
              // 注册scheme的配置。
              testNapi.registerCustomSchemes();
              // 初始化Web组件内核,该操作会初始化Browser进程以及创建BrowserContext。
              webview.WebviewController.initializeWebEngine();
              // 创建并设置ArkWeb_SchemeHandler。
              testNapi.setSchemeHandler();
          }
          ...
      };
    

    获取被拦截请求的请求信息

    通过OH_ArkWebResourceRequest_*接口获取被拦截请求的信息。可以获取url、method、referrer、headers、resourceType等信息。

     

      char* url;
      OH_ArkWebResourceRequest_GetUrl(resourceRequest_, &url);
      OH_ArkWeb_ReleaseString(url);
    
      char* method;
      OH_ArkWebResourceRequest_GetMethod(resourceRequest_, &method);
      OH_ArkWeb_ReleaseString(method);
    
      int32_t resourceType = OH_ArkWebResourceRequest_GetResourceType(resourceRequest_);
    
      char* frameUrl;
      OH_ArkWebResourceRequest_GetFrameUrl(resourceRequest_, &frameUrl);
      OH_ArkWeb_ReleaseString(frameUrl);
      ...
    

    支持获取PUT/POST类请求的上传数据。数据类型支持BYTES、FILE、BLOB和CHUNKED。

     

      // 获取被拦截请求的上传数据。
      OH_ArkWebResourceRequest_GetHttpBodyStream(resourceRequest(), &stream_);
      // 设置读取上传数据的读回调。
      OH_ArkWebHttpBodyStream_SetReadCallback(stream_, ReadCallback);
      // 初始化ArkWeb_HttpBodyStream,其它OH_ArkWebHttpBodyStream*函数需要在初始化进行调用。
      OH_ArkWebHttpBodyStream_Init(stream_, InitCallback);
    

    为被拦截的请求提供自定义的响应体

    Web组件的网络拦截支持在worker线程以流的方式为被拦截的请求提供自定义的响应体。也可以以特定的网络错误码结束当前被拦截的请求。

     

      // 为被拦截的请求创建一个响应头。
      ArkWeb_Response *response;
      OH_ArkWeb_CreateResponse(&response);
    
      // 设置HTTP状态码为200。
      OH_ArkWebResponse_SetStatus(response, 200);
      // 设置响应体的编码格式。
      OH_ArkWebResponse_SetCharset(response, "UTF-8");
      // 设置响应体的大小。
      OH_ArkWebResponse_SetHeaderByName(response, "content-length", "1024", false);
      // 将为被拦截的请求创建的响应头传递给Web组件。
      OH_ArkWebResourceHandler_DidReceiveResponse(resourceHandler, response);
    
      // 该函数可以调用多次,数据可以分多份来传递给Web组件。
      OH_ArkWebResourceHandler_DidReceiveData(resourceHandler, buffer, bufLen);
    
      // 读取响应体结束,当然如果希望该请求失败的话也可以通过调用OH_ArkWebResourceHandler_DidFailWithError(resourceHandler_, errorCode);
      // 传递给Web组件一个错误码并结束该请求。
      OH_ArkWebResourceHandler_DidFinish(resourceHandler);
    

    完整示例

    使用DevEco Studio创建一个默认的Native C++工程,需要提前准备一个mp4文件,命名为test.mp4,将test.mp4放到main/resources/rawfile下。

    main/ets/pages/index.ets


     

    import testNapi from 'libentry.so';
    import { webview } from '@kit.ArkWeb';
    import { resourceManager } from '@kit.LocalizationKit';
    
    @Entry
    @Component
    struct Index {
      mycontroller: webview.WebviewController = new webview.WebviewController("scheme-handler");
    
      build() {
        Row() {
          Column() {
            Button("goback").onClick( event => {
              this.mycontroller.backward();
            })
    
            Web({ src: $rawfile("test.html"), controller: this.mycontroller})
              .javaScriptAccess(true)
              .width('100%')
              .height('100%')
              .databaseAccess(true)
              .fileAccess(false)
              .domStorageAccess(true)
              .cacheMode(CacheMode.Default)
              .onPageBegin( event => {
                testNapi.initResourceManager(getContext().resourceManager);
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    

    main/ets/entryability/EntryAbility.ets

     

    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { window } from '@kit.ArkUI';
    import testNapi from 'libentry.so';
    import { webview } from '@kit.ArkWeb';
    
    export default class EntryAbility extends UIAbility {
        onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
            // 注册三方协议的配置。
            testNapi.registerCustomSchemes();
            // 初始化Web组件内核,该操作会初始化Browser进程以及创建BrowserContext。
            webview.WebviewController.initializeWebEngine();
            // 设置SchemeHandler。
            testNapi.setSchemeHandler();
        }
    
        onDestroy(): void {
    
        }
    
        onWindowStageCreate(windowStage: window.WindowStage): void {
            windowStage.loadContent('pages/Index', (err, data) => {
                if (err.code) {
                    return;
                }
            });
        }
    
        onWindowStageDestroy(): void {
    
        }
    
        onForeground(): void {
    
        }
    
        onBackground(): void {
    
        }
    };
    

    main/cpp/hello.cpp

     

    #include "hilog/log.h"
    #include "napi/native_api.h"
    #include "rawfile_request.h"
    #include "rawfile/raw_file_manager.h"
    #include "web/arkweb_scheme_handler.h"
    #include "web/arkweb_net_error_list.h"
    
    #undef LOG_TAG
    #define LOG_TAG "ss-handler"
    
    ArkWeb_SchemeHandler *g_schemeHandler;
    ArkWeb_SchemeHandler *g_schemeHandlerForSW;
    NativeResourceManager *g_resourceManager;
    
    // 注册三方协议的配置,需要在Web内核初始化之前调用,否则会注册失败。
    static napi_value RegisterCustomSchemes(napi_env env, napi_callback_info info)
    {
        OH_LOG_INFO(LOG_APP, "register custom schemes");
        OH_ArkWeb_RegisterCustomSchemes("custom", ARKWEB_SCHEME_OPTION_STANDARD | ARKWEB_SCHEME_OPTION_CORS_ENABLED);
        OH_ArkWeb_RegisterCustomSchemes("custom-local", ARKWEB_SCHEME_OPTION_LOCAL);
        OH_ArkWeb_RegisterCustomSchemes("custom-csp-bypassing", ARKWEB_SCHEME_OPTION_CSP_BYPASSING | ARKWEB_SCHEME_OPTION_STANDARD);
        OH_ArkWeb_RegisterCustomSchemes("custom-isolated", ARKWEB_SCHEME_OPTION_DISPLAY_ISOLATED);
        return nullptr;
    }
    
    // 请求开始的回调,在该函数中我们创建一个RawfileRequest来实现对Web内核请求的拦截。
    void OnURLRequestStart(const ArkWeb_SchemeHandler *schemeHandler,
                           ArkWeb_ResourceRequest *resourceRequest,
                           const ArkWeb_ResourceHandler *resourceHandler,
                           bool *intercept)
    {
        *intercept = true;
        RawfileRequest* request = new RawfileRequest(resourceRequest, resourceHandler, g_resourceManager);
        OH_ArkWebResourceRequest_SetUserData(resourceRequest, request);
        request->Start();
    }
    
    // 请求结束的回调,在该函数中我们需要标记RawfileRequest已经结束了,内部不应该再使用ResourceHandler。
    void OnURLRequestStop(const ArkWeb_SchemeHandler *schemeHandler,
                          const ArkWeb_ResourceRequest *request)
    {
        if (!request) {
            OH_LOG_ERROR(LOG_APP, "on request stop request is nullptr.");
            return;
        }
    
        RawfileRequest *rawfileRequest = (RawfileRequest *)OH_ArkWebResourceRequest_GetUserData(request);
        if (rawfileRequest) {
            rawfileRequest->Stop();
        }
    }
    
    void OnURLRequestStartForSW(const ArkWeb_SchemeHandler *schemeHandler,
                                ArkWeb_ResourceRequest *resourceRequest,
                                const ArkWeb_ResourceHandler *resourceHandler,
                                bool *intercept)
    {
        *intercept = true;
        RawfileRequest* request = new RawfileRequest(resourceRequest, resourceHandler, g_resourceManager);
        OH_ArkWebResourceRequest_SetUserData(resourceRequest, request);
        request->Start();
    }
    
    void OnURLRequestStopForSW(const ArkWeb_SchemeHandler *schemeHandler,
                               const ArkWeb_ResourceRequest *request)
    {
        if (!request) {
            OH_LOG_ERROR(LOG_APP, "on request stop request is nullptr.");
            return;
        }
    
        RawfileRequest *rawfileRequest = (RawfileRequest *)OH_ArkWebResourceRequest_GetUserData(request);
        if (rawfileRequest) {
            rawfileRequest->Stop();
        }
    }
    
    // 设置SchemeHandler。
    static napi_value SetSchemeHandler(napi_env env, napi_callback_info info)
    {
        OH_LOG_INFO(LOG_APP, "set scheme handler");
        OH_ArkWeb_CreateSchemeHandler(&g_schemeHandler);
        OH_ArkWeb_CreateSchemeHandler(&g_schemeHandlerForSW);
    
        OH_ArkWebSchemeHandler_SetOnRequestStart(g_schemeHandler, OnURLRequestStart);
        OH_ArkWebSchemeHandler_SetOnRequestStop(g_schemeHandler, OnURLRequestStop);
    
        OH_ArkWebSchemeHandler_SetOnRequestStart(g_schemeHandlerForSW, OnURLRequestStart);
        OH_ArkWebSchemeHandler_SetOnRequestStop(g_schemeHandlerForSW, OnURLRequestStop);
    
        OH_ArkWeb_SetSchemeHandler("custom", "scheme-handler", g_schemeHandler);
        OH_ArkWeb_SetSchemeHandler("custom-csp-bypassing", "scheme-handler", g_schemeHandler);
        OH_ArkWeb_SetSchemeHandler("custom-isolated", "scheme-handler", g_schemeHandler);
        OH_ArkWeb_SetSchemeHandler("custom-local", "scheme-handler", g_schemeHandler);
        OH_ArkWeb_SetSchemeHandler("https", "scheme-handler", g_schemeHandler);
        OH_ArkWeb_SetSchemeHandler("http", "scheme-handler", g_schemeHandler);
    
        OH_ArkWebServiceWorker_SetSchemeHandler("https", g_schemeHandlerForSW);
        return nullptr;
    }
    
    static napi_value InitResourceManager(napi_env env, napi_callback_info info)
    {
        size_t argc = 2;
        napi_value argv[2] = {nullptr};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
        g_resourceManager = OH_ResourceManager_InitNativeResourceManager(env, argv[0]);
        return nullptr;
    }
    
    EXTERN_C_START
    static napi_value Init(napi_env env, napi_value exports)
    {
        napi_property_descriptor desc[] = {
            {"setSchemeHandler", nullptr, SetSchemeHandler, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"initResourceManager", nullptr, InitResourceManager, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"registerCustomSchemes", nullptr, RegisterCustomSchemes, 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);
    }
    

    main/cpp/CMakeLists.txt

     

    # the minimum version of CMake.
    cmake_minimum_required(VERSION 3.4.1)
    project(schemehandler)
    
    set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
    
    if(DEFINED PACKAGE_INFO_FILE)
        include(${PACKAGE_INFO_FILE})
    endif()
    
    include_directories(${NATIVERENDER_ROOT_PATH}
                        ${NATIVERENDER_ROOT_PATH}/include)
    
    add_library(entry SHARED rawfile_request.cpp hello.cpp)
    target_link_libraries(entry PUBLIC librawfile.z.so libace_napi.z.so libohweb.so libhilog_ndk.z.so)
    

    main/cpp/types/index.d.ts

     

    export const registerCustomSchemes: () => void;
    export const setSchemeHandler: () => void;
    export const initResourceManager: (resmgr: resourceManager.ResourceManager) => void;
    

    main/cpp/rawfile_request.h

     

    #ifndef RAWFILE_REQUEST_H
    #define RAWFILE_REQUEST_H
    
    #include <mutex>
    #include <string>
    
    #include <rawfile/raw_file_manager.h>
    #include "web/arkweb_scheme_handler.h"
    #include "web/arkweb_net_error_list.h"
    
    class RawfileRequest {
    public:
        RawfileRequest(const ArkWeb_ResourceRequest *resourceRequest,
                       const ArkWeb_ResourceHandler *resourceHandler,
                       const NativeResourceManager* resourceManager);
        ~RawfileRequest();
    
        void Start();
        void Stop();
        void ReadRawfileDataOnWorkerThread();
    
        const ArkWeb_ResourceHandler *resourceHandler() { return resourceHandler_; }
        const ArkWeb_ResourceRequest *resourceRequest() { return resourceRequest_; }
        const NativeResourceManager *resourceManager() { return resourceManager_; }
        ArkWeb_Response *response() { return response_; }
        ArkWeb_HttpBodyStream *stream() { return stream_; }
        const std::string rawfilePath() { return rawfilePath_; }
    
        void DidReceiveResponse();
        void DidReceiveData(const uint8_t *buffer, int64_t bufLen);
        void DidFinish();
        void DidFailWithError(ArkWeb_NetError errorCode);
    
    private:
        const ArkWeb_ResourceRequest *resourceRequest_{nullptr};
        const ArkWeb_ResourceHandler *resourceHandler_{nullptr};
        const NativeResourceManager *resourceManager_{nullptr};
        ArkWeb_Response *response_;
        bool stopped_{false};
        std::string rawfilePath_;
        ArkWeb_HttpBodyStream *stream_{nullptr};
        std::mutex mutex_;
    };
    
    #endif  // RAWFILE_REQUEST_H
    

    main/cpp/rawfile_request.cpp

     

    #include "rawfile_request.h"
    
    #include "threads.h"
    
    #include "hilog/log.h"
    #include "rawfile/raw_file.h"
    #include "rawfile/raw_file_manager.h"
    
    #undef LOG_TAG
    #define LOG_TAG "ss-handler"
    
    namespace {
    
    uint8_t buffer[1024];
    cnd_t http_body_cnd;
    mtx_t http_body_mtx;
    
    // HttpBodyStream的读回调。
    void ReadCallback(const ArkWeb_HttpBodyStream  *httpBodyStream, uint8_t* buffer, int bytesRead)
    {
        OH_LOG_INFO(LOG_APP, "read http body back.");
        bool isEof = OH_ArkWebHttpBodyStream_IsEof(httpBodyStream);
        if (!isEof && bytesRead != 0) {
            memset(buffer, 0, 1000);
            OH_ArkWebHttpBodyStream_Read(httpBodyStream, buffer, 1000);
        } else {
            RawfileRequest *rawfileRequest = (RawfileRequest *)OH_ArkWebHttpBodyStream_GetUserData(httpBodyStream);
            if (rawfileRequest) {
                rawfileRequest->ReadRawfileDataOnWorkerThread();
                cnd_signal(&http_body_cnd);
            }
        }
    }
    
    int ReadHttpBodyOnWorkerThread(void* userData)
    {
        memset(buffer, 0, 1000);
        ArkWeb_HttpBodyStream *httpBodyStream = (ArkWeb_HttpBodyStream *)userData;
        OH_ArkWebHttpBodyStream_Read(httpBodyStream, buffer, 1000);
        cnd_init(&http_body_cnd);
        mtx_init(&http_body_mtx, mtx_plain);
        cnd_wait(&http_body_cnd, &http_body_mtx);
        return 0;
    }
    
    int ReadRawfileOnWorkerThread(void* userData)
    {
        RawfileRequest * rawfileRequest = (RawfileRequest *)userData;
        if (rawfileRequest) {
            rawfileRequest->ReadRawfileDataOnWorkerThread();
        }
        return 0;
    }
    
    // ArkWeb_HttpBodyStream的初始化回调。
    void InitCallback(const ArkWeb_HttpBodyStream *httpBodyStream, ArkWeb_NetError result)
    {
        OH_LOG_INFO(LOG_APP, "init http body stream done %{public}d.", result);
        bool isChunked = OH_ArkWebHttpBodyStream_IsChunked(httpBodyStream);
        OH_LOG_INFO(LOG_APP, "http body stream is chunked %{public}d.", isChunked);
        thrd_t th;
        if (thrd_create(&th, ReadHttpBodyOnWorkerThread, (void *)httpBodyStream) != thrd_success) {
            OH_LOG_ERROR(LOG_APP, "create thread failed.");
            return;
        }
    
        if (thrd_detach(th) != thrd_success) {
            OH_LOG_ERROR(LOG_APP, "detach thread failed.");
        }
    }
    
    const int blockSize = 1024 * 8;
    
    }  // namespace
    
    RawfileRequest::RawfileRequest(const ArkWeb_ResourceRequest *resourceRequest,
                                   const ArkWeb_ResourceHandler *resourceHandler,
                                   const NativeResourceManager* resourceManager)
            : resourceRequest_(resourceRequest),
              resourceHandler_(resourceHandler),
              resourceManager_(resourceManager) {}
    
    RawfileRequest::~RawfileRequest() {}
    
    void RawfileRequest::Start()
    {
        OH_LOG_INFO(LOG_APP, "start a rawfile request.");
        char* url;
        OH_ArkWebResourceRequest_GetUrl(resourceRequest_, &url);
        std::string urlStr(url);
        std::size_t position = urlStr.rfind('/');
        if (position != std::string::npos) {
            rawfilePath_ = urlStr.substr(position + 1);
        }
        OH_ArkWeb_ReleaseString(url);
    
        OH_ArkWeb_CreateResponse(&response_);
        OH_ArkWebResourceRequest_GetHttpBodyStream(resourceRequest(), &stream_);
        if (stream_) {
            OH_LOG_ERROR(LOG_APP, "have http body stream");
            OH_ArkWebHttpBodyStream_SetUserData(stream_, this);
            OH_ArkWebHttpBodyStream_SetReadCallback(stream_, ReadCallback);
            OH_ArkWebHttpBodyStream_Init(stream_, InitCallback);
        } else {
            thrd_t th;
            if (thrd_create(&th, ReadRawfileOnWorkerThread, (void *)this) != thrd_success) {
                OH_LOG_ERROR(LOG_APP, "create thread failed.");
                return;
            }
    
            if (thrd_detach(th) != thrd_success) {
                OH_LOG_ERROR(LOG_APP, "detach thread failed.");
            }
        }
    }
    
    // 在worker线程中读取rawfile,并通过ResourceHandler返回给Web内核。
    void RawfileRequest::ReadRawfileDataOnWorkerThread()
    {
        OH_LOG_INFO(LOG_APP, "read rawfile in worker thread.");
        const struct UrlInfo {
            std::string resource;
            std::string mimeType;
        } urlInfos[] = {
            {"test.html", "text/html"},
            {"video.html", "text/html"},
            {"isolated.html", "text/html"},
            {"csp_bypassing.html", "text/html"},
            {"post_data.html", "text/html"},
            {"chunked_post_stream.html", "text/html"},
            {"local.html", "text/html"},
            {"service_worker.html", "text/html"},
            {"csp_script.js", "text/javascript"},
            {"sw.js", "text/javascript"},
            {"isolated_script.js", "text/javascript"},
            {"local_script.js", "text/javascript"},
            {"test.mp4", "video/mp4"},
            {"xhr", "application/json"}
        };
    
        if (!resourceManager()) {
            OH_LOG_ERROR(LOG_APP, "read rawfile error, resource manager is nullptr.");
            return;
        }
    
        RawFile *rawfile = OH_ResourceManager_OpenRawFile(resourceManager(), rawfilePath().c_str());
        if (!rawfile) {
            OH_ArkWebResponse_SetStatus(response(), 404);
        } else {
            OH_ArkWebResponse_SetStatus(response(), 200);
        }
    
        for (auto &urlInfo : urlInfos) {
            if (urlInfo.resource == rawfilePath()) {
                OH_ArkWebResponse_SetMimeType(response(), urlInfo.mimeType.c_str());
                break;
            }
        }
        OH_ArkWebResponse_SetCharset(response(), "UTF-8");
    
        long len = OH_ResourceManager_GetRawFileSize(rawfile);
        OH_ArkWebResponse_SetHeaderByName(response(), "content-length", std::to_string(len).c_str(), false);
        DidReceiveResponse();
    
        long consumed = 0;
        uint8_t buffer[blockSize];
        while (true) {
            int ret = OH_ResourceManager_ReadRawFile(rawfile, buffer, blockSize);
            OH_LOG_INFO(LOG_APP, "read rawfile %{public}d bytes.", ret);
            if (ret == 0) {
                break;
            }
            consumed += ret;
            OH_ResourceManager_SeekRawFile(rawfile, consumed, 0);
            DidReceiveData(buffer, ret);
            memset(buffer, 0, blockSize);
        }
    
        OH_ResourceManager_CloseRawFile(rawfile);
        DidFinish();
    }
    
    void RawfileRequest::Stop()
    {
        OH_LOG_INFO(LOG_APP, "stop the rawfile request.");
        std::lock_guard<std::mutex> guard(mutex_);
        stopped_ = true;
        if (response_) {
            OH_ArkWeb_DestroyResponse(response_);
        }
        OH_ArkWebResourceRequest_Destroy(resourceRequest_);
        OH_ArkWebResourceHandler_Destroy(resourceHandler_);
    }
    
    void RawfileRequest::DidReceiveResponse()
    {
        OH_LOG_INFO(LOG_APP, "did receive response.");
        std::lock_guard<std::mutex> guard(mutex_);
        if (!stopped_) {
            OH_ArkWebResourceHandler_DidReceiveResponse(resourceHandler_, response_);
        }
    }
    
    void RawfileRequest::DidReceiveData(const uint8_t *buffer, int64_t bufLen)
    {
        OH_LOG_INFO(LOG_APP, "did receive data.");
        std::lock_guard<std::mutex> guard(mutex_);
        if (!stopped_) {
            OH_ArkWebResourceHandler_DidReceiveData(resourceHandler_, buffer, bufLen);
        }
    }
    
    void RawfileRequest::DidFinish()
    {
        OH_LOG_INFO(LOG_APP, "did finish.");
        std::lock_guard<std::mutex> guard(mutex_);
        if (!stopped_) {
            OH_ArkWebResourceHandler_DidFinish(resourceHandler_);
        }
    }
    
    void RawfileRequest::DidFailWithError(ArkWeb_NetError errorCode)
    {
        OH_LOG_INFO(LOG_APP, "did finish with error %{public}d.", errorCode);
        if (!stopped_) {
            OH_ArkWebResourceHandler_DidFailWithError(resourceHandler_, errorCode);
        }
    }
    

    main/resources/rawfile/test.html

     

    <html>
    <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    </head>
    
    <body>
    <h1> 网络拦截测试demo</h1>
    <a href="https://www.example.com/video.html">拦截视频资源请求,读取本地mp4文件</a><br/>
    <a href="https://www.example.com/csp_bypassing.html">测试三方协议忽略csp检查,并成功拦截</a><br/>
    <a href="https://www.example.com/isolated.html">测试拦截设置ISOLATED属性的三方协议</a><br/>
    <a href="https://www.example.com/local.html">测试拦截设置LOCAL属性的三方协议</a><br/>
    <a href="https://www.example.com/service_worker.html">测试拦截service worker触发的请求</a><br/>
    <a href="https://www.example.com/post_data.html">测试读取blob类型http body stream</a><br/>
    <a href="https://www.example.com/chunked_post_stream.html">测试读取chunked类型http body stream</a>
    </body>
    </html>
    

    main/resources/rawfile/cat.svg

     

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.37 10.79"><path d="M12.8 10.18l-.8-.8c-.98-.8-.86-1.92-.87-2.04-.02-.1-.02-.58.02-.74.04-.15 0-.32 0-.32.28-1.18 1.2-.85 1.2-.85.38.04.4-.33.4-.33.25-.13.2-.4.2-.4l-.47-.48c-.18-.48-.7-.6-.7-.6.08-.48-.17-.78-.17-.78-.03.14-.58.72-.62.73-.63.15-.43.26-.83.55-.4.28-1.26.63-1.64.43-.37-.2-3.5-.5-4.86-.5-.4 0-.7.1-.95.2-.23-.16-.52-.52-.73-1.02-.3-.74-.36-1.48-.12-1.98.13-.27.28-.42.44-.45.23-.05.52.16.6.24.17.14.42.13.56-.03.15-.15.14-.4-.02-.55C3.38.4 2.8-.1 2.14.02c-.42.08-.76.38-1 .9-.34.7-.3 1.66.1 2.6.18.44.47.93.83 1.25-.1.13-.13.23-.13.23-.12.27-.44.9-.33 1.45.13.56-.22.82-.3.88-.05.07-.73.47-.73.47L0 9.78c-.08.38.43.6.43.6.18-.03.2-.63.2-.63l.44-1.04 1.66-.6s0 .7-.02.83-.1.35-.1.35c.08.46 1.2 1.5 1.2 1.5h.85v-.26c-.07-.3-.5-.16-.5-.16l-.62-.95c.66-.5.93-1.38.93-1.38.3.26 1.8-.22 1.8-.22l.9.1-.25 2.1c-.07.5.05.68.05.68h.4c.3 0 .48.03.48-.27 0-.28-.4-.23-.4-.23l1-1.95c.93-.58 1.53.26 1.53.26l.05.3c.37.53 2.38 1.9 2.38 1.9h1v-.3c-.18-.32-.6-.2-.6-.2z"/></svg>
    

    main/resources/rawfile/csp_bypassing.html

     

    <html>
    <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; media-src 'self'">
    </head>
    <body>
    <p>scheme: custom-csp-bypassing</p>
    <p>options: ARKWEB_SCHEME_OPTION_CSP_BYPASSING | ARKWEB_SCHEME_OPTION_STANDARD</p>
    <script src="custom-csp-bypassing://www.example.com/csp_script.js"></script>
    </body>
    </html>
    

    main/resources/rawfile/csp_script.js

     

    const body = document.body;
    const element = document.createElement('div');
    element.textContent = 'csp_script.js bypass the csp rules';
    body.appendChild(element);
    

    main/resources/rawfile/isolated_script.js

     

    const element = document.getElementById('isolated_test');
    element.textContent = 'isolated_script.js not blocked';
    

    main/resources/rawfile/isolated.html

     

    <html>
    <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    </head>
    <body>
    <p>scheme: custom-isolated</p>
    <p>options: ARKWEB_SCHEME_OPTION_DISPLAY_ISOLATED</p>
    <div id="isolated_test">isolated_script.js 被拦截</div>
    <script src="custom-isolated://www.example.com/isolated_script.js"></script>
    </body>
    </html>
    

    main/resources/rawfile/local_script.js

     

    const element = document.getElementById('local_test');
    element.textContent = 'local_script.js not blocked.';
    

    main/resources/rawfile/local.html

     

    <html>
    <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    </head>
    <body>
    <p>scheme: custom-local</p>
    <p>options: ARKWEB_SCHEME_OPTION_LOCAL</p>
    <div id="local_test">local_script.js 被拦截</div>
    <script src="custom-local://www.example.com/local_script.js"></script>
    </body>
    </html>
    

    main/resources/rawfile/post_data.html

     

    <html>
    <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script>
        function textPostXhr(url) {
            var formData = new FormData();
            var myBlob = new Blob(["This is my blob content"], {type : "text/plain"});
            formData.append("upload", myBlob);
            var xhr = new XMLHttpRequest();
            xhr.open('POST', url, true);
            xhr.send(formData);
            xhr.onreadystatechange = function (err) {
                console.log(err.target.status);
            }
        }
        function textPutXhr(url) {
            var formData = new FormData();
            var myBlob = new Blob(["This is my blob content"], {type : "text/plain"});
            formData.append("upload", myBlob);
            var xhr = new XMLHttpRequest();
            xhr.open('PUT', url, true);
            xhr.send(formData);
            xhr.onreadystatechange = function (err) {
                console.log(err.target.status);
            }
        }
    </script>
    </head>
    <body>
    <div onclick="textPostXhr('https://www.example.com/xhr')">test xhr post</div>
    <div onclick="textPutXhr('https://www.example.com/xhr')">test xhr put</div>
    </body>
    </html>
    

    main/resources/rawfile/service_worker.html

     

    <html>
    <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script>
    function registerSuccess() {
        const body = document.body;
        const element = document.createElement('div');
        element.textContent = 'register sw successful.';
        body.appendChild(element);
    }
    navigator.serviceWorker.register('/sw.js')
        .then(reg => registerSuccess())
        .catch(error => console.log('failed!', error))
    </script>
    </head>
    <body>
    </body>
    </html>
    

    main/resources/rawfile/sw.js

     

    self.addEventListener('install', event => {
        console.log('v1 installing');
        event.waitUntil(
            caches.open('static-v1').then(cache => cache.add('/cat.svg'))
        );
    });
    
    self.addEventListener('activate', event => {
        console.log("v1 now redy to handle fetches.");
    });
    

    main/resources/rawfile/video.html

     

    <html>
    <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    </head>
    <body>
    <video width="400" height="400" controls>
        <source src="https://www.example.com/test.mp4" type="video/mp4">
    </video>
    </body>
    </html>
    

    main/resources/rawfile/chunked_post_stream.html

     

    <html>
    <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    </head>
    <script>
    let uploaded = 0;
    let buf = new Uint8Array(1024 * 50);
    let start = Date.now();
    var rs = new ReadableStream({
        pull(ctrl) {
            uploaded += buf.byteLength;
            crypto.getRandomValues(buf);
            ctrl.enqueue(buf);
            if ((start + 1000) < Date.now()) ctrl.close();
        }
    });
    function test() {
        fetch('https://www.example.com/xhr', {
            method: 'POST',
            body: rs,
            duplex: 'half'
        }).then(r => r.json()).then(console.log);
    }
    </script>
    <body>
    <div onclick="test()">test post chunked http body.</div>
    </body>
    </html>
    

    main/resources/rawfile/xhr

     

    {}
    

    自定义页面请求响应

     

    Web组件支持在应用拦截到页面请求后自定义响应请求能力。开发者通过onInterceptRequest()接口来实现自定义资源请求响应 。自定义请求能力可以用于开发者自定义Web页面响应、自定义文件资源响应等场景。

    Web网页上发起资源加载请求,应用层收到资源请求消息。应用层构造本地资源响应消息发送给Web内核。Web内核解析应用层响应信息,根据此响应信息进行页面资源加载。

    在下面的示例中,Web组件通过拦截页面请求“https://www.example.com/test.html”, 在应用侧代码构建响应资源,实现自定义页面响应场景。

  • 前端页面index.html代码。

     

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
    <!-- 页面资源请求 -->
    <a href="https://www.example.com/test.html">intercept test!</a>
    </body>
    </html>
    

    应用侧代码。

     

    // xxx.ets
    import { webview } from '@kit.ArkWeb';
    
    @Entry
    @Component
    struct WebComponent {
      controller: webview.WebviewController = new webview.WebviewController();
      responseResource: WebResourceResponse = new WebResourceResponse();
      // 开发者自定义响应数据
      @State webData: string = '<!DOCTYPE html>\n' +
        '<html>\n' +
        '<head>\n' +
        '<title>intercept test</title>\n' +
        '</head>\n' +
        '<body>\n' +
        '<h1>intercept ok</h1>\n' +
        '</body>\n' +
        '</html>'
    
      build() {
        Column() {
          Web({ src: $rawfile('index.html'), controller: this.controller })
            .onInterceptRequest((event) => {
              if (event) {
                console.info('url:' + event.request.getRequestUrl());
                // 拦截页面请求
                if (event.request.getRequestUrl() !== 'https://www.example.com/test.html') {
                  return null;
                }
              }
              // 构造响应数据
              this.responseResource.setResponseData(this.webData);
              this.responseResource.setResponseEncoding('utf-8');
              this.responseResource.setResponseMimeType('text/html');
              this.responseResource.setResponseCode(200);
              this.responseResource.setReasonMessage('OK');
              return this.responseResource;
            })
        }
      }
    }
    

    为自定义的JavaScript请求响应生成 CodeCache:自定义请求响应的资源类型如果是JavaScript脚本,可以在响应头中添加“ResponseDataID”字段,Web内核读取到该字段后会在为该JS资源生成CodeCache,加速JS执行,并且ResponseData如果有更新时必须更新该字段。不添加“ResponseDataID”字段的情况下默认不生成CodeCache。

    在下面的示例中,Web组件通过拦截页面请求“https://www.example.com/test.js”, 应用侧代码构建响应资源,在响应头中添加“ResponseDataID”字段,开启生成CodeCache的功能。

  • 前端页面index.html代码。

     

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
    
    <div id="div-1">this is a test div</div>
    <div id="div-2">this is a test div</div>
    <div id="div-3">this is a test div</div>
    <div id="div-4">this is a test div</div>
    <div id="div-5">this is a test div</div>
    <div id="div-6">this is a test div</div>
    <div id="div-7">this is a test div</div>
    <div id="div-8">this is a test div</div>
    <div id="div-9">this is a test div</div>
    <div id="div-10">this is a test div</div>
    <div id="div-11">this is a test div</div>
    
    <script src="https://www.example.com/test.js"></script>
    </body>
    </html>
    

    应用侧代码。

     

    // xxx.ets
    import { webview } from '@kit.ArkWeb';
    
    @Entry
    @Component
    struct WebComponent {
      controller: webview.WebviewController = new webview.WebviewController();
      responseResource: WebResourceResponse = new WebResourceResponse();
      // 开发者自定义响应数据(响应数据长度需大于等于1024才会生成codecache)
      @State jsData: string = 'let text_msg = "the modified content:version 0000000000001";\n' +
        'let element1 = window.document.getElementById("div-1");\n' +
        'let element2 = window.document.getElementById("div-2");\n' +
        'let element3 = window.document.getElementById("div-3");\n' +
        'let element4 = window.document.getElementById("div-4");\n' +
        'let element5 = window.document.getElementById("div-5");\n' +
        'let element6 = window.document.getElementById("div-6");\n' +
        'let element7 = window.document.getElementById("div-7");\n' +
        'let element8 = window.document.getElementById("div-8");\n' +
        'let element9 = window.document.getElementById("div-9");\n' +
        'let element10 = window.document.getElementById("div-10");\n' +
        'let element11 = window.document.getElementById("div-11");\n' +
        'element1.innerHTML = text_msg;\n' +
        'element2.innerHTML = text_msg;\n' +
        'element3.innerHTML = text_msg;\n' +
        'element4.innerHTML = text_msg;\n' +
        'element5.innerHTML = text_msg;\n' +
        'element6.innerHTML = text_msg;\n' +
        'element7.innerHTML = text_msg;\n' +
        'element8.innerHTML = text_msg;\n' +
        'element9.innerHTML = text_msg;\n' +
        'element10.innerHTML = text_msg;\n' +
        'element11.innerHTML = text_msg;\n';
    
      build() {
        Column() {
          Web({ src: $rawfile('index.html'), controller: this.controller })
            .onInterceptRequest((event) => {
              // 拦截页面请求
              if (event?.request.getRequestUrl() == 'https://www.example.com/test.js') {
                // 构造响应数据
                this.responseResource.setResponseHeader([
                  {
                    // 格式:不超过13位纯数字。js识别码,Js有更新时必须更新该字段
                    headerKey: "ResponseDataID",
                    headerValue: "0000000000001"
                  }]);
                this.responseResource.setResponseData(this.jsData);
                this.responseResource.setResponseEncoding('utf-8');
                this.responseResource.setResponseMimeType('application/javascript');
                this.responseResource.setResponseCode(200);
                this.responseResource.setReasonMessage('OK');
                return this.responseResource;
              }
              return null;
            })
        }
      }
    }
    


    加速Web页面的访问

     

    当Web页面加载缓慢时,可以使用预连接、预加载和预获取post请求的能力加速Web页面的访问。

    预解析和预连接

    可以通过prepareForPageLoad()来预解析或者预连接将要加载的页面。

    在下面的示例中,在Web组件的onAppear中对要加载的页面进行预连接。

     

    // xxx.ets
    import { webview } from '@kit.ArkWeb';
    
    @Entry
    @Component
    struct WebComponent {
      webviewController: webview.WebviewController = new webview.WebviewController();
    
      build() {
        Column() {
          Button('loadData')
            .onClick(() => {
              if (this.webviewController.accessBackward()) {
                this.webviewController.backward();
              }
            })
          Web({ src: 'https://www.example.com/', controller: this.webviewController })
            .onAppear(() => {
              // 指定第二个参数为true,代表要进行预连接,如果为false该接口只会对网址进行dns预解析
              // 第三个参数为要预连接socket的个数。最多允许6个。
              webview.WebviewController.prepareForPageLoad('https://www.example.com/', true, 2);
            })
        }
      }
    }
    

    也可以通过initializeBrowserEngine()来提前初始化内核,然后在初始化内核后调用

    prepareForPageLoad()对即将要加载的页面进行预解析、预连接。这种方式适合提前对首页进行

    预解析、预连接。

    在下面的示例中,Ability的onCreate中提前初始化Web内核并对首页进行预连接。

    
    
    // xxx.ets
    import { webview } from '@kit.ArkWeb';
    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
    
    export default class EntryAbility extends UIAbility {
      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
        console.log("EntryAbility onCreate");
        webview.WebviewController.initializeWebEngine();
        // 预连接时,需要將'https://www.example.com'替换成真实要访问的网站地址。
        webview.WebviewController.prepareForPageLoad("https://www.example.com/", true, 2);
        AppStorage.setOrCreate("abilityWant", want);
        console.log("EntryAbility onCreate done");
      }
    }
    

    预加载

    如果能够预测到Web组件将要加载的页面或者即将要跳转的页面。可以通过prefetchPage()来预加载即将要加载页面。

    预加载会提前下载页面所需的资源,包括主资源子资源,但不会执行网页JavaScript代码。预加载是WebviewController的实例方法,需要一个已经关联好Web组件的WebviewController实例。

    在下面的示例中,在onPageEnd的时候触发下一个要访问的页面的预加载。

     

    // 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(() => {
              // 预加载https://www.iana.org/help/example-domains。
              this.webviewController.prefetchPage('https://www.iana.org/help/example-domains');
            })
        }
      }
    }
    

    预获取post请求

    可以通过prefetchResource()预获取将要加载页面中的post请求。在页面加载结束时,可以通过clearPrefetchedResource()清除后续不再使用的预获取资源缓存。

    以下示例,在Web组件onAppear中,对要加载页面中的post请求进行预获取。在onPageEnd中,可以清除预获取的post请求缓存。

     

    // 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})
            .onAppear(() => {
              // 预获取时,需要將"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);
            })
            .onPageEnd(() => {
              // 清除后续不再使用的预获取资源缓存。
              webview.WebviewController.clearPrefetchedResource(["KeyX",]);
            })
        }
      }
    }
    

    如果能够预测到Web组件将要加载页面或者即将要跳转页面中的post请求。可以通过prefetchResource()预获取即将要加载页面的post请求。

    以下示例,在onPageEnd中,触发预获取一个要访问页面的post请求。

    
    
    // 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(() => {
              // 预获取时,需要將"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);
            })
        }
      }
    }
    

    也可以通过initializeBrowserEngine()提前初始化内核,然后在初始化内核后调用prefetchResource()预获取将要加载页面中的post请求。这种方式适合提前预获取首页的post请求。

    以下示例,在Ability的onCreate中,提前初始化Web内核并预获取首页的post请求。

     

    // xxx.ets
    import { webview } from '@kit.ArkWeb';
    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
    
    export default class EntryAbility extends UIAbility {
      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
        console.log("EntryAbility onCreate");
        webview.WebviewController.initializeWebEngine();
        // 预获取时,需要將"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);
        AppStorage.setOrCreate("abilityWant", want);
        console.log("EntryAbility onCreate done");
      }
    }
    

    预编译生成编译缓存

    可以通过precompileJavaScript()在页面加载前提前生成脚本文件的编译缓存。

    推荐配合动态组件使用,使用离线的Web组件用于生成字节码缓存,并在适当的时机加载业务用Web组件使用这些字节码缓存。下方是代码示例:

  • 首先,在EntryAbility中将UIContext存到localStorage中。

     

    // EntryAbility.ets
    import { UIAbility } from '@kit.AbilityKit';
    import { window } from '@kit.ArkUI';
    
    const localStorage: LocalStorage = new LocalStorage('uiContext');
    
    export default class EntryAbility extends UIAbility {
      storage: LocalStorage = localStorage;
    
      onWindowStageCreate(windowStage: window.WindowStage) {
        windowStage.loadContent('pages/Index', this.storage, (err, data) => {
          if (err.code) {
            return;
          }
    
          this.storage.setOrCreate<UIContext>("uiContext", windowStage.getMainWindowSync().getUIContext());
        });
      }
    }
    

    编写动态组件所需基础代码。

     

    // DynamicComponent.ets
    import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
    
    export interface BuilderData {
      url: string;
      controller: WebviewController;
    }
    
    const storage = LocalStorage.getShared();
    
    export class NodeControllerImpl extends NodeController {
      private rootNode: BuilderNode<BuilderData[]> | null = null;
      private wrappedBuilder: WrappedBuilder<BuilderData[]> | null = null;
    
      constructor(wrappedBuilder: WrappedBuilder<BuilderData[]>) {
        super();
        this.wrappedBuilder = wrappedBuilder;
      }
    
      makeNode(): FrameNode | null {
        if (this.rootNode != null) {
          return this.rootNode.getFrameNode();
        }
        return null;
      }
    
      initWeb(url: string, controller: WebviewController) {
        if(this.rootNode != null) {
          return;
        }
    
        const uiContext: UIContext = storage.get<UIContext>("uiContext") as UIContext;
        if (!uiContext) {
          return;
        }
        this.rootNode = new BuilderNode(uiContext);
        this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller });
      }
    }
    
    export const createNode = (wrappedBuilder: WrappedBuilder<BuilderData[]>, data: BuilderData) => {
      const baseNode = new NodeControllerImpl(wrappedBuilder);
      baseNode.initWeb(data.url, data.controller);
      return baseNode;
    }
    

    编写用于生成字节码缓存的组件,本例中的本地Javascript资源内容通过文件读取接口读取rawfile目录下的本地文件。

     

    // PrecompileWebview.ets
    import { BuilderData } from "./DynamicComponent";
    import { Config, configs } from "./PrecompileConfig";
    
    @Builder
    function WebBuilder(data: BuilderData) {
      Web({ src: data.url, controller: data.controller })
        .onControllerAttached(() => {
          precompile(data.controller, configs);
        })
        .fileAccess(true)
    }
    
    export const precompileWebview = wrapBuilder<BuilderData[]>(WebBuilder);
    
    export const precompile = async (controller: WebviewController, configs: Array<Config>) => {
      for (const config of configs) {
        let content = await readRawFile(config.localPath);
    
        try {
          controller.precompileJavaScript(config.url, content, config.options)
            .then(errCode => {
              console.error("precompile successfully! " + errCode);
            }).catch((errCode: number) => {
              console.error("precompile failed. " + errCode);
          });
        } catch (err) {
          console.error("precompile failed. " + err.code + " " + err.message);
        }
      }
    }
    
    async function readRawFile(path: string) {
      try {
        return await getContext().resourceManager.getRawFileContent(path);;
      } catch (err) {
        return new Uint8Array(0);
      }
    }
    

    JavaScript资源的获取方式也可通过网络请求的方式获取,但此方法获取到的http响应头非标准HTTP响应头格式,需额外将响应头转换成标准HTTP响应头格式后使用。如通过网络请求获取到的响应头是e-tag,则需要将其转换成E-Tag后使用。

  • 编写业务用组件代码。

     

    // BusinessWebview.ets
    import { BuilderData } from "./DynamicComponent";
    
    @Builder
    function WebBuilder(data: BuilderData) {
      // 此处组件可根据业务需要自行扩展
      Web({ src: data.url, controller: data.controller })
        .cacheMode(CacheMode.Default)
    }
    
    export const businessWebview = wrapBuilder<BuilderData[]>(WebBuilder);
    

    编写资源配置信息。

     

    // PrecompileConfig.ets
    import { webview } from '@kit.ArkWeb'
    
    export interface Config {
      url:  string,
      localPath: string, // 本地资源路径
      options: webview.CacheOptions
    }
    
    export let configs: Array<Config> = [
      {
        url: "https://www.example.com/example.js",
        localPath: "example.js",
        options: {
          responseHeaders: [
            { headerKey: "E-Tag", headerValue: "aWO42N9P9dG/5xqYQCxsx+vDOoU="},
            { headerKey: "Last-Modified", headerValue: "Wed, 21 Mar 2024 10:38:41 GMT"}
          ]
        }
      }
    ]
    

    在页面中使用。

     

    // Index.ets
    import { webview } from '@kit.ArkWeb';
    import { NodeController } from '@kit.ArkUI';
    import { createNode } from "./DynamicComponent"
    import { precompileWebview } from "./PrecompileWebview"
    import { businessWebview } from "./BusinessWebview"
    
    @Entry
    @Component
    struct Index {
      @State precompileNode: NodeController | undefined = undefined;
      precompileController: webview.WebviewController = new webview.WebviewController();
    
      @State businessNode: NodeController | undefined = undefined;
      businessController: webview.WebviewController = new webview.WebviewController();
    
      aboutToAppear(): void {
        // 初始化用于注入本地资源的Web组件
        this.precompileNode = createNode(precompileWebview,
          { url: "https://www.example.com/empty.html", controller: this.precompileController});
      }
    
      build() {
        Column() {
          // 在适当的时机加载业务用Web组件,本例以Button点击触发为例
          Button("加载页面")
            .onClick(() => {
              this.businessNode = createNode(businessWebview, {
                url:  "https://www.example.com/business.html",
                controller: this.businessController
              });
            })
          // 用于业务的Web组件
          NodeContainer(this.businessNode);
        }
      }
    }
    

    当需要更新本地已经生成的编译字节码时,修改cacheOptions参数中responseHeaders中的E-Tag或Last-Modified响应头对应的值,再次调用接口即可。

    离线资源免拦截注入

    可以通过injectOfflineResources()在页面加载前提前将图片、样式表或脚本资源注入到应用的内存缓存中。

    推荐配合动态组件使用,使用离线的Web组件用于将资源注入到内核的内存缓存中,并在适当的时机加载业务用Web组件使用这些资源。下方是代码示例:

  • 首先,在EntryAbility中将UIContext存到localStorage中。

     

    // EntryAbility.ets
    import { UIAbility } from '@kit.AbilityKit';
    import { window } from '@kit.ArkUI';
    
    const localStorage: LocalStorage = new LocalStorage('uiContext');
    
    export default class EntryAbility extends UIAbility {
      storage: LocalStorage = localStorage;
    
      onWindowStageCreate(windowStage: window.WindowStage) {
        windowStage.loadContent('pages/Index', this.storage, (err, data) => {
          if (err.code) {
            return;
          }
    
          this.storage.setOrCreate<UIContext>("uiContext", windowStage.getMainWindowSync().getUIContext());
        });
      }
    }
    

    编写动态组件所需基础代码。

     

    // DynamicComponent.ets
    import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
    
    export interface BuilderData {
      url: string;
      controller: WebviewController;
    }
    
    const storage = LocalStorage.getShared();
    
    export class NodeControllerImpl extends NodeController {
      private rootNode: BuilderNode<BuilderData[]> | null = null;
      private wrappedBuilder: WrappedBuilder<BuilderData[]> | null = null;
    
      constructor(wrappedBuilder: WrappedBuilder<BuilderData[]>) {
        super();
        this.wrappedBuilder = wrappedBuilder;
      }
    
      makeNode(): FrameNode | null {
        if (this.rootNode != null) {
          return this.rootNode.getFrameNode();
        }
        return null;
      }
    
      initWeb(url: string, controller: WebviewController) {
        if(this.rootNode != null) {
          return;
        }
    
        const uiContext: UIContext = storage.get<UIContext>("uiContext") as UIContext;
        if (!uiContext) {
          return;
        }
        this.rootNode = new BuilderNode(uiContext);
        this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller });
      }
    }
    
    export const createNode = (wrappedBuilder: WrappedBuilder<BuilderData[]>, data: BuilderData) => {
      const baseNode = new NodeControllerImpl(wrappedBuilder);
      baseNode.initWeb(data.url, data.controller);
      return baseNode;
    }
    

    编写用于注入资源的组件代码,本例中的本地资源内容通过文件读取接口读取rawfile目录下的本地文件。

     

    // InjectWebview.ets
    import { webview } from '@kit.ArkWeb';
    import { resourceConfigs } from "./Resource";
    import { BuilderData } from "./DynamicComponent";
    
    @Builder
    function WebBuilder(data: BuilderData) {
      Web({ src: data.url, controller: data.controller })
        .onControllerAttached(async () => {
          try {
            data.controller.injectOfflineResources(await getData ());
          } catch (err) {
            console.error("error: " + err.code + " " + err.message);
          }
        })
        .fileAccess(true)
    }
    
    export const injectWebview = wrapBuilder<BuilderData[]>(WebBuilder);
    
    export async function getData() {
      const resourceMapArr: Array<webview.OfflineResourceMap> = [];
    
      // 读取配置,从rawfile目录中读取文件内容
      for (let config of resourceConfigs) {
        let buf: Uint8Array = new Uint8Array(0);
        if (config.localPath) {
          buf = await readRawFile(config.localPath);
        }
    
        resourceMapArr.push({
          urlList: config.urlList,
          resource: buf,
          responseHeaders: config.responseHeaders,
          type: config.type,
        })
      }
    
      return resourceMapArr;
    }
    
    export async function readRawFile(url: string) {
      try {
        return await getContext().resourceManager.getRawFileContent(url);
      } catch (err) {
        return new Uint8Array(0);
      }
    }
    

    编写业务用组件代码。

     

    // BusinessWebview.ets
    import { BuilderData } from "./DynamicComponent";
    
    @Builder
    function WebBuilder(data: BuilderData) {
      // 此处组件可根据业务需要自行扩展
      Web({ src: data.url, controller: data.controller })
        .cacheMode(CacheMode.Default)
    }
    
    export const businessWebview = wrapBuilder<BuilderData[]>(WebBuilder);
    

    编写资源配置信息。

     

    // Resource.ets
    import { webview } from '@kit.ArkWeb';
    
    export interface ResourceConfig {
      urlList: Array<string>,
      type: webview.OfflineResourceType,
      responseHeaders: Array<Header>,
      localPath: string, // 本地资源存放在rawfile目录下的路径
    }
    
    export const resourceConfigs: Array<ResourceConfig> = [
      {
        localPath: "example.png",
        urlList: [
          "https://www.example.com/",
          "https://www.example.com/path1/example.png",
          "https://www.example.com/path2/example.png",
        ],
        type: webview.OfflineResourceType.IMAGE,
        responseHeaders: [
          { headerKey: "Cache-Control", headerValue: "max-age=1000" },
          { headerKey: "Content-Type", headerValue: "image/png" },
        ]
      },
      {
        localPath: "example.js",
        urlList: [ // 仅提供一个url,这个url既作为资源的源,也作为资源的网络请求地址
          "https://www.example.com/example.js",
        ],
        type: webview.OfflineResourceType.CLASSIC_JS,
        responseHeaders: [
          // 以<script crossorigin="anoymous" />方式使用,提供额外的响应头
          { headerKey: "Cross-Origin", headerValue:"anonymous" }
        ]
      },
    ];
    

    在页面中使用。

     

    // Index.ets
    import { webview } from '@kit.ArkWeb';
    import { NodeController } from '@kit.ArkUI';
    import { createNode } from "./DynamicComponent"
    import { injectWebview } from "./InjectWebview"
    import { businessWebview } from "./BusinessWebview"
    
    @Entry
    @Component
    struct Index {
      @State injectNode: NodeController | undefined = undefined;
      injectController: webview.WebviewController = new webview.WebviewController();
    
      @State businessNode: NodeController | undefined = undefined;
      businessController: webview.WebviewController = new webview.WebviewController();
    
      aboutToAppear(): void {
        // 初始化用于注入本地资源的Web组件, 提供一个空的html页面作为url即可
        this.injectNode = createNode(injectWebview,
            { url: "https://www.example.com/empty.html", controller: this.injectController});
      }
    
      build() {
        Column() {
          // 在适当的时机加载业务用Web组件,本例以Button点击触发为例
          Button("加载页面")
            .onClick(() => {
              this.businessNode = createNode(businessWebview, {
                url: "https://www.example.com/business.html",
                controller: this.businessController
              });
            })
          // 用于业务的Web组件
          NodeContainer(this.businessNode);
        }
      }
    }
    

    加载的HTML网页示例。

     

    <!DOCTYPE html>
    <html lang="en">
    <head></head>
    <body>
      <img src="https://www.example.com/path1/request.png" />
      <img src="https://www.example.com/path2/request.png" />
      <script src="https://www.example.com/example.js" crossorigin="anonymous"></script>
    </body>
    </html>
    

    Web前进后退缓存

     

    开启Web组件前进后退缓存功能,在前进后退的场景达到秒开的效果。

    开启前进后退缓存

    可以通过enableBackForwardCache()来开启web组件使用前进后退缓存的功能。

    需要在initializeBrowserEngine()初始化内核之前调用。

     

    // xxx.ts
    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { window } from '@kit.ArkUI';
    import { webview } from '@kit.ArkWeb';
    
    export default class EntryAbility extends UIAbility {
        onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
            let features = new webview.BackForwardCacheSupportedFeatures();
            features.nativeEmbed = true;
            features.mediaTakeOver = true;
            webview.WebviewController.enableBackForwardCache(features);
            webview.WebviewController.initializeWebEngine();
            AppStorage.setOrCreate("abilityWant", want);
        }
    }
    

    设置缓存的页面数量和页面留存的时间

    可以通过setBackForwardCacheOptions()来设置每一个web示例前进后退缓存的策略。

    在下面的示例中,设置web组件可以缓存的最大数量为10,每个页面在缓存中停留300s。

     

    // EntryAbility.ts
    import { webview } from '@kit.ArkWeb';
    
    @Entry
    @Component
    struct Index {
      controller: webview.WebviewController = new webview.WebviewController();
    
      build() {
        Column() {
          Row() {
            Button("Add options").onClick((event: ClickEvent) => {
              let options = new webview.BackForwardCacheOptions();
              options.size = 10;
              options.timeToLive = 300;
              this.controller.setBackForwardCacheOptions(options);
            })
            Button("Backward").onClick((event: ClickEvent) => {
              this.controller.backward();
            })
            Button("Forward").onClick((event: ClickEvent) => {
              this.controller.forward();
            })
          }
          Web({ src: "https://www.example.com", controller: this.controller })
        }
        .height('100%')
        .width('100%')
      }
    }
    

  • 28
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值