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');
}
}