Performance Optimization Practices for a Flutter‑Based Hotel App at Ctrip
This article details how Ctrip's hotel team used Flutter to improve app performance over two years by monitoring FPS, TTI, memory, applying widget‑tree optimizations, lazy loading, frame rendering, service‑channel enhancements with Protobuf, and systematic memory‑leak detection and mitigation.
The Ctrip hotel business has been developing with Flutter for nearly two years, integrating list, detail, and gallery pages across platforms, which greatly increased development efficiency but revealed performance issues such as long‑list jank, slow page launches, and delayed data loading.
To address these problems, the team established an online performance monitoring system focusing on quantification, governance, and continuous monitoring, achieving satisfactory FPS, TTI, and memory metrics.
FPS & TTI Optimization – The article explains the definitions of FPS (frames per second) and TTI (time to interactive) and introduces Flutter's three build modes, recommending the use of profile mode with the Performance View and Enhance tracing for analysis.
Key practical measures include:
Controlling setState frequency and using the Provider pattern to limit UI refresh scope.
Pre‑building widgets with AnimatedBuilder to separate static and animated parts. Example code: @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, child: Container( width: 200.0, height: 200.0, color: Colors.green, child: const Center(child: Text('Text!')), ), builder: (BuildContext context, Widget child) { return Transform.rotate( angle: _controller.value * 2.0 * math.pi, child: child, ); }, ); }
Using const widgets for immutable UI elements to reduce rebuild cost.
Offloading heavy calculations to Isolate to keep the UI thread responsive.
Implementing lazy loading with ListView.builder , PageView.builder , and GridView.builder to render only visible items.
Applying frame‑by‑frame rendering by placeholdering long‑draw widgets in the first frame and replacing them in the next frame. Example placeholder code: @override void initState() { super.initState(); result = widget.placeHolder; replaceWidget(); } @override Widget build(BuildContext context) { return result; } void replaceWidget() { SchedulerBinding.instance.addPostFrameCallback((t) { TaskQueue.instance.scheduleTask(() { if (mounted) setState(() { result = widget.child; }); }, Priority.animation, id: widget.index); }); }
Additional GPU‑related checks use checkerboardOffscreenLayers and checkerboardRasterCacheImages to detect expensive layer compositing and missing image caches, guiding developers to reduce saveLayer usage and wrap static images in RepaintBoundary .
Page Pre‑loading for TTI Reduction – Three common pre‑load strategies are described: fetching next‑page data during the previous page, caching data across pages, and sending service requests before navigation. The hotel booking flow applied these techniques, cutting the slow‑load rate from 42.9% to 8.05%.
Flutter Service Channel Optimization – The original data flow involved PB → JSON → MethodChannel (JsonMethodCodec) → Flutter, which was slow. By switching to direct PB transmission with StandardMethodCodec , the chain became PB → MethodChannel → Flutter, dramatically improving performance.
Using Protobuf in Flutter requires generating Dart files with protoc and adding the protobuf dependency. The article outlines installation steps for protoc , the Dart plugin, and provides sample code to serialize/deserialize messages.
Memory Leak Monitoring & Mitigation – Flutter’s Expando weak references and DevTools memory inspector are used to detect leaks. Common leak sources include un‑cancelled Futures from native plugins and lingering observers (e.g., timers, EventBus). Sample leak‑prone native plugin call: void callNative() { FlutterBridge.callNative("method", map).then((value) { // do something }); }
Solution: convert the Future to a Stream and cancel the subscription on page disposal. Example fix: void callNative() { Future future = FlutterBridge.callNative("method"); _streamSubscription?.cancel(); _streamSubscription = future?.asStream()?.listen((value) { // do something }); } void onPageDestroy() { _streamSubscription?.cancel(); }
Other leak sources such as unreleased observers are also addressed.
Conclusion – By applying the described FPS/TTI improvements, widget‑tree refinements, service‑channel redesign, Protobuf adoption, and systematic memory‑leak handling, the Ctrip hotel app achieved significant performance gains and established a foundation for further optimization and abstraction.
Ctrip Technology
Official Ctrip Technology account, sharing and discussing growth.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.