1. ArkWeb
相当于iOS/Android中的WebView控件,给个url就可以展示H5页面。
但是在项目中,我们需要考虑以下需求:
- H5页面加载(处理页面加载的状态:成功、失败、加载中)
- H5页面栈(window.history)管理
- UserAgent管理
- Cookie管理
- Native和H5交互
- 页面加载优化
- Web页面调试
我也是带着这些问题去学习使用ArkWeb的。
2. 使用ArkWeb加载url
页面加载是Web组件最基本的功能了,支持三种场景:
- 加载网络页面(需要配置
ohos.permission.INTERNET
网络访问权限); - 加载本地页面;
- 加载HTML格式的富文本数据;
我们以加载网络页面为例:
// 导入webview
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct Index {
private webviewController = new webview.WebviewController()
build() {
Stack() {
Web({ src: "https://www.baidu.com", controller: this.webviewController })
.onPageBegin((event) => {
// 网页开始加载时触发该回调
})
.onPageEnd((event) => {
// 网页加载完成时触发该回调
})
.onErrorReceive((event) => {
// 网页加载遇到错误时触发该回调. 无网下会触发该回调
})
.onHttpErrorReceive((event) => {
// 网页加载资源遇到的HTTP错误(响应码>=400)时触发该回调
})
.onProgressChange((event) => {
// 网页加载进度变化时触发该回调
})
}
}
}
Web组件暴露了很多回调方法,我们可以在跟加载进度和状态的回调方法中设置webview在加载中、加载失败、加载成功的样式。
3. H5页面栈(window.history)管理
在上面的示例中,可以看到在创建Web组件的时候,传入了一个controller。我们没办法直接操作Web组件,只能通过controller去操作Web组件的行为,包括页面栈管理、页面刷新、执行JS等。
我们先来看下controller提供的管理页面栈的方法,如下所示:
// 判断当前页面是否可前进,是否有前进历史记录
accessForward(): boolean
// 判断页面是否可后退,即当前页面是否有返回历史记录
accessBackward(): boolean
// 按照历史栈,前进一个页面
forward(): void
// 按照历史栈,后退一个页面
backward(): void
// 删除所有前进后退记录
clearHistory(): void
4. UserAgent管理
默认的UserAgent定义如下: Mozilla/5.0 ({deviceType}; {OSName} {OSVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/{ArkWeb VersionCode} {Mobile}
举例:
Mozilla/5.0 (Phone; OpenHarmony 4.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/4.1.6.1 Mobile
4.1 在webInited回调设置UA
目前在Preview1版本测试webInited对应的回调方法不会执行,等升级后再进行验证。🙋🙋🙋
如果我们想基于默认的UserAgent去定制UserAgent:
// xxx.ets
import web_webview from '@ohos.web.webview';
import business_error from '@ohos.base';
@Entry
@Component
struct WebComponent {
controller: web_webview.WebviewController = new web_webview.WebviewController();
@State ua: string = ""
aboutToAppear():void {
web_webview.once('webInited', () => {
try {
// 应用侧用法示例,定制UserAgent。
this.ua = this.controller.getUserAgent() + 'xxx';
} catch(error) {
let e:business_error.BusinessError = error as business_error.BusinessError;
console.error(`ErrorCode: ${e.code}, Message: ${e.message}`);
}
})
}
build() {
Column() {
Web({ src: 'www.example.com', controller: this.controller })
.userAgent(this.ua)
}
}
}
上面的代码是在webInited回调中,通过this.controller.getUserAgent()获取默认的UA。once是webview提供的方法,用于订阅一次指定类型Web事件的回调,目前也仅支持webInited(Web初始化完成)事件。
4.2 在onControllerAttached回调中设置UA
controller中可以通过setCustomUserAgent方法来设置自定义用户代理,会覆盖系统的用户代理,推荐放在onControllerAttached回调中。
若需要setCustomUserAgent,在setCustomUserAgent方法后执行this.controller.loadUrl(this.webUrl),webUrl为要加载的web页面,在原始的web组件的src可以设置一个空字符串。
import web_webview from '@ohos.web.webview'
@Entry
@Component
struct WebComponent {
controller: web_webview.WebviewController = new web_webview.WebviewController();
build() {
Column() {
// 这个src可以先设置为空的字符串
Web({ src: '', controller: this.controller })
.onControllerAttached(()=>{
let userAgent = this.controller.getUserAgent() + "xxx";
this.controller.setCustomUserAgent(userAgent);
// 设置完UA之后,再次加载url
this.controller.loadUrl("www.baidu.com")
})
}
}
}
5. Cookie管理
Web组件提供了WebCookieManager类,用来管理Web组件的Cookie信息,Cookie信息保存在应用沙箱路径下/proc/{pid}/root/data/storage/el2/base/cache/web/Cookiesd的文件中。
下面以configCookieSync接口举例,为 www.example.com设置单个Cookie的值:
// xxx.ets
import web_webview from '@ohos.web.webview';
import business_error from '@ohos.base';
@Entry
@Component
struct WebComponent {
controller: web_webview.WebviewController = new web_webview.WebviewController();
build() {
Column() {
Button('configCookieSync')
.onClick(() => {
try {
web_webview.WebCookieManager.configCookieSync('https://www.example.com', 'value=test');
} catch (error) {
let e: business_error.BusinessError = error as business_error.BusinessError;
console.error(`ErrorCode: ${e.code}, Message: ${e.message}`);
}
})
Web({ src: 'www.example.com', controller: this.controller })
}
}
}
WebCookieManager还提供了其他操作Cookie的方法,截图如下:
6. Native和H5交互
6.1 Native调用H5页面中的函数
Native可以通过runJavaScript()方法执行JavaScript,调用前端页面的JavaScript相关函数。
H5页面:
<!-- index.html -->
<!DOCTYPE html>
<html>
<body>
<script>
var param = "JavaScript Hello World!";
function htmlTest(param) {
console.log(param);
}
</script>
</body>
</html>
Native页面:
// xxx.ets
import web_webview from '@ohos.web.webview';
@Entry
@Component
struct WebComponent {
webviewController: web_webview.WebviewController = new web_webview.WebviewController();
aboutToAppear() {
// 配置Web开启调试模式
web_webview.WebviewController.setWebDebuggingAccess(true);
}
build() {
Column() {
Button('runJavaScript')
.onClick(() => {
// 通过调用webviewController的runJavaScript方法执行js函数
this.webviewController.runJavaScript('htmlTest(param)');
})
Web({ src: $rawfile('index.html'), controller: this.webviewController})
}
}
}
6.2 H5页面调用Native函数
Native需要先将应用侧代码注册到H5页面中,之后H5页面就可以使用注册的对象名称调用应用侧的函数,实现在H5页面中调用Native的方法。
有两种注册方式:
- Web组件初始化调用,使用javaScriptProxy接口;
- Web组件初始化完成后调用,使用registerJavaScriptProxy接口;
只是注册时机不同,其它都一样。这里以Web组件初始化调用为例,在Web组件初始化时进行注册:
// xxx.ets
import web_webview from '@ohos.web.webview';
// 用于注入的对象
class testClass {
constructor() {
}
// H5页面要调用的方法
test(): string {
return 'ArkTS Hello World!';
}
}
@Entry
@Component
struct WebComponent {
webviewController: web_webview.WebviewController = new web_webview.WebviewController();
// 声明需要注册的对象
@State testObj: testClass = new testClass();
build() {
Column() {
// web组件加载本地index.html页面
Web({ src: $rawfile('index.html'), controller: this.webviewController})
// 将对象注入到web端
.javaScriptProxy({
object: this.testObj,
name: "testObjName", // H5会通过这个name调用方法
methodList: ["test"], // 暴露有H5的方法
controller: this.webviewController
})
}
}
}
H5页面触发应用侧代码:
<!-- index.html -->
<!DOCTYPE html>
<html>
<body>
<button type="button" onclick="callArkTS()">Click Me!</button>
<p id="demo"></p>
<script>
function callArkTS() {
// testObjName是Native侧注册的名称
// test是Native侧暴露的方法名称
let str = testObjName.test();
document.getElementById("demo").innerHTML = str;
console.info('ArkTS Hello World! :' + str);
}
</script>
</body>
</html>
双方可以互相通信之后,就可以根据业务需要进行封装和分发事件了。
6.3 兼容DSBridge
当前项目iOS/Android和H5通信用的是DSBridg这个库(当时项目时间紧,所以没考虑自己封装)。
DSBridge的实现原理还是挺简单的,通过H5页面调用prompt方法来向Native传递数据,数据中会包括:
- H5页面的回调函数名
- 调用Native的目标方法名
- 调用Native的目标方法参数
Native会拦截系统Webview的prompt方法,解析数据,在执行完方法之后通过H5页面传过来的回调函数名执行H5的回调方法。
如果我们要做鸿蒙版本的App,就需要考虑对鸿蒙版本App的兼容,有两个选择:
- 鸿蒙App中实现DSBridge的逻辑,这样H5侧不需要任何改动;
- 基于鸿蒙Web组件提供的方法封装一套鸿蒙App特有的逻辑,这需要H5侧在通信层进行系统判断处理;
由于当时这个库只支持了API9,我们是基于API11进行开发和学习的,为了避免作者更新不及时,我们选择自己仿照这个库的逻辑进行实现。
主要是能拦截到H5侧DSBridge的方法调用,先来看下DSBridge在H5侧的实现:
if(window._dsbridge) {
// 这个是关键代码
ret = _dsbridge.call(method, arg)
}else if(window._dswk || navigator.userAgent.indexOf("_dsbridge") != -1){
ret = prompt("_dsbridge=" + method, arg);
}
_dsbridge.call(method, arg)是关键代码,跟上面6.2中H5调用Native的方法是很相似的。
所以,我们可以在Native侧对其进行注册:
import web_webview from '@ohos.web.webview'
class JSBridge {
// 暴露给H5的唯一的方法
call(methodName: string, params: string): string {
// 这里可以获取到methodName和params
return ""
}
}
@Entry
@Component
struct WebComponent {
controller: web_webview.WebviewController = new web_webview.WebviewController();
jsbridge: JSBridge = new JSBridge()
build() {
Column() {
// 这个src可以先设置为空的字符串
Web({ src: '', controller: this.controller })
.onControllerAttached(()=>{
let userAgent = this.controller.getUserAgent() + "xxx";
this.controller.setCustomUserAgent(userAgent);
// 设置完UA之后,再次加载url
this.controller.loadUrl("www.baidu.com")
})
.javaScriptProxy({
object: this.jsbridge,
name: "_dsbridge", // _dsbridge是固定名字
methodList: ["call"], // call也是固定方法名
controller: this.controller
})
}
}
}
这样DSBridge在H5页面就可以通过_dsbridge.call(method, arg)调用到Native的call方法了,然后Native在call方法中解析数据,按照iOS/Android的实现逻辑进行实现即可。
怎样学习鸿蒙?
首先必学的是开发语言 ArkTS,这是重中之重,然后就是ArkUI声明式UI开发、Stage模型、网络/数据库管理、分布式应用开发、进程间通信与线程间通信技术、OpenHarmony多媒体技术……。中间还有许多的知识点,都整理成思维导图来分享给大家~
此外,小编精心准备了一份联合鸿蒙官方发布笔记整理收纳的《鸿蒙开发学习笔记》,内容包含ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
【有需要的小伙伴,可以扫描下方二维码免费领取!!!】
快速入门
- 开发准备
- 构建第一个ArkTS应用(Stage模型)
- 构建第一个ArkTS应用(FA模型)
- 构建第一个JS应用(FA模型)
开发基础知识
- 应用程序包基础知识
- 应用配置文件(Stage模型)
- 应用配置文件概述(FA模型)
资源分类与访问
- 资源分类与访问
- 创建资源目录和资源文件
- 资源访问
学习ArkTs语言
- 初识ArkTS语言
- 基本语法
- 状态管理
- 其他状态管理
- 渲染控制
基于ArkTS声明式开发范式
- UI开发(ArkTS声明式开发范式)概述
- 开发布局
- 添加组件
- 显示图片
- 使用动画
- 支持交互事件
- 性能提升的推荐方法
兼容JS的类Web开发范式
- 概述
- 框架说明
- 构建用户界面
- 常见组件开发指导
- 动效开发指导
- 自定义组件
Web组件
- 概述
- 设置基本属性和事件
- 并发
- 窗口管理
- WebGL
- 媒体
- 安全
- 网络与连接
- 电话服务
- 数据管理
- …
应用模型
- 概述
- Stage模型开发指导
- FA模型开发指导