鸿蒙:启动本地 http-server 加载 h5 游戏

之前也讲过

Android 端启动本地 http-server 加载 h5游戏:Android:启动本地 http-server 加载 h5 游戏

原理相似,都是使用 webview 加载本地资源游戏入口,实现通过本机 localhost 加载其他游戏资源

区别:

  • 鸿蒙侧有哪些 http-server 框架可用?
  • 鸿蒙侧资源拦截实现(实现读取资源的真实路径)

1、安装依赖

仓库找的 http-server 依赖,用于启动本地 http 服务,可以让我们访问 http://127.0.0.1

ohpm i @webabcd/harmony-httpserver

2、自定义 page 使用 Web 组件

主要介绍点:

【1】路由跳转到 har 包内页面:

  • 可使用 router.replaceNamedRoute
  • page 定义路由名称 @Entry({ routeName: GlobalUrl.ROUTER_NAME_H5_GAME_PAGE })
  • 注意导入页面,否则报错找不到路径 import('../../pages/H5GamePage');

【2】应用侧与 H5 侧的交互通道

  • 官方文档:前端页面调用应用侧函数
  • Android 端交互需要设置 webView.addJavascriptInterface(new InJavaScriptLocalObj(), "game_js_object");
  • 鸿蒙端交互在 Web 组件调用 javaScriptProxy

【3】找到游戏入口 index.html 文件

  • 游戏资源文件可以存放在该目录下 src/main/resources/resfile
  • 通过getContext(this).resourceDir 获得 resfile目录,使用 recursiveDirFindFileSync 递归查找到 index.html 文件链接
  • 获取 index.html 文件父目录路径,为后续资源请求路径重定向读取资源真实路径做准备

【4】Web 请求资源重定向

  • 重写 Web 组件 onInterceptRequest 请求拦截,重定向返回请求资源

【5】启动 http-server 服务

  • 使用第三方依赖 httpserver
ts 复制代码
import { webview } from '@kit.ArkWeb';
import fs from '@ohos.file.fs';
import { httpServer } from '@webabcd/harmony-httpserver';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON } from '@kit.ArkTS';


//【1】
@Entry({ routeName: GlobalUrl.ROUTER_NAME_H5_GAME_PAGE })
@Component
export struct H5GamePage {
  private controller: webview.WebviewController = new webview.WebviewController();
  /**
   * 游戏根目录:index.html 父目录路径
   */
  private readonly DEFAULT_PORT: number = 8082;
  /**
   * js 交互【2】
   */
  private readonly GAME_JS_OBJECT: string = "game_js_object";
  private h5JavaScriptObj: H5JavaScriptObj = new H5JavaScriptObj();
  /**
   * http 服务
   */
  private serverPort: number = this.DEFAULT_PORT;
  private host: string = "http://127.0.0.1:";
  /**
   * 本地地址 + index.html 路径
   */
  @State httpGameUrl: string = "";
  private gameRootPath: string = "";

  async aboutToAppear(): Promise<void> {
    this.initParams();
  }

  private async initParams() {
    await KVUtils.getNumber(Constant.KEY_HTTP_PORT).then((value) => {
      if (value !== undefined) {
        this.serverPort = value;
        this.startHttpServer();
      }
    });

	//【3】
    let path = getContext(this).resourceDir;
    const indexHtmlPath = FileUtil.recursiveDirFindFileSync(path, "index.html");
    if (!CommonUtil.isEmpty(indexHtmlPath)) {
      this.gameRootPath = fs.openSync(indexHtmlPath).getParent();
    }
    this.httpGameUrl = this.host + this.serverPort + "/index.html";

    Logger.warn("port=" + this.serverPort + "  indexHtmlPath=" + indexHtmlPath
      + "\ngameRootPath=" + this.gameRootPath + "  httpGameUrl=" + this.httpGameUrl)
  }

  startHttpServer() {
  	//【5】
    httpServer.enableLog(true);
    httpServer.start(this.serverPort, (err: BusinessError, port: number) => {
      this.serverPort = port;
      if (err) {
        Logger.error("http server error: " + JSON.stringify(err))
      } else {
        this.httpGameUrl = this.host + this.serverPort + "/index.html";
        Logger.info("http 服务启动并加载: " + this.serverPort + "  " + this.httpGameUrl)
        this.controller.loadUrl(this.httpGameUrl)
        KVUtils.putNumber(Constant.KEY_HTTP_PORT, this.serverPort);
      }
    })
  }

  aboutToDisappear(): void {
    httpServer.stop();
    this.controller.deleteJavaScriptRegister(this.GAME_JS_OBJECT);
  }

  build() {
    Column() {
      Web({ src: this.httpGameUrl, controller: this.controller })
        .onInterceptRequest((event) => {
          return this.processRequest(event)//【4】
        })
        .javaScriptProxy({//【2】
          object: this.h5JavaScriptObj,
          name: this.GAME_JS_OBJECT,
          methodList: this.h5JavaScriptObj.getMethodList(),
          controller: this.controller
        })
        .javaScriptAccess(true)
        .width('100%')
        .height('100%')
        .backgroundColor(Color.Black)
    }
    .width('100%')
    .height('100%')
  }

  onBackPress(): boolean | void {
    return DNSDK.onBackPress();
  }

  private processRequest(event: OnInterceptRequestEvent): WebResourceResponse | null {
    const requestUrl = event.request.getRequestUrl();
    const localUrl = this.host + this.serverPort;
    const path = requestUrl.substring(requestUrl.indexOf(localUrl) + localUrl.length)
    const reallyPath = this.gameRootPath + path;

    const url = event.request.getRequestUrl()
    if (url == undefined) {
      return null;
    }

    if (url.startsWith("http://127.0.0.1") || url.startsWith("http://localhost")) {
      if (fs.accessSync(reallyPath)) {
        const st = fs.statSync(reallyPath);
        const buffer = new ArrayBuffer(st.size);
        fs.readSync(fs.openSync(reallyPath).fd, buffer);

        const response = new WebResourceResponse();
        response.setResponseData(buffer);
        response.setResponseEncoding('utf-8');
        response.setResponseMimeType(CommonUtil.getMimeType(reallyPath));
        response.setResponseCode(200);
        return response;
      }
    }

    Logger.error('\t文件不存在 =' + reallyPath);
    return null;
  }
}

跳转到 h5 页面

ts 复制代码
 router.replaceNamedRoute({
          name: GlobalUrl.ROUTER_NAME_H5_GAME_PAGE,
        }).catch((error: Error) => {
          Logger.error('CustomDialog  pushUrl error =' + JSON.stringify(error));
        });

资源类型匹配

ts 复制代码
 public getMimeType(path: string): string {
    if (path.endsWith('.png')) {
      return 'image/png';
    }
    if (path.endsWith('.css')) {
      return 'text/css';
    }
    if (path.endsWith('.js')) {
      return 'application/javascript';
    }
    if (path.endsWith('.html')) {
      return 'text/html';
    }
    if (path.endsWith('.xml')) {
      return 'text/xml';
    }
    if (path.endsWith('.java')) {
      return 'text/x-java-source, text/java';
    }
    if (path.endsWith('.gif')) {
      return 'image/gif';
    }
    if (path.endsWith('.jpg') || path.endsWith('.jpeg')) {
      return 'image/jpeg';
    }
    if (path.endsWith('.png')) {
      return 'image/png';
    }
    if (path.endsWith('.svg')) {
      return 'image/svg+xml';
    }
    if (path.endsWith('.mp3')) {
      return 'audio/mpeg';
    }
    if (path.endsWith('.mp4')) {
      return 'video/mp4';
    }
    if (path.endsWith('.zip')) {
      return 'application/octet-stream';
    }
    return 'text/plain';
  }
相关推荐
lqj_本人8 小时前
鸿蒙electron跨端框架PC今日打卡实战:频率、连续天数和今日进度怎么放进桌面工具
华为·harmonyos
wanhengidc8 小时前
私有云的作用都有哪些?
运维·服务器·网络·游戏·智能手机
魔法阵维护师10 小时前
从零开发游戏需要学习的c#模块,第十六章(安装 MonoGame 并创建第一个窗口)
学习·游戏·c#·monogame
qq_2651533711 小时前
Redis在游戏服务器中怎么实现开合服数据同步?
服务器·redis·游戏·游戏服务器
qq_3692243312 小时前
Windows系统缺失ddraw.dll文件?游戏闪退、图形报错原因详解及处理办法
windows·游戏·dll·dll修复·dll丢失·dll错误
魔法阵维护师13 小时前
从零开发游戏需要学习的c#模块,第十章(设计模式入门)
学习·游戏·设计模式·c#
GitCode官方13 小时前
直播预约|开源鸿蒙PC命令行工具迁移实战:从环境搭建到真机验证全流程拆解
人工智能·华为·开源·harmonyos·atomgit
lqj_本人13 小时前
鸿蒙electron跨端框架PC工志簿实战:项目、工时、阻塞和下一步都要有位置
数据库·华为·harmonyos