Рубрики
Flutter

Оптимизация производительности Flutter приложений 2025

Полное руководство по оптимизации производительности Flutter приложений: profiling, оптимизация рендеринга, памяти и сети.

Производительность критически важна для пользовательского опыта. Разберём методы оптимизации 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 для профилирования и исправляйте узкие места.