
现在,很多APP都从H5混合开发升级为“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 内部会处理下载、校验、启动的流程。如果本地已经有缓存的小程序包,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 直接跳转到自己的 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 需要从服务器下载小程序包、校验签名、初始化 JS 引擎、渲染页面。首次打开的时候,这一步会比较慢,尤其是网络条件不好的时候。
热启动:用户之前已经打开过这个小程序,小程序还在后台挂着。这时候只需要把后台的小程序切换到前台,不需要重新下载和初始化,速度会快很多。
从 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 原生页面之间来回切换,可以在同一个屏幕上同时操作。
先写到这里,如果感兴趣的话可以自行搜索了解