【开源鸿蒙跨平台开发训练营】Flutter框架开发鸿蒙应用的构建与重建优化
本次优化针对项目中的重复构建问题,主要改进包括:1) 合并主题/语言/字体为单一Listenable减少整树重建;2) 主题数据改为顶层final只创建一次;3) 底部Tab实现懒加载,首屏仅创建必要页面;4) 首页翻页改用ValueNotifier避免整页setState;5) 发现页搜索增加250ms防抖机制。通过重构状态管理和页面加载策略,显著降低了GC开销和内存占用,提升了页面切换流畅度和
·
发现项目中有很多重复构建的地方需要优化
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
文章目录
实现构建与重建优化
本次做了如下优化:合并主题/语言/字体为单一 Listenable、主题只创建一次、底部 Tab 懒加载、首页翻页不整页 setState、发现页搜索防抖。

一、目标与方案
-
目标:
- 切换主题/语言/字体时,减少从
MaterialApp起的整树重建,提升帧率与体感。 - 主题数据不随每次 build 重新创建,降低 GC 与重算开销。
- 底部四个 Tab 仅在首次进入时创建对应页面,降低首屏内存与无关请求。
- 首页 PageView 翻页时仅更新必要状态,不触发整页 rebuild。
- 发现页搜索输入时通过防抖减少全表过滤与 setState 频率,避免输入卡顿。
- 切换主题/语言/字体时,减少从
-
大致方向:
- 在
main.dart中引入AppSettings(themeMode、locale、fontScale)与单一ValueNotifier<AppSettings>,只包一层ValueListenableBuilder;亮/暗主题改为顶层final,只创建一次。 - :
RootPage用List<Widget?> _tabPages按需创建 Tab 页面,在_onTabTapped中_ensureTabCreated(i),首屏仅创建HomePage。 - :
HomePage用ValueNotifier<int> _pageIndexNotifier保存当前页码,onPageChanged只写_pageIndexNotifier.value = index,不调用setState。 - :
SearchPage对_onSearchChanged做 250ms 防抖,再执行过滤与setState。
- 在
二、修改的文件
| 文件 | 说明 |
|---|---|
lib/main.dart |
新增 AppSettings 与 appSettingsNotifier;主题/暗主题改为顶层 final;MyApp 仅一层 ValueListenableBuilder<AppSettings>。 |
lib/pages/profile_page.dart |
设置项与弹窗改为监听/更新 appSettingsNotifier(主题、语言、字体);移除对 appThemeModeNotifier / appLocaleNotifier / appFontScaleNotifier 的引用。 |
lib/pages/root_page.dart |
底部 Tab 懒加载:_tabPages + _ensureTabCreated,仅在首次进入某 Tab 时创建对应页面。 |
lib/pages/home_page.dart |
翻页时用 _pageIndexNotifier.value = index 替代 setState(() => _currentIndex = index),并在 dispose 中 dispose 该 notifier。 |
lib/pages/search_page.dart |
搜索防抖:Timer? _searchDebounce,_onSearchChanged 内先 cancel 再 Timer(250ms, ...) 执行过滤与 setState;dispose 中 _searchDebounce?.cancel()。 |
三、优化说明
3.1 主题/语言/字体合并为单一 Listenable
- AppSettings:不可变类,包含
themeMode、locale、fontScale,提供copyWith({ themeMode, locale, fontScale })。其中locale通过「省略参数」与「显式传 null」区分「不修改」与「设为跟随系统」。 - appSettingsNotifier:全局
ValueNotifier<AppSettings>,在main()中从SettingsService读入初始值后创建。 - MyApp.build:仅一层
ValueListenableBuilder<AppSettings>,内部直接使用settings.themeMode、settings.locale、settings.fontScale配置MaterialApp与MediaQuery.textScaler。 - 设置页:所有原先读写
appThemeModeNotifier/appLocaleNotifier/appFontScaleNotifier的地方改为读写appSettingsNotifier.value(读字段、写copyWith),并持久化到SettingsService不变。
3.2 主题数据只创建一次
_lightTheme、_darkTheme由 getter 改为顶层final常量,应用生命周期内只创建一次,避免每次 build 重算。
3.3 底部 Tab 懒加载
- 结构:
List<Widget?> _tabPages长度为 4,初始均为null。 - 创建时机:
initState中调用_ensureTabCreated(0),保证首屏首页存在;用户点击 Tabi时在_onTabTapped(i)内先_ensureTabCreated(i)再setState,保证当前 Tab 对应页面已创建。 - 展示:
AnimatedSwitcher的 child 为_tabPages[_currentIndex]!。已创建过的 Tab 会一直保留,切换回来不再新建。
3.4 首页翻页不整页 setState
- 状态:用
ValueNotifier<int> _pageIndexNotifier替代原来的_currentIndex。 - onPageChanged:仅执行
_pageIndexNotifier.value = index以及原有的「接近末尾时 _loadMore」逻辑,不再setState。页码指示器在每页的_buildImagePage中由itemBuilder的index直接得到,无需依赖当前页码状态。 - 网格切单列:点击网格项时先
_pageIndexNotifier.value = index,再setState(() => _isGridView = false)并animateToPage。dispose中调用_pageIndexNotifier.dispose()。
3.5 发现页搜索防抖
- 防抖:
Timer? _searchDebounce,常量_searchDebounceDuration = 250ms。 - 逻辑:
_onSearchChanged(q)内先_searchDebounce?.cancel(),再_searchDebounce = Timer(_searchDebounceDuration, () { ... }),在回调里做lower = q.trim().toLowerCase()、过滤_allWorks、setState更新_filteredWorks;回调内先if (!mounted) return。 - dispose:
_searchDebounce?.cancel(),避免泄漏与 setState 后使用。
四、部分代码
4.1 AppSettings 与单一 Notifier(main.dart)
class AppSettings {
const AppSettings({
required this.themeMode,
required this.locale,
required this.fontScale,
});
final ThemeMode themeMode;
final Locale? locale;
final double fontScale;
AppSettings copyWith({
ThemeMode? themeMode,
Object? locale = _omitLocale,
double? fontScale,
}) {
return AppSettings(
themeMode: themeMode ?? this.themeMode,
locale: identical(locale, _omitLocale) ? this.locale : locale as Locale?,
fontScale: fontScale ?? this.fontScale,
);
}
}
const Object _omitLocale = Object();
late final ValueNotifier<AppSettings> appSettingsNotifier;
// main(): appSettingsNotifier = ValueNotifier<AppSettings>(AppSettings(...));
4.2 底部 Tab 懒加载(root_page.dart)
final List<Widget?> _tabPages = [null, null, null, null];
void _ensureTabCreated(int index) {
if (_tabPages[index] != null) return;
switch (index) {
case 0: _tabPages[index] = const HomePage(); break;
case 1: _tabPages[index] = const SearchPage(); break;
case 2: _tabPages[index] = const MessagesPage(); break;
case 3: _tabPages[index] = const ProfilePage(); break;
default: _tabPages[index] = const HomePage();
}
}
// initState: _ensureTabCreated(0);
// _onTabTapped(i): _ensureTabCreated(i); setState(...);
// child: _tabPages[_currentIndex]!
4.3 首页翻页仅更新 notifier(home_page.dart)
final ValueNotifier<int> _pageIndexNotifier = ValueNotifier<int>(0);
onPageChanged: (index) {
_pageIndexNotifier.value = index;
if (!_isGridView && index >= _displayImages.length - 2 && _hasMore && !_isLoadingMore) {
_loadMore();
}
},
// dispose: _pageIndexNotifier.dispose();
4.4 发现页搜索防抖(search_page.dart)
Timer? _searchDebounce;
static const Duration _searchDebounceDuration = Duration(milliseconds: 250);
void _onSearchChanged(String q) {
_searchDebounce?.cancel();
_searchDebounce = Timer(_searchDebounceDuration, () {
if (!mounted) return;
setState(() {
final lower = q.trim().toLowerCase();
// ... 过滤 _allWorks → _filteredWorks
});
});
}
// dispose: _searchDebounce?.cancel();
五、注意事项
- AppSettings.copyWith:
locale使用Object? locale = _omitLocale,调用方传null表示「跟随系统」,不传表示保持当前;设置页切换语言时使用copyWith(locale: newLocale)。 - Tab 懒加载:首次进入发现/消息/我的时才会执行对应页的
initState(如请求、初始化),首屏仅首页会发起首页相关请求。 - 防抖:防抖仅减少输入过程中的 setState 次数,列表很大时若单次过滤仍耗时,可后续考虑将过滤放入
compute(isolate)。
六、小结
通过合并主题/语言/字体为单一 AppSettings + 一层 ValueListenableBuilder、主题常量只创建一次、底部 Tab 按需创建、首页翻页仅更新 ValueNotifier、发现页搜索 250ms 防抖,完成了「二、构建与重建」中高优先级优化,缩小了设置切换与翻页时的重建范围,降低了首屏与输入时的开销,体感更顺滑。
结束语
感谢阅读本帖,如对贴中内容有意见和建议的,欢迎与我联系交流,也欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)