本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、概述

前端页面调用应用侧函数是HarmonyOS Web组件的重要功能,它建立了Web页面与原生应用之间的通信桥梁。通过这种机制,H5页面能够调用应用侧注册的方法,实现丰富的交互功能和业务逻辑处理。

二、通信机制原理

2.1 基本架构

  • Web组件:作为容器加载和显示Web内容
  • JavaScript代理:在Web环境中暴露原生方法
  • 双向通信:支持H5到原生和原生到H5的双向方法调用

2.2 核心组件

  • WebViewController:控制Web组件的行为和交互
  • JavaScript代理对象:在Web环境中注册的原生对象
  • 权限控制系统:确保通信安全性和数据保护

三、两种注册方式

3.1 初始化时注册(javaScriptProxy)

3.1.1 使用场景
  • Web组件初始化时立即注册
  • 需要与Web页面加载同步进行的方法注册
  • 简单的、不需要动态更新的方法注册
3.1.2 实现方式
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';

// 定义应用侧功能类
class AppFunctionHandler {
  private version: string = '1.0.0';

  // 处理业务数据
  processBusinessData(data: string): string {
    return `处理结果: ${data.toUpperCase()} [版本: ${this.version}]`;
  }

  // 获取设备信息
  getDeviceInfo(): Object {
    return {
      platform: 'HarmonyOS',
      version: '4.0',
      language: navigator.language
    };
  }

  // 带回调的异步方法
  async fetchRemoteData(params: Object): Promise<Object> {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({
          status: 'success',
          data: params,
          timestamp: new Date().getTime()
        });
      }, 1000);
    });
  }
}

@Entry
@Component
struct EnhancedWebComponent {
  controller: webview.WebviewController = new webview.WebviewController();
  
  // 声明应用功能处理器
  @State appHandler: AppFunctionHandler = new AppFunctionHandler();

  build() {
    Column() {
      // 控制按钮区域
      Row() {
        Button('移除注册')
          .onClick(() => {
            try {
              this.controller.deleteJavaScriptRegister("appHandler");
              console.log('应用侧方法注册已移除');
            } catch (error) {
              console.error(`错误代码: ${(error as BusinessError).code}, 消息: ${(error as BusinessError).message}`);
            }
          })
          
        Button('重新注册')
          .onClick(() => {
            this.registerAppFunctions();
          })
      }
      .padding(10)

      // Web组件
      Web({ 
        src: $rawfile('enhanced.html'), 
        controller: this.controller
      })
      .javaScriptProxy({
        object: this.appHandler,
        name: "appHandler",
        methodList: ["processBusinessData", "getDeviceInfo", "fetchRemoteData"],
        controller: this.controller,
        asyncMethodList: ["fetchRemoteData"],
        permission: JSON.stringify({
          javascriptProxyPermission: {
            urlPermissionList: [
              {
                scheme: "https",
                host: "api.example.com",
                port: "",
                path: "/data"
              },
              {
                scheme: "resource",
                host: "rawfile",
                port: "",
                path: ""
              }
            ],
            methodList: [
              {
                methodName: "processBusinessData",
                urlPermissionList: [
                  {
                    scheme: "https",
                    host: "service.example.com",
                    port: "",
                    path: ""
                  }
                ]
              }
            ]
          }
        })
      })
    }
  }

  // 独立的注册方法
  private registerAppFunctions(): void {
    try {
      this.controller.registerJavaScriptProxy(
        this.appHandler,
        "appHandler",
        ["processBusinessData", "getDeviceInfo", "fetchRemoteData"],
        ["fetchRemoteData"]
      );
    } catch (error) {
      console.error('方法注册失败:', error);
    }
  }
}
3.1.2 实现方式
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';

// 定义应用侧功能类
class AppFunctionHandler {
  private version: string = '1.0.0';

  // 处理业务数据
  processBusinessData(data: string): string {
    return `处理结果: ${data.toUpperCase()} [版本: ${this.version}]`;
  }

  // 获取设备信息
  getDeviceInfo(): Object {
    return {
      platform: 'HarmonyOS',
      version: '4.0',
      language: navigator.language
    };
  }

  // 带回调的异步方法
  async fetchRemoteData(params: Object): Promise<Object> {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({
          status: 'success',
          data: params,
          timestamp: new Date().getTime()
        });
      }, 1000);
    });
  }
}

@Entry
@Component
struct EnhancedWebComponent {
  controller: webview.WebviewController = new webview.WebviewController();
  
  // 声明应用功能处理器
  @State appHandler: AppFunctionHandler = new AppFunctionHandler();

  build() {
    Column() {
      // 控制按钮区域
      Row() {
        Button('移除注册')
          .onClick(() => {
            try {
              this.controller.deleteJavaScriptRegister("appHandler");
              console.log('应用侧方法注册已移除');
            } catch (error) {
              console.error(`错误代码: ${(error as BusinessError).code}, 消息: ${(error as BusinessError).message}`);
            }
          })
          
        Button('重新注册')
          .onClick(() => {
            this.registerAppFunctions();
          })
      }
      .padding(10)

      // Web组件
      Web({ 
        src: $rawfile('enhanced.html'), 
        controller: this.controller
      })
      .javaScriptProxy({
        object: this.appHandler,
        name: "appHandler",
        methodList: ["processBusinessData", "getDeviceInfo", "fetchRemoteData"],
        controller: this.controller,
        asyncMethodList: ["fetchRemoteData"],
        permission: JSON.stringify({
          javascriptProxyPermission: {
            urlPermissionList: [
              {
                scheme: "https",
                host: "api.example.com",
                port: "",
                path: "/data"
              },
              {
                scheme: "resource",
                host: "rawfile",
                port: "",
                path: ""
              }
            ],
            methodList: [
              {
                methodName: "processBusinessData",
                urlPermissionList: [
                  {
                    scheme: "https",
                    host: "service.example.com",
                    port: "",
                    path: ""
                  }
                ]
              }
            ]
          }
        })
      })
    }
  }

  // 独立的注册方法
  private registerAppFunctions(): void {
    try {
      this.controller.registerJavaScriptProxy(
        this.appHandler,
        "appHandler",
        ["processBusinessData", "getDeviceInfo", "fetchRemoteData"],
        ["fetchRemoteData"]
      );
    } catch (error) {
      console.error('方法注册失败:', error);
    }
  }
}

3.2 初始化后注册(registerJavaScriptProxy)

3.2.1 使用场景
  • Web组件加载完成后动态注册
  • 需要根据运行时条件决定注册哪些方法
  • 支持动态更新和替换已注册的方法
3.2.2 实现方式
import { webview } from '@kit.ArkWeb';

// 动态功能管理器
class DynamicFunctionManager {
  private registeredMethods: Set<string> = new Set();

  // 动态数据处理
  dynamicDataProcessing(input: any): any {
    console.log('处理数据:', input);
    return {
      processed: true,
      original: input,
      timestamp: Date.now(),
      processedData: typeof input === 'string' ? input.split('').reverse().join('') : input
    };
  }

  // 状态检查
  checkSystemStatus(): { status: string; details: Object } {
    return {
      status: 'normal',
      details: {
        memory: '充足',
        network: '已连接',
        battery: '80%'
      }
    };
  }

  // 获取已注册方法列表
  getRegisteredMethods(): string[] {
    return Array.from(this.registeredMethods);
  }
}

@Entry
@Component
struct DynamicWebComponent {
  controller: webview.WebviewController = new webview.WebviewController();
  @State dynamicManager: DynamicFunctionManager = new DynamicFunctionManager();

  aboutToAppear(): void {
    // 组件创建后延迟注册
    setTimeout(() => {
      this.registerDynamicFunctions();
    }, 2000);
  }

  build() {
    Column() {
      Web({
        src: $rawfile('dynamic.html'),
        controller: this.controller
      })
      .onPageEnd(() => {
        // 页面加载完成后注册额外方法
        this.registerAdditionalFunctions();
      })
    }
  }

  private registerDynamicFunctions(): void {
    try {
      this.controller.registerJavaScriptProxy(
        this.dynamicManager,
        "dynamicManager",
        ["dynamicDataProcessing", "checkSystemStatus", "getRegisteredMethods"]
      );
      
      // 更新注册状态
      this.dynamicManager['registeredMethods'] = new Set([
        'dynamicDataProcessing', 
        'checkSystemStatus', 
        'getRegisteredMethods'
      ]);
      
    } catch (error) {
      console.error('动态注册失败:', error);
    }
  }

  private registerAdditionalFunctions(): void {
    // 注册页面加载后的额外功能
    const pageSpecificFunctions = {
      getPageInfo: () => {
        return {
          title: document.title,
          url: window.location.href,
          loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart
        };
      }
    };

    this.controller.registerJavaScriptProxy(
      pageSpecificFunctions,
      "pageUtils",
      ["getPageInfo"]
    );
  }
}

四、权限管理与安全机制

4.1 权限配置详解

// 完整的权限配置示例
const fullPermissionConfig = {
  javascriptProxyPermission: {
    // URL权限列表
    urlPermissionList: [
      {
        scheme: "https",
        host: "api.service.com",
        port: "443",
        path: "/v1/data"
      },
      {
        scheme: "http",
        host: "internal.api.com",
        port: "8080",
        path: "/data"
      },
      {
        scheme: "resource",
        host: "rawfile",
        port: "",
        path: "/assets"
      }
    ],
    
    // 方法权限配置
    methodList: [
      {
        methodName: "processSensitiveData",
        urlPermissionList: [
          {
            scheme: "https",
            host: "secure.api.com",
            port: "",
            path: "/secure"
          }
        ]
      },
      {
        methodName: "getUserInfo",
        urlPermissionList: [
          {
            scheme: "https",
            host: "user.api.com",
            port: "",
            path: "/profile"
          }
        ]
      }
    ]
  }
};

4.2 安全建议

  1. 最小权限原则:只授予必要的权限
  2. 输入验证:对所有输入参数进行严格验证
  3. 错误处理:避免向Web端暴露敏感错误信息
  4. 定期清理:及时移除不再需要的注册方法

五、内存管理

5.1 内存泄漏预防

// 完善的内存管理示例
class MemorySafeComponent {
  private registeredProxies: Map<string, any> = new Map();

  // 注册代理并跟踪
  registerWithTracking(
    object: any,
    name: string,
    methods: string[],
    asyncMethods: string[] = []
  ): void {
    this.controller.registerJavaScriptProxy(object, name, methods, asyncMethods);
    this.registeredProxies.set(name, {
      object,
      methods,
      asyncMethods,
      registerTime: Date.now()
    });
  }

  // 清理所有注册
  cleanupAllRegistrations(): void {
    for (const [name] of this.registeredProxies) {
      try {
        this.controller.deleteJavaScriptRegister(name);
      } catch (error) {
        console.warn(`清理注册 ${name} 时出错:`, error);
      }
    }
    this.registeredProxies.clear();
  }

  // 按条件清理
  cleanupByCondition(condition: (name: string, info: any) => boolean): void {
    for (const [name, info] of this.registeredProxies) {
      if (condition(name, info)) {
        try {
          this.controller.deleteJavaScriptRegister(name);
          this.registeredProxies.delete(name);
        } catch (error) {
          console.warn(`清理注册 ${name} 时出错:`, error);
        }
      }
    }
  }
}

六、高级应用

6.1 双向通信实现

// 完整的双向通信示例
class BidirectionalCommunication {
  private webViewController: webview.WebviewController;
  private eventCallbacks: Map<string, Function[]> = new Map();

  constructor(controller: webview.WebviewController) {
    this.webViewController = controller;
  }

  // 注册事件监听
  on(event: string, callback: Function): void {
    if (!this.eventCallbacks.has(event)) {
      this.eventCallbacks.set(event, []);
    }
    this.eventCallbacks.get(event)!.push(callback);
  }

  // 触发事件
  emit(event: string, data: any): void {
    const callbacks = this.eventCallbacks.get(event) || [];
    callbacks.forEach(callback => callback(data));
  }

  // 向Web页面发送消息
  sendToWeb(messageType: string, data: any): void {
    const script = `
      if (window.receiveAppMessage) {
        window.receiveAppMessage(${JSON.stringify({
          type: messageType,
          data: data,
          timestamp: Date.now()
        })});
      }
    `;
    this.webViewController.runJavaScript(script);
  }

  // 从Web接收消息
  @JavaScriptProxy()
  receiveFromWeb(message: any): void {
    this.emit(message.type, message.data);
  }
}

6.2 性能优化

// 高性能代理实现

class HighPerformanceProxy {
  private methodCache: Map<string, Function> = new Map();
  private responseCache: Map<string, any> = new Map();
  private readonly CACHE_TTL = 30000; // 30秒缓存

  // 带缓存的方法调用
  async cachedMethodCall(methodName: string, args: any[]): Promise<any> {
    const cacheKey = `${methodName}_${JSON.stringify(args)}`;
    
    // 检查缓存
    if (this.responseCache.has(cacheKey)) {
      const cached = this.responseCache.get(cacheKey);
      if (Date.now() - cached.timestamp < this.CACHE_TTL) {
        return cached.data;
      }
    }

    // 执行实际方法
    const result = await this.executeMethod(methodName, args);
    
    // 更新缓存
    this.responseCache.set(cacheKey, {
      data: result,
      timestamp: Date.now()
    });

    return result;
  }

  private async executeMethod(methodName: string, args: any[]): Promise<any> {
    // 实际的方法执行逻辑
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({ result: `执行 ${methodName} 完成`, args });
      }, 100);
    });
  }
}

八、总结

8.1 开发实践

  1. 模块化设计:将相关功能组织成独立的类
  2. 接口版本控制:为注册的方法提供版本管理
  3. 文档生成:自动生成Web端可用的API文档

8.2 安全实践

  1. 参数验证:对所有输入进行严格验证
  2. 权限审计:定期审查权限配置
  3. 日志监控:记录所有跨环境调用

8.3 性能实践

  1. 批量处理:支持批量操作减少通信次数
  2. 缓存策略:合理使用缓存提高性能
  3. 懒加载:按需注册和加载功能模块
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐