iOS、安卓、鸿蒙APP如何运行同一个小程序

iOS、安卓、鸿蒙APP如何运行同一个小程序

公司已经做好的会员服务小程序,能不能同时放进 iOS、安卓和鸿蒙 APP?对业务团队来说,页面、接口和办理流程已经有了,新增一个客户端,当然希望继续使用原来的成果。按三个平台分别重写页面,后续每次修改活动规则、增加表单字段,还要再跟着维护三遍。

借助小程序容器,可以把业务页面和逻辑保留在同一套小程序代码中,由三个 APP 分别嵌入对应的小程序 SDK,提供加载和运行环境。客户端团队完成宿主接入,业务团队继续开发小程序,管理平台负责版本上传和发布。同一项业务就能进入不同系统的 APP,后续更新也有了统一的管理入口。

以 FinClip 为例,可以让三个 APP 打开同一个测试小程序,再逐步接入登录、支付等业务能力。接入示例使用官方 SDK 接口,并补充项目侧状态判断和错误处理;尖括号中的版本、凭据和服务地址需要替换成项目配置。代码放入已有宿主工程,省略工程结构及部分导入声明。

小程序与三个客户端的运行关系

小程序代码包含页面结构、样式、业务脚本和资源文件。容器在 APP 内加载代码包,提供页面渲染、脚本执行、路由、生命周期以及设备能力调用等运行支持。用户在 APP 首页点击某项服务,打开的就是运行在宿主内部的小程序页面。

iOS APP 使用 iOS 版 SDK,安卓 APP 使用 Android 版 SDK,鸿蒙 APP 使用 HarmonyOS 版 SDK。各端运行时对接自己的系统环境,向小程序提供相应的组件和接口,业务开发者可以复用页面布局、表单校验、查询逻辑和后端请求。三端共用业务代码,各自保留原有客户端工程和原生导航。

例如,会员积分小程序里有积分明细、兑换列表和订单详情。业务团队维护一套页面,三个客户端都从各自的入口打开它。积分怎么计算、订单怎么生成仍由原有业务后台处理,小程序容器负责让页面在不同 APP 中运行。

平台配置与小程序准备

开始集成前,需要在 FinClip 管理平台创建小程序,取得平台分配的小程序 AppID,再配置宿主应用及各端应用标识,获取相应的 SDK Key 和 SDK Secret。小程序还要与允许承载它的宿主应用建立关联,客户端才能按平台配置打开服务。

接入时可以把信息分成两组:SDK 初始化使用宿主凭据和平台服务地址;打开具体业务时使用小程序 AppID、目标页面与业务参数。宿主凭据标识的是哪个 APP 在接入平台,小程序 AppID 标识的是要运行哪一项业务,两者在工程配置中分别维护。

已有微信小程序可以导入 FinClip 开发者工具,检查组件和 API 兼容情况,复用受支持的页面与逻辑。登录、支付、消息以及微信云开发等平台相关能力,需要对接自有 APP 和后端服务。为了验证 SDK 接入,先准备一个只显示入口参数的小程序页面,避免把容器接入与业务接口问题混在一起。

共用测试小程序

在小程序项目中注册 pages/index/index。项目侧测试示例的 app.json 如下;已有工程只需合并页面配置。


{

  "pages": ["pages/index/index"],

  "window": {

    "navigationBarTitleText": "多端运行测试"

  }

}

pages/index/index.js 读取启动参数,pages/index/index.wxml 将参数显示出来:


Page({

  data: { source: '未传入' },

  onLoad(options) {

    this.setData({ source: options.source || '未传入' });

  }

});


<view>

  <text>小程序已打开,入口参数:{{source}}</text>

</view>

用开发者工具编译并上传代码包,完成审核、上架和宿主关联。三端示例统一使用同一个 <MINIAPP_ID>,通过 source=iossource=androidsource=harmony 区分入口。source 只是客户端传入的测试标记,不代表系统检测结果。示例按 AppID 打开正式版;体验版和开发版应使用对应的二维码打开接口,并配置体验成员。

iOS:依赖引入、初始化与打开页面

在现有 Podfile 的 APP target 中加入官方依赖,将 <IOS_SDK_VERSION> 替换为选定版本,执行 pod install,再通过生成的 .xcworkspace 打开工程。


pod 'FinApplet', '<IOS_SDK_VERSION>'

在 AppDelegate 的应用启动方法中加入初始化片段。示例使用 Objective-C;Swift 工程可以按官方集成方式导入同一 SDK,再按 Swift 调用形式接入。


#import <FinApplet/FinApplet.h>



// 放在应用启动方法内,初始化一次。

FATStoreConfig *store = [[FATStoreConfig alloc] init];

store.sdkKey = @"<IOS_SDK_KEY>";

store.sdkSecret = @"<IOS_SDK_SECRET>";

store.apiServer = @"<FINCLIP_API_SERVER>";



FATConfig *config = [FATConfig configWithStoreConfigs:@[store]];

NSError *initError = nil;

BOOL ready = [[FATClient sharedClient] initWithConfig:config

                                               error:&initError];

if (!ready) {

    NSLog(@"小程序 SDK 初始化失败:%@", initError);

}

宿主保存初始化结果,成功后启用业务入口。在当前可见的 UIViewController 中响应按钮点击,使用 FATAppletRequest 指定小程序和目标页面。代码中的 self 是当前页面控制器,打开操作在主线程执行。


FATAppletRequest *request = [[FATAppletRequest alloc] init];

request.appletId = @"<MINIAPP_ID>";

request.apiServer = @"<FINCLIP_API_SERVER>";

request.startParams = @{

    @"path": @"/pages/index/index",

    @"query": @"source=ios"

};



[[FATClient sharedClient] startAppletWithRequest:request

    InParentViewController:self

    completion:^(BOOL result, FATError *error) {

        if (!result) {

            NSLog(@"小程序打开失败:%@", error);

        }

    }

    closeCompletion:^{

        NSLog(@"已返回宿主页面");

    }];

测试页显示 ios,即可确认代码包加载、页面跳转和参数传递已经连通。接入相机、定位等业务时,再补充相应权限描述和扩展模块。

安卓:初始化状态与业务入口

按官方集成指引配置 FinClip Maven 仓库后,在 APP 模块的 build.gradle 中加入 SDK 依赖。仓库地址与访问配置由工程维护,<ANDROID_SDK_VERSION> 替换为项目使用的固定版本。


dependencies {

    implementation 'com.finogeeks.lib:finapplet:<ANDROID_SDK_VERSION>'

}

同时按该版本接入文档配置原生库打包与混淆规则,在使用代码压缩的工程中加入:


-keep class com.finogeeks.** {*;}

初始化放入宿主现有 Application。Kotlin 示例使用 FinStoreConfigFinAppConfigFinAppClient 和 FinCallback 的官方接口;类名 HostApplication、状态字段及提示语是项目侧示例,导入声明由 IDE 从已安装 SDK 补齐。若项目已有 Application,将字段和初始化逻辑合入原类,并确认 Manifest 指向该类。


class HostApplication : android.app.Application() {

    @Volatile var finclipReady = false

        private set



    override fun onCreate() {

        super.onCreate()

        if (FinAppClient.isFinAppProcess(this)) return



        val store = FinStoreConfig(

            "<ANDROID_SDK_KEY>",

            "<ANDROID_SDK_SECRET>",

            "<FINCLIP_API_SERVER>",

            "<FINCLIP_APM_SERVER>",

            "/api/v1/mop/",

            "",

            "MD5",

            false,

            true

        )

        val config = FinAppConfig.Builder()

            .setFinStoreConfigs(listOf(store))

            .build()



        FinAppClient.init(this, config, object : FinCallback<Any?> {

            override fun onSuccess(result: Any?) {

                finclipReady = true

            }

            override fun onError(code: Int, error: String?) {

                finclipReady = false

                android.util.Log.e("FinClip", "初始化失败:$code $error")

            }

            override fun onProgress(status: Int, info: String?) {}

        })

    }

}

MD5 是示例中的 SDK 通信配置值,需要与平台配置一致;末尾两个布尔值分别关闭返回数据加密、开启基础库预加载。APM 地址填写平台的数据上报地址。进程判断也应覆盖其他宿主级初始化,避免小程序进程重复启动整套业务组件。

Activity 的按钮点击处理使用真实的 startApplet 接口。只有初始化成功才继续打开,失败结果交回当前页面处理。


val app = application as HostApplication

if (!app.finclipReady) {

    android.widget.Toast.makeText(

        this, "小程序服务尚未就绪", android.widget.Toast.LENGTH_SHORT

    ).show()

} else {

    val request = IFinAppletRequest.fromAppId(

        "<FINCLIP_API_SERVER>", "<MINIAPP_ID>"

    ).setStartParams(mapOf(

        "path" to "/pages/index/index",

        "query" to "source=android"

    ))

    FinAppClient.appletApiManager.startApplet(

        this, request, object : FinCallback<String?> {

            override fun onSuccess(result: String?) {}

            override fun onProgress(status: Int, info: String?) {}

            override fun onError(code: Int, error: String?) {

                android.util.Log.e("FinClip", "打开失败:$code $error")

            }

        }

    )

}

打开接口接收当前 Activity,测试页应显示 android。初始化成功却打不开时,优先核对小程序发布状态、关联关系和请求的服务地址。

鸿蒙:HAR 依赖与 Navigation 接入

鸿蒙可以采用线上依赖或本地 HAR 包。以本地包为例,将同一交付版本的 FinClipSDK.har 和 FinClipSDKCore.har 放进工程根目录的 har 文件夹,在 entry/oh-package.json5 中合并:


{

  "dependencies": {

    "@finclip/sdk": "file:../har/FinClipSDK.har"

  }

}

在工程根目录的 oh-package.json5 中合并底层依赖覆盖配置,随后执行 ohpm install


{

  "overrides": {

    "@finclip/sdk-core": "file:./har/FinClipSDKCore.har"

  }

}

依照接入指引,在根目录 build-profile.json5 的目标 app.products 元素内合并配置;JSON 片段均保留工程已有字段:


{

  "buildOption": {

    "strictMode": {

      "useNormalizedOHMUrl": true

    }

  }

}

entry/src/main/module.json5 的 module.requestPermissions 中至少配置网络权限:


{

  "name": "ohos.permission.INTERNET"

}

启动方式采用官方 Navigation 方案,该方式文档标注最低支持 SDK 1.1.0。小程序使用宿主已挂载的 NavPathStack 打开,因此无需额外注册承载小程序的 Ability。将示例加入工程已登记的入口页面,已有 Navigation 工程则复用自己的页面栈。


import common from '@ohos.app.ability.common';

import {

  EAppletStartMode, FinAppClient,

  IFinAppConfig, IFinAppStartMode

} from '@finclip/sdk';



@Entry

@Component

struct Index {

  @State pageInfos: NavPathStack = new NavPathStack();

  @State status: string = '等待打开';

  client?: FinAppClient;



  async openMiniProgram() {

    try {

      if (!this.client) {

        const config: IFinAppConfig.IFinAppConfig = {

          finStoreConfigs: [{

            apiServer: '<FINCLIP_API_SERVER>',

            sdkKey: '<HARMONY_SDK_KEY>',

            sdkSecret: '<HARMONY_SDK_SECRET>',

            cryptType: 'md5'

          }]

        };

        const mode: IFinAppStartMode = {

          startMode: EAppletStartMode.Navigation,

          routerState: this.pageInfos,

          uiContext: this.getUIContext()

        };

        this.client = FinAppClient.init(

          config,

          getContext(this) as common.UIAbilityContext,

          '',

          mode

        );

      }



      this.status = '正在打开';

      await this.client.startApplet({

        appId: '<MINIAPP_ID>',

        apiServer: '<FINCLIP_API_SERVER>',

        startParams: {

          path: '/pages/index/index',

          query: 'source=harmony'

        }

      });

      this.status = '打开调用已返回,请检查小程序页面';

    } catch {

      this.status = '调用异常,请查看 SDK 日志后重试';

    }

  }



  @Builder

  PageMap(name: string) {

    // 在已有工程中保留宿主原有的页面路由映射。

  }



  build() {

    Navigation(this.pageInfos) {

      Column({ space: 16 }) {

        Text(this.status)

        Button('打开测试小程序')

          .onClick(() => { this.openMiniProgram(); })

      }

    }

    .mode(NavigationMode.Stack)

    .id('root')

    .title('小程序接入测试')

    .navDestination(this.PageMap)

  }

}

openMiniProgram 是页面自定义方法,内部调用的是 SDK 的 FinAppClient.init 和 startApplet。初始化在用户点击后执行,此时页面已经创建,能够提供 UI 上下文;routerState 与页面上的 Navigation 使用同一个实例。示例将通信方式显式设为 md5,实际值按平台配置填写。已有工程统一保存客户端实例,不随页面重建重复初始化 SDK。

点击按钮后,小程序应显示 harmonystartApplet 的调用返回并不单独作为页面成功展示的证据,实际验收以测试页出现、参数正确和 SDK 日志为准。

宿主登录与设备能力对接

小程序页面能够打开后,接下来要让它接上 APP 已有的业务环境。比如用户已经登录 APP,进入会员小程序时,应该能够继续查询自己的积分和订单,无需再走一遍注册流程。

项目可以通过容器提供的扩展机制,把获取业务登录态、发起支付、打开原生页面等操作封装为统一的宿主能力。小程序调用约定接口,各端宿主分别完成原生实现,再按一致的数据结构返回结果。登录过程由企业账号服务衔接,业务后台继续校验用户身份和访问权限。

对于扫码、拍照、定位等能力,三个客户端需要分别配置系统权限、接入相关 SDK 模块。业务侧保持统一的调用约定,平台差异由运行时和宿主适配处理。后续再增加新的小程序时,已经接好的登录、支付和设备能力就能继续复用。

返回行为也要与 APP 原有体验接上:小程序内部返回上一页,退出小程序则回到宿主入口;业务完成后需要刷新原生列表的,由宿主接收办理结果并更新页面。用户感受到的是同一个 APP 内连续的服务流程。

三端联调与发布检查

三台设备使用同一个小程序 AppID,分别从宿主按钮打开测试页。先看页面能否显示,再核对参数和返回行为,比直接接入完整业务更容易定位接入问题。

检查项操作与预期结果未通过时检查
初始化iOS 返回成功;安卓收到成功回调;鸿蒙完成初始化调用,并结合后续打开结果验证各端凭据、应用标识、平台地址和网络
页面加载三端都显示“多端运行测试”页面上架状态、宿主关联、AppID 和页面路径
参数传递分别显示 ios、android、harmonystartParams.query 与页面 onLoad的参数读取
返回宿主关闭小程序后回到原入口,入口仍能再次使用页面控制器、Activity 或 Navigation 页面栈
业务更新修改测试页文字并发布新版,按 SDK 更新策略重新进入后确认版本发布范围、实际加载版本和本地缓存策略

测试页通过后,将相同的打开方式接到首页宫格、活动位或消息入口,再把目标路径换成真实业务页面。例如订单消息可以携带订单标识,直接进入详情页。随后在三个宿主中完成登录、查询、提交、返回的全流程,检查设备授权、网络中断和前后台切换。错误日志同时记录宿主版本、SDK 版本及小程序版本,方便区分客户端接入问题和业务更新问题。

统一发布与业务独立更新

三端接入完成后,小程序代码包进入同一个管理平台,经过体验验证、审核和发布,再由各端 SDK 获取适用版本。团队可以统一管理小程序资产,也可以通过灰度策略控制新版本的发布范围,结合各端测试结果逐步上线。

日常调整会员页面、增加活动专区、修改表单等业务更新,只要使用的能力已由当前宿主和运行时提供,就可以走小程序发布流程,无需等待三个主 APP 同步发版。容器 SDK 升级、新增原生能力等宿主工程变更,继续走各客户端的构建和发布流程。

业务更新与客户端更新分开后,团队的协作方式也会随之变化:业务开发人员维护共用的小程序,客户端人员维护各端运行环境和公共能力,运营人员在平台查看版本、审核和上线状态。新增业务可以继续接入现有容器,已有微信小程序资源也能在完成适配后加入自有 APP。

FinClip 将三端运行 SDK、开发者工具和管理平台连接起来,让同一套小程序拥有跨客户端运行和持续发布的能力。企业保留各端 APP 已有的原生体验,同时把频繁变化的业务交给共用小程序承载,减少重复开发,让功能上线与日常维护围绕同一份业务成果推进。

可私有化的小程序生态管理系统 - FinClip

立即了解
见字如面
Wannz | Developer & Designer