ArkWeb实战学习笔记05-综合实战:构建混合应用

背景

ArkWeb 官方文档中只给出了组件的基本 API 和简单示例,缺少完整的混合应用开发流程。在实际项目中,需要同时处理 URL 加载、JS 双向通信、本地存储同步以及下拉刷新等场景,如果每个模块单独调试,后期集成时容易出现接口冲突和状态不一致。

下面从零构建一个新闻列表阅读器,包含 Web 页面加载、Native 向 Web 注入数据、Web 回调 Native 打开详情、本地存储缓存摘要、下拉刷新 Web 内容等完整流程。所有代码均基于 HarmonyOS NEXT 的 ArkWeb 组件(API 12)。


1. 项目结构与基础配置

创建工程后,在 entry/src/main/ets 下新建 pages/NewsApp.ets 作为主页面。同时在 rawfile 目录下放入网页资源 news.html(后面会给出完整 HTML)。

typescript 复制代码
// NewsApp.ets 骨架
import web_webview from '@ohos.web.webview';
import webController from '@ohos.web.webview';
import { BusinessError } from '@ohos.base';
import storage from '@ohos.data.preferences';

@Entry
@Component
struct NewsApp {
  private controller: web_webview.WebviewController = new web_webview.WebviewController();
  @State newsSummary: string = '';
  @State isLoading: boolean = false;

  build() {
    Column() {
      // 下拉刷新区域 + Web 组件
      Refresh({ refreshing: this.isLoading, onRefresh: () => this.onPullRefresh() }) {
        Web({ src: $rawfile('news.html'), controller: this.controller })
          .javaScriptAccess(true)
          .domStorageAccess(true)
          .onPageEnd(() => this.injectNativeData())
          .javascriptProxy({
            object: this,
            name: 'nativeBridge',
            methodList: ['openDetail', 'saveCache', 'getCache']
          })
      }
    }
    .height('100%')
    .width('100%')
  }
}

2. 网页端 HTML(rawfile/news.html

所有 JS 交互的方法名必须与 .javascriptProxy 中定义的 methodList 一致。

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
    body { margin:0; padding:16px; font-family: system-ui; }
    .news-item { border-bottom:1px solid #eee; padding:12px 0; cursor: pointer; }
    .news-title { font-size:16px; font-weight:500; }
    .news-summary { font-size:14px; color:#666; margin-top:4px; }
    #refresh-btn { margin-bottom:8px; padding:8px 16px; }
  </style>
</head>
<body>
  <button id="refresh-btn">手动刷新</button>
  <div id="news-list"></div>
  <script>
    // 接收 Native 注入的新闻数据
    window.renderNews = function(data) {
      const list = JSON.parse(data);
      let html = '';
      list.forEach(item => {
        html += `<div class="news-item" onclick="onItemClick('${item.id}')">
          <div class="news-title">${item.title}</div>
          <div class="news-summary">${item.summary}</div>
        </div>`;
      });
      document.getElementById('news-list').innerHTML = html;
    };

    function onItemClick(id) {
      // 调用 native 端 openDetail 方法
      if (window.nativeBridge && window.nativeBridge.openDetail) {
        window.nativeBridge.openDetail(id);
      }
    }

    // 手动刷新按钮 - 通知Native重新加载数据
    document.getElementById('refresh-btn').addEventListener('click', function() {
      // 也可以通过 Native 回调触发,这里展示双向通信
      if (window.nativeBridge && window.nativeBridge.getCache) {
        window.nativeBridge.getCache('lastRefresh').then(cacheVal => {
          // 仅做演示,实际可交给Native处理
          console.log('last refresh:', cacheVal);
        });
      }
    });
  </script>
</body>
</html>

3. Native 端核心逻辑

3.1 注入本地新闻数据

页面加载完成后(onPageEnd)触发 injectNativeData,通过 runJavaScript 将 JSON 字符串传入 Web 端。

typescript 复制代码
private injectNativeData() {
  const mockData = [
    { id: '1', title: 'HarmonyOS NEXT 新特性', summary: '原生应用流畅度提升 30%' },
    { id: '2', title: 'ArkUI 组件更新', summary: '新增 Canvas 2D 支持' }
  ];
  const jsonStr = JSON.stringify(mockData);
  // 调用网页端全局函数 renderNews
  this.controller.runJavaScript(`renderNews('${jsonStr}')`, (error, result) => {
    if (error) {
      console.error('数据注入失败:', error.message);
    }
  });
}
3.2 JS 回调 Native 打开详情

Web 组件中通过 javascriptProxy 暴露的对象必须实现 openDetail 方法。该方法会被网页端直接调用。

typescript 复制代码
openDetail(id: string): void {
  console.log('NewsApp 收到打开详情请求, id:', id);
  // 实际场景跳转到详情页面,这里只做日志记录
  // 注意:javascriptProxy 的方法不能是异步函数,否则 Web 端无法获取返回值
  // 如果需要返回数据,使用 promise 或同步返回值
}
3.3 本地存储:缓存上次刷新时间

NewsApp 类中定义 saveCachegetCache 方法,通过 @ohos.data.preferences 操作持久化数据。

typescript 复制代码
// 需要注意:javascriptProxy 暴露的方法默认在 UI 线程执行,
// 如果涉及异步操作,需要将结果通过回调返回给 Web 端
async saveCache(key: string, value: string): Promise<void> {
  const dataPreferences = await storage.getPreferences(this, 'newsCache');
  await dataPreferences.put(key, value);
  await dataPreferences.flush();
}

async getCache(key: string): Promise<string> {
  const dataPreferences = await storage.getPreferences(this, 'newsCache');
  const val = await dataPreferences.get(key, '');
  return val as string;
}

注意事项

如果 Web 端期望 getCache 返回字符串,而 Native 方法是 async,则返回的是一个 Promise 对象,Web 端需要 await.then 处理。更稳妥的做法是:在 javascriptProxy 声明 getCache 为同步方法,内部使用 runJavaScript 通过回调把结果传回。但 ArkWeb 当前版本支持返回 Promise 类型,上述写法可直接使用。


4. 下拉刷新实现

Refresh 组件触发后,调用 Native 重新注入最新数据。这里需要先清空网页现有内容。

typescript 复制代码
private onPullRefresh() {
  this.isLoading = true;
  // 模拟刷新延迟 1 秒
  setTimeout(() => {
    this.controller.runJavaScript('document.getElementById("news-list").innerHTML = ""', () => {
      // 重新注入数据
      this.injectNativeData();
      this.isLoading = false;
    });
  }, 1000);
}

常见误区

下拉刷新时若直接调用 injectNativeData(),由于 Web 端原有数据未被清除,新旧新闻会叠加显示。所以先执行 runJavaScript 清空容器,再注入新数据。此外,Refresh 组件的 onRefresh 回调需要手动设置 isLoading 结束状态,否则刷新动画不会停止。


5. 完整运行效果


6. 可能遇到的问题

  • JS 交互方法未定义:检查 javascriptProxymethodList 是否包含所有暴露方法,且方法名与 Web 端调用名完全一致(区分大小写)。
  • 注入中文数据乱码:确保 runJavaScript 的字符串参数使用 encodeURIComponent 转义,但 ArkWeb 当前版本直接传递 UTF-8 字符串无问题,若遇到特殊字符异常再做编码处理。
  • 多个 Web 实例共用同一个 WebviewController:每个 Web 组件必须拥有独立的 controller,否则页面错乱。

如果你在实际集成中遇到了其他 ArkWeb 的坑,或者发现 JS 交互在特定机型上报错,欢迎在评论区留言讨论。

相关推荐
橙橙笔记1 小时前
Python的学习第一部分
python·学习
bush42 小时前
嵌入式linux学习记录二
linux·运维·学习
CC大煊2 小时前
一个Javaer的AI转型笔记(1):入坑LangChain,我的第一个hello world
笔记·langchain
芒鸽3 小时前
鸿蒙应用测试实战:从单元测试到自动化测试
华为·单元测试·harmonyos
Davina_yu3 小时前
Hello HarmonyOS:搭建DevEco Studio开发环境与第一个应用运行(1)
harmonyos·鸿蒙原生开发
2501_919749033 小时前
鸿蒙 Flutter 实战:video_compress 3.1.4 适配 3.27-ohos 全流程
flutter·华为·harmonyos
元气少女小圆丶3 小时前
SenseGlove Nova 2+Unity开发笔记1
笔记·学习·unity
nashane4 小时前
HarmonyOS 6学习:应用退出动画优化实战——从“闪退“到优雅退出的完美蜕变
学习·华为·harmonyos
冰暮流星4 小时前
javascript之history对象介绍
前端·笔记