前端 Web 与原生应用端 WebView 通信交互 - Har
2024-06-04 本文已影响0人
survivorsfyh
基于鸿蒙 HarmonyOS Next 与前端 Vue 通信交互相关小结;
DevEco Studio NEXT Developer Preview2
Vue js
两端相互拟定好协议后,通过前端页面的点击事件,将所需的数据传输给原生移动端组件方法中,处理后将消息回传至前端.
根据官方文档的案例尝试,但没成功 ... 后经过几经尝试后两端握手成功 ... (官方文档略显粗糙,好一番折腾)
一.应用端
基于 import web_webview from '@ohos.web.webview' 并初始化 Web 组件;
调用 javaScriptProxy 方法,定义配置协议、参数和方法相关,具体可参考如下 code 片段;
name: 交互协议
object: 定义交互对象
methodList: 交互对象中所涵盖的方法,支持多个,也可以通过一个对象方法再细化多个子方法
controller: 组件
import web_webview from '@ohos.web.webview'
@Entry
@Component
export struct HomePage {
controller: web_webview.WebviewController = new web_webview.WebviewController()
ports: web_webview.WebMessagePort[] = [];
nativePort: web_webview.WebMessagePort | null = null;
message: web_webview.WebMessageExt = new web_webview.WebMessageExt();
// 声明注册对象
@State WebCallAppMethod: WebCallAppClass = new WebCallAppClass()
aboutToAppear(): void {
try {
// 启用网页调试功能
web_webview.WebviewController.setWebDebuggingAccess(true);
} catch (error) {
let e: business_error.BusinessError = error as business_error.BusinessError;
console.log(`Error Code: ${e.code}, Message: ${e.message}`);
this.controller.refresh(); // 页面异常,刷新
}
}
build() {
Row() {
Column({ space: 20 }) {
Web({ src: 'http://192.168.12.108:8080', controller: this.controller })
.width('100%')
.height('100%')
.backgroundColor(Color.White)
.multiWindowAccess(true)
.javaScriptAccess(true)
.geolocationAccess(true)
.imageAccess(true)
.onlineImageAccess(true)
.domStorageAccess(true)
.fileAccess(true)
.mediaPlayGestureAccess(true)
.mixedMode(MixedMode.All)
.layoutMode(WebLayoutMode.FIT_CONTENT) // 自适应布局
.verticalScrollBarAccess(true)
.horizontalScrollBarAccess(false)
.cacheMode(CacheMode.Default)
.zoomAccess(false)// 禁止手势缩放
.onConsole((event) => {
console.log('[交互] - onConsole')
LogUtils.info(event?.message.getMessage())
return false
})
.onPageBegin(() => { // 页面加载中
// this.registerWebJavaScript()
})
.onPageEnd(() => { // 页面加载完成
console.log('[Web] - 页面加载完成')
// this.registerWebJavaScript()
})
.onErrorReceive((event) => { // 异常: 无网络,页面加载错误时
if (event) {
console.info('getErrorInfo:' + event.error.getErrorInfo());
console.info('getErrorCode:' + event.error.getErrorCode());
console.info('url:' + event.request.getRequestUrl());
console.info('isMainFrame:' + event.request.isMainFrame());
console.info('isRedirect:' + event.request.isRedirect());
console.info('isRequestGesture:' + event.request.isRequestGesture());
console.info('getRequestHeader_headerKey:' + event.request.getRequestHeader().toString());
let result = event.request.getRequestHeader();
console.info('The request header result size is ' + result.length);
for (let i of result) {
console.info('The request header key is : ' + i.headerKey + ', value is : ' + i.headerValue);
}
}
})
.onHttpErrorReceive((event) => { // 异常: 网页加载资源 Http code >= 400 时
if (event) {
console.info('url:' + event.request.getRequestUrl());
console.info('isMainFrame:' + event.request.isMainFrame());
console.info('isRedirect:' + event.request.isRedirect());
console.info('isRequestGesture:' + event.request.isRequestGesture());
console.info('getResponseData:' + event.response.getResponseData());
console.info('getResponseEncoding:' + event.response.getResponseEncoding());
console.info('getResponseMimeType:' + event.response.getResponseMimeType());
console.info('getResponseCode:' + event.response.getResponseCode());
console.info('getReasonMessage:' + event.response.getReasonMessage());
let result = event.request.getRequestHeader();
console.info('The request header result size is ' + result.length);
for (let i of result) {
console.info('The request header key is : ' + i.headerKey + ' , value is : ' + i.headerValue);
}
let resph = event.response.getResponseHeader();
console.info('The response header result size is ' + resph.length);
for (let i of resph) {
console.info('The response header key is : ' + i.headerKey + ' , value is : ' + i.headerValue);
}
}
})
.onConfirm((event) => { // 提示框处理相关
AlertDialog.show({
title: '温馨提示',
message: event?.message,
confirm: {
value: 'onAlert',
action: () => {
event?.result.handleConfirm()
}
},
cancel: () => {
event?.result.handleCancel()
}
})
return true;
})
.onShowFileSelector((event) => { // 文件上传处理相关
console.log('MyFileUploader onShowFileSelector invoked');
const documentSelectOptions = new picker.PhotoSelectOptions();
let uri: string | null = null;
const documentViewPicker = new picker.PhotoViewPicker();
documentViewPicker.select(documentSelectOptions).then((documentSelectResult) => {
uri = documentSelectResult[0];
console.info('documentViewPicker.select to file succeed and uri is:' + uri);
if (event) {
event.result.handleFileList([uri]);
}
}).catch((err: BusinessError) => {
console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
return true;
})
.javaScriptProxy({ // web call app
// 对象注入 web
object: this.WebCallAppMethod,
name: 'WebCallApp', // AppCallWeb WebCallAppSsss WebCallApp
methodList: ['WebCallApp', 'CmdTest', 'CmdOpenUrl'],
controller: this.controller
})
}.width('100%').height('100%')
}
}
}
interface CommandModel { // 泛型: 交互
sn: string,
args: Object,
command: string,
}
class WebCallAppClass {
constructor() {
}
WebCallApp(value: string): string { // 采用该 Json 对象模式,通过解析对象中的 type 后细化对应不同的子方法
console.log('[交互] --- WebCallApp - 测试')
console.log(value)
let params:CommandModel = JSON.parse(value)
console.info(params.sn)
console.info(params.command)
console.info(JSON.stringify(params.args))
return '[交互] --- WebCallApp - 测试 - 回调123123123'
}
CmdOpenUrl(): Object {
console.log('[交互] --- WebCallApp - CmdOpenUrl');
new Object({
'command': '111',
})
return Object;
}
CmdTest(value: string): string {
console.log('[交互] --- WebCallApp - test');
console.log(value);
return '[交互] --- WebCallApp - test';
}
}
二.前端
前端配置有两种方式,可以通过 index.html 配置 js 后调用,也可以单独另起一个 js 类方法去适配,具体可参考如下 code 按需尝试;
方式一.通过 index.html
在项目根目录的 index.html 文件中添加如下 script 段落后,在业务所需的地方调用即可
// index.html
<script>
// eruda.init()
(function () {
if (!window.applicationCache && typeof(Worker)=='undefined') {
alert("E001-检测到您的环境不支持HTML5,程序终止运行!");//不支持HTML5
return;
}
var global = window; // create a pointer to the window root
if (typeof WebCallAppSsss === 'undefined') {
global.WebCallApp = { // 此处的 WebCallApp 与原生端 javaScriptProxy 中的 name 相互匹配
// 如下方法对应的是 javaScriptProxy 中 object 对象中的方法
WebCallApp: (arg) => {},
CmdOpenUrl: (arg) => {},
CmdTest: (arg) => {},
}; // create elf root if not existed
}
window.WebCallAppSsss.global = global; // add a pointer to window
})();
</script>
// 业务所需的点击事件方法中调用
methods : {
onClickGoBack() {
let str = WebCallAppSsss.CmdTest('[交互] - 测试'); // 方式一
// this.webApp.WebCallApp('CmdTest', '111111'); // 方式二
Toast.success('abc');
},
}
方式二.通过自定义 js
自定义 webCallApp 类,通过引入类方法调用
// WebCallApp.js
import {
AppCallBacks,
AppCommendBackHandlers,
} from './AppMsgHandlers'
export default {
WebCallApp(command,args,callback,context) {
/**
* 交互
*
* 协议:command
* 入参:args
* 回调:callback
* 参数:context
* */
if (typeof Elf.AppCallWeb != "undefined") {
context = context || window;//默认为window对象
args = args || {};
let sn = null;
//IOS调用相机--sn特殊处理
if (command == "callCamera") {
sn = "examCamera";
} else {
sn = this.getSerialNumber();//请求App统一加水单号
}
let params = {
args : args,
command : command,
sn : sn,
};
//绑定回调函数
if (callback) {
AppCallBacks[ sn ] = {
callback : callback,
context : context
};
}
if (window.webkit && window.webkit.messageHandlers) { // iOS
params.sn = sn;
window.webkit.messageHandlers[ "WebCallApp" ].postMessage(JSON.stringify(params));
} else if (Elf.WebCallApp) { // Android
params.sn = sn;
Elf.WebCallApp(JSON.stringify(params));
} else if (this.isInCef()) { // PC
params.sn = sn;
Elf.WebCallCef(JSON.stringify(params));
} else if (this.isInHarmonyOS()) { // harmonyOS Next
console.log('[鸿蒙] - 入参');
console.log(params);
let result = WebCallApp[ 'WebCallApp' ](JSON.stringify(params));
console.log('[鸿蒙] - 回调');
console.log(result);
// callback(注:具体回调格式可自行定义,此处是对数据进行转JSon并Encode处理)
if (result && typeof result == "string") {
result = decodeURIComponent(result.replace(/\+/g, '%20'));
try {
result = JSON.parse(result);//解决空格变成+的问题
} catch (error) {
AppCallBacks[ sn ].callback.call(AppCallBacks[ sn ].context, result);
return;
}
if (result.sn) {
AppCallBacks[ sn ].callback.call(AppCallBacks[ sn ].context, result);
return;
}
}
} else {
// ...
}
}
},
isInApp() {
if (typeof Elf.AppCallWeb != "undefined") {
return !!((window.webkit && window.webkit.messageHandlers) || typeof Elf.WebCallApp == "function" || typeof Elf.WebCallCef == "function");
}
},
isInIOS() {
return window.webkit && window.webkit.messageHandlers;
},
isInAndroid() {
if (typeof Elf.AppCallWeb != "undefined") {
return typeof Elf.WebCallApp == "function";
}
},
isInHarmonyOS() {
if (navigator.userAgent.toLowerCase().indexOf('openharmony') !== -1) {
return true;
} else {
return false;
}
},
getSerialNumber() {
var uuid = this.UUID(3,8);
return new Date().format("yyyyMMddhhmmssS") + uuid;
},
UUID(len,radix) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid = [],
i;
radix = radix || chars.length;
if (len) {
for (i = 0; i < len; i++) {
uuid[i] = chars[0 | Math.random() * radix];
}
} else {
var r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random() * 16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
},
}
/* eslint-disable */
// AppMsgHandlers
import webApp from './index';
/*
*
* app对接
* 移动端种植Elf对象
* window => Elf
*
*/
(function () {
if (!window.applicationCache&&typeof(Worker)=='undefined') {
alert("E001-检测到您的环境不支持HTML5,程序终止运行!");//不支持HTML5
return;
}
// iOS & Android
var global = window;//create a pointer to the window root
if (typeof Elf === 'undefined') {
global.Elf = {};//create elf root if not existed
}
Elf.global = global;//add a pointer to window
// Harmony
if (typeof WebCallApp === 'undefined') { // var global = window;
global.WebCallApp = {
WebCallApp: (args) => {}
};
}
WebCallApp.global = global;
})();
var AppCallBacks = {},//动态数据流水列表
AppCommendBackHandlers = [],//APP后退监听事件列表
APPCommendBookStateHandlers = [],//下载状态监听事件列表
AppCommendRefreshHandlers = [],//刷新监听事件列表
APPCommendAddToBookShelfHandlers = [],//添加到书架监听事件列表
APPCommendAddToObtainedBookHandlers = [],//添加到已获得图书列表监听
APPCommendReBackHandlers = [],//监听重新回到页面通知
AppCommendNetworkHandlers = [],//监听网络链接状态
AppCommendAppStartingHandlers = [],//监听APP进入后台运行
AppCommendAppReactivateHandlers = [],//监听APP重新进入前台运行
AppCommendScreenShotss = [],//监听手机截屏
AppCommendKeyboardBounceUp = [];//监听移动端软键盘事件
if (typeof Elf != "undefined") {
Elf.AppCallWeb = (sn,result) => {
if (result && typeof result == "string") {
result = decodeURIComponent(result.replace(/\+/g,'%20'));
try {
result = JSON.parse(result);//解决空格变成+的问题
} catch (error) {
AppCallBacks[sn].callback.call(AppCallBacks[sn].context,result);
return;
}
if (result.sn) {
AppCallBacks[sn].callback.call(AppCallBacks[sn].context,result.QrCode);
return;
}
}
if (AppCallBacks[sn]) {
if (JSON.parse(result.opFlag)) {
//执行对应回调
AppCallBacks[sn].callback.call(AppCallBacks[sn].context,(typeof result.serviceResult == "string") ? JSON.parse(result.serviceResult) : result.serviceResult);
} else {
//接口调用返回失败信息,统一处理错误消息
Toast(result.errorMessage ? result.errorMessage : "服务器异常!");
}
//调用完成删除对象
delete AppCallBacks[sn];
} else if (AppMsgHandlers[sn] && typeof AppMsgHandlers[sn] == "function") {
//处理消息通知
AppMsgHandlers[sn].call(window,result);
}
};
}
// if (typeof WebCallApp === 'undefined') { // var global = window;
// Elf.WebCallApp = {
// WebCallApp: (args) => {}
// };
// }
// window.WebCallApp.Elf = Elf;
export {
AppCallBacks,
AppCommendBackHandlers
}
业务方法调用
前端 Vue 部分
// 业务所需的点击事件方法中调用
methods : {
onClickGoBack() {
// let str = WebCallAppSsss.CmdTest('[交互] - 测试'); // 方式一
// 方式二
this.webApp.WebCallApp('CmdTest', {
'JSBridge' : '交互测试'
}, function (callback) {
console.log('[AppCallWeb] - 回调');
console.log(callback);
});
Toast.success('abc');
},
}
鸿蒙端部分
WebCallApp(value: string): string {
console.log('[交互] --- WebCallApp - 测试')
console.log(value)
let params:CommandModel = JSON.parse(value)
let sn = params.sn
let command = params.command
let args: Object = params.args
console.info(sn)
console.info(command)
console.info(JSON.stringify(args))
//let appCallWeb = AppCallWeb.shareInstance()
//let callback = appCallWeb.callbackBasic(sn, args, null)
return encodeURI(JSON.stringify(params))
// return '[交互] --- WebCallApp - 测试 - 回调123123123'
}
以上便是此次分享的全部内容,希望能对大家有所帮助!