技术点解析

image.png

战场一:HOMEHD 大图浏览优化(外接纹理技术)

简历原话:“基于 Flutter 外接纹理渲染技术… 内存峰值降低 40%”

1. 为什么要用外接纹理(External Texture)?

  • 痛点:Flutter 原生 Image 组件在加载高清大图(如 4K 装修图)时,图片数据会经过 Skia 引擎解码,这部分内存往往由 Dart VM 或 Engine 管理,容易造成:

    • 双重内存:Native 加载一份,Flutter 渲染又拷贝一份。

    • GC 压力:大图数据在 Dart 堆内存中,导致频繁 GC,引起卡顿。

  • 原理:外接纹理允许 Native 端(iOS)直接将解码后的共享显存(GPU Texture)传递给 Flutter 渲染管线,Flutter 端只持有一个 int textureId,不接触真实的像素数据

2. 技术实现细节(iOS 端)

  • 核心类:FlutterTextureRegistry 协议。

  • 数据流

    • Native 下载与解码:使用 SDWebImage 或自定义 Downloader 下载图片。

    • 解码为 CVPixelBuffer:将图片解码为 CVPixelBufferRef(核心点:必须是基于 IOSurface 的,这样才能 GPU 共享)。

    • 注册纹理:调用 [registry registerTexture:textureObject] 拿到一个 int64_t textureId。

    • 回传 ID:通过 MethodChannel 将 textureId 和图片的宽高等元数据传给 Flutter。

    • Flutter 渲染:使用 Texture(textureId: id) Widget 进行展示。

    • 资源释放:当 Flutter 页面销毁或图片划出屏幕时,通知 Native 调用 unregisterTexture,释放 CVPixelBuffer。

3. 面试追问(防守点)

  • Q: 图片解码是在哪个线程?

    • A: 必须在后台线程解码,否则卡顿主线程。拿到 CVPixelBuffer 后,在主线程通知 Flutter 刷新(textureFrameAvailable)。
  • Q: 内存到底省在哪里?

    • A: 省去了从 Native 堆内存 copy 到 Dart/Skia 内存的过程,实现了零拷贝(Zero-copy)渲染

战场二:AI 场景实现(SEE 协议 + 动态卡片 DSL)

简历原话:“基于 SEE 协议 + Flutter… 动态卡片生成… 语音识别准确率提升”

1. SEE 协议 (Server-Sent Events) 实现流式传输

  • 场景:AI 机器人的回复是“打字机”效果,是一个持续的数据流,而不是一次性 HTTP 请求。

  • 为什么选 SEE(SSE) 而不是 WebSocket?

    • 回答:AI 问答主要是单向流(服务端 -> 客户端),SSE 基于 HTTP 长连接,比 WebSocket 更轻量,且更容易处理断线重连和防火墙穿透。
  • Flutter 实现

    • 使用 http 库构建 Request,设置 Accept: text/event-stream。

    • 调用 client.send(request).asStream() 获取数据流。

    • 监听 Stream,通过 \n\n 分割数据块,解析 JSON,增量更新 UI 状态(例如:每收到一个字,调用 setState 追加文本)。

2. 动态卡片 DSL(Domain Specific Language)引擎

  • 痛点:AI 回复的不只是文本,可能是“推荐房源卡片”、“拨打电话按钮”、“HTML 列表”,客户端发版太慢,需要服务端下发 UI 结构。

  • 实现方案

    • 协议设计:服务端下发 JSON,包含 type (card/text/image) 和 data (payload)。

    • Widget Factory:维护一个 Map\ 注册表。

      codeDart

      // 伪代码
      Widget buildCard(Map json) {
        switch (json['type']) {
          case 'house_card': return HouseCard(data: json['data']);
          case 'action_btn': return ActionButton(action: json['action']);
          case 'html_text': return HtmlWidget(html: json['content']); // 使用 flutter_html
        }
      }
      
    • HTML 渲染:对于富文本,使用了 flutter_html 或自定义 TextSpan 解析器,支持超链接点击和样式定制。

3. 语音识别准确率提升 15%(客户端做了什么?)

  • 回答策略:不要说是模型好了,要强调端侧优化

    • VAD(Voice Activity Detection)前端处理:在端侧集成轻量级 VAD 算法,自动裁剪静音片段,只上传有效人声,减少噪点干扰,提高服务端识别率。

    • 音频采样率优化:将采样率从标准的 8k 提升至 16k(宽带音频),虽然数据量变大,但在 4G/5G 下无压力,显著提升了识别清晰度。

    • 流式上传:边录边传,而不是录完再传,降低了首字响应延迟(Latency)。


战场三:跨平台架构(Flutter + 原生混合栈)

简历原话:“跨平台模块接入规范… 迭代周期缩短 25%”

1. 混合栈管理(最难点)

  • 问题:原生 -> Flutter -> 原生 -> Flutter,如果每次都 new 一个 FlutterViewController,内存会爆炸(每个 VC 对应一个 Engine,一个 Engine 约占用 30MB+ 内存)。

  • 解决方案

    • 方案 A(早期/简单)单引擎复用。全局单例 FlutterEngine。缺点是页面栈状态难以维护,需要手动截图保存上一页状态(Snapshot)。

    • 方案 B(进阶/贝壳可能采用)FlutterEngineGroup(Flutter 2.0+ 特性)。

      • 原理:多个 Engine 实例共享底层的 GPU Context、Skia 资源和字体资源。

      • 优势:创建新 Engine 的内存消耗极低(约 180KB),实现了真正的多 Engine 隔离,每个 ViewController 对应一个轻量级 Engine。

  • 导航桥接:使用 pigeon 或自定义 Channel 封装一套统一的 Navigator API,拦截 Flutter 的 push/pop,转发给 Native 的 UINavigationController 处理,保证手势侧滑返回的体验一致性。


战场四:高性能优化体系(启动 & 崩溃)

简历原话:“启动速度缩短 40%… 崩溃率降至 0.05%”

1. 启动速度优化(Pre-main & Post-main)

  • Pre-main(通过 Instruments 的 System Trace 分析)

    • 动态库治理:减少自定义动态库数量(Merge),官方建议不超过 6 个。

    • +load 治理:Hook +load 方法耗时,将非必须的逻辑移至 +initialize 甚至业务调用时懒加载。

  • Post-main(业务层)

    • 二进制重排(Binary Reordering):利用 Clang 插桩(SanitizerCoverage)收集启动阶段执行的符号(函数)顺序,生成 .order 文件,减少 Page Fault(缺页中断)次数。(这是一个 P8 级别的杀手锏技术点)

    • 多阶段启动:将 didFinishLaunching 拆分为:首屏必须 > 3秒后加载 > 空闲时加载。

2. 崩溃率治理(0.05% 的秘诀)

  • Unrecognized Selector 防护

    • 利用 Runtime 消息转发机制。Hook NSObject 的 forwardingTargetForSelector:。如果方法找不到,动态创建一个 “Stub Object” 吞掉该消息,并上报日志,而不是直接 Crash。
  • Container 防护

    • Swizzle NSArray, NSMutableArray, NSDictionary 的插入和访问方法(如 objectAtIndex:),对 nil 参数和越界索引进行 try-catch 保护。
  • 野指针防护(Zombie Objects)

    • 在对象 dealloc 时,不真正释放内存,而是将 isa 指针指向一个“僵尸类”。当再次向该内存发送消息时,僵尸类拦截并报警。