Angular Web和Native App间交互

主要是记录为什么花了一些时间才解决的问题。
也就是调试之前那个安卓版app的时候,在web端和native端之间交互的问题。
angular 写的 service里ai加了些内容,导致了native端无法正常解析(经过分析是方法名的参数必须参数名一致才能,后面去掉了相关内容后,就和参数名无关了,这才对么,不然太局限了)。

サンプルコード

/* eslint-disable max-lines */
/* eslint-disable complexity */

import { Injectable, NgZone } from '@angular/core';
import { Observable } from 'rxjs';

/**
 * Native Bridge 服务 - Web 与原生 App 通信桥接
 * 
 * 核心功能:
 * - iOS: 通过 webkit.messageHandlers 通信
 * - Android: 通过 window.android 对象通信
 * - 使用 Observable 处理异步回调
 */
declare global {
  /**
   * App 回调处理器接口定义
   */
  interface Window {
    onCallback1?: (payload: string) => void;
    onCallback2?: (payload: string) => void;
    onCallback3?: (payload: string) => void;
    onCallback4?: (payload: string) => void;
    onCallback5?: (payload: string) => void;
    onCallback6?: (payload: string) => void;
    onCallback7?: (payload: string) => void;
    onCallback8?: (payload: string) => void;
    onCallback9?: (payload: string) => void;
    onCallback10?: (payload: string) => void;
  }
}

/**
 * 请求方法枚举 - Web <-> App 通信协议
 */
enum NativeMethod {
  // 导航相关
  NAV_1 = 'nav_1',
  NAV_2 = 'nav_2',
  NAV_3 = 'nav_3',
  NAV_4 = 'nav_4',

  // 权限相关
  PERM_1 = 'perm_1',
  PERM_2 = 'perm_2',
  PERM_3 = 'perm_3',
  PERM_4 = 'perm_4',
  PERM_5 = 'perm_5',
  PERM_6 = 'perm_6',

  // 功能相关
  FUNC_1 = 'func_1',
  FUNC_2 = 'func_2',
  FUNC_3 = 'func_3',
  FUNC_4 = 'func_4',
  FUNC_5 = 'func_5',
  FUNC_6 = 'func_6',
  FUNC_7 = 'func_7',
}

/**
 * Native Bridge 全局服务
 * 
 * 设计要点:
 * 1. 平台检测:自动识别 iOS/Android
 * 2. 回调管理:使用 Observable 封装一次性回调
 * 3. 错误处理:超时机制和错误日志
 * 4. Zone 安全:确保 Angular 变更检测正常工作
 */
@Injectable({
  providedIn: 'root',
})
export class NativeBridgeGlobalService {
  private readonly defaultTimeoutMs = 60000;

  constructor(
    private ngZone: NgZone,
  ) {}

  /**
   * 核心方法:调用原生功能
   * 
   * 支持的通信方式(按优先级):
   * 1. iOS: webkit.messageHandlers[method].postMessage()
   * 2. Android: window.android[method]() 或 window.android.postMessage()
   * 3. iOS 兼容: webkit.messageHandlers.app.postMessage()
   */
  private async callNative(method: string, payload?: any): Promise<void> {
    try {
      // iOS: 使用 WKWebView messageHandlers
      const iosHandler = (window as any).webkit?.messageHandlers?.[method];
      if (iosHandler?.postMessage) {
        iosHandler.postMessage(payload);
        return;
      }

      // Android: 直接调用方法或使用 postMessage
      const android = (window as any).android;
      if (android) {
        // 直接方法调用(优先)
        if (typeof android[method] === 'function' && android[method] !== android.postMessage) {
          return payload !== undefined ? android[method](payload) : android[method]('');
        }
        // 通用 postMessage 方法
        if (typeof android.postMessage === 'function') {
          return android.postMessage(method, payload);
        }
      }

      // iOS 兼容模式:使用通用 app handler
      const iosBus = (window as any).webkit?.messageHandlers?.app;
      if (iosBus?.postMessage) {
        iosBus.postMessage(method, payload);
        return;
      }

      console.warn('No native bridge available for method:', method, payload);
    } catch (e) {
      console.warn('Native call failed:', method, payload, e);
      throw e;
    }
  }

  /**
   * 平台检测
   */
  public testIsAndroid(): string {
    const ios = /iPhone|iPad|iPod/i.test(navigator.userAgent);
    const android = /Android/i.test(navigator.userAgent);
    return ios ? 'ios' : android ? 'android' : 'unknown';
  }

  public isIOS(): boolean {
    return /iPhone|iPad|iPod/i.test(navigator.userAgent);
  }

  /**
   * 核心模式:一次性全局回调注册
   * 
   * 特点:
   * - 执行后自动清理,避免内存泄漏
   * - 支持超时处理
   * - 在 Angular Zone 内执行,确保变更检测
   * - 返回 Observable,便于链式调用
   */
  private onceGlobalCallback<T>(
    key: keyof Window | string,
    map: (raw: any) => T,
    timeoutMs = this.defaultTimeoutMs,
  ): Observable<T> {
    return new Observable<T>((observer) => {
      let finished = false;
      const cleanup = () => {
        (window as any)[key] = undefined;
      };

      // 注册全局回调
      (window as any)[key] = (raw: any) => {
        if (finished) {
          return;
        }
        finished = true;
        clearTimeout(timer);
        
        // 在 Angular Zone 内执行,确保变更检测
        this.ngZone.run(() => {
          try {
            observer.next(map(raw));
            observer.complete();
          } finally {
            cleanup();
          }
        });
      };

      // 超时处理
      const timer = setTimeout(() => {
        if (finished) {
          return;
        }
        finished = true;
        cleanup();
        this.ngZone.run(() => {
          console.error(`Native callback "${String(key)}" timed out after ${timeoutMs}ms`);
          observer.error(new Error(`Native callback "${String(key)}" timed out after ${timeoutMs}ms`));
        });
      }, timeoutMs);

      // 清理函数
      return () => {
        clearTimeout(timer);
        cleanup();
      };
    });
  }

  // 工具函数:结果映射
  private mapBoolean = (v: string) => v === '1'; // 权限:"0"=无权限 / "1"=有权限
  private mapSuccess = (v: string) => v === '0'; // 操作:"0"=成功 / "1"=失败
  private mapString = (v: string) => v; // 字符串直传

  /********** 导航功能 **********/
  
  navigateToHome(): void {
    this.callNative(NativeMethod.NAV_1);
  }

  navigateToUrl(urlString: string): void {
    this.callNative(NativeMethod.NAV_2, urlString);
  }

  navigateToPage1(params: { id: string; pageId: string; options?: any }): void {
    const payload = {
      id: params.id,
      page_id: params.pageId,
      options: params.options ? JSON.stringify(params.options) : undefined
    };
    this.callNative(NativeMethod.NAV_3, JSON.stringify(payload));
  }

  navigateToPage2(pageId: string, pageParam: string): void {
    const payload = { page_id: pageId, page_param: pageParam };
    this.callNative(NativeMethod.NAV_4, JSON.stringify(payload));
  }

  /********** 权限请求 **********/
  
  /** 请求权限类型1 */
  requestPermissionType1(): Observable<boolean> {
    const obs = this.onceGlobalCallback('onCallback1', this.mapBoolean);
    this.callNative(NativeMethod.PERM_1);
    return obs;
  }

  /** 请求权限类型2 */
  requestPermissionType2(): Observable<boolean> {
    const obs = this.onceGlobalCallback('onCallback2', this.mapBoolean);
    this.callNative(NativeMethod.PERM_2);
    return obs;
  }

  /** 请求权限类型3 */
  requestPermissionType3(): Observable<boolean> {
    const obs = this.onceGlobalCallback('onCallback3', this.mapBoolean);
    this.callNative(NativeMethod.PERM_3);
    return obs;
  }

  /** 请求权限类型4 */
  requestPermissionType4(): Observable<boolean> {
    const obs = this.onceGlobalCallback('onCallback4', this.mapBoolean);
    this.callNative(NativeMethod.PERM_4);
    return obs;
  }

  /** 请求权限类型5 */
  requestPermissionType5(): Observable<boolean> {
    const obs = this.onceGlobalCallback('onCallback5', this.mapBoolean);
    this.callNative(NativeMethod.PERM_5);
    return obs;
  }

  /** 请求权限类型6 */
  requestPermissionType6(): Observable<boolean> {
    const obs = this.onceGlobalCallback('onCallback6', this.mapBoolean);
    this.callNative(NativeMethod.PERM_6);
    return obs;
  }

  /********** 原生功能调用 **********/
  
  /** 执行功能1,返回操作结果 */
  executeFunction1(data: string): Observable<boolean> {
    const obs = this.onceGlobalCallback('onCallback7', this.mapSuccess);
    this.callNative(NativeMethod.FUNC_1, data);
    return obs;
  }

  /** 执行功能2,返回字符串数据 */
  executeFunction2(): Observable<string> {
    const obs = this.onceGlobalCallback('onCallback8', this.mapString);
    this.callNative(NativeMethod.FUNC_2);
    return obs;
  }

  /** 执行功能3,返回字符串数据 */
  executeFunction3(): Observable<string> {
    const obs = this.onceGlobalCallback('onCallback8', this.mapString);
    this.callNative(NativeMethod.FUNC_3);
    return obs;
  }

  /** 执行功能4,返回字符串数据 */
  executeFunction4(): Observable<string> {
    const obs = this.onceGlobalCallback('onCallback9', this.mapString);
    this.callNative(NativeMethod.FUNC_4);
    return obs;
  }

  /** 执行功能5,返回字符串流 */
  executeFunction5(): Observable<string> {
    return new Observable<string>((observer) => {
      let isActive = true;

      // 注册连续回调
      (window as any)['onCallback10'] = (raw: any) => {
        if (!isActive) {
          return;
        }
        this.ngZone.run(() => {
          try {
            observer.next(raw);
          } catch (error) {
            observer.error(error);
          }
        });
      };

      // 启动功能
      this.callNative(NativeMethod.FUNC_5);

      // 清理函数:停止并移除回调
      return () => {
        isActive = false;
        (window as any)['onCallback10'] = undefined;
        this.callNative(NativeMethod.FUNC_6);
      };
    });
  }

  /** 停止功能5 */
  stopFunction5(): void {
    this.callNative(NativeMethod.FUNC_6);
  }

  /** 完成初始化流程 */
  completeSetup(): void {
    this.callNative(NativeMethod.FUNC_7, 'APP_ID');
  }
}