鸿蒙ArkTs 优化webview的加载速度

81 阅读1分钟

1、思路 把h5页面的静态资源缓存到沙箱、再请求时根据原资源如果命中沙箱保存的资源,则返回

import fs, { ReadOptions } from '@ohos.file.fs';
import { common } from '@kit.AbilityKit';
import { XLogUtil } from "../Utils/XLogUtil";
import { http } from '@kit.NetworkKit';
import { CommonFunctions } from "../CommonFunctions/CommonFunctions"
import fileIo from '@ohos.file.fs';


// 需要缓存的文件格式
const CACHEABLE_EXTENSIONS = [
// 图片
  "png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "ico",
  // 样式
  "css",
  // 脚本
  "js", "xml", "json",
  // 字体
  "woff", "woff2", "ttf"
];

export class H5CacheManager {
  /**
   * 下载资源并保存到内存和磁盘缓存
   * @param url 资源URL
   */

  static isNeedResource(url: string, webResourceResponse: WebResourceResponse): WebResourceResponse | null {
    //检查磁盘缓存是不是有了,有就不要下载了
    let localResource = CommonFunctions.getH5Resource(url)

    if (localResource.length > 0) {
      XLogUtil.debug("返回的路径是" + localResource)

      let data: string | number | Resource | ArrayBuffer

      // 判断是否为图片资源(根据URL后缀)
      const isImage = /\.(jpg|jpeg|png|gif|svg|webp)$/i.test(url);

      if (isImage) {
        let file = fs.openSync(localResource)
        let stat = fs.statSync(file.fd)
        let buf = new ArrayBuffer(stat.size)
        fs.readSync(file.fd, buf)
        data = buf;
        fs.closeSync(file)
      } else {
        //否则
        data = fileIo.readTextSync(localResource);
      }
      XLogUtil.debug("返回的路径的数据:" + JSON.stringify(data))
      const mimeType = H5CacheManager.getMimeTypeFromUrl(url);
      // 构造拦截响应
      webResourceResponse.setResponseData(data); // 设置缓存数据
      webResourceResponse.setResponseMimeType(mimeType); // MIME类型
      webResourceResponse.setResponseEncoding('utf-8');
      webResourceResponse.setResponseCode(200);
      webResourceResponse.setReasonMessage('OK');
      webResourceResponse.setResponseHeader([{ headerKey: 'from-cache', headerValue: 'true' }]); // 标记来自缓存
      webResourceResponse.setResponseIsReady(true);
      XLogUtil.debug(`有缓存,拦截并使用缓存 webResourceResponse:${JSON.stringify(webResourceResponse.getResponseData())}`);
      return webResourceResponse;
    }
    XLogUtil.debug("没有缓存,去保存" + url)
    H5CacheManager.prefetchResource(url)
    return null
  }

  static async prefetchResource(url: string): Promise<void> {
    try {

      // 检查是否为需要缓存的格式,不符合则直接返回
      if (!H5CacheManager.isCacheableResource(url)) {
        XLogUtil.debug(`资源格式不支持缓存,跳过下载:${url}`);
        return;
      }

      // 3. 从网络下载(遵循http模块规范)
      const request = http.createHttp();
      const response = await request.request(url, { method: http.RequestMethod.GET, expectDataType: 2 });

      if (response.result && response.responseCode === 200) {
        const arrayBuffer = response.result as ArrayBuffer;
        XLogUtil.debug("资源数据" + JSON.stringify(arrayBuffer))

        CommonFunctions.saveToFiles(arrayBuffer, url);
      } else {
        XLogUtil.warn(`资源下载失败,状态码:${response.responseCode},URL:${url}`);
      }
    } catch (error) {
      XLogUtil.error(`资源下载失败:${url},错误:${error.message}`);
    }
  }

  static isCacheableResource(url: string): boolean {
    const ext = H5CacheManager.getFileExtension(url);
    return CACHEABLE_EXTENSIONS.includes(ext);
  }

  /**
   * 从URL中提取文件扩展名(不含`.`)
   * @param url 资源URL
   * @returns 扩展名(小写),如"png";无扩展名则返回空字符串
   */
  static getFileExtension(url: string): string {
    // 去除URL中的查询参数(?后面)和哈希(#后面)
    const cleanUrl = url.split(/[?#]/)[0];
    // 分割文件名与扩展名
    const parts = cleanUrl.split('.');
    // 无扩展名或扩展名长度为0的情况(如".gitignore"视为无有效扩展名)
    if (parts.length < 2 || parts[parts.length - 1].length === 0) {
      return '';
    }
    // 返回小写扩展名
    return parts[parts.length - 1].toLowerCase();
  }

  /**
   * 获取应用专属缓存目录路径(基于官方沙箱路径)
   * @param context 应用上下文
   * @returns 缓存目录完整路径
   */
  getCacheDir(context: common.Context): string {
    // 文档说明:cacheDir为应用专属缓存目录,无需手动创建
    return context.cacheDir + '/h5_resources/';
  }
  
  /**
   * 根据URL获取MIME类型(辅助方法)
   * @param url 资源URL
   * @returns MIME类型字符串
   */
  static getMimeTypeFromUrl(url: string): string {
    if (url.endsWith('.css')) {
      return 'text/css';
    }
    if (url.endsWith('.js')) {
      return 'application/javascript';
    }
    if (url.endsWith('.png')) {
      return 'image/png';
    }
    if (url.endsWith('.jpg') || url.endsWith('.jpeg')) {
      return 'image/jpeg';
    }
    if (url.endsWith('.svg')) {
      return 'image/svg+xml';
    }
    if (url.endsWith('.json')) {
      return 'application/json';
    }
    return 'text/plain';
  }

}

2、保存方法

  // 保存文件
  static async saveToFiles( buffer: ArrayBuffer, url: string, callback?: (sanPath:string) => void): Promise<void> {
    let fd : number | undefined = undefined;0
    try {
      let dirPath = getContext().cacheDir + `/h5_cache`;

      // 如果目录不存在
      if (!fs.accessSync(dirPath)) {
        // 则创建
        fs.mkdirSync(dirPath, true);
      }

      const fileName = CommonFunctions.getFileNameFromUrl(url);
      const getDirPath = dirPath + "/"+ fileName

      XLogUtil.debug(TAG,"存储路径:" + getDirPath)
      const mode = fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE;
      fd = (await fs.open(getDirPath, mode)).fd;
      await fs.truncate(fd);
      await fs.write(fd, buffer);
      callback?.(getDirPath)
      XLogUtil.debug(TAG,"创建目录并写入成功" + getDirPath)
    } catch (err) {
      XLogUtil.error(TAG,`保存文件失败${JSON.stringify(err)}`)
    } finally {
      if (fd) {
        fs.close(fd);
      }
    }
  }

3、使用

            .onInterceptRequest((event) => {
              XLogUtil.info('资源请求数量:'+ this.onInterceptRequestCount++)
              const url = event.request.getRequestUrl();

             let getData = H5CacheManager.isNeedResource(url,this.webResourceResponse)
              if (getData){
                return getData
              }
            })

4、欢迎留意讨论哦