如今,越来越多的 APP 采用“Native+小程序”的混合架构,以兼顾产品灵活性与性能优化。当 APP 内嵌小程序后,用户每次打开一个小程序,背后究竟经历了哪些技术流程?本文将从启动方式、冷热启动机制、关闭与销毁逻辑,以及预加载策略等维度,逐一拆解。
小程序启动方式详解
在 APP 中,小程序可通过多种途径被唤醒,每种方式对应不同的业务场景。
最常见的方式:通过小程序 ID 直接唤起
FATAppletRequest *request = [[FATAppletRequest alloc] init];
request.appletId = @"小程序id"; // 必填
request.apiServer = @"服务器地址";// 必填
[[FATClient sharedClient] startAppletWithRequest:request
inParentViewController:self
completion:^(BOOL result, FATError *error) {
NSLog(@"打开小程序结果: %@", error);
}
closeCompletion:^{
NSLog(@"小程序被关闭了");
}];
该接口仅需两个核心参数:小程序的唯一 ID 与后端服务器地址。SDK 接收到请求后,会自动完成下载、校验与初始化等全套流程。若本地已有缓存,则优先打开本地版本,同时静默检查服务器是否有更新——若有新版,则后台下载以供下次使用。
通过扫码打开
扫描 FinClip 管理平台上的二维码,即可打开小程序的多个版本:正式版、体验版、审核版、真机调试版或预览版,一个二维码即可覆盖所有需求。
FATAppletQrCodeRequest *qrcodeRequest = [[FATAppletQrCodeRequest alloc] init];
qrcodeRequest.qrCode = qrCode;// 二维码里的内容
[[FATClient sharedClient] startAppletWithQrCodeRequest:qrcodeRequest
inParentViewController:self
requestBlock:^(BOOL result, FATError *error) {
NSLog(@"校验二维码完成: %@", error);
}
completion:^(BOOL result, FATError *error) {
NSLog(@"打开完成: %@", error);
}
closeCompletion:^{
NSLog(@"关闭了");
}];
通过 URL Scheme 从外部唤起
若希望从 Safari 或其他 APP 直接跳转到自己的应用并打开某个小程序,URL Scheme 是理想方案。需提前在 APP 中配置 URL Types,格式为 fat 后拼接 SDKKey 的 MD5 值。
// 在 AppDelegate 中实现
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
if ([[FATClient sharedClient] handleOpenURL:url]) {
return YES;
return NO;
外部调用的 URI 格式是:
fat705b46f78820c7a8://applet/appid/617bb42f530fb30001509b27?path=pages/index/index&query=key%3Dvalue
冷启动 vs 热启动:背后发生了什么

要理解小程序完整的生命周期,首先需要厘清“冷”“热”两种启动状态的本质区别。
冷启动:用户首次打开小程序,或小程序被彻底销毁后再次打开。此时 SDK 需要从服务器下载小程序包、校验签名、初始化 JavaScript 引擎并渲染页面。首次启动通常会比较耗时,尤其在网络状况不佳时更为明显。
热启动:用户此前已打开过该小程序,且它仍处于后台挂起状态。此时仅需将后台实例切换至前台,免去重新下载与初始化步骤,速度明显更快。
从 SDK 视角来看,打开小程序遵循如下判断逻辑:
- 检查本地是否已有缓存的小程序包
- 若没有 → 下载小程序包 → 下载基础库 → 初始化 → 渲染
- 若有 → 先启动本地包 → 后台检查新版本 → 若发现新版则下载备用
因此,当本地存在缓存时,冷启动的用户体验会显著提升。
离线包:加速首次启动

冷启动慢的根本原因在于网络下载。为此,SDK 提供了“离线包”机制——提前将小程序包与基础库打包进 APP,在合适的时机(如用户刚登录完成)完成预下载。
FATAppletRequest *request = [[FATAppletRequest alloc] init];
request.appletId = @"小程序id";
request.apiServer = @"服务器地址";
// 小程序离线包地址,可以是 bundle 路径,也可以是沙盒路径
request.offlineMiniprogramZipPath = [[NSBundle mainBundle] pathForResource:@"api_demo" ofType:@"zip"];
// 基础库离线包地址
request.offlineFrameworkZipPath = [[NSBundle mainBundle] pathForResource:@"framework-3.0.49" ofType:@"zip"];
[[FATClient sharedClient] startAppletWithRequest:request
inParentViewController:self.window.rootViewController
completion:^(BOOL result, FATError *error) {
NSLog(@"打开小程序: %@", error);
}
closeCompletion:^{
NSLog(@"关闭小程序");
}];
注意:离线启动时,offlineMiniprogramZipPath 与 offlineFrameworkZipPath 必须同时传入,否则 SDK 会忽略离线配置,转为走正常下载流程。
还有更省心的做法:初始化 SDK 时开启基础库预下载。
FATStoreConfig *storeConfig = [[FATStoreConfig alloc] init];
storeConfig.sdkKey = @"##sdkKey##";
storeConfig.sdkSecret = @"##sdkSecret##";
storeConfig.apiServer = @"##apiServer##";
// 开启基础库预下载
storeConfig.enablePreloadFramework = YES;
FATConfig *config = [FATConfig configWithStoreConfigs:@[storeConfig]];
[[FATClient sharedClient] initWithConfig:config error:nil];
开启此选项后,SDK 会在初始化阶段自动下载基础库到本地,后续打开任何小程序都会显著加快。
热启动的参数控制
热启动有一个容易踩坑的细节——启动参数的处理。
用户首次打开小程序时携带了参数(比如从某个营销页面跳转而来),小程序已经处于打开状态。之后用户退到后台,过一会儿又从后台切回,此时如果携带了新的启动参数,SDK 会如何响应?
SDK 提供了 reLaunchMode 参数来控制该行为,共四种模式:
FATReLaunchModeParamsExist:启动参数中包含 path、query 或 referrerInfo,且其中任意参数不为空,则执行 reLaunchFATReLaunchModeOnlyParamsDiff:仅当启动参数与上一次不同时,才执行 reLaunchFATReLaunchModeAlways:每次热启动都执行 reLaunchFATReLaunchModeNever:每次热启动都不执行 reLaunch
默认采用 ParamsExist 模式,即只要携带新参数就触发 reLaunch。若业务对页面栈行为有特殊要求,可在初始化或每次调用 startAppletWithRequest 时显式设置该参数。
关闭小程序不等于销毁小程序
许多开发者容易混淆“关闭”与“销毁”这两个操作。
点击右上角胶囊的关闭按钮,或调用 SDK 的关闭接口:
// 关闭指定的小程序(animated 是否动画,completion 关闭完成回调,closeCompletion 小程序关闭回调)
[[FATClient sharedClient] closeApplet:@"小程序id" animated:YES
completion:^{
NSLog(@"关闭完成");
}
closeCompletion:^{
NSLog(@"小程序被关闭了");
}];
// 关闭所有小程序
[[FATClient sharedClient] closeAllAppletsWithCompletion:^{
NSLog(@"全部关闭了");
}];
关闭操作并不会从内存中删除小程序——它只是退到后台,进入“挂起”状态。当用户下次再次打开时,会立即切换至前台,无需重新下载。
若要彻底释放内存,必须调用销毁接口:
// 销毁指定小程序(会清掉内存和缓存)
[[FATClient sharedClient] clearMemeryApplet:@"小程序id"];
// 销毁所有小程序
[[FATClient sharedClient] clearMemoryCache];
注意:如果小程序正处于前台显示状态,直接调用销毁接口不会生效——需先调用关闭接口将其退至后台,再调用销毁接口。这是 SDK 为防止误操作而设计的保护机制。
删除小程序:连缓存一并清除
如果不仅想清空内存,还要连带本地存储的小程序包和配置信息一起移除,需要调用删除接口:
// 删除指定小程序的所有本地信息
[[FATClient sharedClient] removeAppletFromLocalCache:@"小程序id"];
// 删除所有小程序的本地信息
[[FATClient sharedClient] clearLocalApplets];
删除后再次打开,SDK 会重新从服务器下载,效果等同于首次安装。当用户切换账号、清理缓存或需强制重新初始化时,使用此接口。
批量预加载:改善用户体验
如果 APP 内有多个小程序入口,用户可能会频繁打开多个。可以提前将需要的小程序包下载到本地,减少等待时间。
// 批量下载小程序
[[FATClient sharedClient] downloadApplets:appIds
apiServer:apiServer
complete:^(NSArray *results, FATError *error) {
// results 是一个数组,每个元素是 @{@"appId": xxx, @"success": xxx, @"needUpdate": xxx}
// needUpdate 为 false 说明本地已经是最新版本,不需要重复下载
}];
该接口会检查本地缓存,如果某个小程序已是最新版本,则不会重复下载,仅返回结果通知调用方。
iPad 多窗口模式:小程序不只全屏打开
在 iPad 上,小程序可以不按全屏方式打开。SDK 支持多种窗口模式,方便用户同时操作小程序和原生页面。
SDK 提供以下模式:
- fullScreen:默认全屏模式
- currentContext:在指定的上下文控制器中显示,可配合 UISplitViewController 使用
- overCurrentContext:遮罩模式,不影响原有视图栈
- pageSheet:半屏弹窗,尺寸不可自定义
- formSheet:居中弹窗,大小由系统决定
- popover:气泡弹窗,带箭头指向触发按钮
- custom:自定义窗口区域
// 以 pageSheet 模式打开小程序
request.presentationConfig = [FATAppletPresentationConfig pageSheetPresentationConfig];
若 APP 主要面向 iPad 用户,这些窗口模式能显著提升操作体验——用户无需在 APP 原生页面与小程序之间频繁切换,可在同一屏幕上并行处理任务。
