FlutterBoostメモリーリーク事例解析と解決策

FlutterBoostメモリーリークの原因と対処法

FlutterBoostはネイティブアプリにFlutterを統合するための混合開発フレームワークとして広く利用されていますが、複雑なページ遷移処理においてメモリーリークが発生するケースがあります。本記事では具体的な事例解析を通じて、その検出方法と根本的な解決策を解説します。

メモリーリークが発生する主要パターン

1. コンテナ管理不備

class ContainerManager extends ChangeNotifier {
  final List<PageItem> _pageStack = [];
  
  bool removePage(PageItem item, {Object? result}) {
    if (_pageStack.length == 2) {
      ChannelController.enableGesture(id: item.uniqueId);
    }
    final removed = _pageStack.remove(item);
    if (removed) item.onClose(result);
    return removed;
  }
}

2. iOS側のビュー破棄処理

- (void)dealloc {
  if (self.cleanupHandler != nil) {
    self.cleanupHandler();
  }
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  self.gesture.delegate = nil;
}

3. イベントハンドラの循環参照

@property(nonatomic, strong) NSMutableDictionary<NSString*,NSArray<EventHandler>*>* eventMap;

診断ツールの活用法

  • Flutter DevToolsによるメモリ監視
    1. Memoryタブを開く
    2. 遷移前後のスナップショット比較
    3. オブジェクトの割り当て履歴分析
  • iOS Instrumentsでのリーク検出
    1. Leaksテンプレートを選択
    2. ページ遷移操作を実行
    3. リークオブジェクトのスタックトレース確認

実装上の対応策

コンテナライフサイクル管理の強化

@override
void dispose() {
  container.removeListener(_onStateChange);
  _clearPageStack();
  super.dispose();
}

iOS側のメモリ解放ロジック

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];
  [[NSNotificationCenter defaultCenter] removeObserver:self];
}

__weak typeof(self) weakSelf = self;
self.cleanupHandler = ^{
  __strong typeof(weakSelf) strongSelf = weakSelf;
  if (!strongSelf) return;
  strongSelf.listener = nil;
};

イベントハンドラ管理の最適化

class EventDispatcher {
  final Map<String, List<Function>> _handlers = {};
  
  void removeHandler(String type, Function handler) {
    final list = _handlers[type];
    if (list != null) {
      list.remove(handler);
      if (list.isEmpty) _handlers.remove(type);
    }
  }
}

代表的な事例分析

ホーム→Aページ→Bページ→ホーム戻り時のリーク問題は、以下の原因で発生します:

  • ページスタックの誤ったクリーンアップ
  • 状態保持の不備
  • 未解除のイベントリスナー
class AppManager extends State<MainApp> {
  void cleanup() {
    final targets = <String>[];
    for (var entry in containers.entries) {
      if (entry.value.isEmpty) {
        targets.add(entry.key);
      }
    }
    for (var id in targets) {
      containers.remove(id);
    }
  }
}

予防策とベストプラクティス

  • ライフサイクルAPIの適切な利用
  • 定期的なメモリーストレステストの実施
  • コードレビュー時の重点チェック項目

タグ: FlutterBoost メモリーリーク iOS開発 混合アプリ

7月7日 00:33 投稿