手把手搭建前端跨平台服务(IPlatform + iOS / Android / Web)

引言

在现代前端开发中,我们经常会遇到一个问题:同一套业务代码需要运行在不同平台上,比如 iOS App、Android App、Web 端,甚至小程序。

每个平台都有自己的差异:有的支持硬件能力(相机、震动、保存图片),有的完全不支持。直接在业务层调用原生功能,很容易出错:报错、崩溃,或者逻辑不统一。

为了统一管理这些差异,我们可以设计一个 前端跨平台服务(PlatformService) ,而 IPlatform 就是这个服务的核心契约。

Platform 的作用

  • 1.定义标准接口:告诉业务层"我可以调用哪些功能"
  • 2.提供安全兜底:即便某个平台不支持某个功能,也不会崩溃
  • 3.支撑 统一入口 + 生命周期管理 + JSBridge 通信,让跨平台调用变得简单可靠

简单来说,IPlatform 就像一份"合同":业务层只关心接口,不用管底层每个平台的差异。

在本文中,我们将手把手讲解如何从 接口定义 → BasePlatform → 具体平台实现 → JSBridge → 统一入口 → 生命周期管理,完整搭建一个企业级前端跨平台架构。

1.定义标准interface

在动手写下一行代码之前,我们必须先跳出实现细节,思考一个核心问题:我们的平台究竟要对外提供什么样的能力?

定义标准 Interface(接口)不仅是写代码,它实际上是在制定协议

它本质上就是在回答三个问题:

  1. 1.我要什么? (输入参数)
  2. 2.我给什么? (返回结果)
  3. 3.我保证能干什么? (功能定义)

所以第一步,我们是声明约定平台需要有哪些功能

我们只需要声明,一个标准的平台应该具备什么样的功能函数 (不需要实现:空函数)

这一层是抽象封装层,而不是具体实现

1.1功能分类

平台的功能大致能分为三类 平台信息类系统能力类通信能力类

1. 平台信息类

这类接口主要用于获取环境数据,不涉及任何操作。

  • 例如:

    • 平台判断: 当前是 iOS 还是 Android?
    • 设备信息: 屏幕宽高是多少?电量还剩多少?
    • 网络状态: 当前是 Wi-Fi 还是 5G?
    • 用户信息: 获取当前登录人的 ID 和 Token
2. 系统功能类

这类接口涉及调用硬件或底层能力,需要平台去执行动作。

  • 例如:

    • 拍照/相册: 唤起摄像头拍照,或从相册选图。
    • 扫码: 调起系统原生的扫码界面。
    • 地理位置: 获取经纬度坐标。
    • 本地存储: 往手机本地存入或读取数据。
3. 通信能力类

这类接口负责处理事件和通知。

  • 例如:

    • 事件监听: 监听用户按了返回键,或监听网络断开。
    • 消息推送: 在通知栏弹出消息。
    • 实时进度: 文件上传或下载时的进度回调。

1.2封装interface示例

这样我们可以得到一个定义:一个标准平台应该符合的标准interface

js 复制代码
// file: iplatform.ts

/**
 * 平台能力分类:
 * 1. 平台信息类(只读,描述性信息)
 * 2. 系统能力类(有副作用,需要权限)
 * 3. 通信能力类(底层 JSBridge)
 */

export interface IPlatformService {
  // ==========================
  // 平台信息类
  // ==========================

  /**
   * 获取系统信息
   * @returns Promise<{ os: string, version: string }>
   */
  getSystemInfo(): Promise<{ os: string; version: string }>;

  /**
   * 获取网络状态
   * @returns Promise<{ connected: boolean; type: string }>
   */
  getNetworkStatus(): Promise<{ connected: boolean; type: string }>;

  // ==========================
  // 系统能力类
  // ==========================

  /**
   * 保存图片到相册
   * @param url 图片地址
   * @returns Promise<true> 成功,false 或 reject 表示失败或不支持
   */
  saveImg(url: string): Promise<boolean>;

  /**
   * 触发振动
   * @param duration 振动时长,单位 ms
   * @returns Promise<void>
   */
  vibrate(duration: number): Promise<void>;

  /**
   * 调用相机拍照
   * @returns Promise<{ localPath: string }>
   */
  takePhoto(): Promise<{ localPath: string }>;

  // ==========================
  // 通信能力类 (Bridge)
  // ==========================

  /**
   * 调用 Native 方法
   * @param method 方法名
   * @param params 参数对象
   * @returns Promise<any>
   */
  callNative(method: string, params?: Record<string, any>): Promise<any>;

  /**
   * 注册 Native 回调事件
   * @param eventName 事件名
   * @param callback 回调函数
   */
  onNative(eventName: string, callback: (data: any) => void): void;

  /**
   * 移除 Native 回调事件
   * @param eventName 事件名
   * @param callback 可选,不传则移除该事件所有回调
   */
  offNative(eventName: string, callback?: (data: any) => void): void;
}

注意: 我们一般不把NativeCall封装到interfacePlatform.ts

你现在大概率会想

既然interface声明了JS_call_Native -----JS通信原生的能力

那么是不是应该也有 Native_call-JS---------原生通信JS的能力

从对称性的直觉上看,这非常合理

但是 架构的设计不追求对称,而是职责清晰

Platform它表达的是

"JS 主动能向平台要什么能力

JS → Native 是「能力调用」

  • 1.主动
  • 2.有返回值
  • 3.有结果
  • 4.有生命周期

🏀 所以它天然适合被定义成 函数

Native → JS 本质上是什么?

其实是事件通知

我们来看 Native 主动找 JS 的真实场景

  • 1.登录态失效
  • 2.网络状态变化
  • 3.支付结果通知
  • 4.App 前后台切换
  • 5.推送点击

你可以想想:

这些是"JS 主动要的吗"?

答案是:不是

Native → JS 是「事件 / 通知」

它的本质是:

"平台在某个时刻,告诉 JS 一件事发生了"

所以我们一般不把Native封装到InterfacePlatform里面

(很多新手在刚开始学习这个跨平台服务的时候都会犯这个问题,把Native_Call_Js也封装进去了)

1.3小结

InterfacePlatform 用于定义"平台应该具备哪些能力"

它是平台能力的声明与约束,而不涉及任何具体的实现。

2.安全兜底BasePlatform

2.1为什么需要BasePlatform

当我们写完interface定义完一个平台需要哪些能力之后,我们是不是就开始写这个平台的能力了呢?

比如说: AndroidService.js / IosService.js /WebService.js

其实不是

在实际业务开发中,一个非高频的情况就是

你定义的平台功能,不一定是全部的平台都能实现

比方说:你在interface定义一个叫震动的功能

js 复制代码
  /**
   * 触发振动
   * @param duration 振动时长,单位 ms
   * @returns Promise<void>
   */
  vibrate(duration: number): Promise<void>;   //注意是声明的空实现

这个功能 iosService.js /Android.js (苹果端/安卓端) 可以实现

但是这个功能在web端 webService.js可以实现吗?

你在接口里定义了一个炫酷的功能:vibrate()(震动)。

  • Android 说:"没问题,我有振动器 API。"
  • iOS 说:"简单,我有 Core Haptics。"
  • Web 傻了:"我跑在浏览器里,有的浏览器根本不支持震动,或者用户用的是台式机,牛魔的,你让我怎么震? "

我们知道,我们功能平台的类(ios/Android/web)是继承interface定义的类

所以,你声明了的功能是必须被实现的

那么我们在web Service要怎么实现这个震动的函数的呢?

显然是实现不了的

但是如果我们空着不写,业务层调用了webService.vibrate(),程序就会报错崩溃。

这就引出了 BasePlatform 的必要性。

与其让每个具体的平台去纠结----怎么处理我做不到的事,

不如我们在中间加一层 '安全防护网' ,这就是 BasePlatform

2.2BasePlatform干了什么?

简单来说

BasePlatform 就是在接口(理想)与实现(现实)之间,加了一个 "默认值系统"

意思是,它给全部在interface定义了的函数都写了一个默认的基础实现

我们学前端的同学可以这样思考

它就类似于我们前端的prop的default

当父组件没有传递具体的prop的时候,我们可以用default

这样保证了 前端在使用这个prop的时候,不会因为父组件没有传递,就报undefined的错误

在我们跨平台设计这里也是

在Baseplatform,我们给全部在interface的函数都定义了一个默认的基础实现

以震动功能为例

js 复制代码
//震动功能
 vibrate(_duration: number) {
    // 无副作用,直接 resolve
    return Promise.resolve()   //一个无副作用的兜底实现
  }

这样,我们的子类即使无法实现这个功能函数

那么业务层调用了webService.vibrate(),程序也不会因为某个平台没有实现,而报错崩溃。

2.3完整的BasePlatform示例

注意:BasePlatfrom 实现了interface的全部定义

而平台子类(ios/Android/web)继承BasePlatfrom

js 复制代码
// BasePlatform.ts
import { IPlatformService } from './iplatform'

/**
 * BasePlatform = 所有具体平台的安全兜底
 * 不支持的功能统一返回 reject,行为失败返回 resolve(false)
 */
export class BasePlatform implements IPlatformService {
  // ==========================
  // 平台信息类
  // ==========================
  getSystemInfo() {
    return Promise.reject(new Error('getSystemInfo not supported on this platform'))
  }

  getNetworkStatus() {
    return Promise.reject(new Error('getNetworkStatus not supported on this platform'))
  }

  // ==========================
  // 系统能力类
  // ==========================
  saveImg(_url: string) {
    return Promise.reject(new Error('saveImg not supported on this platform'))
  }

  vibrate(_duration: number) {
    // 无副作用,直接 resolve
    return Promise.resolve()
  }

  takePhoto() {
    return Promise.reject(new Error('takePhoto not supported on this platform'))
  }

  // ==========================
  // 通信能力类 (Bridge)
  // ==========================
  callNative(_method: string, _params?: Record<string, any>) {
    return Promise.reject(new Error(`callNative(${_method}) not supported on this platform`))
  }

  onNative(_eventName: string, _callback: (data: any) => void) {
    // 默认不做任何事情
  }

  offNative(_eventName: string, _callback?: (data: any) => void) {
    // 默认不做任何事情
  }
}

而对于有能力实现这些功能的平台类来说

比如说 AndroidService.js

子类继承之后,只需要覆盖这个方法就行

js 复制代码
//继承BasePlatform
class WebPlatform extends BasePlatform {

//然后重载这个函数

  vibrate(duration: number) {
    if (navigator.vibrate) {
      return navigator.vibrate(duration) ? Promise.resolve(true) : Promise.resolve(false)
    }
    return Promise.resolve(false)
  }
  
  
}

对于比如说 WebService.js不能够支持这个功能的子类,不需要重载函数

比如说 WebService.js

js 复制代码
class WebService extends BasePlatform {

     //无须重载
     执行WebService.vibrate()  函数时候,自动运行父类的逻辑

}

这样就保证了 统一规则 + 安全兜底 + 减少重复代码

这样你在实现业务的时候就很

1.调用的时候 : 对于写 UI 逻辑的同学,他只需要写 platform.vibrate()

他不需要关心现在是 Web 还是 App,也不需要担心这行代码会不会把整个程序搞崩。因为他知道,底层已经有人帮他"兜底"了。

2.实现的时候: 对于负责 Web 端的同学,他可以"偷懒"。

既然 Web 实现不了震动,他甚至不需要在 WebService.js 里写这个函数。BasePlatform 就像一个漏斗,子类没接住的功能,父类全接住了

3.具体平台实现Concrete Implementation

既然有了接口(合同)和 BasePlatform(兜底),现在需要一个真正干活的人。

3.1什么是具体平台实现?
  • 它是继承了 BasePlatform 的子类。
  • 负责在特定平台上把接口方法"填满",让功能真正可用
  • 核心目标:对外保持接口一致,对内处理平台差异
3.2为什么需要具体平台实现?
  • 不同平台 的功能接口不同不同

    • 1.iOS 可以调用 Core Haptics 来震动
    • 2.Android 可以调用 Vibrator API
    • 3.Web 浏览器可能不支持震动
  • 如果没有具体实现

    • 1.业务层只能使用默认兜底方法(BasePlatform),功能"白板"
    • 2.用户体验受限,功能无法真正落地

BasePlatform 是"安全网",具体实现才是"真正动手的人"。

3.3具体平台实现的原则
  1. 1.只覆盖自己支持的方法

    • 子类继承 BasePlatform,先默认兜底
    • 只需要实现平台支持的接口,不支持的保持 BasePlatform 的默认实现
  2. 2.对内处理差异,对外保持接口一致

    • 内部处理权限、参数转换、回调逻辑
    • 对业务层来说,API 依然是统一的 saveImg()vibrate()takePhoto()
  3. 3.调用底层 JSBridge / 原生能力

    • 所有系统能力最终依赖 JSBridge
3.4具体平台示例

以iosService平台为例

js 复制代码
// platform_ios.ts
import { BasePlatform } from './BasePlatform'
import { IPlatformService } from './iplatform'

/**
 * iOS 平台具体实现
 * 继承 BasePlatform,覆盖自己支持的方法
 */
export class CPlatformAppiOS extends BasePlatform implements IPlatformService {
  
  // ==========================
  // 系统能力类
  // ==========================

  /**
   * 保存图片到相册
   */
  saveImg(url: string): Promise<boolean> {
    // 调用底层 JSBridge 的 callNative
    return this.callNative('SaveImg', { url })
      .then(() => true)
      .catch(() => false) // 如果调用失败,统一返回 false
  }

  /**
   * 触发振动
   */
  vibrate(duration: number): Promise<void> {
    return this.callNative('Vibrate', { duration })
  }

  /**
   * 调用相机拍照
   */
  takePhoto(): Promise<{ localPath: string }> {
    return this.callNative('TakePhoto')
  }

  // ==========================
  // 通信能力类 (Bridge)
  // ==========================

  /**
   * 调用 Native 方法
   */
  callNative(method: string, params?: Record<string, any>): Promise<any> {
    return new Promise((resolve, reject) => {
      if (!(window as any).WebViewJavascriptBridge) {
        return reject(new Error('WebViewJavascriptBridge not ready'))
      }

      (window as any).WebViewJavascriptBridge.callHandler(
        method,
        params,
        (response: any) => {
          resolve(response)
        }
      )
    })
  }

  /**
   * 注册 Native 回调事件
   */
  onNative(eventName: string, callback: (data: any) => void) {
    (window as any).WebViewJavascriptBridge.registerHandler(eventName, callback)
  }

  /**
   * 移除 Native 回调事件
   */
  offNative(eventName: string, callback?: (data: any) => void) {
    // iOS 端 JSBridge 通常没有直接移除接口,可做空实现或维护回调映射
  }
}

说明

  1. 1.继承 BasePlatform

    • 只覆盖 iOS 支持的方法
    • 不支持的功能(比如某些特殊能力)仍然沿用 BasePlatform 默认实现 → 安全兜底
  2. 2.统一返回规范

    • 成功 → resolve / 返回数据
    • 失败 → reject 或 resolve(false)
  3. 3.内部调用 JSBridge

    • iOS 端通过 WebViewJavascriptBridge 与 Native 通信
    • 所有系统能力最终通过 callNative 调用
  4. 4.对外接口一致

    • 业务层调用 saveImg()vibrate()takePhoto() 时,无需关心底层差异
3.5对于分层架构的理解

当我们把代码写到 具体实现 (Concrete Implementation )这一层时,我们其实是在做翻译工作

我们把统一的 震动功能 vibrate() 指令,翻译成了各个平台听得懂的方言。因为有了前面的 InterfaceBasePlatform 铺路,

这种翻译工作变得异常轻松且安全。即便哪天你要接入一个 '鸿蒙平台'

你只需要写一个 HarmonyService 并继承 BasePlatform,哪怕你只实现了一个功能,剩下的 99 个功能也会自动拥有'保底'能力。"

4.底层通信JSbridge

在前一步,我们看到 iOS 具体平台实现

js 复制代码
 /**
   * 调用相机拍照
   */
  takePhoto(): Promise<{ localPath: string }> {
    return this.callNative('TakePhoto')
    
    注意这个 //this.callNative('TakePhoto')
  }

这里的 callNative 方法只是把请求"交给了JSbridge通信桥梁"

4.1什么是JSbridge?

这里我不打算花篇幅介绍JSbridge的通信机制

我这里只简短介绍一下,初步了解可以去看我的

什么是H5混合开发? 新手指南--- 里面对JSbridge有初步的介绍

(我以后有空会写一篇博客介绍JSbridge的通信机制)

简单来说:

  • 1.当前端需要调用手机的硬件能力(比如拍照、震动、保存图片)时,JS 本身无法直接操作原生能力
  • 2.所以我们需要通过 JSBridge 发起请求给 App 客户端
  • 3.App 客户端接收到请求 → 执行操作 → 将结果返回给 JS

协作链条

JS (发出指令)Bridge (格式转换)Native (执行动作)OS (调用硬件)

JSbridge 就是 原生语言(Android和IOS) 和 前端JS之间的通信桥梁

它主要由两个部分组成 JS_Call_Native.js 和Native_Call_JS.js

4.2JS_Call_Native.js

主要功能:

  • JS 向 Native 发起请求
  • 负责参数封装、回调注册
  • 在 iOS 中会调用 WebViewJavascriptBridge.callHandler()
  • 在 Android 中会调用 window.AndroidBridge.method(...)

以JS_Call_Android为例

  • 1.默认通信机制是 API 注入
  • 2.客户端会在 WebView 中注入一个全局对象,例如 window.AndroidBridge
  • 3.JS 可以通过这个对象发送事件和参数

以Android_call_js为例

js 复制代码
// CJSCallAndroid.ts
/**
 * JS 调用 Native 的桥梁类
 * JS 通过 callNative 调用 Android 注入的方法
 */
export class CJSCallAndroid {
  private mBridge: any;

  constructor() {
    // 获取原生注入的 WebViewJavascriptBridge 对象
    this.mBridge = (window as any).WebViewJavascriptBridge;
  }

  /**
   * 调用 Native 方法
   * @param event Native 注册的方法名
   * @param params JS 传给 Native 的参数
   * @param callback Native 执行完成后回调给 JS
   */
  callNative(event: string, params: any, callback: (ret: any) => void) {
    this.mBridge.callHandler(event, params, (retString: string) => {
      // Native 返回的结果通常是 JSON 字符串,需要解析
      let ret;
      try {
        ret = JSON.parse(retString);
      } catch {
        ret = retString;
      }

      // 调用回调,将结果交给业务逻辑
      callback(ret);
    });
  }
}

默认通信机制是API注入

(不知道什么是API注入的,去看什么是H5混合开发? 新手指南)

主要是在拿到客户端开发的同学给我们注入的全局对象

js 复制代码
  constructor() {
    // 获取原生注入的 WebViewJavascriptBridge 对象
    this.mBridge = (window as any).WebViewJavascriptBridge;
  }

通过这个全局对象,进行通信

传递 事件 和 参数

js 复制代码
/**
   * 调用 Native 方法
   * @param event Native 注册的方法名
   * @param params JS 传给 Native 的参数
   * @param callback Native 执行完成后回调给 JS
   */
  callNative(event: string, params: any, callback: (ret: any) => void) {
    this.mBridge.callHandler(event, params, (retString: string) => {
      // Native 返回的结果通常是 JSON 字符串,需要解析
      let ret;
      try {
        ret = JSON.parse(retString);
      } catch {
        ret = retString;
      }

      // 调用回调,将结果交给业务逻辑
      callback(ret);
    });
  }

前端的同学可以理解为前端中的"事件监听"

1.前端发送一个事件 (携带参数)

2.客户端监听这个事件,并执行对应的处理函数

3.客户端将结果result返回给前端

业务中使用示例

js 复制代码
// JS 调用 Native 拍照
const jsCall = new CJSCallAndroid();  //创建实例

                  //事件名          事件参数       回调函数
jsCall.callNative('takePhoto', { quality: 80 }, (res) => {
  console.log('拍照结果:', res.localPath);
});
4.3Native_Call_JS.js

主要功能:

  • Native 向 JS 发送事件或回调
  • 比如拍照完成后返回图片路径
  • JS 端通过注册好的回调接收结果

以Android_call_js为例

js 复制代码
// CAndroidCallJS.ts
/**
 * Native 调用 JS 的桥梁类
 * 通过 register 方法,JS 可以暴露方法给 Native 调用
 */
export class CAndroidCallJS {
  private mBridge: any;

  constructor() {
    // 获取原生注入的 WebViewJavascriptBridge 对象
    this.mBridge = (window as any).WebViewJavascriptBridge;
  }

  /**
   * 初始化 Bridge
   * Native 调用前必须先 init
   */
  init() {
    this.mBridge.init((msg: string, resp: (data: any) => void) => {
      // Native 发消息时必须调用 resp 回复
      resp({});
    });
  }

  /**
   * 注册 JS 方法供 Native 调用
   * @param funcName 方法名,Native 调用时使用
   * @param handler JS 方法实现,接收 Native 传来的参数并返回结果
   */
  register(funcName: string, handler: (data: any) => any) {
    this.mBridge.registerHandler(
      funcName,
      (data: string, callback: (ret: string) => void) => {
        // data 是 Native 传来的 JSON 字符串
        let parsedData;
        try {
          parsedData = data ? JSON.parse(data) : null;
        } catch {
          parsedData = data;
        }

        // 执行 JS 方法,获取返回结果
        const ret = handler(parsedData);

        // 将结果返回给 Native
        callback(JSON.stringify(ret));
      }
    );
  }
}

JS 通过注册全局方法接收 Native 主动发送的事件

js 复制代码
/**
   * 注册 JS 方法供 Native 调用
   * @param funcName 方法名,Native 调用时使用
   * @param handler JS 方法实现,接收 Native 传来的参数并返回结果
   */
  register(funcName: string, handler: (data: any) => any) {
    this.mBridge.registerHandler(
      funcName,
      (data: string, callback: (ret: string) => void) => {
        // data 是 Native 传来的 JSON 字符串
        let parsedData;
        try {
          parsedData = data ? JSON.parse(data) : null;
        } catch {
          parsedData = data;
        }

        // 执行 JS 方法,获取返回结果
        const ret = handler(parsedData);

        // 将结果返回给 Native
        callback(JSON.stringify(ret));
      }
    );
  }

Native 通过 evaluateJavascript 或类似方法调用 JS

(前端同学不需要关注)

业务中使用示例

js 复制代码
// Native 调用 JS 方法
const androidCallJS = new CAndroidCallJS(); //创建

androidCallJS.init();//注意这里需要初始化

androidCallJS.register('showAlert', (data) => {
  alert(`Native 调用 JS: ${JSON.stringify(data)}`);
  return { ok: true };
});
4.4不同平台实现的演示

iosService.js实现

js 复制代码
//导入接口
import { CJSCalliOS } from './js_call_ios';
//导入父类
import { BasePlatform } from './base_platform';

export class CPlatformAppiOS extends BasePlatform {
  private bridge = new CJSCalliOS();

  // 调用拍照
  takePhoto(options?: any) {
    return this.bridge.callNative('TakePhoto', options);
  }

  // 调用震动
  vibrate() {
    return this.bridge.callNative('Vibrate');
  }
}

AndroidService.js实现

js 复制代码
import { CJSCallAndroid } from './js_call_android';
import { BasePlatform } from './base_platform';

export class CPlatformAppAndroid extends BasePlatform {
  private bridge = new CJSCallAndroid();

  takePhoto(options?: any) {
    return this.bridge.callNative('takePhoto', options, (res) => res);
  }

  vibrate() {
    return this.bridge.callNative('vibrate', {}, (res) => res);
  }
}

WebService.js

js 复制代码
import { BasePlatform } from './base_platform';

export class CPlatformAppWeb extends BasePlatform {

//你可以重载 也可以不重载
  takePhoto() {
    alert('Web 平台不支持拍照');
    return Promise.resolve(false);
  }

  vibrate() {
    if (navigator.vibrate) {
      navigator.vibrate(200);
      return Promise.resolve(true);
    }
    return Promise.resolve(false);
  }
}

5.统一入口 (The Factory)

5.1统一入口的背景

当我们写好了:

  1. 1.接口(IPlatformService) → 定义"我需要什么能力"
  2. 2.BasePlatform → 安全兜底,保证调用不会报错
  3. 3.Concrete Platform → 各个平台真实实现功能

业务层已经可以调用平台能力了

例如:拍照

js 复制代码
const platform = new CPlatformAppiOS();
platform.takePhoto();

但问题来了:

  • 业务层真的要知道当前运行的是哪个平台吗?

    • iOS → CPlatformAppiOS
    • Android → CPlatformAppAndroid
    • Web → CPlatformWeb
  • 如果业务层直接 new,不仅增加耦合,还容易出错:

    • 写成 new CPlatformAppiOS() → Android 或 Web 上会报错
    • 业务层每次都要写判断逻辑 → 冗余、重复

总结问题

业务层的核心需求是 "我只想调用功能,不关心底层是哪个平台"

既然业务层只想"调用功能",那谁来决定使用哪个具体平台呢?

我们需要一个 统一入口,在应用启动时根据运行环境自动选择平台实现,并对外暴露统一接口。

5.2统一入口实例化示例

js 复制代码
import { CPlatformAppiOS } from './platform_app_ios';
import { CPlatformAppAndroid } from './platform_app_android';
import { CPlatformAppWeb } from './platform_app_web';
import { getPlatformRuntime } from './platform_detect';

export class PlatformService {
  private static _instance: any;

  static instance() {
    if (!this._instance) {
      const runtime = getPlatformRuntime(); // ← 运行时判断平台
      switch (runtime) {
        case 'iOS_APP':
          this._instance = new CPlatformAppiOS();
          break;
        case 'ANDROID_APP':
          this._instance = new CPlatformAppAndroid();
          break;
        default:
          this._instance = new CPlatformAppWeb();
      }
    }
    return this._instance;
  }
}

1.单例模式_instance 保证全局只有一个平台实例

2.运行时判断getPlatformRuntime() 返回当前环境

3.动态分发 → 根据平台实例化对应的具体平台实现

PlatformService 就是这个分发中心 / 工厂

  • 1.运行时判断当前平台
  • 2.实例化对应的 Concrete Platform
  • 3.返回统一接口给业务层

这样业务层就可以直接调用:

js 复制代码
//拍照
PlatformService.instance().takePhoto();
//震动
PlatformService.instance().vibrate();

不需要关心到底是 iOS、Android 还是 Web

6.生命周期管理

在前面我们已经实现了:

  1. 1.接口(IPlatformService) → 定义了平台能力
  2. 2.BasePlatform → 安全兜底,避免业务层调用报错
  3. 3.Concrete Platform → 各平台真实实现能力
  4. 4.JSBridge → 底层通信桥梁
  5. 5.统一入口(PlatformService / Factory) → 根据环境实例化对应平台

此时,如果业务层一直使用 PlatformService.instance() ,平台实例会长期驻留在内存中,可能会有以下问题:

  • 1.资源未释放 → 比如注册了监听事件、定时器等
  • 2.事件重复注册 → 再次初始化时可能重复触发
  • 3.内存泄漏 → 长期运行的 SPA / Hybrid App,尤其是移动端

所以,我们需要为 平台实例增加生命周期管理

6.1生命周期四阶段

Concrete Platform 为例,通常可以设计四个阶段:

阶段 方法 作用
构造 create() 保存依赖,不创建资源
初始化 setup() 创建资源,只执行一次(比如 JSBridge、监听器)
激活 active() 提供 API,响应业务调用
销毁 destroy() 释放资源、解除监听,确保不可逆
6.2Interface 定义标准

IPlatformService 或类似的接口里,只定义生命周期方法的"合同"

js 复制代码
interface ILifecycle {
  create(): void;
  setup(): void;
  active(): void;
  destroy(): void;
}
6.3 BasePlatform 提供基础实现

BasePlatform / NullPlatform 会给出 兜底实现,比如:

js 复制代码
class BasePlatform implements ILifecycle {
  create() {}
  setup() {}
  active() {}
  destroy() {}
}

目的

  • 1.避免具体平台忘写某个生命周期方法 → 安全兜底
  • 2.提供默认行为 → 业务层可以放心调用
  • 3.如果某个功能在某个平台不支持,也不会报错
6.4 Concrete Platform 具体实现

在具体平台类(CPlatformAppiOSCPlatformAppAndroid)里 覆盖 BasePlatform 的方法:

以ios为例

js 复制代码
//依旧继承Base
class CPlatformAppiOS extends BasePlatform {
  setup() {
    // 真正初始化 JSBridge
    this.bridge = window.WebViewJavascriptBridge;
  }

  active() {
    // 对外提供 API
    console.log('iOS 平台激活完成');
  }

  destroy() {
    // 清理资源、解除监听
    this.bridge = null;
  }
}

只有具体平台知道如何真正"激活"和"释放"资源

BasePlatform 只是提供空壳 / 安全兜底

6.5使用示例
js 复制代码
<template>
  <div>
    <h2>平台信息</h2>
    <!-- 显示操作系统和版本 -->
    <p>系统: {{ systemInfo.os }} {{ systemInfo.version }}</p>

    <h2>操作示例</h2>
    <!-- 点击按钮触发拍照 -->
    <button @click="takePhoto">拍照</button>

    <!-- 如果拍照完成,显示照片本地路径 -->
    <p v-if="photoPath">照片路径: {{ photoPath }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'

//导入
import platform from './platform'

export default defineComponent({
  name: 'PlatformDemo',
  setup() {

    // 保存系统信息
    const systemInfo = ref<{ os: string; version: string }>({ os: '', version: '' })

    // 保存拍照得到的本地图片路径
    const photoPath = ref<string>('')

 
    // 生命周期钩子
    // 组件挂载完成后,获取系统信息
    onMounted(async () => {
      try {
        // 调用平台统一接口获取系统信息
        systemInfo.value = await platform.getSystemInfo()
      } catch (err) {
        console.error('获取系统信息失败:', err)
      }
    })


    // 拍照
    const takePhoto = async () => {
      try {
        // 调用平台接口唤起相机拍照
        const photo = await platform.takePhoto()
        
        // 拍照完成后保存图片路径到响应式变量
        photoPath.value = photo.localPath
      } catch (err) {
      
        console.error('拍照失败:', err)
      }
    }


    return {
      systemInfo, // 系统信息
      photoPath,  // 拍照路径
      takePhoto,  // 拍照方法
    }
  },
})
</script>

总结

理解这套体系,你可以写出一套 可扩展、可维护、跨平台统一调用的前端平台服务

本文内容基于实际企业级项目中的 IPlatform 架构设计编写

文中示例和思路均来源于真实项目的改写,本文的全部示例不涉及源码

可供读者在企业级项目中参考和落地。

相关推荐
AI_56782 小时前
Webpack从“配置到提速”,4步解决“打包慢、体积大”问题
前端·javascript·vue.js
江启年2 小时前
对useEffect和 useMemo的一些总结与感悟
前端
中微子2 小时前
Web 安全:跨域、XSS 攻击与 CSRF 攻击
前端
2501_948122632 小时前
React Native for OpenHarmony 实战:Steam 资讯 App 关于我们页面
javascript·react native·react.js·游戏·harmonyos
Aotman_2 小时前
Vue el-table 字段自定义排序
前端·javascript·vue.js·es6
LaiYoung_2 小时前
🛡️ 代码质量的“埃癸斯”:为什么你的项目需要这面更懂业务的 ESLint 神盾?
前端·代码规范·eslint
AAA阿giao2 小时前
qoder-cli:下一代命令行 AI 编程代理——全面解析与深度实践指南
开发语言·前端·人工智能·ai编程·mcp·context7·qoder-cli
我有一棵树2 小时前
Vite 7 中 dev 没样式、build 却正常:一次由 CSS import 位置引发的工程化问题
前端·javascript·vue.js
@Autowire2 小时前
CSS 中 px、%、vh、vw 这四种常用单位的区别
前端·css