Kodokon kodokon.com

परफ़ॉर्मेंस: const, रीबिल्ड, keys और DevTools

const, keys और रिकंसीलिएशन की बदौलत बेकार रीबिल्ड खत्म करें, फिर DevTools से मापें।

10 मिनट · 3 प्रश्न

इस पाठ को Kodokon में खोलें

Flutter तीन ट्री का संचालन करता है: विजेट (अपरिवर्तनीय, फेंकने योग्य कॉन्फ़िगरेशन), Element (जीवित इंस्टेंस जो स्टेट धारण करते हैं) और render object (लेआउट, पेंटिंग)। किसी विजेट को दोबारा बनाना सस्ता है; जो महँगा है वह है लेआउट और शृंखलाबद्ध पेंट। किसी रीबिल्ड के दौरान, Element.updateChild क्रम से तीन नियम लागू करता है: यदि नया विजेट पुराने के समरूप (एक ही इंस्टेंस) है, तो पूरा सबट्री शॉर्ट-सर्किट कर दिया जाता है; यदि Widget.canUpdate सही है (वही runtimeType, वही key), तो Element को रखा जाता है और बस पुनः कॉन्फ़िगर किया जाता है; अन्यथा, Element को नष्ट कर फिर से inflate किया जाता है, स्टेट सहितconst कीवर्ड पहले नियम का लाभ उठाता है: एक स्थिर व्यंजक को कम्पाइलर द्वारा कैननिकलाइज़ किया जाता है, हर build पर वही इंस्टेंस लौटता है, identical सही होता है - और पूरी शाखा छोड़ दी जाती है।

DART
import 'package:flutter/material.dart';

class CounterScreen extends StatefulWidget {
  const CounterScreen({super.key});

  @override
  State<CounterScreen> createState() =>
      _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

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

class _Header extends StatelessWidget {
  const _Header();

  @override
  Widget build(BuildContext context) {
    debugPrint('build _Header');
    return const Text('Counter');
  }
}
लॉग केवल एक बार दिखाई देता है: const सबट्री को छोड़ देता है।

children की सूचियों के लिए, रिकंसीलिएशन पहले स्थिति के आधार पर जोड़ी बनाता है। बिना keys के stateful आइटम को पुनः क्रमबद्ध करें और हर Element अपनी स्थिति के पिछले धारक का स्टेट रख लेता है: गलत जगह टिक हुए चेकबॉक्स, आपस में बदले हुए टेक्स्ट फ़ील्ड। एक Key जोड़ी बनाने का नियम बदल देती है: एल्गोरिथ्म उसी key वाले Element को ढूँढ लेता है, भले ही उसे सूची के भीतर हिला दिया गया हो। किसी स्थिर व्यावसायिक पहचानकर्ता पर ValueKey का उपयोग करें। GlobalKey इससे भी आगे जाती है - यह आपको उसका स्टेट खोए बिना पूरे सबट्री को दोबारा पैरेंट करने देती है - पर इसकी एक वास्तविक कीमत होती है (ग्लोबल रजिस्ट्री, पूरे ट्री में विशिष्टता की बाध्यता): इसे Form या reparenting जैसी असल ज़रूरतों के लिए ही सुरक्षित रखें।

DART
import 'package:flutter/material.dart';

class TodoList extends StatelessWidget {
  const TodoList({super.key, required this.items});

  final List<String> items;

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        for (final item in items)
          Dismissible(
            key: ValueKey(item),
            onDismissed: (direction) {},
            child: ListTile(title: Text(item)),
          ),
      ],
    );
  }
}
बिना किसी स्थिर key के, जैसे ही आप हटाते हैं, Dismissible एक त्रुटि फेंकता है।

अनुकूलन से पहले मापें। DevTools में, Performance व्यू दो अहम थ्रेड के लिए प्रति-फ़्रेम समय को ट्रेस करता है: UI (आपका Dart कोड: build, layout) और raster (परतों का रास्टराइज़ेशन)। 60 Hz पर बजट लगभग 16 ms है। raster की ओर का jank आपके builds को अनुकूलित करने से ठीक नहीं होता: यह पेंटिंग की लागत से आता है (छायाएँ, saveLayer, बहुत बड़े इमेज)। कौन दोबारा बना रहा है यह देखने के लिए Track widget builds सक्षम करें, और दोबारा पेंट हुए क्षेत्रों को देखने के लिए debugRepaintRainbowEnabled - हर repaint पर बॉर्डर का रंग बदलता है। RepaintBoundary किसी सबट्री को उसकी अपनी परत में पृथक कर देता है: लगातार दोबारा पेंट होता एक स्पिनर अब अपने पड़ोसियों को गंदा नहीं करता।

DART
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  debugRepaintRainbowEnabled = true;
  runApp(const DemoApp());
}

class DemoApp extends StatelessWidget {
  const DemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      showPerformanceOverlay: true,
      home: Scaffold(
        body: Center(
          child: RepaintBoundary(
            child: CircularProgressIndicator(),
          ),
        ),
      ),
    );
  }
}
फ़्रेम ओवरले + repaint rainbow: डायग्नोस्टिक्स की जोड़ी।

ज्ञान जांच

सुनिश्चित करें कि आपको इस पाठ के मुख्य बिंदु याद हैं।

  1. जब किसी const विजेट का पैरेंट दोबारा बनता है, तब उसे अनदेखा क्यों कर दिया जाता है?
    • इंस्टेंस कैननिकल होता है: identical सही होता है, और Element.updateChild पूरे सबट्री को शॉर्ट-सर्किट कर देता है
    • कम्पाइलर release में विजेट को ट्री से हटा देता है
    • const विजेट के पास कोई build मेथड नहीं होता
  2. आप बिना keys के stateful विजेट की एक सूची को पुनः क्रमबद्ध करते हैं। क्या होता है?
    • Element स्थिति के आधार पर जोड़े जाते हैं: हर आइटम अपनी स्थिति के पिछले धारक का स्टेट विरासत में पा लेता है
    • Flutter debug मोड में एक एक्सेप्शन फेंकता है
    • स्टेट स्वतः ही हर हिलाए गए विजेट के पीछे चला जाता है
    • सूची दोबारा बनने से इनकार कर देती है
  3. परफ़ॉर्मेंस ओवरले के दो ग्राफ़ क्या मापते हैं?
    • UI थ्रेड (Dart कोड) और raster थ्रेड (परत रेंडरिंग) का प्रति-फ़्रेम समय
    • आवंटित मेमोरी और विजेट की संख्या
    • Build समय और garbage-collection समय