• 7.09.2026 21:34:21
  • Admin Admin

Flutter mobil uygulama geliştirmede ilk açılış ve geçişlerde görülen shader jank'ini FrameTiming, DevTools ve hedefli shader warm-up ile ölçün. Skia ve runtime shader senaryolarında doğru müdahale noktalarını ayırın.

Flutter Mobil Uygulama Geliştirmede Shader Jank'ini Ölçme ve Azaltma

Flutter mobil uygulama geliştirmede shader jank için ölçüm tabanı

Shader kaynaklı takılmayı debug derlemede değerlendirmeyin. Debug moddaki Dart VM servisleri, assertion'lar ve JIT davranışı kare sürelerini değiştirir. Aynı fiziksel cihazda profile derlemesiyle ölçün:

flutter build apk --profile
adb install -r build/app/outputs/flutter-apk/app-profile.apk
Ardından başlangıç rotası, liste geçişi veya efektli ekran için tekrarlanabilir bir kullanıcı akışı tanımlayın. 60 Hz bir cihazda ilk hedef, 16.67 ms üzerindeki karelerin sayısını; 120 Hz cihazda ise 8.33 ms üzerindeki karelerin sayısını ayrı raporlamaktır. Eşik cihazın yenileme hızına bağlıdır; her cihaz için 16 ms kullanmak 120 Hz cihazdaki jank'i gizler.

Uygulama içinden UI ve raster sürelerini toplayarak DevTools kaydını sayısal bir sonuçla destekleyin. Bu kodu yalnızca profil ölçümünde etkinleştirmek için kProfileMode kullanın; ham örnekleri bellek içinde sınırsız tutmak uzun oturumda ölçüm aracının kendisini bellek baskısı kaynağı yapar.

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

final List<FrameTiming> frameSamples = [];

void collectFrameTimings(List<FrameTiming> timings) {
  if (!kProfileMode) return;
  frameSamples.addAll(timings);
  if (frameSamples.length > 2000) {
    frameSamples.removeRange(0, 500);
  }
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  WidgetsBinding.instance.addTimingsCallback(collectFrameTimings);
  runApp(const App());
}
Test akışı sonunda buildDuration, rasterDuration ve totalSpan için p50, p95 ve eşik üstü kare sayısını çıkarın. Raster süresi yükselirken build süresi düşük kalıyorsa sorun çoğunlukla GPU iş yükü, shader derlemesi veya compositing tarafındadır; state katmanını rastgele yeniden yazmak doğru ilk müdahale değildir.

DevTools Performance görünümünde aynı akışı iki kez kaydedin: soğuk başlatma sonrası ilk geçiş ve uygulama açıkken ikinci geçiş. İlk kayıtta uzun raster dilimleri, ikincide kayboluyorsa shader pipeline'ın ilk kullanım maliyetinden şüphelenin. Her flutter eğitimi veya flutter kursu materyalinde bu ayrımın özellikle uygulanması gerekir: sürekli pahalı bir blur ile sadece ilk kez derlenen bir shader, zaman çizelgesinde benzer görünse de çözüm yöntemleri tamamen farklıdır.

Skia tabanlı çizimlerde hedefli ShaderWarmUp kullanımı

Skia kullanan hedeflerde uygulamanın gerçekten ilk ekranda çizdiği efektleri hedefleyen bir ShaderWarmUp tanımlayın. Amaç rastgele çok sayıda efekt çizmek değil, açılışta veya ilk rota geçişinde kullanılan pipeline kombinasyonlarını önceden oluşturmaktır. Aşağıdaki örnek, bulanık yuvarlatılmış kartın ilk görünümünde oluşan raster sıçramasını test etmek için gerçek çizim yolunu ısındırır:

import 'package:flutter/material.dart';

class AppShaderWarmUp extends ShaderWarmUp {
  @override
  void warmUpOnCanvas(Canvas canvas) {
    final paint = Paint()
      ..color = const Color(0xAA1565C0)
      ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 12);

    canvas.drawRRect(
      RRect.fromRectAndRadius(
        const Rect.fromLTWH(0, 0, 280, 96),
        const Radius.circular(20),
      ),
      paint,
    );
  }
}

class AppBinding extends WidgetsFlutterBinding {
  @override
  void initInstances() {
    shaderWarmUp = AppShaderWarmUp();
    super.initInstances();
  }
}

void main() {
  AppBinding();
  runApp(const App());
}
shaderWarmUp değerini binding başlatılırken, super.initInstances() çağrısından önce atamak önemlidir. runApp sonrasında atama yapmak, binding'in başlangıç warm-up aşamasını kaçırabilir.

Önce-sonra karşılaştırmasını aynı APK, cihaz sıcaklığı, ekran parlaklığı ve rota akışıyla yapın. DevTools'ta ilk kart geçişindeki maksimum rasterDuration değerini ve 16.67 ms üstü kare sayısını kaydedin; warm-up sonrası bu iki metriği tekrar ölçün. Eğer açılış süresi artıyor fakat ilk rota geçişi düzeliyorsa maliyet sadece yer değiştirmiştir. Bu durumda warmUpOnCanvas içindeki çizimleri azaltın veya warm-up'ı açılış yerine kullanıcı girişinden sonraki boşta kalma anına taşıyın.

Yaygın hata, üretimdeki efektle warm-up efektinin parametrelerini eşleştirmemektir. Örneğin kartta MaskFilter.blur(..., 24), clip, opacity katmanı ve gradient birlikte kullanılıyorsa yalnızca düz renkli bir drawRect çizmek aynı pipeline'ı garanti etmez. Warm-up kodunu tasarım sistemindeki gerçek BoxShadow, ClipRRect ve gradient kombinasyonundan türetin. Flutter'ın kullandığı renderer ve platform GPU sürücüsü pipeline önbelleklemesini farklı ele alabildiği için sonucu her hedef cihaz sınıfında ölçmeden genelleme yapmayın.

Runtime shader varlıklarını rota öncesinde hazırlama

GLSL benzeri runtime shader kullanan ekranlarda FragmentProgram.fromAsset çağrısını ilk paint anına bırakmayın. Programı rota açılmadan önce yükleyin ve pubspec.yaml içinde shader'ın paketlendiğini doğrulayın:

flutter:
  shaders:
    - shaders/glow.frag
Uygulama kodunda program yüklemesini servis düzeyinde tekilleştirin:
import 'dart:ui';

class GlowShaderRepository {
  GlowShaderRepository()
      : _program = FragmentProgram.fromAsset('shaders/glow.frag');

  final Future<FragmentProgram> _program;

  Future<void> preload() async {
    await _program;
  }

  Future<FragmentShader> createShader() async {
    return (await _program).fragmentShader();
  }
}
Giriş ekranı başarıyla tamamlandıktan sonra, efektli rota açılmadan önce await repository.preload() çağırın. Bu, varlık okuma ve program hazırlama maliyetini kullanıcı doğrudan rota geçişi yaparken oluşan kritik yoldan çıkarır.

Bir FragmentShader nesnesi mutable'dır: setFloat ve sampler ayarları nesnenin iç durumunu değiştirir. Bu nedenle aynı shader örneğini aynı anda iki CustomPainter veya iki liste hücresinde paylaşmayın; biri uniform değerini değiştirirken diğeri yanlış parametrelerle çizebilir. Paylaşılacak birim FragmentProgram, her eşzamanlı çizim yüzeyi için oluşturulacak birim ise FragmentShader olmalıdır. Bu ayrım, dart programlama eğitimi kapsamında sadece async asset yükleme olarak anlatılırsa kaçabilecek bir eşzamanlılık ayrıntısıdır.

Program yüklemesi tamamlandı diye ilk GPU çiziminin her hedefte ücretsiz olduğu varsayılmamalıdır. Profil kaydında ilk runtime shader karesinde raster süresi halen sıçrıyorsa, kullanıcı görmeden küçük bir offscreen PictureRecorder yüzeyinde o shader ile temsil edici bir çizim yapın ve tekrar ölçün. Bu ek çizim açılışta zaman tükettiğinden, kabul kriteri 'ilk efektli rotadaki p95 raster süresi düştü ve açılış bütçesi aşılmadı' olmalıdır; yalnızca ortalama kare süresinin düşmesi yeterli değildir.

Flutter state management ile efekt parametrelerini build'den ayırma

Yanlış flutter state management tasarımı shader derlemesi yapmasa bile raster sorununu build ve layout maliyetiyle birleştirebilir. Kaydırma veya animasyonla değişen tek bir progress değeri için tüm sayfayı setState ile yeniden kurmak yerine, repaint sinyalini doğrudan CustomPainter'a verin.

class GlowPainter extends CustomPainter {
  GlowPainter({required this.progress, required this.shader})
      : super(repaint: progress);

  final ValueListenable<double> progress;
  final FragmentShader shader;

  @override
  void paint(Canvas canvas, Size size) {
    shader
      ..setFloat(0, size.width)
      ..setFloat(1, size.height)
      ..setFloat(2, progress.value);
    canvas.drawRect(Offset.zero & size, Paint()..shader = shader);
  }

  @override
  bool shouldRepaint(covariant GlowPainter oldDelegate) => false;
}

Widget buildGlow(ValueNotifier<double> progress, FragmentShader shader) {
  return RepaintBoundary(
    child: CustomPaint(
      painter: GlowPainter(progress: progress, shader: shader),
      size: Size.infinite,
    ),
  );
}
Bu yapıda progress değiştiğinde widget ağacı yeniden build edilmez; painter yalnızca repaint bildirimi alır. RepaintBoundary ekledikten sonra DevTools'ta katman sayısını ve raster süresini yeniden kontrol edin, çünkü çok büyük bir boundary eklemek her karede pahalı bir offscreen katman üretme riskini taşır.

Efekt parametresini ağdan gelen state ile bağlıyorsanız güncelleme hızını sınırlayın. Örneğin 200 Hz telemetri akışını doğrudan ValueNotifier'a yazmak, 60 Hz ekranda birden fazla gereksiz repaint üretir. Son değer kazanacak şekilde ekran yenileme aralığında birleştirin:

double? pending;
bool scheduled = false;

void onTelemetry(double value, ValueNotifier<double> progress) {
  pending = value;
  if (scheduled) return;
  scheduled = true;
  SchedulerBinding.instance.scheduleFrameCallback((_) {
    progress.value = pending!;
    scheduled = false;
  });
}
Bu teknik, cross platform mobil uygulama geliştirme senaryolarında özellikle önemlidir: hızlı Android cihazda fark edilmeyen fazla repaint, daha düşük GPU bütçeli bir iOS veya Android hedefinde raster kuyruğunu büyütebilir.

Profil sonuçlarını cihaz matrisi ve sürüm kontrolüyle koruma

Ölçümü kişisel cihazdaki tek bir DevTools oturumuna bırakmayın. CI veya gece testinde en az bir 60 Hz fiziksel cihazda profile APK kurun, sabit bir rota akışını çalıştırın ve uygulamanın topladığı FrameTiming özetini JSON olarak dışarı alın. Değişiklik incelemesinde şu üç alanı birlikte karşılaştırın: ilk efektli rotadaki maksimum raster süresi, p95 totalSpan ve eşik üstü kare oranı. Örneğin kabul kuralı, 'p95 16.67 ms altında ve 300 karede 5'ten az eşik aşımı' olabilir; eşik değerini hedef cihazın yenileme hızına göre yapılandırma dosyasında tutun.

Karşılaştırılan iki çalıştırmada shader cache durumunu not edin. Soğuk çalıştırma için uygulamayı kaldırıp tekrar kurmak veya uygulama verisini temizlemek, sıcak çalıştırma için aynı kurulumda ikinci rota geçişini yapmak gerekir. Android tarafında paket verisini temizlemek için kullanılan komut şudur:

adb shell pm clear com.example.app
adb shell monkey -p com.example.app 1
Paket adını uygulamanızın gerçek applicationId değeriyle değiştirin. Bu ayrımı kayıt altına almazsanız, bir merge request'teki iyileşme aslında cihazın önceden oluşturduğu cache'den kaynaklanabilir.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter eğitimi sırasında shader jank'i DevTools ile nasıl doğrularım?

Uygulamayı debug yerine profile modda çalıştırın, DevTools Performance kaydında ilk efektli rota geçişini izleyin ve uzun UI ile raster dilimlerini ayırın. Aynı akışta WidgetsBinding.addTimingsCallback ile FrameTiming toplayıp p95 rasterDuration ile eşik üstü totalSpan sayısını önce-sonra karşılaştırın.

Flutter kursu projelerinde ShaderWarmUp ne zaman kullanılmalı?

Yalnızca ilk kullanımı ölçülerek pahalı olduğu kanıtlanan ve üretimdeki çizim yoluyla eşleşen efektler için kullanın. MaskFilter, clip, gradient ve opacity kombinasyonunu warm-up canvas'ında temsil edin; ardından açılış maliyeti ile ilk rota geçişindeki rasterDuration değişimini aynı profile APK'da ölçün.

Flutter state management runtime shader performansını etkiler mi?

Evet, shader'ın derleme maliyetinden bağımsız olarak state güncellemeleri gereksiz build, layout ve repaint üretebilir. Sık değişen uniform değerlerini ValueListenable ve CustomPainter repaint parametresiyle iletin, 60 Hz ekranda telemetri güncellemelerini scheduleFrameCallback ile kare başına en fazla bir güncellemeye indirin.

Cross platform mobil uygulama geliştirme için shader ölçümü hangi cihazlarda yapılmalı?

En az bir 60 Hz fiziksel cihazda soğuk ve sıcak geçişi ayrı ölçün; ürün hedefliyorsa 90 veya 120 Hz sınıfında ikinci bir cihaz ekleyin. Her cihazda yenileme hızına göre 16.67 ms veya 8.33 ms bütçesi kullanın ve aynı profile build, aynı rota akışı ve aynı shader cache durumu ile karşılaştırma yapın.

AI / LLM Discovery

Bu makale Opendart Akademi Flutter eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt