ایپ سٹٹر کو کیسے ٹھیک کریں: ڈیو ٹولز کے ساتھ فلٹر ایپس کی پروفائلنگ کے لیے ایک عملی گائیڈ

پھڑپھڑاہٹ آپ کو خوبصورت UI تیزی سے بنانے دیتی ہے۔ اگرچہ یہ رفتار فریم ورک کے سب سے بڑے فوائد میں سے ایک ہے، لیکن اس سے باریک مسائل بھی پیدا ہوتے ہیں۔ کارکردگی کے مسائل کا سامنا کرنا آسان ہے اور صحیح ٹولز کے بغیر تلاش کرنا مشکل ہے۔

ہکلانا (مرئی ہکلانا، ہکلانا، اور جمنا) شاذ و نادر ہی ہوتا ہے جہاں ڈویلپرز اس کی توقع کرتے ہیں۔ جب مسئلہ ویجیٹ کی تعمیر نو کا ہے، تو نیٹ ورکنگ اس کا ذمہ دار ہے۔ اگر مسئلہ مقامی تنہائی میں مطابقت پذیر تجزیہ ہے، تو سست API کی چھان بین کریں۔ اگر اصل مجرم ایک اینیمیشن ہے جو ہر فریم میں SaveLayer بناتی ہے، تو ریاستی انتظامیہ کو ری فیکٹر کیا جاتا ہے۔

کارکردگی کا مسئلہ قیاس کرنا اور اس کی پروفائلنگ دو بالکل مختلف سرگرمیاں ہیں۔ Flutter DevTools پروفائلنگ کو قابل رسائی، درست اور قابل عمل بناتا ہے۔ یہ مضمون اسے مؤثر طریقے سے استعمال کرنے کے لیے ایک عملی رہنما ہے۔

انڈیکس

جیک واقعی کیا ہے۔

پھڑپھڑانے والی ایپ کے UI میں ہچکچاہٹ، ہچکچاہٹ، منجمد یا ہچکچاہٹ دکھائی دیتی ہے۔ ایسا محسوس ہوتا ہے کہ کچھ تھوڑا سا غلط ہے، انیمیشنز کے ساتھ جو دھڑکن کو چھوڑ دیتے ہیں، اسکرولنگ جو ایک لمحے کے لیے چھلانگ لگاتے ہیں، اور ٹرانزیشنز جو بھاری محسوس ہوتی ہیں۔

ہکلانے کی وجہ تقریباً ہمیشہ ایک جیسی ہوتی ہے۔ اس کا مطلب ہے کہ فریم بنانے میں بہت زیادہ وقت لگتا ہے۔

فلٹر زیادہ تر ڈیوائسز پر 60 فریم فی سیکنڈ اور جدید ہارڈ ویئر پر 120fps پر رینڈر کرتا ہے۔ 60fps پر، Flutter کے پاس ہر فریم بنانے کے لیے بالکل 16 ملی سیکنڈز ہوتے ہیں۔ یہ ڈارٹ کوڈ چلاتا ہے، ویجیٹ ٹری بناتا ہے، لے آؤٹ کی گنتی کرتا ہے، فریموں کو پینٹ کرتا ہے، اور انہیں GPU تک پہنچاتا ہے۔ اگر آپ ڈیڈ لائن کو کھو دیتے ہیں، تو صارفین اپنے فریموں کو گرتے ہوئے دیکھیں گے۔

Normal frames (smooth):
│████████░░░░░░░│  12ms — within 16ms budget ✓
│████████░░░░░░░│  12ms — smooth
│████████░░░░░░░│  12ms — smooth

Dropped frame (jank):
│████████░░░░░░░│  12ms — smooth
│████████████████████████│  28ms — OVER BUDGET ✗
│████████░░░░░░░│  12ms — smooth again

جنک کے دو مختلف ذرائع ہیں اور درست فکس مکمل طور پر اس بات پر منحصر ہے کہ کس کو لاگو کیا گیا ہے۔

  1. UI تھریڈ سٹٹرز: آپ کا ڈارٹ کوڈ بہت زیادہ کام کر رہا ہے۔ مہنگی ویجیٹ عمارت، ضرورت سے زیادہ حساب اور مقامی تنہائی پر ہم وقت ساز تجزیہ۔

  2. راسٹر تھریڈ ہکلانا: آپ کا GPU جدوجہد کر رہا ہے۔ اس کے نتیجے میں مہنگے بصری اثرات، اوور ڈرا، بہت زیادہ پرتیں مرکب، اور SaveLayer آپریشنز ہوتے ہیں۔

DevTools آپ کو بتاتا ہے کہ ذمہ دار کیا ہے۔ کوڈ کی ایک لائن کو تبدیل کرنے سے پہلے یہ فرق اہم ہے۔

درست پروفائلنگ کے لیے ترتیبات

ایک رکاوٹ دوسرے سے زیادہ اہم ہے۔ یعنی ہمیشہ پروفائل موڈ میں پروفائلز بنائیں اور انہیں ڈیبگ موڈ میں کبھی استعمال نہ کریں۔

ڈیبگ موڈ اہم اوور ہیڈ کا اضافہ کرتا ہے، بشمول اضافی دعوے، ہاٹ ری لوڈ انفراسٹرکچر، ڈیبگ پینٹنگ، اور وربوز لاگنگ۔

ڈیبگ موڈ میں ایپس پروڈکشن موڈ کے مقابلے میں کافی آہستہ چلتی ہیں۔ ڈیبگ موڈ میں پروفائلنگ پریت کے مسائل کو ظاہر کرتی ہے جو صارفین کے لیے موجود نہیں ہیں، جبکہ حقیقی پیداوار کے مسائل پوشیدہ رہتے ہیں۔

# Debug mode — distorts measurements, do not use for profiling
flutter run

# Profile mode — matches production performance
# with DevTools still connected
flutter run --profile

پروفائل موڈ ڈیبگ اوور ہیڈ کو ختم کرتا ہے جبکہ DevTools کنیکٹوٹی کو برقرار رکھتا ہے۔ یہ حقیقی صارف کے تجربے کی قریب ترین پیمائش ہے۔

VS کوڈ میں DevTools کھولیں:

Cmd+Shift+P → Flutter: Open DevTools → select Performance

پرفارمنس اوورلیز کو ڈیولپمنٹ کے دوران براہ راست آپ کی ایپ میں بھی فعال کیا جا سکتا ہے تاکہ DevTools کو کھولے بغیر فریم بجٹ کی خلاف ورزیوں کے فوری بصری سگنل فراہم کیے جا سکیں۔

MaterialApp(
  // Two bars appear at the top of the screen.
  // Top bar: UI thread. Bottom bar: raster thread.
  // Green means within budget. Red means over budget.
  showPerformanceOverlay: true,
  home: const MyScreen(),
)

پرفارمنس ویو: ریڈنگ فریم ٹائم لائن

پرفارمنس ویو جنک کی تحقیقات کا نقطہ آغاز ہے۔ فریم چارٹ کو بھرتے ہوئے دیکھتے ہوئے ایپ کے ساتھ تعامل کریں۔ فہرستوں کے ذریعے سکرول کریں، متحرک تصاویر کو متحرک کریں، اور اسکرینوں کے درمیان نیویگیٹ کریں۔

فریم چارٹ

ہر عمودی بار ایک فریم کی نمائندگی کرتا ہے۔ اونچائی مدت کی نمائندگی کرتی ہے۔ سرخ افقی لکیر 16ms بجٹ کو نشان زد کرتی ہے۔

Frame chart:
     ▲ ms
  28 │           ██
  20 │           ██
  16 │─────────────────── red line (16ms budget)
  12 │ ██  ██    ██  ██
   8 │ ██  ██    ██  ██
   0 └─────────────────────────────→ frames
       ok  ok  JANK  ok

سرخ لکیر کے اوپر کی سلاخیں جنکی فریم ہیں۔ اس پر کلک کرنے سے آپ کو اس مخصوص فریم کے دوران کیا ہوا اس کی تفصیلی بریک ڈاؤن دکھائی دے گی۔

دو دھاگے

جانکی فریم پر کلک کرنے سے شعلہ چارٹ دو حصوں میں تقسیم ہو جائے گا۔

UI Thread     ████████████████░░░░  — Dart code execution
Raster Thread ████░░░░░░░░░░░░      — GPU work

ہائی UI تھریڈ بارز آپ کے ڈارٹ کوڈ میں کسی مسئلے کی نشاندہی کرتے ہیں۔ ہائی راسٹر تھریڈ بارز اس بات کی نشاندہی کرتے ہیں کہ آپ کا GPU پینٹ کرنے کے لیے جدوجہد کر رہا ہے۔

شعلہ چارٹ پڑھیں

شعلہ چارٹ ایک افقی بار چارٹ ہے۔ ہر لائن ایک فنکشن کال ہے۔ چوڑائی مدت کی نمائندگی کرتی ہے۔ کرنسی کے درجہ بندی کو دکھانے کے لیے قطاروں کو سجا دیا گیا ہے۔

Frame (28ms total)
├── dart:ui (16ms)
│   └── build (14ms)
│       ├── ExpensiveList.build (8ms)
│       │   └── _buildItem (8ms)     ← wide bar = expensive
│       └── AppBar.build (2ms)
└── layout (4ms)

اسٹیک کے اوپری حصے کے قریب چوڑی بار وہ فنکشن ہے جو زیادہ وقت صرف کرتا ہے۔ اس کے نیچے کی ہر چیز صرف یہ ظاہر کرتی ہے کہ اسے کیا کہتے ہیں۔

CPU پروفائلر: بنیادی وجہ تلاش کریں۔

کارکردگی کا منظر سست فریموں کی شناخت کرتا ہے۔ سی پی یو پروفائلر بالکل اس بات کی نشاندہی کرتا ہے کہ کون سی خصوصیت مسئلہ کا باعث بن رہی ہے۔

پروفائل کی تاریخ

  1. DevTools میں CPU پروفائلر ٹیب کھولیں۔

  2. ریکارڈ پر کلک کریں۔

  3. جنکی تعاملات کی تولید

  4. سٹاپ پر کلک کریں۔

  5. DevTools تاریخ سے ایک Flame گراف بناتا ہے۔

شعلہ گراف پڑھیں

CPU Profiler flame graph:
                                         ← time →
_CounterScreenState.build [████████████████] 45ms
  Column.build            [████████████   ] 35ms
    ExpensiveWidget.build [████████████   ] 35ms
      _buildRows          [████████       ] 25ms
        jsonDecode        [████████       ] 25ms  ← root cause

سب سے چوڑا بار اشارہ کرتا ہے کہ وقت کہاں گزارا جاتا ہے۔ اس مثال میں jsonDecode تعمیر کے طریقہ کار کے اندر کہا جاتا ہے. ہر دوبارہ تعمیر پر چلتا ہے، نہ صرف ایک بار ویجیٹ درخت کے باہر۔

نیچے کی میز

نیچے کی میز سے پتہ چلتا ہے کہ کون سے انفرادی افعال سب سے براہ راست کام کرتے ہیں۔

  • سیلف ٹائم: کالنگ فنکشن کو چھوڑ کر فنکشن کے اندر ہی گزارا ہوا وقت۔ ہائی سیلف ٹائم کا مطلب ہے کہ یہ فیچر فطری طور پر مہنگا ہے۔

  • کل وقت: وقت بشمول تمام ڈاؤن اسٹریم فنکشن کالز۔ زیادہ کل وقت کا مطلب ہے کہ یہ خصوصیت کہیں نیچے مہنگے کام کا سبب بنے گی۔

اپنے وقت کے مطابق ترتیب دینے سے آپ کو اصل وجہ کی شناخت میں مدد مل سکتی ہے۔ کل وقت کے لحاظ سے ترتیب دینے سے محرکات کی شناخت ہوتی ہے۔

CPU بائنڈنگ رکاوٹوں کو حل کرنا

ڈیفالٹ آئسولیشن میں بڑے جوابات کو ہم وقت سازی سے پارس کرنا UI تھریڈ جنک کی سب سے عام وجوہات میں سے ایک ہے۔ حل یہ ہے کہ اس کام کو الگ قرنطینہ میں منتقل کیا جائے۔

// Before — blocking the main isolate on every search result
Future> processResults(dynamic data) async {
  return (data as List)
      .map((json) => User.fromJson(json))
      .toList();
}

// After — parsing in a background isolate
// The main isolate stays free to render frames
// while parsing happens concurrently
Future> processResults(dynamic data) async {
  return Isolate.run(() {
    return (data as List)
        .map((json) => User.fromJson(json as Map))
        .toList();
  });
}

سمجھنے کے لیے ایک انتباہ: بڑے آبجیکٹ گراف Isolate.run اس ڈیٹا کو قرنطینہ کی حد کے پار کاپی کریں۔ بڑے پے لوڈز کے لیے، کاپی اوور ہیڈ آف لوڈ کیے جانے والے آپریشنز کی لاگت سے موازنہ کیا جا سکتا ہے۔

بڑے JSON ردعمل کے لیے ایک محفوظ نمونہ یہ ہے کہ پہلے سے ڈی کوڈ شدہ آبجیکٹ کو پاس کرنے کے بجائے خام رسپانس سٹرنگ کو تنہائی میں منتقل کرنا اور اسے وہاں پارس کرنا ہے۔

// Safer for large payloads — the raw string is copied
// into the isolate, parsed there, and only the final
// typed list is copied back. No intermediate object graph crossing.
Future> processResults(String rawJson) async {
  return Isolate.run(() {
    final data = jsonDecode(rawJson) as List;
    return data
        .map((json) => User.fromJson(json as Map))
        .toList();
  });
}

فلٹر انسپکٹر: غیر ضروری دوبارہ تعمیرات تلاش کریں۔

تمام جنک نتائج انفرادی، مہنگے کاموں سے نہیں ہوتے۔ اس میں سے کچھ بہت زیادہ تعمیر نو سے آتا ہے۔ یعنی، ایک ویجیٹ جس کو دوبارہ تعمیر کو اپ ڈیٹ کرنے کی ضرورت نہیں ہے کیونکہ اس کے والدین نے اسے بلایا ہے۔ setState.

دوبارہ بنانے والے حسابات کو فعال کریں۔

DevTools Inspector ٹیب میں، ترتیبات کھولیں اور ‘Track Widget Build Count’ کو فعال کریں۔ جیسے ہی آپ اپنی ایپ کے ساتھ تعامل کرتے ہیں، DevTools ہر ویجیٹ کے آگے دوبارہ تعمیرات کی تعداد دکھاتا ہے۔

Widget Tree with rebuild counts:
MyApp                         0 rebuilds
└── MaterialApp               0 rebuilds
    └── CounterScreen         0 rebuilds
        └── Scaffold          0 rebuilds
            └── Column       24 rebuilds
                ├── Text     24 rebuilds  — necessary
                ├── Text     24 rebuilds  — necessary
                └── ExpensiveList  24 rebuilds  — PROBLEM

ExpensiveList کاؤنٹر سٹیٹ پر انحصار نہ ہونے کے باوجود اسے 24 بار دوبارہ بنایا گیا ہے۔ اسے دوبارہ بنایا جائے گا کیونکہ یہ ویجیٹ کے اسی ذیلی درخت میں ہے جسے اپ ڈیٹ کرنے کی ضرورت ہے۔

حل یہ ہے کہ ریاستی حصے کو اپنے ویجیٹ میں نکالا جائے۔ جب ریاست بدلتی ہے تو صرف وہی ویجیٹ دوبارہ بنایا جاتا ہے۔ باقی سب کچھ ویسا ہی رہتا ہے۔

// Before — the entire Scaffold rebuilds on every setState
class _CounterScreenState extends State {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Text('Count: $_count'),
          ElevatedButton(
            onPressed: () => setState(() => _count++),
            child: const Text('Increment'),
          ),
          // This never changes but rebuilds on every tap
          const ExpensiveList(),
        ],
      ),
    );
  }
}
// After — CounterDisplay owns its own state
// ExpensiveList never rebuilds
class CounterDisplay extends StatefulWidget {
  const CounterDisplay({super.key});

  @override
  State createState() => _CounterDisplayState();
}

class _CounterDisplayState extends State {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: () => setState(() => _count++),
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

// The screen is now stateless — it never rebuilds
class CounterScreen extends StatelessWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Column(
        children: [
          CounterDisplay(),   // rebuilds when count changes
          ExpensiveList(),    // never rebuilds
        ],
      ),
    );
  }
}

RepaintBoundary کا استعمال کرتے ہوئے مہنگی پینٹنگ کو الگ کریں۔

جب UI کا ایک حصہ بار بار دوبارہ تیار کیا جاتا ہے اور ملحقہ حصے جامد رہتے ہیں، RepaintBoundary ان حصوں کو الگ الگ تہوں پر رکھیں۔ کثرت سے اپ ڈیٹ شدہ حصوں کو جامد مواد کو چھوئے بغیر آزادانہ طور پر دوبارہ تیار کیا جاتا ہے۔

// Without RepaintBoundary — the animation causes
// the entire Column to repaint on every frame
Column(
  children: [
    AnimatedWidget(controller: _controller),
    const ExpensiveStaticContent(),
  ],
)

// With RepaintBoundary — ExpensiveStaticContent
// lives on its own layer and is never repainted
// during the animation
Column(
  children: [
    AnimatedWidget(controller: _controller),
    const RepaintBoundary(
      child: ExpensiveStaticContent(),
    ),
  ],
)

RepaintBoundary اسے جان بوجھ کر استعمال کیا جانا چاہئے نہ کہ وسیع پیمانے پر۔ ہر باؤنڈری ایک اضافی کمپوزٹنگ پرت بناتی ہے جس پر GPU کو عمل کرنا ہوتا ہے۔ زیادہ استعمال کے نتیجے میں راسٹر تھریڈ اوور ہیڈ ہوگا جو UI تھریڈ کی بچت کو آفسیٹ کرتا ہے۔

میموری ویو: صارف کے سنبھالنے سے پہلے لیک کو پکڑنا

میموری لیک ہونے کی وجہ سے ہونے والے ہنگامے دیگر اقسام کے مقابلے مختلف طریقے سے برتاؤ کرتے ہیں۔ یہ فوری طور پر ظاہر نہیں ہوتا ہے۔

ایپ ابتدائی چند منٹوں تک اچھی طرح کام کرتی ہے، پھر کارکردگی آہستہ آہستہ خراب ہوتی جاتی ہے کیونکہ میموری میں اضافہ ہوتا ہے اور کوڑا اٹھانے والا جگہ پر دوبارہ دعوی کرنے کے لیے زیادہ محنت کرتا ہے۔ جب تک صارفین غیر معمولی رویے یا سست روی کی اطلاع دیتے ہیں، کچھ عرصے سے لیک جمع ہو رہا ہے۔

DevTools میں میموری کا اخراج

میموری ویو وقت کے ساتھ ہیپ کے استعمال کو چارٹ کرتا ہے۔ صحت مند ایپس آری ٹوتھ کا نمونہ دکھاتی ہیں۔ اس کا مطلب یہ ہے کہ جب اشیاء کو مختص کیا جاتا ہے تو میموری بڑھتی ہے اور پھر جب کوڑا اٹھانے والا چلتا ہے تو تیزی سے کم ہوجاتا ہے۔

Healthy memory:
     ▲ MB
  60 │     ▲       ← GC runs, heap returns to baseline
  40 │   ██│██
  20 │ ██  │  ██▼  ← rises then drops back down
   0 └──────────────→ time
       stable baseline

Memory leak:
     ▲ MB
  80 │               ██
  60 │         ████
  40 │    ████         ← never returns to baseline
  20 │████
   0 └──────────────→ time
       baseline rising

لیک تلاش کرنا

رساو کو الگ کرنے کا عمل:

  1. میموری ویو کھولیں اور موجودہ ہیپ سائز کو چیک کریں۔

  2. مشتبہ اسکرین پر جائیں۔

  3. اس سے دور دریافت کریں

  4. زبردستی کوڑا اٹھانے کے لیے، DevTools میں GC بٹن پر کلک کریں۔

  5. اپنے کولہوں کو دیکھیں۔ اگر آپ پچھلی سطح کے قریب کہیں نہیں گرتے ہیں، تو اس اسکرین پر موجود آئٹمز اب بھی قابل رسائی ہوں گے۔

نیویگیشن سے پہلے اور بعد میں سنیپ شاٹس لے کر اور دو آبجیکٹس کا موازنہ کرکے، آپ دیکھ سکتے ہیں کہ کون سی اشیاء میموری میں رہ گئی تھیں جب انہیں اکٹھا کیا جانا چاہیے تھا۔

لیک ہونے کی سب سے عام وجوہات

غیر عمل شدہ حرکت پذیری کنٹرولر:

class _AnimatedScreenState extends State
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
  }

  @override
  void dispose() {
    // Without this, the Ticker fires on every frame
    // indefinitely, holding the State in memory
    _controller.dispose();
    super.dispose();
  }
}

غیر منسوخ شدہ سلسلہ کی رکنیتیں:

class _ChatScreenState extends State {
  StreamSubscription? _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = messageStream.listen((message) {
      if (mounted) setState(() => messages.add(message));
    });
  }

  @override
  void dispose() {
    // Without cancel(), the stream holds a reference
    // to this callback, which holds a reference to
    // the State, preventing garbage collection
    _subscription?.cancel();
    super.dispose();
  }
}

سے بنائے گئے تمام آئٹمز initState یہ ہے dispose()، cancel()یا close() کسی طریقہ کو کال کرنے کے لیے آپ کو اس طریقہ کی ضرورت ہے۔ dispose(). اس اصول میں کوئی استثنا نہیں ہے۔

سب سے عام ہکلانے والے پیٹرن کو درست کریں۔

DevTools مسلسل پروڈکشن فلٹر ایپس میں جنک کے ایک ہی زمرے دکھاتا ہے۔ ایک بار جب بنیادی وجہ معلوم ہوجائے تو، اصلاحات براہ راست لاگو ہوتی ہیں۔

ریاستی قرنطینہ پر مہنگا ہم وقت ساز کام۔

DevTools سگنلز: ہائی UI تھریڈ بارز، CPU پروفائلر ہائی سیلف ٹائم کے ساتھ فنکشنز کو پارس کرنے یا ترتیب دینے کو دکھاتا ہے۔

// Before — sorting 10,000 items synchronously
// blocks the main isolate for 80-200ms on slower devices
final sorted = List.from(items)
  ..sort((a, b) => a.name.compareTo(b.name));

// After — sorting in a background isolate
final sorted = await Isolate.run(() {
  final copy = List.from(items);
  copy.sort((a, b) => a.name.compareTo(b.name));
  return copy;
});

مستقبل اندر سے باہر سے بنایا گیا ہے۔

DevTools سگنلز: نیٹ ورک ویو ڈپلیکیٹ API کالز کو اسی اختتامی نقطہ پر دکھاتا ہے۔ CPU پروفائلر دکھاتا ہے کہ نیٹ ورک کے فنکشنز فی صارف کے تعامل کو متعدد بار بلایا جاتا ہے۔

// Before — a new Future is created on every rebuild.
// FutureBuilder treats each new Future as a fresh
// operation and resets to loading state.
@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: repository.fetchUser(userId),
    builder: (context, snapshot) { ... },
  );
}

// After — the Future is created once in initState
// and reused across all subsequent rebuilds
late final Future _userFuture;

@override
void initState() {
  super.initState();
  _userFuture = repository.fetchUser(widget.userId);
}

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: _userFuture,
    builder: (context, snapshot) { ... },
  );
}

کالم کے طور پر پیش کردہ بڑی فہرست

DevTools سگنل: فہرست اسکرین پر جانے کے بعد، پہلا فریم بعد کے فریموں کے مقابلے میں نمایاں طور پر سست ہے۔ انسپکٹر ایک کالم دکھاتا ہے جس میں سینکڑوں بچے ہیں۔

// Before — builds all items at once regardless
// of how many are currently visible
Column(
  children: items
      .map((item) => ItemCard(item: item))
      .toList(),
)

// After — builds only the items currently
// visible on screen plus a small buffer
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return ItemCard(item: items[index]);
  },
)

اینیمیشن کی دھندلاپن راسٹر تھریڈ سٹٹر کا باعث بن رہی ہے۔

DevTools سگنل: لمبی راسٹر تھریڈ بار۔ فلیم چارٹ اینیمیشن کے دوران SaveLayer کی سرگرمی دکھاتا ہے۔

Opacity جب قدر میں تبدیلی آتی ہے، تو Flutter چائلڈ ویجٹ کو ہدف کی دھندلاپن میں کمپوزٹ کرنے سے پہلے ہر فریم کو آف اسکرین بفر میں پیش کرنے پر مجبور کرتا ہے۔ یہ SaveLayer آپریشن سب سے مہنگے آپریشنز میں سے ایک ہے جو ایک راسٹر تھریڈ انجام دے سکتا ہے۔

امپیلر کے بارے میں ایک نوٹ: فلٹر کا تازہ ترین رینڈرنگ بیک اینڈ، امپیلر (اب iOS پر ڈیفالٹ ہے اور اینڈرائیڈ پر جاری کیا گیا ہے)، SaveLayer آپریشنز کے جرمانے کو نمایاں طور پر کم کرتا ہے اور پرانے Skia انجن کو متاثر کرنے والے shader compilation stutter کو ختم کرتا ہے۔

اگر آپ کی ایپ صرف امپیلر کے ساتھ فلٹر کے تازہ ترین ورژن کو نشانہ بناتی ہے، تو مبہم اینیمیشنز میں راسٹر تھریڈ جنک اسکیا کی نسبت کم شدید ہو سکتا ہے۔ ترجیحی ہدایات FadeTransition حد سے زیادہ متحرک Opacity اگرچہ اب بھی درست ہے، لیکن امپیلر عجلت تاریخی اعتبار سے کم ہے۔

// Bad — Opacity with a changing value creates a SaveLayer
// on every animation frame, causing raster thread jank
Opacity(
  opacity: _animationValue,
  child: myWidget,
)

// Good — FadeTransition uses the compositor directly
// without a SaveLayer offscreen buffer
FadeTransition(
  opacity: _animation,
  child: myWidget,
)

تصدیق کریں کہ آپ کی درستگی نے واقعی کام کیا۔

کارکردگی کی اصلاح رکاوٹوں کو دور کرنے کے بجائے منتقل کرنے کا رجحان رکھتی ہے۔ ایک سست خصوصیت کو درست کرنے سے پتہ چلتا ہے کہ اگلا سست ترین کام اب فریم ٹائم پر حاوی ہے۔

اگر آپ ہر ترمیم سے پہلے اور بعد میں پیمائش کرتے ہیں، تو یہ مسئلہ کسی کا دھیان نہیں جائے گا۔

تصدیقی عمل:

  1. براہ کرم کوئی بھی تبدیلی کرنے سے پہلے پروفائل موڈ میں اپنا پروفائل پُر کریں۔

  2. مشکل تعاملات کے دوران بدترین فریم ٹائم ریکارڈ کریں۔

  3. چیک کریں کہ کون سا دھاگہ رکاوٹ کا سبب بن رہا ہے۔

  4. ترمیمات کا اطلاق کریں۔

  5. انہی حالات میں دوبارہ پروفائلنگ

  6. فریم ٹائم اور تھریڈ تجزیہ کا موازنہ کرنا

فریم ٹائمنگ کو پروگرام کے لحاظ سے بھی پکڑا جا سکتا ہے، جو وقت کے ساتھ ساتھ بہتریوں کو ٹریک کرنے یا CI میں ترمیم کی توثیق کرنے کے لیے مفید ہے۔

WidgetsBinding.instance.addTimingsCallback((timings) {
  for (final timing in timings) {
    if (timing.totalSpan.inMilliseconds > 16) {
      debugPrint(
        'Slow frame: ${timing.totalSpan.inMilliseconds}ms '
        'build: ${timing.buildDuration.inMilliseconds}ms '
        'raster: ${timing.rasterDuration.inMilliseconds}ms',
      );
    }
  }
});

اگر تصحیح کے بعد پیمائش میں بہتری آتی رہتی ہے، تو اصل وجہ کی درست نشاندہی کی گئی ہے۔ اگر پیمائش بہتر نہیں ہوتی ہے تو، اصل رکاوٹ کہیں اور ہے اور کوڈ میں مزید تبدیلیاں کرنے سے پہلے ایک اور پروفائلنگ کی ضرورت ہے۔

نتیجہ

فلٹر ایپلی کیشنز میں کارکردگی کے مسائل شاذ و نادر ہی پیدا ہوتے ہیں جہاں ڈویلپرز کو پہلے شبہ ہوتا ہے۔

سب سے قابل اعتماد طریقہ یہ ہے کہ پہلے پروفائل بنائیں اور پھر اسے ٹھیک کریں۔ دوسری طرف نہیں۔

DevTools فریم ٹائمنگ، CPU کے استعمال، ویجیٹ کی دوبارہ تعمیر کی فریکوئنسی، اور میموری کے رویے میں مکمل مرئیت فراہم کرتا ہے۔ کارکردگی کا منظر سست فریموں کے لیے ذمہ دار تھریڈز کی نشاندہی کرتا ہے۔ ایک CPU پروفائلر ان مخصوص خصوصیات کی نشاندہی کرتا ہے جو اس کا سبب بنتے ہیں۔ انسپکٹر غیر ضروری دوبارہ تعمیر کے پروپیگنڈہ کو ظاہر کرتا ہے۔ میموری ویو صارفین کو متاثر کرنے سے پہلے ہی لیک دکھاتا ہے۔

پروفائل موڈ میں پروفائلنگ، اصلاح سے پہلے پروفائلنگ، اور اصلاح کے بعد پیمائش تین عادات ہیں جو جنک کو ایک بار بار چلنے والی پہیلی کے بجائے ایک قابل حل انجینئرنگ کا مسئلہ بناتی ہیں۔

زیادہ تر Flutter کارکردگی کے سوالات کا جواب پہلے ہی DevTools میں دیا گیا ہے۔ کوڈ کو تبدیل کرنے سے پہلے اسے کھولیں۔

Scroll to Top