本期视频发布于 2022 年。文中的技术状态、个人经历与观点均为当时情况。
第 2 期 T Chat 的前半场,新宿介绍了在闲鱼开发 Flutter 业务和中间件的经历,重点讲解图片库 PowerImage,并简要展示富文本编辑器。后半场,闲鱼客户端团队负责人于佳(宗心)加入,与新宿和主持人讨论团队为什么选择 Flutter、怎样推进核心业务迁移,以及工程师在这个过程中如何成长。两段录播合计约 85 分钟。宗心的身份也可在闲鱼团队公开访谈中核对。
PowerImage:让 Flutter 使用已有的原生图片能力
问题不在于能否显示图片,而在于两套体系怎样协作
新宿提到,成熟原生图片库往往已经具备按显示尺寸请求合适 CDN 资源、支持特定格式、处理静态资源等能力。混合应用如果在 Flutter 侧另建一套完整实现,就可能重复建设,也可能为同一资源保存多份内容。
此前团队使用的图片方案主要基于外接纹理,仍遇到一些限制:Flutter 侧无法直接取得实际的 ui.Image,部分开发环境中的展示受到版本条件影响,缓存和生命周期也与 Flutter 原生图片体系分开。相册还可能有自己的加载通道,使同一应用里的图片管理更加分散。
PowerImage 因此希望同时解决能力复用与管理统一:连接 Native 图片库,保留纹理路径,再用 FFI 补充相应能力,并尽量与 Flutter 的 ImageCache 协作。
Texture 路径:注册纹理,再让 Flutter 按标识绘制
分享以 iOS 为例解释外接纹理。原生侧实现相应纹理接口,注册后得到标识,再把标识交给 Flutter。Flutter 中的 Texture 组件最终通过这一标识找到对应原生纹理,取得像素缓冲并进入渲染过程。
这一通路让原生侧已有的加载、解码能力可以参与显示,但显示一个外接纹理,不等于 Flutter 侧已经拿到了普通图片流程中的真实 ui.Image。后续缓存计量、图片处理与生命周期管理,需要针对这一差异设计。
先理解原生 Image 的加载和缓存过程
新宿随后沿着 Flutter Image 的流程展开:ImageProvider 负责描述加载来源并参与缓存查找,加载结果通知界面刷新,最后由图片数据进入绘制。缓存里又存在等待加载、已经缓存和仍被使用等不同状态。
他特别指出两个连接点。一个是界面绘制所需要的图片对象,另一个是缓存怎样根据图片信息计算占用、怎样判断资源可以释放。将 Texture 接入原生体系,不能只替换最终显示组件,而不处理这些关联行为。
三个适配问题:组件、缓存计量与原生资源释放
第一,原有图片组件的绘制路径依赖 ui.Image,纹理方案需要提供可扩展的构建入口,允许最终生成 Texture 组件。
第二,缓存大小计算依赖图片宽高等信息。分享介绍了用占位对象衔接类型要求,同时让缓存取得实际纹理尺寸的做法。这里的占位不代表资源实际只占一个像素的内存,计量仍需反映真实内容。
第三,原生纹理有自己的生命周期。团队对 ImageCache 作适配,观察资源从相关缓存状态中释放的时机,再通知原生侧处理。这样可以减少仅依赖页面栈管理图片资源带来的割裂。
FFI 补足真实图片对象的需求,也带来不同成本
FFI 路径把原生像素数据的地址、长度等信息传到 Flutter 侧,再生成实际的 ui.Image,以支持需要图片对象的使用场景。分享中的实现会发生内存复制,完成相应处理后再释放原生资源。
因此,Texture 与 FFI 的比较需要同时看能力和资源成本:前者适合已有纹理路径,后者更自然地接入需要图片对象的流程,但可能产生不同的内存峰值。新宿没有把两者概括为一个在所有情况下都更优的方案。
应用接口尽量熟悉,原生加载器由接入方提供
应用层使用的接口尽量接近原有 Image 使用方式,包括网络图片、原生静态资源和自定义来源。业务还可以定义图片类型,例如相册图片,并把资源标识传给对应原生加载器。
PowerImage 不假设每个团队都使用同一种 Native 图片库。接入方注册不同类型的加载器,完成自己的加载与解码,再把结果交回公共框架;框架内部根据选定路径处理 Texture 或 FFI。这样,业务能复用既有原生能力,也不必自行重复组织整条跨端显示流程。
项目名称、双模式入口及自定义加载器关系,可与 PowerImage 官方仓库对照。实际接入仍应检查所用 Flutter 与扩展包版本,而不是照搬录播中的版本组合。
动图、性能实验和测试一起进入工程设计
分享中的动图能力来自团队间协作,当时主要使用纹理路径。加载器返回帧与时间信息,再按节奏更新显示;资源不再展示时,需要相应处理缓存和原生侧工作,避免无意义地持续刷新。
性能部分,新宿展示了指定设备、Flutter 版本、列表图片数量与缓存配置下的对比。FFI 中的复制可能增加内存峰值,图片大小也会影响结果;原生图片体系还有滚动时延迟加载、尺寸处理等策略,比较时应注意这些条件。录播表达了团队之后更关注 FFI 路径的倾向,不能据此跳过当前场景验证。
他也强调可维护性。核心图片能力与版本适配部分分开,单元测试覆盖关键行为;初始化时配置缓存、默认渲染方式与异常回调,再在原生侧注册加载协议。图片库不只是让演示页面显示成功,还要支持升级、定位问题与持续维护。
富文本编辑器:协议、输入与交互都需要衔接
技术分享最后简要介绍了富文本编辑器 Mural。它不只是把格式化文字画出来,还需要组织内容协议、选区与输入状态,并与原生文本输入能力衔接。新宿展示了发布、详情等使用场景,也提到放大镜和光标移动等交互细节,以及不同平台返回信息的差异。
输入链路需要双向转换:编辑器侧的内容和选区状态,要转换成系统输入协议交给原生侧;键盘等系统输入带来变化后,又要把返回的编辑状态映射回编辑器自身的操作。这样,内容协议、界面展示与系统输入才能保持同步,而不是各自维护一套脱节的状态。
这部分是能力概览,录播没有完整展开编辑器实现。团队之后的协议设计说明明确使用 Mural 名称,并介绍了参考 Slate 的操作协议;可以作为进一步阅读的原始资料,但不应把后来文章的全部内容补写成现场已经讲过的内容。
团队为什么选择 Flutter
技术氛围从好奇心和实际问题开始
新宿从一线开发者的角度描述团队氛围:主动了解新技术,从技术角度分析产品体验,再把发现反馈给产品同事。例如图片上传与展示的格式、分辨率,既是技术细节,也会影响用户感受。
团队会鼓励调研和分享,把自愿交流放进周会,不限定主题;日常也关注用户反馈中的体验问题。这让好奇心能够进入实际工作,而不只停留在业余讨论。
人效与人的长期成长,需要找到结合点
宗心回顾,团队在业务增长之后开始思考:除了完成业务目标,还能给成员留下什么长期价值?技术能力、个人品牌和行业口碑,是他当时关注的方向。同时,团队规模和人员结构也带来现实的人效问题,需要寻找能够帮助多端协作的技术。
选择开源、较通用的技术,还有让个人积累能够离开单一公司继续使用的考虑。团队曾比较不同跨端路径,对性能、工具和使用体验作验证。按他的回忆,从早期调研到扩大应用经历了较长时间,并非看完一个演示就全量迁移。
创新需要判断,也需要知道哪些事情不值得重复做
宗心谈到,新的方向在早期未必已有共识,推进者需要在验证基础上形成判断,并承担持续投入、反复改善体验的工作。但这并不意味着所有自研都值得做。
如果原生基础能力在团队、集团或行业里已经成熟,他更倾向于直接使用,把有限精力放到自己的痛点和尚未解决的关键问题上。客户端容器化是他当时认可的长期探索方向,录播同时保留了对未来形态的不确定性。
深入 Flutter,怎样从业务走向框架和引擎
不只绕过问题,还要理解问题为什么出现
新宿此前从事 iOS 开发,进入闲鱼后持续围绕 Flutter Framework 工作。他提出的学习方法很具体:业务中遇到组件嵌套或表现异常,不要只找到一种能显示的写法就结束;继续追踪原因,阅读相关源码。
除了当前代码,他还建议查看提交历史、修改说明和关联 issue。一次变更为什么发生、此前有哪些讨论,能够帮助理解框架设计的约束。源码因此不仅是现成答案,也是观察他人怎样分析问题的材料。
学习内容需要连接到具体工程场景
关于成为 Flutter 工程师需要准备什么,新宿列出原生基础、Flutter 原理与 Framework、Dart 语言特性,以及开发工具的使用。图片库就是一个例子:业务写在 Flutter 侧,仍要理解原生资源、内存和平台能力。
他自己的入门也结合了官方示例与项目实践。主持人由此强调,找到一个实际场景,边做边学习,往往更容易让抽象知识形成连接,而不必先把所有资料看完再开始。
Flutter 3.0 的讨论,聚焦多终端与生产升级成本
访谈恰逢 Flutter 3.0 发布,几位参与者谈到桌面、多终端支持与性能改进。宗心更关心已有应用怎样走向平板、折叠设备和桌面环境:如果同一应用需要多个界面独立运行,原有混合栈与单引擎方案会遇到怎样的限制?
团队希望探索多引擎、不同 isolate 之间的协作、较低的内存占用,以及尽量减少业务层改动。这些是当时正在验证的方向,并不是已经交付的通用能力。
他也提醒,生产应用升级需要验证稳定性、能力覆盖和迁移成本。录播当时团队仍使用较早版本,不能把新版本发布直接等同于适合立即在核心业务全量部署。
推进核心链路,先回答收益和能力是否成立
先判断问题,再判断 Flutter 是否合适
对于“核心业务不敢上 Flutter,该怎样推动”的提问,宗心先反问,这项技术是否确实解决当前团队的问题。闲鱼当时的人员规模、客户端技术背景和双端人员分布,是其选型的重要条件;其他团队未必相同。
如果主要矛盾来自音视频底层能力或其他高度平台相关的工作,统一页面开发未必是最关键的收益。图片、表单、复杂业务逻辑较多的场景,则可能更容易从共享实现和一致性中获益。评价应回到实际业务,而不只是证明某个框架值得用。
列出所需能力,先验证,再持续投入
确认有价值之后,需要摸清核心链路的要求:图片、播放器、富文本、性能等能力是否具备?可以逐项列出、形成文档和可验证的演示,避免在关键体验明显受损时急于迁移。
宗心强调,新技术最初往往会降低效率,因为要支付学习和基础设施成本,之后才可能出现收益。团队是否有条件跨过这一阶段,也是选型的一部分。不能只借用别人的最终效率数字,却忽略其前期投入和持续维护。
体验、基础设施与人的能力要一起建设
回顾此前一年的工作,宗心坦率承认,享受共享开发收益的同时,还要补齐社区和自身实现中的缺口。图片、富文本及其他中间件改善用户体验,测试用例和 CI 支持推广,构建工具、代码评审和质量要求则改善工程过程。
人的能力也在变化。团队最初更多在框架之上开发业务,后来逐渐有人深入构建、Framework 和引擎,参与上游修复。这些能力不是一开始就具备,而是在真实问题与持续投入中发展出来。分享提到的贡献规模属于团队当时回顾,不是对今天社区贡献排名的统计。
容器化的展望,需要与已落地工作分开
宗心进一步讨论了统一终端容器的可能性,希望上层开发方式能够更少受到操作系统差异影响,并提到与行业伙伴交流、自研能力和标准建设的愿望。
这些内容属于长期判断。更直接的工程路径,仍是先解决当前混合开发、多屏、性能和基础设施问题,再通过实践判断哪些部分有机会形成更通用的能力。
工具、面试与开源贡献
先把官方调试工具用起来
被问到提效工具时,新宿推荐 Flutter DevTools。组件检查可以帮助理解界面层级、定位对应代码和观察刷新行为;性能、内存与包体分析则服务于不同阶段的问题排查。
这些能力对刚接触一块业务的新成员也有价值:从屏幕上的组件回到实现,比在陌生代码里盲目搜索更直接。工具不是替代分析,而是帮助缩小需要理解的范围。
面试看怎样解决问题,不只看是否做过同一框架
宗心说,团队招人时更关心候选人做过什么、如何分析和解决问题、怎样学习,以及日常代码习惯。此前没有使用 Flutter,并不意味着没有可迁移的工程能力。
对有 Flutter 经验的人,他更愿意讨论生产环境中的混合栈、性能、基础设施与困难问题。做出一个演示,与把方案放进持续演进的真实应用,需要面对的复杂度不同。没有正式项目经验的人,也可以通过自己的尝试和对现有问题的判断,展示好奇心与理解。
上游贡献从清楚描述问题开始
新宿介绍了自己参与 Flutter 上游的流程:先搜索已有 issue 和讨论;如果问题尚未被清楚记录,就提供环境、最小复现和必要的录屏。提出修改时,需要说明它对应的问题,并补充覆盖改动的测试。
随后是项目要求的贡献协议、自动检查和维护者评审,作者要根据意见继续调整。他特别提醒,即使改动进入主分支,后续测试仍可能发现问题并回退,需要继续修正。开源贡献不是提交一次代码便结束,而是与项目共同维护质量的过程。具体标签和流程会随项目变化,应以当前贡献说明为准。
编程方式与构建效率
声明式界面和组件组织,改变多人协作方式
谈到 Flutter 带来的编程方式变化,新宿从声明式与命令式的区别展开:声明界面结构有助于快速搭建和阅读页面,但复杂场景仍需要清楚地组织状态、逻辑与交互。
他介绍了团队当时在详情、发布和交易等业务中使用 Fish Redux 的经验,认为组件化组织有利于分工和定位改动,同时也承认概念较多、初期有学习门槛。项目名称可在 Fish Redux 官方仓库核对;这段是历史实践回顾,不是对当前状态管理方案的选型推荐。
共享实现的价值,需要结合业务逻辑复杂度
在适用场景的追问中,新宿再次强调复杂业务逻辑和多端一致性。两端分别理解、实现并维护同一需求,可能逐渐产生差异;共享实现能够减少其中一部分重复工作。需要同时支持手机与桌面的应用,也有进一步评估跨端方案的理由。
这种收益仍依赖实际能力和平台适配,不能把“一套代码”直接解释为不再有差异。现场关于动态化、热更新的提问没有展开,主持人将其留作后续专题,因此本文也不补写成当场已经给出的方案。
构建慢,先区分 Flutter 产物与原生宿主
最后的问题是混合工程编译时间。宗心建议先拆开两段链路:Flutter 产物构建,与原生宿主构建。前者可以使用配置与宿主一致的占位工程,避免每次都带上完整宿主代码,再将产物交给原生工程集成。
原生侧则要根据构建日志定位具体耗时,分析哪些代码、依赖和产物真正需要重新编译,清理无效引用,并在适当情况下使用预构建产物。CI 环境也应复用已经准备好的依赖和缓存,避免每次重新下载;独立产物上传等步骤可以评估并行执行。
这组建议并没有给出一个适用于所有工程的加速倍数,而是把构建过程拆开,逐段识别重复工作与等待,再分别改进。

