说明:本文使用可直接复用的代码范式,不绑定具体业务与项目。
这篇文章不做泛泛而谈,而是给出一套可直接复用的启动代码结构:分层初始化、依赖注入、延迟任务、错误边界。你可以直接把这些代码放进项目,替代“一个 main 写到底”的做法。
1. 先把启动拆成“步骤”
启动最大的痛点是“不可测试”。解决方案是把启动拆成可组合的步骤:
abstract class StartupTask {
String get name;
Future<void> run(StartupContext context);
}
class StartupContext {
final Stopwatch stopwatch;
final AppContainer container;
StartupContext({required this.stopwatch, required this.container});
}
这样每一步都可以独立测试,也可以统计耗时。
2. 依赖容器:集中创建、统一管理生命周期
核心依赖不要散落在各个 Widget 里,统一放到容器中:
class AppContainer {
final LocalStore store;
final ApiClient api;
final AuthService auth;
AppContainer({required this.store, required this.api, required this.auth});
static Future<AppContainer> build(Env env) async {
final store = await LocalStore.init();
final api = ApiClient(baseUrl: env.baseUrl);
final auth = AuthService(api: api, store: store);
return AppContainer(store: store, api: api, auth: auth);
}
}
关键点:容器只做构建,不做业务逻辑。
3. 启动编排器:按顺序执行并记录耗时
class Bootstrapper {
final List<StartupTask> tasks;
Bootstrapper(this.tasks);
Future<void> run(StartupContext context) async {
for (final task in tasks) {
final before = context.stopwatch.elapsedMilliseconds;
await task.run(context);
final cost = context.stopwatch.elapsedMilliseconds - before;
Log.d('startup', '${task.name} ${cost}ms');
}
}
}
这段代码让“启动耗时”具备可观测性。
4. 把“必须同步”和“可延迟”拆开
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final env = await EnvLoader.load();
final container = await AppContainer.build(env);
final context = StartupContext(
stopwatch: Stopwatch()..start(),
container: container,
);
final bootstrapper = Bootstrapper([
InitCrashReporter(),
InitUserSession(),
InitTheme(),
]);
await bootstrapper.run(context);
runApp(AppBootstrap(container));
// 延迟任务
unawaited(BackgroundInit.run(container));
}
启动时只执行“必要任务”,其余任务放到后台。
5. 延迟初始化任务的范式
class BackgroundInit {
static Future<void> run(AppContainer container) async {
await Future.wait([
Analytics.init(),
RemoteConfig.refresh(),
ImSdk.init(container.auth),
]);
}
}
关键点:后台任务必须可失败但不阻塞主流程。
6. 错误边界:让启动阶段可观测
class InitCrashReporter implements StartupTask {
@override
String get name => 'CrashReporter';
@override
Future<void> run(StartupContext context) async {
await CrashReporter.init();
FlutterError.onError = (details) {
CrashReporter.report(details.exception, details.stack);
};
}
}
启动阶段的异常如果不可见,就是最危险的异常。
7. 依赖注入与状态管理分离
不要在 Provider/Bloc 里创建服务依赖:
class AppBootstrap extends StatelessWidget {
final AppContainer container;
const AppBootstrap(this.container, {super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider.value(value: container.api),
ChangeNotifierProvider(create: (_) => SessionModel(container.auth)),
],
child: const MyApp(),
);
}
}
这样 UI 只关心状态,不关心服务创建。
8. 一份可复用的启动清单
- 运行时绑定 / Crash 监控
- 环境配置与本地存储
- 依赖容器构建
- 启动步骤执行与耗时记录
- 延迟初始化后台任务
- Provider/状态注入
总结
一套可维护的启动链路,必须做到:
- 可拆分:每一步都是可测试任务
- 可观测:每一步都能统计耗时
- 可延迟:重任务不阻塞首屏
这样才能让 Flutter 项目在规模扩展时保持稳定。