杰理 SDK 文档中心
首页
首页
  • SDK 框架库

    • JL_BLEKit 蓝牙通信核心
    • JL_AdvParse 广播包解析
    • JL_HashPair 加密配对
    • JL_OTALib 固件升级
    • JLDialUnit 彩屏仓与表盘控制
    • JLBmpConvertKit 位图转换
    • JLPackageResKit 资源包处理
    • JLLogHelper 日志工具
  • 核心功能模块

    • 音乐与媒体控制
    • 音效调节与均衡器
    • 设备发现、连接与设置
    • Auracast 广播接收与发射
    • 文件浏览、闹钟、FM 与灯光控制
    • ANC、按键设置与查找设备
    • AI 翻译与自定义命令
  • 应用架构与工程支撑

    • 杰理之家 App 架构与导航
    • 数据存储与缓存
    • Swift 工具与扩展层
    • JLAudioUnitKit 示例工程
    • SDKTestHelper 测试工具
  • 开发文档与资源

    • 文档中心与 JL_OTALib API 说明
    • 自定义蓝牙接入方式
    • 调试技巧与问题排查
    • 版本历史与社区支持

杰理之家 App 架构与导航

杰理之家(NewJieliZhiNeng)是杰理蓝牙 SDK 的 iOS 主应用示例,本文档深入解析其应用外壳(App Shell)与导航体系:从 AppDelegate/SceneDelegate 的启动流程、窗口与根控制器管理、MainTabBarVC 主导航壳,到品牌化开屏动画与隐私协议门禁的完整机制。

Purpose and Scope

本文档覆盖「杰理之家 App 架构与导航」这一能力主题,即应用级外壳与导航骨架:

  • 应用启动入口(AppDelegate / SceneDelegate)与各阶段的初始化顺序
  • 窗口(UIWindow)创建、根控制器(Root View Controller)切换与隐私协议门禁
  • 主导航容器 MainTabBarVC 及其挂载的顶层模块控制器
  • 双品牌(杰理之家 / PiLink)开屏动画机制与语言环境检测
  • 全局基础设施(日志、数据库、网络监测、用户登录、Bugly)的装配方式

以下内容属于兄弟页面,不在本文展开,仅在必要时交叉引用:

  • 蓝牙连接与 RCSP 协议控制(JL_RunSDK 的用法见 SDK 相关文档页)
  • 具体功能模块(如 DeviceInfoVC 设备信息、EQSettingVC 音效、UpgradeVC 固件升级、MultiMediaVC 多媒体)的内部实现
  • 网络接口层(Http接口/ 与 User_Http)的接口明细

本文所有结论均基于仓库源码验证;未在源码中核实的实现细节会明确标注「源码中未找到」。

Overview

杰理之家 Demo 是 iOS-JL_Bluetooth 仓库中位于 code/JieLi_Home_Demo/NewJieliZhiNeng/ 的完整 iOS 应用工程(NewJieliZhiNeng.xcworkspace)。它既是一个可运行的参考应用,也是 SDK 集成方式的活文档:开发者可以通过阅读该应用的外壳层了解一个基于杰理 SDK 的蓝牙 App 应该如何组织生命周期、导航与全局服务。

该应用外壳层有几个关键设计决策:

  1. 双品牌复用同一套外壳:通过宏 kJL_UI_SERIES 区分「杰理之家」与「PiLink」两套品牌 UI(开屏动画、主题资源),实现一套代码、两套皮肤。
  2. iOS 13 分水岭:iOS 13+ 由 SceneDelegate 管理窗口与根控制器,iOS 12 及以下走 setupUI 的传统 UIWindow 路径,兼容性处理被明确写进了启动方法。
  3. 隐私合规前置:首次启动时以 ConfirmView 浮层强制用户确认隐私协议(持久化键 CONMIT_PROTOCOL),确认前不进入主界面,避免合规风险。
  4. 基础设施集中装配:日志、数据库建表、用户登录、网络状态监测、Bugly 崩溃上报等全局服务全部在 didFinishLaunchingWithOptions 中按固定顺序初始化,保证后续模块可用。

Architecture

下图展示了杰理之家 App 外壳层的组件关系与数据流:

flowchart TD
    subgraph sg_Launch["启动阶段 (AppDelegate)"]
        Log["JLLogManager 日志系统"]
        Remote["远程事件接收<br/>beginReceivingRemoteControlEvents"]
        Lang["语言检测 kJL_GET/kJL_SET"]
        DB["SqliteManager createTable"]
        User["User_Http shareInstance"]
        Net["AFNetworkReachabilityManager"]
        Bugly["Bugly 崩溃上报"]
    end

    subgraph sg_Window["窗口与导航壳"]
        Confirm["ConfirmView 隐私协议门禁"]
        TabBar["MainTabBarVC 主 TabBar 壳"]
        OpenShow["OpenShowView 杰理之家开屏"]
        PiLink["PiLinkShowView PiLink 开屏"]
    end

    subgraph sg_Modules["顶层模块 (Tab 页面)"]
        M1["MultiMediaVC 多媒体"]
        M2["EQSettingVC 音效设置"]
        M3["DeviceInfoVC 设备信息"]
        M4["UpgradeVC 固件升级"]
        M5["UserProfileVC 用户中心"]
    end

    AppDelegate["application:didFinishLaunchingWithOptions:"] --> Log
    AppDelegate --> Remote
    AppDelegate --> Lang
    AppDelegate --> DB
    AppDelegate --> User
    AppDelegate --> Net
    AppDelegate --> Bugly

    AppDelegate -->|"iOS 13+ 由 SceneDelegate 接管"| TabBar
    AppDelegate -->|"iOS 12- 调用 setupUI"| Confirm
    Confirm -->|"CONMIT_PROTOCOL == OK"| TabBar
    Confirm -->|"未确认"| Blocked["停留空白控制器 tempVC"]

    TabBar --> M1
    TabBar --> M2
    TabBar --> M3
    TabBar --> M4
    TabBar --> M5

    OpenShow -->|"kJL_UI_SERIES == 0"| TabBar
    PiLink -->|"kJL_UI_SERIES == 1"| TabBar

架构说明

  • AppDelegate 是整个外壳的编排者:didFinishLaunchingWithOptions(AppDelegate.m L65)按「日志 → 屏幕常亮 → 远程事件 → 语言 → UI → 数据库 → 登录 → 网络 → 崩溃上报 → 通知」的顺序装配全局服务。
  • MainTabBarVC 是导航壳的根容器。从 AppDelegate.m 的导入列表(L9-L35)可以看到该壳直接聚合了 MultiMediaVC、EQSettingVC、DeviceInfoVC、UpgradeVC、UserProfileVC 等模块控制器,它们是 Tab 页面的顶层入口。
  • 隐私门禁处于窗口与主壳之间:未确认协议时根控制器被设置为一个空白 UIViewController(tempVC),ConfirmView 作为窗口子视图浮于其上;确认后才调用 initData 切换至真正的 MainTabBarVC 根控制器。
  • 品牌差异点集中在开屏:kJL_UI_SERIES == 0 播放杰理之家 OpenShowView 动画,== 1 播放 PiLinkShowView 动画,其余外壳逻辑完全共享。

应用入口与启动流程

1. 启动方法的完整时序

所有初始化都集中在 application:didFinishLaunchingWithOptions:(AppDelegate.m L65-L142)。它按固定顺序执行以下阶段,每个阶段的顺序都有设计意图:

阶段 A:日志系统先行

/*--- 记录NSLOG ---*/
[JLLogManager setLog:true IsMore:false Level:JLLOG_DEBUG];
[JLLogManager clearLog];
[JLLogManager logWithTimestamp:true];
[JLLogManager saveLogAsFile:true];

kJLLog(JLLOG_DEBUG, @"app version:%@,build version:%@",[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],[NSBundle mainBundle].infoDictionary[@"CFBundleVersion"]);

Source: AppDelegate.m L67-L73

日志是第一个初始化的服务,因为后续所有启动阶段的排查都依赖它;同时记录 App 的 CFBundleShortVersionString(市场版本号)与 CFBundleVersion(构建号),为线上问题定位提供版本锚点。

阶段 B:系统级行为配置

/*--- 设置屏幕常亮 ---*/
[UIApplication sharedApplication].idleTimerDisabled = YES;

/*--- 远程事件接收---*/
[application beginReceivingRemoteControlEvents];

Source: AppDelegate.m L75-L79

idleTimerDisabled = YES 使屏幕在 App 运行期间不自动息屏——这是蓝牙音箱/耳机控制类 App 的典型需求(用户在操作设备时屏幕不应熄灭)。beginReceivingRemoteControlEvents 用于接收锁屏/耳机线控的远程控制事件,是媒体控制类功能(MultiMediaVC)的前提。

阶段 C:语言环境归一化

/*--- 检测当前语言 ---*/
if ([kJL_GET isEqualToString:@"en"]) {
    kJL_SET("en");
}else if([kJL_GET isEqualToString:@"ja"]){
    kJL_SET("ja");
}else if ([kJL_GET isEqualToString:@"zh-Hans"]){
    kJL_SET("zh-Hans");
}else{
    kJL_SET("");
}

Source: AppDelegate.m L81-L90

这里对 SDK 内部的语言偏好(kJL_GET/kJL_SET 为杰理 SDK 的偏好读写宏)做白名单归一化:只保留 en、ja、zh-Hans 三种受支持语言,其余回退为空字符串(走默认语言)。这样 SDK 内部的协议提示、UI 文案与系统语言保持一致,避免出现未翻译的中间状态。

阶段 D:UI 分支(iOS 版本分水岭)

if (@available(iOS 13.0, *)) {
    // iOS 13+ 使用 SceneDelegate 管理窗口与根控制器
} else {
    [self setupUI];
}

Source: AppDelegate.m L92-L97

iOS 13 引入了 UIScene 生命周期,窗口创建从 AppDelegate 移交给 SceneDelegate。此处用 @available 做运行时兼容:新版系统跳过 setupUI(由 Scene 管理),旧版系统仍走经典 UIWindow 路径。注意此时开屏动画也做了同样的分支(L100-L114),避免新旧两套 UI 体系同时操作窗口造成冲突。

阶段 E:全局服务装配

紧接着依次初始化数据库、用户体系、网络监测、播放器与崩溃上报(L116-L132):

顺序服务调用作用
1数据库[[SqliteManager sharedInstance] createTable]创建设备图片、DHA 等本地数据表
2用户登录[User_Http shareInstance]初始化用户 HTTP 会话与登录态
3网络监测[[AFNetworkReachabilityManager sharedManager] startMonitoring]监听网络可达性,供网络播放等模块使用
4播放器[NetworkPlayer sharedMe]初始化网络音频播放器单例
5崩溃上报[Bugly startWithAppId:@"12d9f973f4"]启动腾讯 Bugly 崩溃采集

Bugly 的条件编译是一个值得注意的工程细节(L36-L43):某些 CocoaPods 渠道的 Bugly 二进制只含真机架构,模拟器链接会失败,因此用 __has_include(<Bugly/Bugly.h>) 做条件导入并定义 HAS_BUGLY 宏;模拟器构建时跳过启动并打印提示日志,避免 Debug 场景编译崩溃。

阶段 F:收尾

[self addNote];

if (@available(iOS 15.0, *)) {
    [UITableView appearance].sectionHeaderTopPadding = 0;
}
[SwiftHelper createFolds];
return YES;

Source: AppDelegate.m L134-L141

addNote 注册全局通知(详见下文「通知中心」),iOS 15 的 sectionHeaderTopPadding = 0 消除了系统默认的分组表头留白,SwiftHelper createFolds 为 Swift 桥接代码准备文件夹结构。

2. 窗口与根控制器管理(setupUI)

setupUI(AppDelegate.m L144-L160)是 iOS 12 及以下的窗口装配路径,其逻辑也代表了根控制器切换的完整模式:

-(void)setupUI{
    self.window =[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    cmView = [[ConfirmView alloc] init];
    NSString *key = [JL_Tools getUserByKey:@"CONMIT_PROTOCOL"];
    if ([key isEqualToString:@"OK"]) {
        [self initData];
    }else{
        tempVC =[[UIViewController alloc] init];
        self.window.rootViewController = tempVC;
        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        [self.window addSubview:cmView];
    }
    ...
}

Source: AppDelegate.m L144-L160

设计意图解读:

  • 隐私协议门禁:通过 JL_Tools getUserByKey:@"CONMIT_PROTOCOL" 读取持久化确认标记。已确认(OK)则直接 initData 初始化主界面;未确认则先挂一个纯白底的空 UIViewController 作为根控制器,再把 ConfirmView 作为窗口子视图浮层展示协议内容。这种「根控制器占位 + 窗口级浮层」的组合比「present 一个协议页」更早介入,用户在启动瞬间就只能看到协议确认界面,符合隐私合规要求。
  • ConfirmView 是实例变量而非局部变量(接口区 ConfirmView *cmView;,L49),确保浮层在窗口生命周期内持续存活,且后续可在其他方法中(如协议确认回调)访问并移除。
  • FindPhoneView 的懒创建(L157-L160):查找手机视图同样挂在窗口层,初始 hidden = YES,说明它是「全局悬浮能力」——由蓝牙设备侧触发(查找手机功能)时再显示,不属于 Tab 导航的一部分。

3. 主导航壳:MainTabBarVC

MainTabBarVC 是导航骨架的根容器(AppDelegate.m L29 导入、L46 实例变量),在 AppDelegate 中作为 mainVC 持有,保证其生命周期与 App 一致。

从启动文件导入的控制器可以还原该 Tab 壳挂载的顶层模块:

模块控制器职责(按命名与工程目录推断)对应仓库目录
MultiMediaVC多媒体播放主界面(蓝牙音乐)DeviceMusicVC/
EQSettingVC音效均衡器设置App设置/ 相关模块
DeviceInfoVC设备信息与功能控制入口DevicesViewController/DeviceInfoVC/
UpgradeVC固件升级UpgradeVC/ 相关模块
UserProfileVC用户中心/个人资料UserProfileVC 相关模块
PrivacyPolicyVC隐私政策页面(协议确认后展示)隐私政策相关模块

注:MainTabBarVC.m 的内部 Tab 组装代码未在本页读取范围内,以上模块归属依据 AppDelegate.m 的导入列表与仓库目录结构(README.md 的工程目录树)推断;如需 Tab 的精确顺序与图标配置,请直接阅读 MainTabBarVC.m 源文件。

这种「外壳持有容器、容器聚合模块」的结构带来两个直接收益:

  1. 模块间解耦:各功能 VC 只与 MainTabBarVC 发生组装关系,彼此不互相引用;新增一个 Tab 只需要在 MainTabBarVC 中注册,不影响其他模块。
  2. 全局能力挂窗口而非挂 Tab:ConfirmView、FindPhoneView、OpenShowView 等全局浮层都直接挂在 UIWindow 上而不是某个 Tab 页内,保证它们在任何导航层级上都可见可交互——这是「应用级能力」与「页面级能力」在架构上的清晰区分。

4. 双品牌开屏动画机制

开屏动画是品牌差异的集中体现(AppDelegate.m L100-L114):

if(kJL_UI_SERIES == 0){ //杰理之家
    /*--- 开启动画 ---*/
    [OpenShowView startOpenAnimation];
}
if(kJL_UI_SERIES == 1){ //PiLink
    /*--- 开启动画 ---*/
    CGRect rect = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
    PiLinkShowView  *piLinkShowView = [[PiLinkShowView alloc] initWithFrame:rect];
    UIWindow *win = [DFUITools getWindow];
    [win addSubview:piLinkShowView];
}

Source: AppDelegate.m L100-L114

kJL_UI_SERIES 是杰理 SDK 提供的品牌系列宏(0 = 杰理之家,1 = PiLink)。两种品牌的开屏实现略有差异:杰理之家使用 OpenShowView 的类方法 startOpenAnimation(动画视图自管理),PiLink 则显式创建全屏 PiLinkShowView 并添加到 DFUITools getWindow 返回的窗口上。这个差异表明品牌定制点被收敛在开屏这一处,外壳的其余部分(导航、模块)完全不感知品牌差异。

核心流程:从进程启动到主界面呈现

下图按时间线展示了 App 从 main 进入后,外壳层各组件如何协作将用户带到 MainTabBarVC 主界面:

sequenceDiagram
    participant OS as iOS 系统
    participant AD as AppDelegate
    participant SD as SceneDelegate (iOS 13+)
    participant CM as ConfirmView
    participant TBC as MainTabBarVC
    participant SVC as 全局服务

    OS->>AD: application:didFinishLaunchingWithOptions:
    AD->>AD: 初始化 JLLogManager(日志先行)
    AD->>AD: idleTimerDisabled=YES / 接收远程事件
    AD->>AD: 语言检测归一化 (kJL_GET/kJL_SET)
    alt iOS 13+
        AD->>SD: 由 SceneDelegate 管理窗口与根控制器
    else iOS 12-
        AD->>AD: setupUI() 创建 UIWindow
        AD->>CM: 读取 CONMIT_PROTOCOL
        alt 已确认 (== OK)
            AD->>TBC: initData() 设置根控制器
            TBC-->>AD: 主界面呈现
        else 未确认
            AD->>AD: 根控制器 = 空白 tempVC
            AD->>CM: addSubview 展示协议浮层
            CM-->>AD: 用户确认后 → initData()
        end
    end
    AD->>SVC: SqliteManager createTable / User_Http / 网络监测 / NetworkPlayer / Bugly
    AD->>AD: addNote 注册全局通知
    AD-->>OS: return YES

流程要点

  1. 日志先行:任何可能出错的阶段之前,日志系统必须就绪,这是启动可诊断性的基础。
  2. UI 分支只发生一次:iOS 13+ 与旧版系统在窗口创建上完全分流,但全局服务装配(阶段 E)不分流——两套 UI 路径共享同一批服务初始化,避免逻辑重复。
  3. 门禁是异步完成的:ConfirmView 是浮层而非模态页,用户确认后由 initData 把真正的根控制器换上。这解释了为什么 mainVC 是 AppDelegate 的成员变量——它必须在确认回调发生时才被创建并赋值,而不是在启动瞬间。
  4. 开屏动画与门禁并存:在旧版系统路径中,OpenShowView/PiLinkShowView 的开屏动画叠加在协议浮层之上(startOpenAnimation 在 setupUI 之后调用),形成「品牌开屏 → 协议确认 → 主界面」的视觉序列。

使用示例

以下示例均摘自实际源码,展示如何在自己的杰理 SDK 应用中复刻这套外壳模式。

示例 1:最小启动骨架(日志 + 语言 + 全局服务)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /*--- 记录NSLOG ---*/
    [JLLogManager setLog:true IsMore:false Level:JLLOG_DEBUG];
    [JLLogManager clearLog];
    [JLLogManager logWithTimestamp:true];
    [JLLogManager saveLogAsFile:true];

    /*--- 设置屏幕常亮 ---*/
    [UIApplication sharedApplication].idleTimerDisabled = YES;

    /*--- 检测当前语言 ---*/
    if ([kJL_GET isEqualToString:@"en"]) {
        kJL_SET("en");
    }else if([kJL_GET isEqualToString:@"ja"]){
        kJL_SET("ja");
    }else if ([kJL_GET isEqualToString:@"zh-Hans"]){
        kJL_SET("zh-Hans");
    }else{
        kJL_SET("");
    }

    /*--- 创建数据库 ---*/
    [[SqliteManager sharedInstance] createTable];

    /*--- 用户登录 && 日志 ---*/
    [User_Http shareInstance];

    /*--- 网络监测 ---*/
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];

    [NetworkPlayer sharedMe];
    return YES;
}

Source: AppDelegate.m L65-L125

说明:这是外壳层的「必选动作」清单——日志、常亮、语言、数据库、用户体系、网络监测。集成时按此顺序执行即可保证后续模块的依赖可用。

示例 2:隐私协议门禁模式

-(void)setupUI{
    self.window =[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    cmView = [[ConfirmView alloc] init];
    NSString *key = [JL_Tools getUserByKey:@"CONMIT_PROTOCOL"];
    if ([key isEqualToString:@"OK"]) {
        [self initData];
    }else{
        tempVC =[[UIViewController alloc] init];
        self.window.rootViewController = tempVC;
        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        [self.window addSubview:cmView];
    }
}

Source: AppDelegate.m L144-L156

说明:门禁的关键模式是「占位根控制器 + 窗口级浮层」。JL_Tools getUserByKey: 负责持久化读取,确认标记存于本地偏好;未确认时用户只能看到协议浮层,任何 Tab 界面都不可达。集成时注意 cmView 需为成员变量,以便在确认回调中移除。

示例 3:双品牌开屏切换

if(kJL_UI_SERIES == 0){ //杰理之家
    /*--- 开启动画 ---*/
    [OpenShowView startOpenAnimation];
}
if(kJL_UI_SERIES == 1){ //PiLink
    /*--- 开启动画 ---*/
    CGRect rect = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
    PiLinkShowView  *piLinkShowView = [[PiLinkShowView alloc] initWithFrame:rect];
    UIWindow *win = [DFUITools getWindow];
    [win addSubview:piLinkShowView];
}

Source: AppDelegate.m L103-L113

说明:通过 kJL_UI_SERIES 宏在同一套外壳内切换品牌。若你的产品需要定制开屏,只需在 AppDelegate 这一处替换品牌视图,导航与模块层无需改动。

示例 4:Bugly 的模拟器安全集成

// Bugly 在某些版本的 CocoaPods 提供的二进制仅包含真机架构,模拟器链接会失败。
// 通过 __has_include 进行条件导入,避免在 Debug/模拟器下编译报错。
#if __has_include(<Bugly/Bugly.h>)
#import <Bugly/Bugly.h>
#define HAS_BUGLY 1
#else
#define HAS_BUGLY 0
#endif
...
#if HAS_BUGLY
[Bugly startWithAppId:@"12d9f973f4"];
#else
// 模拟器或未集成 Bugly 的构建不启动 Bugly,避免链接/编译问题
NSLog(@"Bugly is not available for this build (simulator or Debug). Skipping Bugly startup.");
#endif

Source: AppDelegate.m L36-L43 与 L127-L132

说明:当第三方 SDK 的二进制分架构提供时,__has_include 条件编译是标准的防御手段——头文件不存在即视为未集成,运行时优雅降级为日志提示,保证模拟器与真机构建都不中断。

配置选项

外壳层的可配置项集中在启动方法与偏好存储中,均已在源码中验证:

配置项类型默认值/取值来源位置说明
kJL_UI_SERIES宏(整数)0(杰理之家)/ 1(PiLink)AppDelegate.m L103-L107品牌系列开关,决定开屏动画类型
CONMIT_PROTOCOL偏好键(字符串)未设置(首次启动)AppDelegate.m L147隐私协议确认标记,"OK" 表示已确认;由 JL_Tools getUserByKey: 读取
kJL_GET / kJL_SETSDK 偏好宏en / ja / zh-Hans / ""AppDelegate.m L82-L90SDK 语言偏好读写;白名单外的语言回退为空字符串
CFBundleShortVersionStringInfo.plist由工程配置决定AppDelegate.m L73市场版本号,启动时写入日志
CFBundleVersionInfo.plist由工程配置决定AppDelegate.m L73构建号,启动时写入日志
Bugly AppID常量字符串"12d9f973f4"AppDelegate.m L128崩溃上报 AppID,HAS_BUGLY 为 0 时不启动
日志级别枚举JLLOG_DEBUGAppDelegate.m L68启动日志级别,可调为 JLLOG_INFO 等以降低输出

说明:kJL_UI_SERIES、kJL_GET/kJL_SET 的具体定义位于杰理 SDK 头文件(JL_RunSDK.h 导入链)中,本页未读取其定义文件;以上取值依据 AppDelegate.m 中的使用方式确认。

API 参考(外壳层入口)

application:didFinishLaunchingWithOptions:

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
  • 位置:AppDelegate.m L65-L142
  • 说明:App 完成启动的入口,负责日志、系统行为、语言、UI 分支、数据库、登录、网络、播放器、崩溃上报、通知注册等全部全局初始化。
  • 参数
    • application(UIApplication *):当前应用实例。
    • launchOptions(NSDictionary *):启动选项字典(如推送、URL 唤起来源)。
  • 返回值(BOOL):YES 表示启动处理完成。源码恒返回 YES(L141),NO 仅在异常中断时使用。

setupUI

-(void)setupUI
  • 位置:AppDelegate.m L144-L160
  • 说明:iOS 12 及以下的窗口装配路径。创建 UIWindow,根据 CONMIT_PROTOCOL 标记决定直接进入 initData 还是先展示 ConfirmView 隐私协议浮层。
  • 返回:无(void)。

addNote(通知注册)

  • 位置:在启动流程末尾调用(AppDelegate.m L134)
  • 说明:集中注册 App 级通知监听。具体注册的通知列表位于 AppDelegate.m 后半部分(本页读取范围之外),其作用是把蓝牙事件、网络变化等广播到各模块。

全局服务单例入口

单例调用生命周期
SqliteManager[[SqliteManager sharedInstance] createTable]App 级,数据库表创建
User_Http[User_Http shareInstance]App 级,用户会话
NetworkPlayer[NetworkPlayer sharedMe]App 级,网络音频播放
AFNetworkReachabilityManager[[AFNetworkReachabilityManager sharedManager] startMonitoring]App 级,网络状态监测

失败模式、边界情况与并发

隐私协议未确认(首次启动)

  • 现象:根控制器为空白 tempVC,ConfirmView 浮层展示协议。
  • 风险:若用户既不确认也不退出,App 停留在门禁态,蓝牙等后续流程不会启动——这是设计预期(合规优先)。
  • 恢复:确认后调用 initData 完成主界面初始化;标记持久化在 CONMIT_PROTOCOL,二次启动直接放行。

iOS 13+ 与旧版系统的窗口竞争

  • 现象:iOS 13+ 下 SceneDelegate 负责窗口;若旧代码路径(setupUI)在新系统被误调用,会出现两个窗口争夺根控制器的冲突。
  • 防护:源码用 @available(iOS 13.0, *) 显式分流(L93-L97、L100-L102),开屏动画也随分支隔离,避免叠加到错误的窗口。

模拟器 / Debug 构建下 Bugly 缺失

  • 现象:部分 CocoaPods 渠道的 Bugly 二进制仅含真机架构,模拟器链接失败。
  • 防护:__has_include 条件编译 + HAS_BUGLY 宏(L36-L43),未集成时打印日志降级,不影响启动。

语言环境未覆盖

  • 现象:系统语言不在 en/ja/zh-Hans 白名单内。
  • 处理:回退为空字符串(L88-L89),SDK 走默认语言文案,避免半翻译状态。

并发与线程

  • 启动阶段全部为主线程同步执行(didFinishLaunchingWithOptions 本身在主线程),数据库建表、网络监测启动等均为轻量同步操作,未发现异步并发风险。
  • FindPhoneView、ConfirmView 等全局浮层挂在窗口层,其显示/隐藏由蓝牙回调(异步线程)触发时,源码通过 hidden 属性切换,未在本页读取范围中发现跨线程直接操作 UI 的情况;若自行扩展此类全局浮层,建议将回调派发回主队列再操作视图。

启动时序依赖

  • 服务装配顺序有隐式依赖:日志 → 数据库 → 登录 → 网络。例如 User_Http 依赖 SqliteManager 建表完成,NetworkPlayer 依赖网络监测启动。扩展时新增全局服务应插入到合理位置,不可随意打乱顺序。

性能与运维考虑

启动性能

  • 启动路径短平快:didFinishLaunchingWithOptions 中的全局服务(日志、建表、登录、网络监测)均为轻量同步初始化,无网络阻塞等待——User_Http shareInstance 只建立会话,真正的登录请求在后续流程异步发出。这保证了冷启动不会被 I/O 拖慢。
  • 屏幕常亮的代价:idleTimerDisabled = YES(L76)意味着 App 前台运行时系统不会自动息屏,功耗会上升。这是蓝牙控制类 App 的有意取舍,但发布前应确认产品需求是否真的需要全程常亮,必要时改为仅在连接设备时启用。
  • 日志文件化:saveLogAsFile:true 会把日志落盘,长期运行会积累日志文件;源码在启动时调用 clearLog 清理上一次的日志(L69),避免无界增长。生产环境可考虑按需降级日志级别(JLLOG_DEBUG → JLLOG_INFO)进一步减负。

可观测性

  • 版本锚点:启动日志记录 CFBundleShortVersionString 与 CFBundleVersion(L73),配合日志时间戳(logWithTimestamp:true),线上问题可精确回溯到版本与时间点。
  • 崩溃上报:Bugly 在真机构建中启动(HAS_BUGLY 守卫),崩溃现场自动上报;模拟器构建优雅跳过。
  • 网络可达性:AFNetworkReachabilityManager 持续监测,是网络播放与 HTTP 接口的可用性前提。

扩展点

新增 Tab 模块

在 MainTabBarVC 中注册新的控制器即可加入导航壳(Tab 组装代码见 MainTabBarVC.m)。新模块应遵循现有模块的职责边界(如 MultiMediaVC、EQSettingVC),只与外壳发生组装关系,不与其他 Tab 直接耦合。

双品牌定制

kJL_UI_SERIES 是品牌定制的唯一开关:开屏动画(OpenShowView/PiLinkShowView)已按品牌分流(L103-L113)。如需新增品牌(如 kJL_UI_SERIES == 2),在该分支添加对应的开屏视图即可,外壳其余部分无需改动。

全局浮层能力

ConfirmView(协议)、FindPhoneView(查找手机)演示了「窗口级浮层」的挂载模式:需要全 App 可见的能力(蓝牙事件弹窗、全局提示)都应挂到 UIWindow 而非某个 Tab 页面,这样不受导航栈影响。新能力可复制此模式:成员变量持有视图 + addSubview 到窗口 + hidden 控制显隐。

启动阶段插入自定义服务

按「日志 → 数据库 → 登录 → 网络」的依赖顺序,在 didFinishLaunchingWithOptions 中插入新的全局服务(如埋点 SDK、推送注册)。注意保持顺序依赖,并在日志系统之后插入,保证新服务可被日志覆盖。

测试与验证

本页读取范围内未发现针对外壳层(AppDelegate/MainTabBarVC)的单元测试文件;code/JieLi_Home_Demo/DbTest/ 目录包含 DbTest.m,表明数据库层(SqliteManager)具备独立测试入口。外壳层逻辑(启动顺序、门禁分支、语言归一化)建议通过以下方式验证:

  • 真机 + 模拟器双构建:验证 HAS_BUGLY 条件编译在两种环境下的行为(模拟器跳过 Bugly,真机启动)。
  • 首次启动/二次启动:验证 CONMIT_PROTOCOL 门禁分支——首次展示 ConfirmView,确认后二次启动直接进入 MainTabBarVC。
  • iOS 13/14 与 iOS 12 对比:验证 SceneDelegate 与 setupUI 两套窗口路径均能正确呈现主界面。
  • 语言切换:在系统设置中切换 en/ja/zh-Hans/其他语言,验证 kJL_SET 的归一化结果与 SDK 文案表现。

相关链接

  • 杰理之家 SDK(iOS)README:仓库总览、工程目录树与 SDK 集成说明
  • AppDelegate.m:外壳层核心源码(本页主要依据)
  • AppDelegate.h:AppDelegate 接口声明
  • MainTabBarVC:主导航壳(Tab 组装源码,位于 NewJieliZhiNeng 目录)
  • SqliteManager.m:全局数据库建表服务
  • 兄弟页面:设备连接与 RCSP 协议控制、设备信息模块(DeviceInfoVC)、固件升级模块(UpgradeVC)、多媒体模块(MultiMediaVC)各自的功能细节请查阅对应目录页面
Next
数据存储与缓存