تحليل التقطّع باستخدام المخطط الزمني في DevTools
التقط مخططات الإطارات وحدد مراحل البناء والتنقيط المكلفة في DevTools
تحليل التقطّع باستخدام المخطط الزمني في DevTools درس مجاني في Flutter Mobile Development على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Flutter Mobile Development، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Flutter Mobile Development 4 دروس في المجموع.
بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.
What 'Jank' Actually Means
Jank is any visible stutter caused by Flutter missing a frame deadline. On a 60Hz display the engine has roughly 16.7ms per frame; on 120Hz devices only 8.3ms.
- If the UI thread or the raster thread runs over budget, the frame is dropped and the user sees a hitch.
- Profiling jank means finding which frames are slow and which phase (build/layout/paint vs. rasterization) ate the time.
The DevTools Performance view (the Timeline) is the primary tool for this investigation.
Always Profile in Profile Mode
Never trust timings from a debug build. Debug mode disables JIT optimizations, asserts run, and the Dart VM is slower, so numbers are meaningless.
- Run with
flutter run --profileon a real device, not a simulator. - Profile mode keeps service extensions (so DevTools works) but uses AOT-compiled, release-grade code.
- Simulators use your Mac's CPU/GPU and hide real raster cost.
// Launch the app in profile mode from the terminal:
// flutter run --profile -d <deviceId>
//
// List attached physical devices first:
// flutter devices
//
// Then open DevTools at the printed URL and select the
// Performance tab to capture the timeline.
void main() {
// The flag matters: assert() bodies are stripped in profile/release.
bool inDebug = false;
assert(() {
inDebug = true;
return true;
}());
print(inDebug ? 'debug build' : 'profile/release build');
}The Two Threads Behind Every Frame
Each frame is produced by two cooperating threads, and the Timeline shows both as separate tracks:
- UI thread (Dart): runs your
build(), layout, and paint phases, then records a list of drawing commands. - Raster thread (formerly 'GPU thread'): takes those commands and turns them into actual pixels via Skia/Impeller.
A frame is only smooth if both tracks finish inside the budget. A bar over budget on either track is a dropped frame.
Reading the Frame Chart
The Frames chart at the top of the Performance view shows one bar per rendered frame.
- Each bar is split into a blue UI portion and a teal Raster portion.
- The horizontal line marks your target budget (16.7ms or 8.3ms).
- Bars that cross the line are highlighted; tap one to load its detailed timeline events below.
Color tells you the culprit immediately: tall blue = expensive build/layout; tall teal = expensive rasterization (shaders, large images, clips).
Drilling Into the Timeline Events
Selecting a janky frame populates the Timeline Events flame chart. On the UI track you will typically see this nesting:
Frame→Animate→Build→Layout→Paint
The widest box is your hotspot. A wide Build box usually means too much work in build(); a wide Layout means expensive constraint resolution (deep trees, intrinsic sizing).
On the Raster track look for PipelineConsume and GPU rasterization spans.
A Classic Cause of Wide Build Bars
Rebuilding a large subtree on every animation tick is the most common build-phase jank. Here a whole list rebuilds because the parent's setState is called 60 times a second.
- The fix is to push state down or use
constconstructors so subtrees are skipped. - In the Timeline you would see a wide Build box shrink dramatically after the fix.
// ANTI-PATTERN: every tick rebuilds the entire list.
class _BadClock extends State<BadClock>
with SingleTickerProviderStateMixin {
late final AnimationController _c =
AnimationController(vsync: this, duration: const Duration(seconds: 1))
..repeat();
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _c,
builder: (_, __) {
// BAD: builds 1000 rows on every frame.
return ListView(children: [
for (var i = 0; i < 1000; i++) ExpensiveRow(i),
]);
},
);
}
}Isolate Work With const and Subtree Rebuilds
The corrected version restricts the rebuild to only the part that animates. Everything else is const and is skipped by the framework's element diffing.
- Pass static children through the
child:parameter ofAnimatedBuilderso they are built once. - After this change the Build box in the Timeline becomes a thin sliver.
// FIXED: only the rotating widget rebuilds each tick.
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _c,
// Built once, reused every frame.
child: const _StaticList(),
builder: (context, child) {
return RotationTransition(
turns: _c,
child: child, // const subtree, not rebuilt
);
},
);
}Diagnosing Raster-Thread Jank
When the teal Raster portion is the tall one, the UI thread is fine but the GPU is struggling to paint. Common causes:
- saveLayer calls from opacity, clips, or blend modes over large areas.
- Expensive shader compilation on first use (shader jank), visible as a one-off spike.
- Large, unscaled images decoded at full resolution.
DevTools offers debugging toggles to confirm these: enable Track Widget Rebuilds, Highlight Repaints, and Render Layer Borders from the Performance view.
Programmatic saveLayer Hotspots
A wrapping Opacity widget forces a saveLayer, which is one of the most expensive raster operations. Prefer cheaper alternatives:
- For a single image, use the
opacityparameter ofImageor anAnimatedOpacityonly when needed. - For solid colors, bake the alpha into the
Colorinstead of wrapping inOpacity.
// EXPENSIVE: Opacity triggers saveLayer on the raster thread.
Widget bad() => Opacity(
opacity: 0.5,
child: Container(color: Colors.blue, width: 300, height: 300),
);
// CHEAP: fold the alpha straight into the color, no saveLayer.
Widget good() => Container(
color: Colors.blue.withOpacity(0.5),
width: 300,
height: 300,
);Adding Your Own Timeline Markers
To attribute time to your code rather than framework internals, wrap suspicious sections in Timeline.timeSync from dart:developer. These appear as named spans in the DevTools Timeline.
- Markers are no-ops in release mode, so they are safe to leave in.
- Use them to confirm whether a slow frame is your parsing/computation versus Flutter's layout.
import 'dart:developer';
List<int> parsePayload(List<int> raw) {
// This named span shows up on the UI track in DevTools.
return Timeline.timeSync('parsePayload', () {
final out = <int>[];
for (final b in raw) {
out.add(b * 2 + 1);
}
return out;
});
}
void main() {
final result = parsePayload(List<int>.generate(8, (i) => i));
print(result);
}A Repeatable Profiling Workflow
Turn ad-hoc poking into a method you can repeat for every regression:
- 1. Run in
--profileon a representative physical device. - 2. Reproduce the janky interaction while recording the Frames chart.
- 3. Tap the tallest over-budget bar; note whether it is blue (UI) or teal (Raster).
- 4. Open Timeline Events, find the widest box, and read its name.
- 5. Apply a targeted fix (const subtree, remove saveLayer, precache, etc.), then re-record and compare.
You can also compute the budget yourself from the display refresh rate to know exactly which bars are over the line.
// Frame budget in milliseconds for a given refresh rate.
double frameBudgetMs(int refreshHz) => 1000 / refreshHz;
bool isJanky(double frameMs, int refreshHz) =>
frameMs > frameBudgetMs(refreshHz);
void main() {
for (final hz in [60, 90, 120]) {
final budget = frameBudgetMs(hz);
print('${hz}Hz budget = ${budget.toStringAsFixed(2)}ms');
}
// A 19ms frame is fine at 60Hz? No - it's over the 16.67ms budget.
print('19ms @60Hz janky: ${isJanky(19, 60)}');
}Quick Check: Diagnosing the Bar
You record a scroll and see frames where the teal raster portion is well over the budget line while the blue UI portion stays tiny. What is the most likely cause and first fix?
Recap
You learned how to profile jank with the DevTools Timeline:
- Jank is a missed frame deadline (16.7ms at 60Hz, 8.3ms at 120Hz); always measure in profile mode on a real device.
- Every frame has a UI thread (build/layout/paint) and a Raster thread (pixels). The Frames chart colors them blue and teal.
- Tall blue = expensive build/layout, fixed by const subtrees and narrower rebuilds. Tall teal = costly rasterization, fixed by removing saveLayer and shrinking images.
- Drill into Timeline Events to find the widest box, and add
Timeline.timeSyncmarkers to attribute time to your own code. - Follow a repeatable record → identify thread → find widest box → fix → re-measure loop.
تعلم Dart مع معلم ذكاء اصطناعي — مجانًا
اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.
- الدورات
- 22
- الدروس
- 88
الأسئلة الشائعة
هل درس «تحليل التقطّع باستخدام المخطط الزمني في DevTools» مجاني؟
نعم — نص درس «تحليل التقطّع باستخدام المخطط الزمني في DevTools» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Flutter Mobile Development، انتقل إلى CoddyKit PRO. تتضمن دورة Flutter Mobile Development 4 دروس في المجموع.
ماذا ستتعلم في «تحليل التقطّع باستخدام المخطط الزمني في DevTools»؟
التقط مخططات الإطارات وحدد مراحل البناء والتنقيط المكلفة في DevTools تتمرن على Flutter Mobile Development مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Flutter Mobile Development؟
لا تُشترط خبرة سابقة. Flutter Mobile Development على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «تحليل التقطّع باستخدام المخطط الزمني في DevTools»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Flutter Mobile Development هذا؟
نعم. كل درس في Flutter Mobile Development يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- الأشجار الثلاثة: Widget وElement وRenderObject
- تحليل التقطّع باستخدام المخطط الزمني في DevTools
- RepaintBoundary وعناصر Const وتقليص إعادة البناء
- الإحماء المسبق للمظللات والترحيل إلى Impeller