之前也讲过
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
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 页面
router.replaceNamedRoute({
name: GlobalUrl.ROUTER_NAME_H5_GAME_PAGE,
}).catch((error: Error) => {
Logger.error('CustomDialog pushUrl error =' + JSON.stringify(error));
});
资源类型匹配
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';
}