Производительность критически важна для пользовательского опыта. Разберём методы оптимизации Flutter приложений.
Performance Overlay
Включение overlay
MaterialApp(
showPerformanceOverlay: true,
home: MyApp(),
)
Dart DevTools
flutter pub global activate devtools
flutter pub global run devtools
Оптимизация рендеринга
Const конструкторы
// Плохо
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Text('Hello'),
);
}
}
// Хорошо
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const SizedBox(
child: Text('Hello'),
);
}
}
RepaintBoundary
// Изолировать дорогостоящую отрисовку
RepaintBoundary(
child: ExpensiveWidget(),
)
Оптимизация ListView
// Плохо
ListView(
children: [
for (int i = 0; i < 10000; i++) ListTile(title: Text('Item $i')),
],
)
// Хорошо
ListView.builder(
itemCount: 10000,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
)
addAutomaticKeepAlives
// Отключение сохранения состояния для больших списков
ListView.builder(
addAutomaticKeepAlives: false,
addRepaintBoundaries: false,
itemCount: items.length,
itemBuilder: (context, index) {
return ItemWidget(items[index]);
},
)
Оптимизация состояния
StatefulWidget правильно
class GoodStatefulWidget extends StatefulWidget {
final String data;
const GoodStatefulWidget({required this.data, super.key});
@override
State<GoodStatefulWidget> createState() => _GoodStatefulWidgetState();
}
class _GoodStatefulWidgetState extends State<GoodStatefulWidget> {
@override
Widget build(BuildContext context) {
return Text(widget.data);
}
}
rebuild на виджет
// Плохо - весь UI пересобирается
class BadExample extends StatefulWidget {
@override
State<BadExample> createState() => _BadExampleState();
}
class _BadExampleState extends State<BadExample> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $counter'),
Expanded(
child: ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
),
),
ElevatedButton(
onPressed: () => setState(() => counter++),
child: Text('Increment'),
),
],
);
}
}
// Хорошо - пересобирается только нужная часть
class GoodExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
CounterWidget(),
Expanded(
child: ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
),
),
],
);
}
}
class CounterWidget extends StatefulWidget {
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('Counter: $counter'),
ElevatedButton(
onPressed: () => setState(() => counter++),
child: Text('Increment'),
),
],
);
}
}
Оптимизация изображений
Кэширование
dependencies:
cached_network_image: ^3.3.0
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
Оптимизация размера
// Укажите размер
Image.network(
'https://example.com/image.jpg',
width: 100,
height: 100,
fit: BoxFit.cover,
)
Lazy loading
class ImageLazyLoader extends StatefulWidget {
@override
State<ImageLazyLoader> createState() => _ImageLazyLoaderState();
}
class _ImageLazyLoaderState extends State<ImageLazyLoader> {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return CachedNetworkImage(
imageUrl: 'https://example.com/image$index.jpg',
width: 100,
height: 100,
fit: BoxFit.cover,
);
},
);
}
}
Оптимизация памяти
Dispose ресурсов
class ProperDispose extends StatefulWidget {
@override
State<ProperDispose> createState() => _ProperDisposeState();
}
class _ProperDisposeState extends State<ProperDispose> {
late AnimationController _controller;
late TextEditingController _textController;
late StreamSubscription _subscription;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
_textController = TextEditingController();
_subscription = someStream.listen((data) {
// Обработка данных
});
}
@override
void dispose() {
_controller.dispose();
_textController.dispose();
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
Оптимизация списков
// Плохо - все данные в памяти
final allItems = List.generate(100000, (i) => Item(id: i));
// Хорошо - загрузка по требованию
class LazyList extends StatefulWidget {
@override
State<LazyList> createState() => _LazyListState();
}
class _LazyListState extends State<LazyList> {
final List<Item> _items = [];
final ScrollController _controller = ScrollController();
bool _isLoading = false;
@override
void initState() {
super.initState();
_loadMore();
_controller.addListener(_scrollListener);
}
void _scrollListener() {
if (_controller.position.pixels >=
_controller.position.maxScrollExtent - 200) {
_loadMore();
}
}
Future<void> _loadMore() async {
if (_isLoading) return;
setState(() => _isLoading = true);
final newItems = await api.fetch(offset: _items.length);
setState(() {
_items.addAll(newItems);
_isLoading = false;
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: _controller,
itemCount: _items.length + (_isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index == _items.length) {
return Center(child: CircularProgressIndicator());
}
return ItemWidget(_items[index]);
},
);
}
}
Оптимизация сети
Batch запросы
// Плохо
for (final id in ids) {
final item = await api.getItem(id);
}
// Хорошо
final items = await api.getItems(ids);
Кэширование
class CachedApiService {
final Map<String, dynamic> _cache = {};
final ApiService _api;
CachedApiService(this._api);
Future<T> get<T>(String key, Future<T> Function() fetch) async {
if (_cache.containsKey(key)) {
return _cache[key] as T;
}
final result = await fetch();
_cache[key] = result;
return result;
}
void invalidate(String key) {
_cache.remove(key);
}
}
Сжатие данных
final response = await http.get(
Uri.parse('https://api.example.com/data'),
headers: {
'Accept-Encoding': 'gzip',
},
);
Isolates для тяжёлых задач
Compute функция
// Изолированная функция
int heavyComputation(int n) {
int result = 0;
for (int i = 0; i < n; i++) {
result += i * i;
}
return result;
}
// Использование
class ComputeWidget extends StatefulWidget {
@override
State<ComputeWidget> createState() => _ComputeWidgetState();
}
class _ComputeWidgetState extends State<ComputeWidget> {
int? result;
Future<void> _runComputation() async {
final value = await compute(heavyComputation, 1000000);
setState(() => result = value);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Result: ${result ?? "Not computed"}'),
ElevatedButton(
onPressed: _runComputation,
child: Text('Compute'),
),
],
);
}
}
Isolate для JSON parsing
String parseJsonInBackground(String jsonString) {
final decoded = json.decode(jsonString);
return decoded.toString();
}
// Использование
final parsed = await compute(parseJsonInBackground, jsonString);
Profiling
Performance timeline
class ProfiledWidget extends StatefulWidget {
@override
State<ProfiledWidget> createState() => _ProfiledWidgetState();
}
class _ProfiledWidgetState extends State<ProfiledWidget> {
@override
Widget build(BuildContext context) {
return Timeline.startSync('build ProfiledWidget', () {
return Container();
});
}
}
Best Practices
1. Используйте const где возможно
const SizedBox(height: 16)
const Text('Hello')
2. Минимизируйте rebuild
// Используйте отдельные виджеты для динамических частей
class StaticContainer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
StaticPart(),
DynamicPart(),
],
);
}
}
3. Кэшируйте данные
// Используйте cached_network_image для изображений
// Кэшируйте API ответы
// Сохраняйте данные локально
4. Ленивая загрузка
// ListView.builder вместо ListView
// FutureBuilder для асинхронных данных
// Lazy loading для больших списков
Заключение
Оптимизация производительности — это постоянный процесс. Используйте DevTools для профилирования и исправляйте узкие места.