문서 목차
이 페이지에서목차

flutter-inspector-image

Image debugging specialist. Use for cache analysis and memory usage inspection

항목내용
모델haiku
사용 도구Read, Glob, Grep
연계 스킬flutter-inspector

A specialized agent for analyzing image cache and memory usage at runtime.

Triggers#

Automatically activated when @flutter-inspector-image is invoked or the following keywords are detected:

  • Image cache, memory
  • Image loading, optimization
  • Cache size, clear

MCP Tools#

img_get_cache_stats#

Returns image cache statistics.

{
  "name": "img_get_cache_stats",
  "description": "Image cache statistics",
  "inputSchema": {
    "type": "object",
    "properties": {}
  }
}

Response example:

{
  "cache": {
    "currentSize": 52428800,
    "currentSizeFormatted": "50 MB",
    "maximumSize": 104857600,
    "maximumSizeFormatted": "100 MB",
    "usagePercent": 50,
    "liveImageCount": 25,
    "pendingImageCount": 3
  },
  "memory": {
    "currentUsage": 157286400,
    "currentUsageFormatted": "150 MB",
    "peakUsage": 209715200,
    "peakUsageFormatted": "200 MB"
  },
  "network": {
    "cachedImages": 120,
    "totalDownloaded": 314572800,
    "totalDownloadedFormatted": "300 MB"
  }
}

img_analyze_warnings#

Analyzes image-related warnings and issues.

{
  "name": "img_analyze_warnings",
  "description": "Image warning analysis",
  "inputSchema": {
    "type": "object",
    "properties": {
      "threshold": {
        "type": "integer",
        "description": "Warning threshold (MB)",
        "default": 5
      }
    }
  }
}

Response example:

{
  "warnings": [
    {
      "type": "oversized",
      "severity": "high",
      "message": "Image is 4x larger than display size",
      "image": "banner_home.png",
      "details": {
        "displaySize": "390x200",
        "actualSize": "1560x800",
        "wastedMemory": "4.7 MB"
      },
      "suggestion": "Recommend using cacheWidth/cacheHeight"
    },
    {
      "type": "memory",
      "severity": "medium",
      "message": "Cache usage exceeds 75%",
      "details": {
        "usage": "75 MB / 100 MB"
      },
      "suggestion": "Recommend clearing old cache"
    },
    {
      "type": "duplicate",
      "severity": "low",
      "message": "Same image cached at different sizes",
      "image": "profile_user123.jpg",
      "details": {
        "variants": ["100x100", "200x200", "400x400"]
      },
      "suggestion": "Recommend using consistent sizes"
    }
  ],
  "score": 65,
  "grade": "C"
}

img_clear_cache#

Clears the image cache.

{
  "name": "img_clear_cache",
  "description": "Clear image cache",
  "inputSchema": {
    "type": "object",
    "properties": {
      "type": {
        "type": "string",
        "description": "Clear type",
        "enum": ["all", "memory", "disk", "expired"],
        "default": "expired"
      }
    }
  }
}

Response example:

{
  "cleared": {
    "type": "expired",
    "freedMemory": 31457280,
    "freedMemoryFormatted": "30 MB",
    "removedImages": 45
  },
  "after": {
    "currentSize": 20971520,
    "currentSizeFormatted": "20 MB",
    "liveImageCount": 15
  }
}

App Integration Code#

// lib/debug/mcp_image_tools.dart
import 'package:mcp_toolkit/mcp_toolkit.dart';
import 'package:flutter/widgets.dart';
import 'package:cached_network_image/cached_network_image.dart';

class ImageAnalyzer {
  static final instance = ImageAnalyzer._();
  ImageAnalyzer._();

  final List<Map<String, dynamic>> _loadedImages = [];

  void trackImage({
    required String url,
    required Size displaySize,
    required Size actualSize,
    required int bytes,
  }) {
    _loadedImages.add({
      'url': url,
      'displaySize': displaySize,
      'actualSize': actualSize,
      'bytes': bytes,
      'loadedAt': DateTime.now(),
    });
  }

  Map<String, dynamic> getCacheStats() {
    final imageCache = PaintingBinding.instance.imageCache;
    return {
      'cache': {
        'currentSize': imageCache.currentSize,
        'currentSizeFormatted': _formatBytes(imageCache.currentSize),
        'maximumSize': imageCache.maximumSize,
        'maximumSizeFormatted': _formatBytes(imageCache.maximumSize),
        'usagePercent': (imageCache.currentSize / imageCache.maximumSize * 100).round(),
        'liveImageCount': imageCache.liveImageCount,
        'pendingImageCount': imageCache.pendingImageCount,
      },
    };
  }

  List<Map<String, dynamic>> analyzeWarnings({int threshold = 5}) {
    final warnings = <Map<String, dynamic>>[];
    final thresholdBytes = threshold * 1024 * 1024;

    for (final img in _loadedImages) {
      final displaySize = img['displaySize'] as Size;
      final actualSize = img['actualSize'] as Size;

      // Oversize check
      if (actualSize.width > displaySize.width * 2 ||
          actualSize.height > displaySize.height * 2) {
        warnings.add({
          'type': 'oversized',
          'severity': 'high',
          'message': 'Image is larger than display size',
          'image': img['url'],
          'details': {
            'displaySize': '${displaySize.width.toInt()}x${displaySize.height.toInt()}',
            'actualSize': '${actualSize.width.toInt()}x${actualSize.height.toInt()}',
          },
          'suggestion': 'Recommend using cacheWidth/cacheHeight',
        });
      }
    }

    // Cache usage check
    final cache = PaintingBinding.instance.imageCache;
    if (cache.currentSize > cache.maximumSize * 0.75) {
      warnings.add({
        'type': 'memory',
        'severity': 'medium',
        'message': 'Cache usage exceeds 75%',
        'suggestion': 'Recommend clearing old cache',
      });
    }

    return warnings;
  }

  Map<String, dynamic> clearCache(String type) {
    final cache = PaintingBinding.instance.imageCache;
    final beforeSize = cache.currentSize;

    switch (type) {
      case 'all':
      case 'memory':
        cache.clear();
        break;
      case 'expired':
        cache.clearLiveImages();
        break;
    }

    return {
      'cleared': {
        'type': type,
        'freedMemory': beforeSize - cache.currentSize,
        'freedMemoryFormatted': _formatBytes(beforeSize - cache.currentSize),
      },
      'after': {
        'currentSize': cache.currentSize,
        'currentSizeFormatted': _formatBytes(cache.currentSize),
      },
    };
  }

  String _formatBytes(int bytes) {
    if (bytes < 1024) return '$bytes B';
    if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
    if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
    return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
  }
}

void registerImageTools() {
  if (!kDebugMode) return;

  addMcpTool(MCPCallEntry.tool(
    handler: (_) => MCPCallResult(
      message: 'Cache stats',
      parameters: ImageAnalyzer.instance.getCacheStats(),
    ),
    definition: MCPToolDefinition(
      name: 'img_get_cache_stats',
      description: 'Image cache statistics',
      inputSchema: {'type': 'object', 'properties': {}},
    ),
  ));

  addMcpTool(MCPCallEntry.tool(
    handler: (params) {
      final threshold = params['threshold'] as int? ?? 5;
      return MCPCallResult(
        message: 'Image warnings',
        parameters: {
          'warnings': ImageAnalyzer.instance.analyzeWarnings(threshold: threshold),
        },
      );
    },
    definition: MCPToolDefinition(
      name: 'img_analyze_warnings',
      description: 'Image warning analysis',
      inputSchema: {
        'type': 'object',
        'properties': {
          'threshold': {'type': 'integer', 'default': 5},
        },
      },
    ),
  ));

  addMcpTool(MCPCallEntry.tool(
    handler: (params) {
      final type = params['type'] as String? ?? 'expired';
      return MCPCallResult(
        message: 'Cache cleared',
        parameters: ImageAnalyzer.instance.clearCache(type),
      );
    },
    definition: MCPToolDefinition(
      name: 'img_clear_cache',
      description: 'Clear image cache',
      inputSchema: {
        'type': 'object',
        'properties': {
          'type': {
            'type': 'string',
            'enum': ['all', 'memory', 'disk', 'expired'],
            'default': 'expired',
          },
        },
      },
    ),
  ));
}

Usage Examples#

Check cache status#

Q: Show me the image cache status
A: Run img_get_cache_stats
   -> 50 MB / 100 MB (50%), 25 images

Analyze image issues#

Q: Are there any image optimization issues?
A: Run img_analyze_warnings
   -> 3 warnings: 2 oversized, 1 memory, grade C

Clear cache#

Q: Clear the image cache
A: Run img_clear_cache type="expired"
   -> 30 MB freed, 45 images removed

Common Problem Diagnosis#

Memory warning#

1. Check current usage with img_get_cache_stats
2. Identify problematic images with img_analyze_warnings
3. Clear cache with img_clear_cache
4. Consider applying cacheWidth/cacheHeight

Slow image loading#

1. Check cache hit rate with img_get_cache_stats
2. Check network image downloads (@flutter-inspector-network)
3. Review image size optimization

App crash (OOM)#

1. Check peak memory with img_get_cache_stats
2. Find oversized images with img_analyze_warnings
3. Apply large image optimization
4. Consider adjusting maximum cache size

Optimization Recommendations#

Image Loading Patterns#

// CORRECT: Specify cache size
Image.network(
  imageUrl,
  cacheWidth: 200,
  cacheHeight: 200,
)

// CORRECT: Use CachedNetworkImage
CachedNetworkImage(
  imageUrl: imageUrl,
  memCacheWidth: 200,
  placeholder: (context, url) => CircularProgressIndicator(),
)

// WRONG: Loading at original size
Image.network(imageUrl)  // 4000x4000 image displayed at 100x100

Cache Settings#

// main.dart
void main() {
  // Cache size configuration
  PaintingBinding.instance.imageCache.maximumSize = 100 * 1024 * 1024; // 100 MB
  PaintingBinding.instance.imageCache.maximumSizeBytes = 200 * 1024 * 1024; // 200 MB

  runApp(const MyApp());
}
  • @flutter-inspector: Master inspector
  • @flutter-image-optimizer: Image optimization specialist agent
  • @flutter-inspector-log: Image loading related logs

검색어를 입력하면 문서를 찾습니다.

↑ ↓ 이동 · Enter 열기Esc 닫기