HarmonyOS鸿蒙Next应用中如何使用WebView加载网页?

HarmonyOS鸿蒙Next应用中如何使用WebView加载网页? 在HarmonyOS应用中如何使用WebView加载网页?
如何实现Web与原生的交互、拦截网络请求和处理页面事件?

4 回复
import { webview } from '@kit.ArkWeb';

@Entry
@Component
struct WebPage {
  controller: webview.WebviewController = new webview.WebviewController();
  @State webUrl: string = 'https://example.com'; // 替换为目标网址

  build() {
    Column() {
      Web({ 
        src: this.webUrl, 
        controller: this.controller 
      })
      .width('100%')
      .height('100%')
    }
  }
}

web加载页面的大概是类似上述这种代码,具体的实践可以参考ArkWeb简介使用Web组件加载页面这个章节的内容,其他跟web相关的可以参考ArkWeb简介中其他对应的章节。

更多关于HarmonyOS鸿蒙Next应用中如何使用WebView加载网页?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


解决方案

1. 基础WebView使用

import web_webview from '@ohos.web.webview'

@Entry
@Component
struct BasicWebView {
  controller: web_webview.WebviewController = new web_webview.WebviewController()

  build() {
    Column() {
      // 导航栏
      Row() {
        Button('后退')
          .enabled(this.controller.accessBackward())
          .onClick(() => {
            this.controller.backward()
          })
        
        Button('前进')
          .enabled(this.controller.accessForward())
          .onClick(() => {
            this.controller.forward()
          })
        
        Button('刷新')
          .onClick(() => {
            this.controller.refresh()
          })
      }
      .width('100%')
      .padding(12)
      .justifyContent(FlexAlign.SpaceAround)

      // WebView
      Web({
        src: 'https://www.example.com',
        controller: this.controller
      })
        .width('100%')
        .layoutWeight(1)
        .onPageBegin((event) => {
          console.log('页面开始加载:', event?.url)
        })
        .onPageEnd((event) => {
          console.log('页面加载完成:', event?.url)
        })
        .onProgressChange((event) => {
          console.log('加载进度:', event?.newProgress)
        })
        .onErrorReceive((event) => {
          console.error('加载错误:', event?.error.getErrorInfo())
        })
    }
  }
}

2. 加载本地HTML

@Entry
@Component
struct LocalWebView {
  controller: web_webview.WebviewController = new web_webview.WebviewController()
  @State htmlContent: string = `
    <!DOCTYPE html>
    <html>
    <head>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <style>
        body {
          font-family: sans-serif;
          padding: 20px;
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          color: white;
        }
        .container {
          background: rgba(255,255,255,0.1);
          padding: 20px;
          border-radius: 12px;
        }
        button {
          background: white;
          color: #667eea;
          border: none;
          padding: 12px 24px;
          border-radius: 6px;
          font-size: 16px;
          margin-top: 10px;
        }
      </style>
    </head>
    <body>
      <div class="container">
        <h1>本地HTML页面</h1>
        <p>这是通过loadData加载的本地HTML内容</p>
        <button onclick="handleClick()">点击我</button>
      </div>
      <script>
        function handleClick() {
          alert('按钮被点击了!');
        }
      </script>
    </body>
    </html>
  `

  build() {
    Column() {
      Web({
        src: '',
        controller: this.controller
      })
        .width('100%')
        .height('100%')
        .onControllerAttached(() => {
          this.controller.loadData(this.htmlContent, 'text/html', 'UTF-8')
        })
    }
  }
}

3. Web与Native交互

// 定义交互对象
class WebBridge {
  // 供Web调用的方法
  [@JavaScriptProxy](/user/JavaScriptProxy)
  showToast(message: string) {
    console.log('Web调用Native:', message)
    // 可以调用原生Toast等
  }

  [@JavaScriptProxy](/user/JavaScriptProxy)
  getUserInfo(): string {
    return JSON.stringify({
      name: '张三',
      age: 25,
      email: 'zhangsan@example.com'
    })
  }

  [@JavaScriptProxy](/user/JavaScriptProxy)
  openNativePage(page: string) {
    console.log('打开原生页面:', page)
    // 可以调用路由跳转
  }
}

@Entry
@Component
struct WebNativeInteraction {
  controller: web_webview.WebviewController = new web_webview.WebviewController()
  bridge: WebBridge = new WebBridge()

  build() {
    Column() {
      Button('调用Web方法')
        .onClick(() => {
          // Native调用Web方法
          this.controller.runJavaScript('webFunction("来自Native的消息")')
        })
        .margin(16)

      Web({
        src: '',
        controller: this.controller
      })
        .width('100%')
        .layoutWeight(1)
        .javaScriptAccess(true) // 启用JavaScript
        .javaScriptProxy({
          object: this.bridge,
          name: 'nativeBridge', // Web中通过window.nativeBridge访问
          methodList: ['showToast', 'getUserInfo', 'openNativePage'],
          controller: this.controller
        })
        .onControllerAttached(() => {
          this.loadInteractivePage()
        })
    }
  }

  private loadInteractivePage() {
    const html = `
      <!DOCTYPE html>
      <html>
      <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <style>
          body { padding: 20px; font-family: sans-serif; }
          button {
            width: 100%;
            padding: 12px;
            margin: 8px 0;
            background: #1890ff;
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 16px;
          }
          #result {
            margin-top: 20px;
            padding: 12px;
            background: #f5f5f5;
            border-radius: 6px;
          }
        </style>
      </head>
      <body>
        <h2>Web与Native交互</h2>
        
        <button onclick="callShowToast()">调用Native显示Toast</button>
        <button onclick="callGetUserInfo()">获取用户信息</button>
        <button onclick="callOpenPage()">打开原生页面</button>
        
        <div id="result"></div>
        
        <script>
          // Web调用Native方法
          function callShowToast() {
            window.nativeBridge.showToast('来自Web的消息');
          }
          
          function callGetUserInfo() {
            const userInfo = window.nativeBridge.getUserInfo();
            document.getElementById('result').innerText = 
              '用户信息: ' + userInfo;
          }
          
          function callOpenPage() {
            window.nativeBridge.openNativePage('DetailPage');
          }
          
          // 供Native调用的方法
          function webFunction(message) {
            document.getElementById('result').innerText = 
              'Native消息: ' + message;
          }
        </script>
      </body>
      </html>
    `
    this.controller.loadData(html, 'text/html', 'UTF-8')
  }
}

4. 拦截网络请求

@Entry
@Component
struct WebViewWithInterceptor {
  controller: web_webview.WebviewController = new web_webview.WebviewController()

  build() {
    Web({
      src: 'https://www.example.com',
      controller: this.controller
    })
      .width('100%')
      .height('100%')
      .onInterceptRequest((event) => {
        // 拦截请求
        const url = event?.request.getRequestUrl()
        console.log('拦截请求:', url)

        // 可以修改请求或返回本地资源
        if (url?.includes('custom-resource')) {
          // 返回本地资源
          return {
            data: '<html><body>自定义内容</body></html>',
            encoding: 'utf-8',
            mimeType: 'text/html',
            reason: 'OK',
            statusCode: 200
          }
        }

        return null // 不拦截,继续请求
      })
      .onUrlLoadIntercept((event) => {
        // URL加载拦截
        const url = event?.data.toString()
        console.log('URL加载:', url)

        // 拦截特定协议
        if (url?.startsWith('myapp://')) {
          console.log('拦截自定义协议')
          // 处理自定义协议
          return true // 返回true表示拦截
        }

        return false // 不拦截
      })
  }
}

5. 完整WebView示例

import web_webview from '@ohos.web.webview'

@Entry
@Component
struct CompleteWebView {
  controller: web_webview.WebviewController = new web_webview.WebviewController()
  @State pageTitle: string = ''
  @State pageUrl: string = ''
  @State progress: number = 0
  @State canGoBack: boolean = false
  @State canGoForward: boolean = false
  @State isLoading: boolean = false

  build() {
    Column() {
      // 顶部工具栏
      Row() {
        Text(this.pageTitle || '加载中...')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .layoutWeight(1)
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })

        if (this.isLoading) {
          LoadingProgress()
            .width(20)
            .height(20)
        }
      }
      .width('100%')
      .padding(12)
      .backgroundColor('#f5f5f5')

      // 地址栏
      Row() {
        Text(this.pageUrl)
          .fontSize(14)
          .fontColor('#666666')
          .layoutWeight(1)
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
      }
      .width('100%')
      .padding({ left: 12, right: 12, top: 4, bottom: 4 })
      .backgroundColor('#ffffff')

      // 进度条
      if (this.progress > 0 && this.progress < 100) {
        Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
          .width('100%')
          .color('#1890ff')
          .height(2)
      }

      // WebView
      Web({
        src: 'https://www.harmonyos.com',
        controller: this.controller
      })
        .width('100%')
        .layoutWeight(1)
        .javaScriptAccess(true)
        .domStorageAccess(true)
        .onPageBegin((event) => {
          this.isLoading = true
          this.pageUrl = event?.url || ''
        })
        .onPageEnd((event) => {
          this.isLoading = false
          this.pageTitle = this.controller.getTitle()
          this.updateNavigationState()
        })
        .onProgressChange((event) => {
          this.progress = event?.newProgress || 0
        })
        .onTitleReceive((event) => {
          this.pageTitle = event?.title || ''
        })

      // 底部导航栏
      Row() {
        Button() {
          Image($r('sys.media.ohos_ic_public_arrow_left'))
            .width(24)
            .height(24)
        }
        .type(ButtonType.Normal)
        .backgroundColor(Color.Transparent)
        .enabled(this.canGoBack)
        .opacity(this.canGoBack ? 1 : 0.3)
        .onClick(() => {
          this.controller.backward()
        })

        Button() {
          Image($r('sys.media.ohos_ic_public_arrow_right'))
            .width(24)
            .height(24)
        }
        .type(ButtonType.Normal)
        .backgroundColor(Color.Transparent)
        .enabled(this.canGoForward)
        .opacity(this.canGoForward ? 1 : 0.3)
        .onClick(() => {
          this.controller.forward()
        })

        Button() {
          Image($r('sys.media.ohos_ic_public_refresh'))
            .width(24)
            .height(24)
        }
        .type(ButtonType.Normal)
        .backgroundColor(Color.Transparent)
        .onClick(() => {
          this.controller.refresh()
        })

        Button() {
          Image($r('sys.media.ohos_ic_public_cancel'))
            .width(24)
            .height(24)
        }
        .type(ButtonType.Normal)
        .backgroundColor(Color.Transparent)
        .onClick(() => {
          this.controller.stop()
        })
      }
      .width('100%')
      .height(56)
      .justifyContent(FlexAlign.SpaceAround)
      .backgroundColor('#f5f5f5')
    }
  }

  private updateNavigationState() {
    this.canGoBack = this.controller.accessBackward()
    this.canGoForward = this.controller.accessForward()
  }
}

关键要点

  1. WebviewController: 控制WebView的加载、导航等行为
  2. JavaScript交互: 通过@JavaScriptProxy实现双向通信
  3. 请求拦截: onInterceptRequest可拦截和修改网络请求
  4. 生命周期: onPageBegin、onPageEnd、onProgressChange等事件
  5. 安全性: 谨慎开启javaScriptAccess,验证来源

最佳实践

  1. 性能优化: 启用缓存、压缩传输数据
  2. 错误处理: 监听onErrorReceive处理加载失败
  3. 内存管理: 及时清理不用的WebView
  4. 安全通信: 验证Web调用来源,过滤敏感数据
  5. 用户体验: 显示加载进度,提供刷新重试

在HarmonyOS Next应用中,使用WebView加载网页需通过ArkUI框架实现。首先导入Web组件模块,然后在UI中声明Web组件并设置初始加载的网页地址。可通过loadUrl方法动态加载指定URL,同时支持配置JavaScript启用、文件访问等参数以控制网页行为。

在HarmonyOS Next应用中,使用WebView加载网页主要涉及@ohos.web.webview模块。以下是核心实现方法:

1. 加载网页

在ArkUI(声明式开发范式)中,使用Web组件:

import webview from '@ohos.web.webview';

@Entry
@Component
struct WebPage {
  controller: webview.WebviewController = new webview.WebviewController();

  build() {
    Column() {
      // 加载在线网页
      Web({ src: 'https://developer.harmonyos.com/', controller: this.controller })
        .width('100%')
        .height('100%')
      
      // 或加载本地HTML文件
      // Web({ src: $rawfile('index.html'), controller: this.controller })
    }
  }
}

2. 实现Web与原生交互

2.1 JavaScript调用原生方法

注册JavaScript接口:

// 原生侧注册对象
this.controller.registerJavaScriptProxy({
  // 定义供JS调用的方法
  showToast: (msg: string) => {
    prompt.showToast({ message: msg });
  },
  getDeviceInfo: () => {
    return { model: 'HarmonyOS Device' };
  }
}, 'nativeBridge'); // 注入到window.nativeBridge

// 在Web页面JavaScript中调用:
// window.nativeBridge.showToast('Hello from JS');
// const info = window.nativeBridge.getDeviceInfo();

2.2 原生调用JavaScript方法

// 执行JS代码
this.controller.runJavaScript('alert("来自原生的调用")');

// 调用JS函数并获取返回值
this.controller.runJavaScript('getUserToken()', (err, result) => {
  if (!err) {
    console.log('JS返回值:', result);
  }
});

3. 拦截网络请求

通过onInterceptRequest回调拦截和修改请求:

this.controller.on('onInterceptRequest', (event) => {
  // 拦截特定URL
  if (event.request.url.indexOf('blocked-domain.com') !== -1) {
    // 阻止请求
    return { intercept: true };
  }
  
  // 修改请求头
  event.request.header = {
    ...event.request.header,
    'Custom-Header': 'HarmonyOS'
  };
  
  // 允许请求继续
  return { intercept: false };
});

4. 处理页面事件

4.1 页面加载事件

this.controller.on('pageBegin', (event) => {
  console.log('开始加载:', event.url);
});

this.controller.on('pageEnd', (event) => {
  console.log('加载完成:', event.url);
});

this.controller.on('pageError', (error) => {
  console.error('加载失败:', error);
});

4.2 其他常用事件

// 进度变化
this.controller.on('progressChange', (progress) => {
  console.log('加载进度:', progress);
});

// 标题变化
this.controller.on('titleReceive', (title) => {
  console.log('页面标题:', title);
});

// JS对话框
this.controller.on('alert', (event) => {
  prompt.showDialog({ message: event.message });
  event.result.handleConfirm(); // 确认对话框
});

5. 完整示例

import webview from '@ohos.web.webview';

@Entry
@Component
struct FullWebViewExample {
  controller: webview.WebviewController = new webview.WebviewController();

  aboutToAppear() {
    // 注册JS接口
    this.controller.registerJavaScriptProxy({
      nativeMethod: (data: string) => {
        console.log('收到JS数据:', data);
      }
    }, 'harmonyBridge');

    // 设置事件监听
    this.controller.on('pageEnd', () => {
      // 页面加载完成后注入CSS
      this.controller.runJavaScript(`
        document.body.style.backgroundColor = '#f5f5f5';
      `);
    });

    // 拦截请求
    this.controller.on('onInterceptRequest', (event) => {
      console.log('请求URL:', event.request.url);
      return { intercept: false };
    });
  }

  build() {
    Column() {
      Web({ src: 'https://example.com', controller: this.controller })
        .width('100%')
        .height('100%')
        .onPageEnd(() => {
          console.log('页面加载完成回调');
        })
    }
  }
}

关键配置

module.json5中配置网络权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}

通过以上方法,可以实现在HarmonyOS Next应用中完整的WebView功能,包括网页加载、双向通信、请求拦截和事件处理。注意Web组件需要合理管理生命周期,避免内存泄漏。

回到顶部