ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

90天用Flutter打造短视频+直播App:架构设计与实践复盘

90天用Flutter打造短视频+直播App:架构设计与实践复盘 从立项到双端上线我们团队用 90 天做了一款完整的短视频加直播 App。这个项目从 0 到 1 全部基于 Flutter 开发覆盖了视频拍摄、编辑、发布、Feed 流播放、直播推拉流、IM 聊天、礼物互动、用户系统、内容审核、运营后台等完整链路。老读者应该知道我一向不吹 Flutter 万能但这个项目做下来我必须承认想在一个季度内同时覆盖 iOS 和 Android并且还要塞进直播这种重交互场景Flutter 确实是当下最现实的选择。这篇内容不是官方文档复读而是把我们在架构选型、核心功能实现、性能优化、踩坑排雷上的真实过程完整复盘一遍适合准备用 Flutter 做视频类、直播类、社交类 App 的团队参考也适合对跨端方案选型犹豫不决的个人开发者看看。我先说一下最终交付的形态App 由短视频 Tab、直播 Tab、消息 Tab、个人中心四大部分组成短视频支持滑动切换、双击点赞、评论转发、关注关系直播支持开播、观看、连麦、弹幕、礼物、榜单。这套东西放在原生开发里两个平台各配一个 Android 和 iOS 小组90 天光写业务代码都够呛更别提联调。但我们实际投入的是 1 名 Flutter 开发、1 名后端、1 名设计外加我负责整体架构和部分原生插件封装最终按期交付并通过各应用市场审核。后面我会把具体的思路和代码级别的实现细节都拆开讲希望能给你提供一套可以直接“抄作业”的范本。1. 项目启动前为什么敢用 Flutter 做短视频加直播1.1 业务需求与技术场景拆解在敲定技术方案之前我们先把业务需求拆成了三层。第一层是基础能力包括账号体系、用户资料、关注关系、内容列表、评论点赞这些本质上就是标准的 CRUD 加缓存任何跨端框架都能胜任。第二层是视频相关能力包括拍摄、裁剪、滤镜、特效、上传、播放、预加载这一层对性能要求开始变得敏感尤其是 Feed 流滑动的流畅度和视频首帧秒开率。第三层是直播能力包括推流、拉流、美颜、IM 聊天、礼物动效、连麦这是整个项目里对原生能力依赖最深的部分。拆完需求之后我心里其实已经比较有底了短视频层的难点在于播放器性能和内存管理直播层的难点在于底层音视频 SDK 的接入和 UI 复杂交互。这两块在 Flutter 里都不能纯靠 Dart 硬扛必须通过 platform channel 与原生 SDK 协作。换句话说Flutter 在这里扮演的是“骨架加皮肤”的角色真正吃性能的部分仍然要依赖原生模块。1.2 技术选型对比Flutter、原生与 RN 的取舍团队里不是没人质疑过 Flutter。反对意见主要集中在两点一是 Flutter 的渲染引擎是自绘的视频纹理接入会不会有坑二是直播间的频繁刷新和高帧率动画会不会拖垮 UI 线程。为了验证这些顾虑我在立项前花了一周时间做了一个垂直视频滑动 Demo用 PageView 加载本地视频循环播放实测在 Android 中端机和 iPhone 11 上都能稳定保持 60 帧内存也没有异常增长。和 React Native 相比Flutter 最大的优势在于 UI 渲染不依赖原生控件这意味着复杂的交互动画、自定义绘制、全局统一状态都有更高的可控性。短视频和直播的 UI 里恰好充满了大量自定义动画比如点赞飘心、礼物连击、直播间浮层Flutter 的 Canvas 能力在这里完全碾压 RN。和原生双端相比Flutter 又省掉了两套 UI 代码对于 90 天这种紧周期来说这是决定性的优势。最终我们确定的技术路线是UI 和交互全部 Flutter视频播放用原生播放器封装成插件直播推拉流用第三方音视频 SDK再通过 channel 桥接给 Flutter 层调用。1.3 90 天节奏下的研发计划排布90 天听起来不短但拆掉需求评审、UI 设计、后端接口开发、联调测试、应用市场上架之后真正留给客户端编码的时间其实只有 60 天左右。我们第一周用来搭工程骨架、CI 流程、设计系统同时让后端把接口文档定死。第二个和第三个月进入功能集中开发期短视频和直播并行推进。最后两周统一做性能优化、机型适配、Bug 修复和上架准备。关键的一条经验是短视频和直播两条业务线必须从第一天就并行。如果把直播拖到短视频完成后再启动90 天根本转不过来。因为两条业务线在底层依赖上是解耦的一个是播放器插件一个是推拉流 SDK但又有大量公共组件可以复用比如用户页、头像组件、路由模块、网络层。我们前期把公共层做扎实后期两边的开发基本就是各自填充页面互不阻塞。2. 短视频模块的核心设计与实现2.1 播放器选型与预加载策略短视频 App 的体验核心说白了就是两个指标首帧耗时和滑动流畅度。首帧耗时指的是用户滑到某条视频画面第一帧出现的延迟这个数字超过 500 毫秒用户就会明显觉得卡。我们最初的方案是直接用官方的 video_player 插件它在简单场景下够用但根本无法满足 Feed 流的多实例需求。最后我们基于原生播放器封装了自定义插件Android 用的是 ExoPlayeriOS 用的是 AVPlayer中间通过 platform channel 暴露 play、pause、seek、release 等接口给 Dart 层调用。预加载是解决首帧耗时的关键。我们的实现思路是Feed 流当前显示第 N 条视频时提前把第 N1 条和第 N2 条的数据加载到内存里并准备好播放器实例。具体做法是维护一个预加载播放器池池子大小固定为 3 个当滑到新条目时把最远的一个播放器释放掉重新绑定新的视频源。这套逻辑和图片加载框架的预取思路完全一致。实测下来预加载播放器池让首帧耗时从平均 800 毫秒降到了 200 毫秒以内。2.2 Feed 流滑动体验优化Feed 流我们用 PageView 来做每个视频占据一个全屏页面这样天然支持滑动切换。但要注意 PageView 默认会把相邻的页面也一起构建如果不做处理内存里同时会存在三个完整页面和播放器实例。我们的做法是给每个视频页设置 AutomaticKeepAliveClientMixin让滑走的页面不要立刻销毁同时配合播放器池的释放策略在用户完全滑过去之后才开始销毁不可见页面的播放器资源。还有几个细节对滑动流畅度影响很大。一个是关闭 PageView 的 physics 弹性效果在 Android 上默认的 ClampingScrollPhysics 就有阻尼感视频类 App 应该改成 RangeMaintainingScrollPhysics 或者直接透传让滑动更跟手。另一个是缩略图封面必须先于视频首帧展示这条很重要因为网络视频加载再快也有延迟封面图可以先填充画面避免白屏。缩略图我们用 extended_image 配合内存缓存和磁盘缓存图片尺寸控制在 720p 以下避免解码耗时拖累滚动。2.3 视频产出的工作流拍摄、编辑与上传短视频 App 不能只做消费端生产端同样重要。拍摄功能我们用的是 camera 插件但只在 Android 和 iOS 原生层做了一层薄封装把分段录制、闪光灯切换、前后摄像头切换、录制时长控制暴露给 Flutter。分段录制的实现细节是每段视频录制完成后立即生成独立文件最后通过 FFmpeg 把这些分片拼接起来。FFmpeg 我们没有直接集成到 App 里而是通过一个单独的视频处理原生模块封装了 concat、裁剪、压缩、加滤镜这些命令Dart 层只需要传文件路径和参数。上传环节最容易被低估尤其在大视频场景下弱网环境中很容易失败。我们做了三步第一步在上传前先对原视频做压缩转码把码率压到 2Mbps 以下第二步使用分片上传每片 1MB断点续传第三步是上传中和上传后分别做二次校验避免服务器拿到的是损坏文件。这里还遇到一个经典问题Flutter 在 iOS 上获取相册权限时如果没有添加相册的用途声明描述访问相册会直接崩溃。这个在踩坑部分再细说。3. 直播模块的核心搭建思路3.1 推拉流方案选型与接入直播模块是整个项目中原生依赖最重的一块。推流端需要采集摄像头画面、音频、做美颜处理然后编码推送到 CDN拉流端需要快速起播、低延迟、自适应码率。这些能力纯 Flutter 插件目前还没有一个能真正做到开箱即用所以我们走了成熟第三方 SDK 加 Flutter 封装的路线。拉流端我们支持了 RTMP、HTTP-FLV 和 HLS 三种协议其中 HTTP-FLV 是直播间首选的播放协议延迟能控制在 1 到 3 秒HLS 作为回放和弱网备用方案。推拉流 SDK 接入 Flutter 的关键在于纹理注册。原生 SDK 渲染画面的方式通常是把渲染 Surface 的 id 返回给 FlutterFlutter 再通过 Texture 控件把视频帧绘制到 Widget 上。这里有个非常容易踩的坑如果你用的是 textureId 方式接入必须确保原生层的 GL 环境在 FlutterView 之前创建否则在某些 Android 机型上会导致黑屏。我们当时的排错花了整整两天最后在切换纹理创建时机后才解决。3.2 IM 聊天与互动消息直播间的弹幕、评论、礼物、进场通知、系统公告这些都是高频实时消息和 Feed 流里一对一的聊天完全不一样。我们的做法是引入一个可水平扩展的消息通道直播间的用户通过 WebSocket 建连以房间 ID 为维度订阅消息数据格式统一为 JSON 协议枚举。客户端收到消息后通过一个全局的 StreamController 广播给直播间内的各个订阅组件。这种设计有一个明显的好处弹幕、礼物、系统消息虽然是三种完全不同的 UI 表现但底层的数据通道完全统一新增一种消息类型只需要在枚举里加一个值然后在 UI 层写对应的渲染逻辑。礼物连击这种高频动画我们不建议直接把每一条礼物消息都触发一次完整动画而是做一个计数器合并机制同一用户同一礼物在短时间内只触发一次动画但连击数字持续累加这也是主流直播平台的标准做法。3.3 礼物系统、榜单与扩展功能直播间除了聊天之外礼物系统是最影响营收和体验的功能。我们的礼物面板是一个底部弹层展示礼物列表、背包、充值入口。礼物数据通过接口拉取每个礼物配置了图片、动画类型、价格、特效优先级。发送礼物时客户端先请求后端扣费接口扣费成功后通过 IM 通道发送一条高优先级礼物消息直播间内所有用户收到消息后触发全屏特效。全屏特效的实现上我们用 Overlay 配合动画控制器实现因为礼物动效需要覆盖直播画面之上用普通 Widget 层级会受布局约束。礼物的飞行动效、旋转、缩放可以通过 Flutter 的 AnimationController 串联起来。要注意的是全屏特效非常消耗性能如果同时有多个不同用户送礼我们会把特效排队执行而不是并发执行否则中低端手机会直接卡到掉帧。榜单功能相对简单本质就是调用后端接口按时间维度和礼物价值汇总客户端只需要做定时刷新和动画展示。4. 工程化与性能优化4.1 状态管理、路由与代码组织项目规模一大状态管理和代码组织如果没规划好后期维护就是灾难。我们选择的状态管理方案是 Riverpod理由有三个编译期安全、天然的依赖注入、支持自动销毁。直播间这种高频刷新页面用 Riverpod 可以很方便地监听房间状态和用户状态的变化而不会引起整棵树重建。目录结构我们按功能模块划分而不是按技术类型划分。每个 feature 下面包含 pages、widgets、providers、models、api、utils 六个子目录这样一个直播功能涉及的所有代码都可以在一个文件夹里找到。路由我们用 go_router它和 Riverpod 配合得比较顺畅而且在处理 deep link 和 Web 端统一路由时优势明显。90 天下来这个目录结构让两个人并行开发几乎不产生冲突这是当时规划时最明智的决定之一。4.2 Flutter SDK 多版本管理与环境搭建开发过程中我们踩过一个版本坑Flutter 3.10 和 3.13 的 Impeller 渲染引擎在部分 Android 机型上表现完全不同新版本更流畅但部分机型的兼容性又有问题。为了在稳定和性能之间取舍我们引入 FVM 做 Flutter SDK 多版本管理每个项目锁定一个 Flutter 版本和 Dart 版本方便团队成员统一环境。这个工具强烈建议所有 Flutter 团队都用起来否则几个人换电脑后版本不一致跑起来一堆诡异问题浪费的时间完全得不偿失。配套的开发环境我们统一用 Android Studio 写代码VS Code 做轻量预览和热重载调试。Android Studio 的 Flutter 插件对 widget 树检查、性能分析、内存监控更完整VS Code 则胜在启动快、内存占用低。实际开发中我是两个混着用的写逻辑用 VS Code查页面层级和性能用 Android Studio。另外Flutter SDK 安装后建议把国内镜像配好不然在部分网络环境下下载依赖包会很痛苦。4.3 启动速度、包体积与渲染引擎优化启动速度直接影响用户留存。我们做了三个优化第一是让启动页和 Flutter 首帧之间无缝过渡不要在原生层停留太久第二是把非必要初始化延后比如登录态检查、推送注册、IM 建连这些都要在首页真正渲染完成之后再异步执行第三是路由懒加载短视频和直播模块的代码拆分后用户没有进入直播间之前直播相关的页面代码不会加载。这三步下来冷启动时间从 2.3 秒压缩到 1.2 秒左右。包体积方面Android 上我们开启了代码混淆、资源压缩和 ABI 分离armeabi-v7a、arm64-v8a、x86_64 三个架构拆成独立包用户只下载自己机型需要的那个架构。iOS 上开启 App Thinning让系统只下发当前设备需要的架构和资源。Flutter 构建时加上 --split-debug-info 和 --obfuscate 参数既能减少包体也能增加源码阅读难度。最终 Android 包体积从 95MB 降到 58MBiOS 从 110MB 降到 62MB这个数字在包含两张完整视频业务线的 App 里已经算比较理想。渲染引擎方面Flutter 3.10 以后默认在 iOS 上启用 ImpellerAndroid 上要手动开启。Impeller 解决了 Skia 在复杂绘制场景下因着色器编译导致的掉帧问题对直播间礼物特效这种高频动画场景提升非常明显。我们实测在 Android 中端机上开启 Impeller 后直播间帧率稳定性提升了约 20%。如果你的项目跑在 Flutter 3.10 以上Android 端强烈建议在 AndroidManifest 里加上启用 Impeller 的标签试一下兼容性。4.4 多线程与异步处理Flutter 是单线程模型Dart 代码跑在 UI isolate 上但这不意味着你不能用多线程。视频封面图对图片做模糊处理、视频上传前的压缩计算、JSON 大规模解析、复杂对象的深拷贝这些 CPU 密集任务如果放到 UI isolate 上跑页面就会卡顿甚至掉帧。我们的处理方式是凡是超过 16ms 的计算任务一律通过 compute 函数或者自定义 isolate 丢到后台去执行。自定义 isolate 适合需要跑很多次的任务因为每次创建 isolate 都有开销。比如视频封面的模板合成我们启动时预创建了一个后台 isolate然后在 Dart 层维护一个任务队列App 生命周期内一直复用这个 isolate。这种长驻后台 isolate 模式在做图像处理、音视频处理的后台任务时特别实用。当然也要注意isolate 之间通信靠消息拷贝不能传递大对象传递的是内存地址拷贝后的数据所以大文件还是走文件路径传递更稳。5. 90 天交付避开的大坑5.1 一定要警惕的 Flutter 兼容性陷阱开发途中我们遇到了不少平台相关的兼容性问题有些问题看起来像业务代码 bug实际却是底层框架或者原生交互导致的。比如 Android 上某些机型的 EditText 在 Flutter 页面内弹出软键盘时会把整个页面顶上去且无法恢复iOS 上使用 TextField 时如果页面有滚动视图点击输入框偶尔会出现文字错位的视觉 Bug。这些问题通常没有通用解只能换实现方式绕过。另一个印象深刻的坑是低功耗蓝牙和后台定位这类需要动态权限申请的权限在 Flutter 里如果只配置了 AndroidManifest 而没有在代码里动态请求首次安装后会直接闪退。像 iOS 的相册、相机、麦克风权限还要在 Info.plist 里写清楚用途描述缺一个就崩溃一次。建议在开发初期就把所需权限清单列全一次性配到位不要等到联调时再补否则会被这些低级的平台配置反复打断节奏。5.2 抓包调试与真机调试实录App 开发过程中接口联调抓包是每天都要做的事。我们用 Charles 做 HTTPS 抓包这里有个注意点Android 7.0 以上默认不信任用户安装的 CA 证书如果 App 没开 debug 模式Charles 会抓不到包。我们的处理是 debug 包和 release 包分离配置debug 包允许所有证书信任release 包关闭网络调试权限这样既方便联调又不会影响上架安全。直播流的排查比普通接口复杂因为音视频数据不走 HTTP 协议Charles 看不到推拉流内容。我们当时遇到一个主播端推流正常但观众端黑屏的严重问题排查了很久最后通过抓取 RTMP 推流地址的连接日志发现是 B 帧设置问题某些推流 SDK 默认开启了 B 帧而播放器对带 B 帧的流兼容性较差。关闭 B 帧后问题解决。这类问题纯靠看 UI 层代码根本发现不了需要从推流端、信令服务、播放端三个维度分层排查。5.3 应用市场上架与审核避坑应用市场上架是整个项目最不可控的环节因为审核标准时有调整并且都是人工审核。我们的踩坑点集中在隐私政策上第一次提审被驳回了原因是应用在启动时申请了麦克风权限但隐私政策文本里没有明确写出采集麦克风数据的目的也没有说明数据的处理方式。后来把隐私政策全文重新梳理了一遍明确每一项权限和采集数据的对应关系才顺利过审。直播类应用还需要考虑资质合规直播相关的内容审核机制必须要在产品设计层面就预留好。接入内容审核能力在客户端的主要工作是上报违规内容、屏蔽敏感词、举报入口、封禁提示。虽然这些功能不出彩但没有审核能力直播类 App 连提审的机会都没有。这里提醒大家内容审核不能等产品做完了再补要在功能设计阶段就作为基础模块一起规划。Android 上架还要求对外提供隐私政策链接并且在实际获取权限前不能默认勾选同意这一点不少开发团队会忽略。5.4 常见问题速查表问题现象根本原因解决方式Android 视频页滑动卡顿未启用 Impeller 且播放器实例未复用开启 Impeller使用播放器池复用实例iOS 直播间黑屏原生纹理创建时机晚于 FlutterView提前初始化 GL 环境并注册纹理弱网下视频上传失败缺少分片和断点续传1MB 分片上传失败自动重试相册权限崩溃Info.plist 缺少相册用途声明配置 NSPhotoLibraryUsageDescription打包后首包体积过大未开启混淆和 ABI 分离开启混淆按 ABI 拆分 Android 包软键盘顶起页面无法复原Flutter 配合原生键盘冲突使用 Scaffold resizeToAvoidBottomInset 并做键盘监听6. 交付后的运维与迭代建议6.1 灰度发布与崩溃监控App 上线不等于项目结束上线后的稳定性监控才真正决定产品质量。我们接入了崩溃监控体系可以按版本、OS、机型、自定义标签过滤崩溃日志。短视频和直播是最容易覆盖中低端机型的应用崩溃监控的告警阈值要设置得比普通 App 更敏感建议崩溃率超过 0.1% 就要触发告警。灰度发布也是必做的环节。客户端发版前我们会先让内部人员使用 beta 包跑 24 小时然后再对 5% 的线上用户放量观察崩溃率和新功能使用数据后再逐步放量到全量。这个策略看起来保守但可以有效地预防打包环境、配置错误、机型兼容性等上线后才暴露的问题。毕竟 Flutter 在抹平大部分跨端差异的同时平台底层的一些坑仍然不会自动消失。6.2 后续版本规划与技术演进方向上线后的版本迭代和技术演进方向我个人的建议是按三个优先级来推进。第一个优先级是稳定性和体验细节比如播放器更换为更底层的自研播放方案降低首帧延迟优化弱网下的码率自适应策略。第二个优先级是数据驱动产品接入更完善的埋点体系分析用户画像、停留时长、送礼转化率用数据指导功能迭代。第三个优先级是功能扩展比如短视频支持合拍、剪同款、本地草稿箱多内容管理直播支持更复杂的 PK 玩法、红包、竞猜。技术栈层面90 天的紧凑开发积累了很多可复用的基础组件后续可以考虑把这些组件抽取成独立的 Flutter 包比如播放器插件、上传组件、直播组件、状态管理基础库一来可以降低模块间的耦合二来也为以后的多个 App 复用打基础。如果团队有精力也可以把这些组件开源反向为 Flutter 社区做一点贡献同时也能通过社区反馈优化组件质量。7. 最后再分享几句实在话这个项目做到今天我对 Flutter 的看法比立项之前更务实单从开发效率来看Flutter 节省的成本是肉眼可见的同一套业务逻辑在两个平台完全等价输出这让我们在 90 天这种极限周期下有了喘息空间。但 Flutter 并不是银弹它承担的更多是 UI 和业务层真正涉及性能的底层能力仍然需要原生代码来补位。如果你准备启动类似项目我的建议是先花两周时间把原生插件边界和通信协议设计清楚再开始写 Flutter 页面。否则后面每接一个原生功能都会像补丁一样越打越多再漂亮的代码也会败给无穷无尽的 channel 对接。另外还有一点90 天走到这一步最重要的不是炫技而是工程纪律。每天固定时间联调、每周一次全量回归、每个技术难点都提前做 Demo 验证这些看起来很朴素的做法才是项目能准时交付的真正原因。如果这篇文章对你的项目有启发记得在方案评审阶段就把播放器池、预加载、权限配置、隐私政策这些容易忽略的点列进清单少踩一个坑就多一天做真正有价值的功能。
返回列表