Skip to content

FPS Optimizasyonu ve Akıcı Kullanıcı Deneyimi

Mobil uygulamalarda akıcı kullanıcı deneyimi sağlamak için hedeflenen frame rate (genellikle 60 FPS) sürdürülmelidir. Bu bölümde FPS optimizasyonu teknikleri ve akıcı kullanıcı deneyimi sağlama yöntemleri ele alınacaktır.

FPS ve Frame Rate Temelleri

60 FPS Hedefi

javascript
// React Native - FPS İzleme
import { PerformanceMonitor } from 'react-native-performance';

const FPSMonitor = () => {
  const [fps, setFps] = useState(60);
  
  useEffect(() => {
    const monitor = new PerformanceMonitor({
      onFPSChange: (currentFPS) => {
        setFps(currentFPS);
        if (currentFPS < 30) {
          console.warn('Düşük FPS tespit edildi:', currentFPS);
        }
      }
    });
    
    return () => monitor.stop();
  }, []);
  
  return (
    <View style={styles.monitor}>
      <Text>FPS: {fps.toFixed(1)}</Text>
    </View>
  );
};

Android FPS Optimizasyonu

kotlin
// GPU Profiler ile FPS izleme
class FPSMonitor {
    private var frameStartTime = 0L
    private val frameMetrics = mutableListOf<Long>()
    
    fun startFrameMonitoring(activity: Activity) {
        activity.window.addOnFrameMetricsAvailableListener(
            { _, frameMetrics, _ ->
                val totalDuration = frameMetrics.getMetric(
                    FrameMetrics.TOTAL_DURATION
                )
                
                // 16.67ms = 60 FPS threshold
                if (totalDuration > 16_670_000) { // nanoseconds
                    Log.w("FPS", "Dropped frame: ${totalDuration / 1_000_000}ms")
                }
                
                this.frameMetrics.add(totalDuration)
                if (this.frameMetrics.size > 100) {
                    analyzePerformance()
                    this.frameMetrics.clear()
                }
            },
            Handler(Looper.getMainLooper())
        )
    }
    
    private fun analyzePerformance() {
        val avgFrameTime = frameMetrics.average()
        val fps = 1_000_000_000 / avgFrameTime
        
        Log.i("FPS", "Ortalama FPS: ${fps.toInt()}")
    }
}

// Choreographer ile frame callback optimizasyonu
class SmoothAnimationManager : Choreographer.FrameCallback {
    private val choreographer = Choreographer.getInstance()
    private var lastFrameTime = 0L
    
    fun startSmoothing() {
        choreographer.postFrameCallback(this)
    }
    
    override fun doFrame(frameTimeNanos: Long) {
        if (lastFrameTime != 0L) {
            val frameDuration = (frameTimeNanos - lastFrameTime) / 1_000_000
            
            if (frameDuration > 16.67) {
                // Frame drop tespit edildi
                optimizeNextFrame()
            }
        }
        
        lastFrameTime = frameTimeNanos
        choreographer.postFrameCallback(this)
    }
    
    private fun optimizeNextFrame() {
        // Gereksiz işlemleri sonraki frame'e ertele
        choreographer.postFrameCallback {
            // Kritik olmayan güncellemeleri yap
        }
    }
}

iOS FPS Optimizasyonu

swift
// CADisplayLink ile FPS izleme
class FPSCounter {
    private var displayLink: CADisplayLink?
    private var lastTimestamp: CFTimeInterval = 0
    private var frameCount: Int = 0
    
    func startMonitoring() {
        displayLink = CADisplayLink(target: self, selector: #selector(displayLinkTick))
        displayLink?.add(to: .main, forMode: .common)
    }
    
    @objc private func displayLinkTick(displayLink: CADisplayLink) {
        if lastTimestamp == 0 {
            lastTimestamp = displayLink.timestamp
            return
        }
        
        frameCount += 1
        let elapsed = displayLink.timestamp - lastTimestamp
        
        if elapsed >= 1.0 {
            let fps = Double(frameCount) / elapsed
            print("FPS: \(Int(fps))")
            
            if fps < 45 {
                // Düşük FPS tespit edildi
                optimizeRendering()
            }
            
            frameCount = 0
            lastTimestamp = displayLink.timestamp
        }
    }
    
    private func optimizeRendering() {
        // Render optimizasyonları
        DispatchQueue.main.async {
            // UI güncellemelerini optimize et
        }
    }
}

// Metal ile GPU optimizasyonu
class MetalRenderer {
    private var device: MTLDevice!
    private var commandQueue: MTLCommandQueue!
    
    func optimizeGPURendering() {
        // Triple buffering kullan
        let bufferCount = 3
        
        // Command buffer pool
        let commandBuffer = commandQueue.makeCommandBuffer()
        
        // Parallel encoding
        let parallelEncoder = commandBuffer?.makeParallelRenderCommandEncoder(
            descriptor: renderPassDescriptor
        )
        
        // GPU iş yükünü dağıt
        for i in 0..<bufferCount {
            if let encoder = parallelEncoder?.makeRenderCommandEncoder() {
                encoder.setRenderPipelineState(pipelineState)
                encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
                encoder.endEncoding()
            }
        }
        
        parallelEncoder?.endEncoding()
        commandBuffer?.commit()
    }
}

Frame Drop'ları Önleme

Main Thread Optimizasyonu

dart
// Flutter - Isolate kullanarak ağır işlemleri main thread'den ayırma
class HeavyComputationManager {
  static Future<List<ProcessedData>> processDataAsync(
    List<RawData> rawData
  ) async {
    final receivePort = ReceivePort();
    
    await Isolate.spawn(_computeInIsolate, {
      'data': rawData,
      'sendPort': receivePort.sendPort,
    });
    
    return await receivePort.first as List<ProcessedData>;
  }
  
  static void _computeInIsolate(Map<String, dynamic> params) {
    final data = params['data'] as List<RawData>;
    final sendPort = params['sendPort'] as SendPort;
    
    // Ağır hesaplama
    final processed = data.map((item) {
      // Karmaşık işlemler
      return ProcessedData(
        result: heavyCalculation(item),
        timestamp: DateTime.now(),
      );
    }).toList();
    
    sendPort.send(processed);
  }
}

// Widget rebuild optimizasyonu
class OptimizedListWidget extends StatefulWidget {
  @override
  _OptimizedListWidgetState createState() => _OptimizedListWidgetState();
}

class _OptimizedListWidgetState extends State<OptimizedListWidget> {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      // addRepaintBoundaries: true (varsayılan)
      itemBuilder: (context, index) {
        return RepaintBoundary(
          child: OptimizedListItem(
            key: ValueKey(items[index].id),
            item: items[index],
          ),
        );
      },
    );
  }
}

class OptimizedListItem extends StatelessWidget {
  final Item item;
  
  const OptimizedListItem({Key? key, required this.item}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    // Gereksiz rebuilds'i önlemek için const constructors kullan
    return const Card(
      child: ListTile(
        title: Text(item.title),
        subtitle: Text(item.description),
      ),
    );
  }
}

Async Operations Optimizasyonu

javascript
// React Native - İşlem kuyruğu ile frame blocking'i önleme
class OperationQueue {
  constructor() {
    this.queue = [];
    this.isProcessing = false;
    this.frameDeadline = 16; // ms
  }
  
  addOperation(operation) {
    this.queue.push(operation);
    if (!this.isProcessing) {
      this.processQueue();
    }
  }
  
  async processQueue() {
    this.isProcessing = true;
    
    while (this.queue.length > 0) {
      const startTime = performance.now();
      
      // Frame deadline'ını aşmamak için zaman kontrolü
      while (
        this.queue.length > 0 && 
        (performance.now() - startTime) < this.frameDeadline
      ) {
        const operation = this.queue.shift();
        try {
          await operation();
        } catch (error) {
          console.error('Operation error:', error);
        }
      }
      
      // Bir sonraki frame'e bekle
      if (this.queue.length > 0) {
        await new Promise(resolve => requestAnimationFrame(resolve));
      }
    }
    
    this.isProcessing = false;
  }
}

// Kullanım
const operationQueue = new OperationQueue();

// Ağır işlemleri kuyruğa ekle
operationQueue.addOperation(() => processLargeDataSet(data1));
operationQueue.addOperation(() => updateComplexUI(uiData));
operationQueue.addOperation(() => performCalculations(calcData));

GPU ve Render Optimizasyonu

Texture ve Shader Optimizasyonu

swift
// iOS Metal Shader optimizasyonu
class OptimizedShaderManager {
    private var pipelineStates: [String: MTLRenderPipelineState] = [:]
    
    func createOptimizedPipeline(for shaderName: String) -> MTLRenderPipelineState? {
        if let cachedState = pipelineStates[shaderName] {
            return cachedState
        }
        
        let pipelineDescriptor = MTLRenderPipelineDescriptor()
        
        // Vertex shader
        pipelineDescriptor.vertexFunction = library.makeFunction(name: "\(shaderName)_vertex")
        
        // Fragment shader - optimized precision
        pipelineDescriptor.fragmentFunction = library.makeFunction(name: "\(shaderName)_fragment")
        
        // Color attachment optimization
        pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
        
        // Alpha blending optimization
        let colorAttachment = pipelineDescriptor.colorAttachments[0]!
        colorAttachment.isBlendingEnabled = true
        colorAttachment.rgbBlendOperation = .add
        colorAttachment.alphaBlendOperation = .add
        colorAttachment.sourceRGBBlendFactor = .sourceAlpha
        colorAttachment.destinationRGBBlendFactor = .oneMinusSourceAlpha
        
        do {
            let pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
            pipelineStates[shaderName] = pipelineState
            return pipelineState
        } catch {
            print("Pipeline state creation failed: \(error)")
            return nil
        }
    }
}

// Texture atlasing için optimizasyon
class TextureAtlasManager {
    private var atlases: [MTLTexture] = []
    private var textureCoordinates: [String: CGRect] = [:]
    
    func createAtlas(from images: [String: UIImage]) -> MTLTexture? {
        let atlasSize = calculateOptimalAtlasSize(imageCount: images.count)
        
        let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(
            pixelFormat: .rgba8Unorm,
            width: atlasSize,
            height: atlasSize,
            mipmapped: true
        )
        
        guard let atlasTexture = device.makeTexture(descriptor: textureDescriptor) else {
            return nil
        }
        
        // Pack images into atlas
        var currentX = 0, currentY = 0, rowHeight = 0
        
        for (name, image) in images {
            let imageSize = image.size
            
            if currentX + Int(imageSize.width) > atlasSize {
                currentX = 0
                currentY += rowHeight
                rowHeight = 0
            }
            
            // Copy image data to atlas
            copyImageToAtlas(image: image, atlas: atlasTexture, x: currentX, y: currentY)
            
            // Store texture coordinates
            textureCoordinates[name] = CGRect(
                x: Double(currentX) / Double(atlasSize),
                y: Double(currentY) / Double(atlasSize),
                width: imageSize.width / Double(atlasSize),
                height: imageSize.height / Double(atlasSize)
            )
            
            currentX += Int(imageSize.width)
            rowHeight = max(rowHeight, Int(imageSize.height))
        }
        
        return atlasTexture
    }
}

Draw Call Optimizasyonu

kotlin
// Android - Draw call batching
class DrawCallOptimizer {
    private val batchedDrawCommands = mutableListOf<DrawCommand>()
    private var currentTexture: Texture? = null
    
    fun addDrawCommand(command: DrawCommand) {
        // Texture değişimi kontrolü
        if (currentTexture != command.texture) {
            flushBatch()
            currentTexture = command.texture
        }
        
        batchedDrawCommands.add(command)
        
        // Batch size limiti
        if (batchedDrawCommands.size >= MAX_BATCH_SIZE) {
            flushBatch()
        }
    }
    
    private fun flushBatch() {
        if (batchedDrawCommands.isEmpty()) return
        
        // Vertex buffer'ı hazırla
        val vertexBuffer = createBatchedVertexBuffer(batchedDrawCommands)
        
        // Tek draw call ile tüm batch'i çiz
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, currentTexture?.id ?: 0)
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffer.id)
        
        GLES20.glVertexAttribPointer(
            positionHandle, 3, GLES20.GL_FLOAT, false,
            VERTEX_STRIDE, 0
        )
        GLES20.glVertexAttribPointer(
            textureCoordHandle, 2, GLES20.GL_FLOAT, false,
            VERTEX_STRIDE, 12
        )
        
        GLES20.glDrawArrays(
            GLES20.GL_TRIANGLES, 0,
            batchedDrawCommands.size * 6
        )
        
        batchedDrawCommands.clear()
    }
    
    private fun createBatchedVertexBuffer(commands: List<DrawCommand>): VertexBuffer {
        val vertices = FloatArray(commands.size * 30) // 6 vertices * 5 floats per vertex
        var index = 0
        
        for (command in commands) {
            val quad = command.generateQuadVertices()
            System.arraycopy(quad, 0, vertices, index, quad.size)
            index += quad.size
        }
        
        return VertexBuffer(vertices)
    }
    
    companion object {
        private const val MAX_BATCH_SIZE = 1000
        private const val VERTEX_STRIDE = 20 // 5 floats * 4 bytes
    }
}

Animation Performance

Hardware Accelerated Animations

dart
// Flutter - Transform ve Opacity animasyonları (GPU accelerated)
class OptimizedAnimationWidget extends StatefulWidget {
  @override
  _OptimizedAnimationWidgetState createState() => _OptimizedAnimationWidgetState();
}

class _OptimizedAnimationWidgetState extends State<OptimizedAnimationWidget>
    with TickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _opacityAnimation;
  
  @override
  void initState() {
    super.initState();
    
    _controller = AnimationController(
      duration: Duration(milliseconds: 300),
      vsync: this,
    );
    
    // GPU ile hızlandırılmış animasyonlar
    _scaleAnimation = Tween<double>(
      begin: 1.0,
      end: 1.2,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
    
    _opacityAnimation = Tween<double>(
      begin: 1.0,
      end: 0.8,
    ).animate(_controller);
  }
  
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Transform.scale(
          scale: _scaleAnimation.value,
          child: Opacity(
            opacity: _opacityAnimation.value,
            child: Container(
              width: 100,
              height: 100,
              decoration: BoxDecoration(
                color: Colors.blue,
                borderRadius: BorderRadius.circular(8),
              ),
            ),
          ),
        );
      },
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

// Custom painter ile optimize edilmiş çizim
class OptimizedCustomPainter extends CustomPainter {
  final Animation<double> animation;
  
  OptimizedCustomPainter(this.animation) : super(repaint: animation);
  
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;
    
    // Animasyonlu çizim
    final progress = animation.value;
    final radius = size.width * 0.5 * progress;
    
    canvas.drawCircle(
      Offset(size.width * 0.5, size.height * 0.5),
      radius,
      paint,
    );
  }
  
  @override
  bool shouldRepaint(OptimizedCustomPainter oldDelegate) {
    return animation.value != oldDelegate.animation.value;
  }
}

Interpolation Optimizasyonu

javascript
// React Native - Native driver ile optimize edilmiş animasyonlar
import { Animated, Easing } from 'react-native';

class OptimizedAnimationManager {
  constructor() {
    this.animatedValues = new Map();
    this.runningAnimations = new Set();
  }
  
  createOptimizedAnimation(key, config) {
    const animatedValue = new Animated.Value(config.initialValue || 0);
    this.animatedValues.set(key, animatedValue);
    
    return {
      start: (toValue, duration = 300, callback) => {
        const animation = Animated.timing(animatedValue, {
          toValue,
          duration,
          easing: Easing.bezier(0.25, 0.1, 0.25, 1),
          useNativeDriver: true, // GPU acceleration
        });
        
        this.runningAnimations.add(key);
        
        animation.start(() => {
          this.runningAnimations.delete(key);
          callback && callback();
        });
        
        return animation;
      },
      
      stop: () => {
        animatedValue.stopAnimation();
        this.runningAnimations.delete(key);
      },
      
      getValue: () => animatedValue._value,
      getAnimatedValue: () => animatedValue,
    };
  }
  
  // Spring animation ile doğal hareket
  createSpringAnimation(key, config) {
    const animatedValue = new Animated.Value(config.initialValue || 0);
    this.animatedValues.set(key, animatedValue);
    
    return {
      spring: (toValue, springConfig = {}) => {
        const animation = Animated.spring(animatedValue, {
          toValue,
          friction: springConfig.friction || 8,
          tension: springConfig.tension || 100,
          useNativeDriver: true,
        });
        
        this.runningAnimations.add(key);
        animation.start(() => this.runningAnimations.delete(key));
        
        return animation;
      }
    };
  }
  
  // Paralel animasyonlar
  createParallelAnimation(animations) {
    const parallelAnimations = animations.map(anim => anim.animation);
    
    return Animated.parallel(parallelAnimations, {
      stopTogether: false
    });
  }
  
  // Sequence animasyonlar
  createSequenceAnimation(animations) {
    const sequenceAnimations = animations.map(anim => anim.animation);
    
    return Animated.sequence(sequenceAnimations);
  }
  
  stopAllAnimations() {
    this.runningAnimations.forEach(key => {
      const animatedValue = this.animatedValues.get(key);
      if (animatedValue) {
        animatedValue.stopAnimation();
      }
    });
    this.runningAnimations.clear();
  }
}

// Kullanım örneği
const animationManager = new OptimizedAnimationManager();

const fadeAnimation = animationManager.createOptimizedAnimation('fade', {
  initialValue: 0
});

const scaleAnimation = animationManager.createSpringAnimation('scale', {
  initialValue: 1
});

// Paralel animasyon
const showModal = () => {
  const parallelAnim = animationManager.createParallelAnimation([
    fadeAnimation.start(1, 250),
    scaleAnimation.spring(1.1)
  ]);
  
  parallelAnim.start();
};

Performans İzleme ve Profiling

Real-time FPS Monitoring

kotlin
// Android - Gerçek zamanlı performans izleme
class PerformanceProfiler {
    private val frameMetrics = FrameMetricsCollector()
    private val memoryMonitor = MemoryMonitor()
    private val listeners = mutableListOf<PerformanceListener>()
    
    fun startProfiling(activity: Activity) {
        // Frame metrics
        frameMetrics.start(activity) { metrics ->
            val fps = calculateFPS(metrics)
            val frameTime = metrics.totalDuration / 1_000_000.0 // ms
            
            notifyListeners(PerformanceData(
                fps = fps,
                frameTime = frameTime,
                droppedFrames = metrics.droppedFrames,
                memoryUsage = memoryMonitor.getCurrentUsage()
            ))
            
            // Performans uyarıları
            if (fps < 30) {
                Log.w("Performance", "Düşük FPS: $fps")
                suggestOptimizations(metrics)
            }
        }
        
        // Memory monitoring
        memoryMonitor.startMonitoring { usage ->
            if (usage.percentUsed > 80) {
                Log.w("Performance", "Yüksek bellek kullanımı: ${usage.percentUsed}%")
                triggerGarbageCollection()
            }
        }
    }
    
    private fun calculateFPS(metrics: FrameMetrics): Double {
        val frameDuration = metrics.totalDuration / 1_000_000.0 // ms
        return 1000.0 / frameDuration
    }
    
    private fun suggestOptimizations(metrics: FrameMetrics) {
        when {
            metrics.layoutDuration > 2_000_000 -> { // 2ms
                Log.i("Optimization", "Layout optimizasyonu gerekli")
            }
            metrics.drawDuration > 8_000_000 -> { // 8ms
                Log.i("Optimization", "Draw call optimizasyonu gerekli")
            }
            metrics.syncDuration > 1_000_000 -> { // 1ms
                Log.i("Optimization", "UI thread senkronizasyonu gerekli")
            }
        }
    }
    
    interface PerformanceListener {
        fun onPerformanceUpdate(data: PerformanceData)
        fun onPerformanceWarning(warning: PerformanceWarning)
    }
}

data class PerformanceData(
    val fps: Double,
    val frameTime: Double,
    val droppedFrames: Int,
    val memoryUsage: MemoryUsage
)

Automated Performance Testing

swift
// iOS - Otomatik performans testleri
class PerformanceTestSuite {
    private let testMetrics = XCTMetric.default
    
    func runFPSPerformanceTest() {
        measure(metrics: [XCTCPUMetric(), XCTMemoryMetric()]) {
            // Test senaryosu
            performHeavyUIOperations()
        }
    }
    
    private func performHeavyUIOperations() {
        let expectation = XCTestExpectation(description: "UI Operations")
        
        DispatchQueue.main.async {
            // Ağır UI işlemleri simüle et
            for i in 0..<1000 {
                let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
                view.backgroundColor = UIColor.random()
                view.layer.cornerRadius = 10
                view.layer.shadowOffset = CGSize(width: 2, height: 2)
                view.layer.shadowOpacity = 0.5
                
                // View hierarchy'ye ekle ve kaldır
                self.testView.addSubview(view)
                view.removeFromSuperview()
            }
            
            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 10.0)
    }
    
    func measureScrollPerformance() {
        let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 375, height: 667))
        
        measure(metrics: [XCTOSSignpostMetric.scrollDecelerationMetric]) {
            // Scroll işlemi simüle et
            tableView.setContentOffset(CGPoint(x: 0, y: 1000), animated: true)
        }
    }
    
    func measureAnimationPerformance() {
        let animationView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        
        measure(metrics: [XCTCPUMetric()]) {
            UIView.animate(
                withDuration: 1.0,
                delay: 0,
                options: [.curveEaseInOut],
                animations: {
                    animationView.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
                    animationView.alpha = 0.5
                }
            )
        }
    }
}

// Custom performance metrics
extension XCTMetric {
    static let frameDropMetric = XCTMetric(
        identifier: "com.app.framedrops",
        displayName: "Frame Drops",
        unitOfMeasurement: "frames"
    )
    
    static let renderTimeMetric = XCTMetric(
        identifier: "com.app.rendertime",
        displayName: "Render Time",
        unitOfMeasurement: "ms"
    )
}

Bu FPS optimizasyonu dokümantasyonu, mobil uygulamalarda akıcı kullanıcı deneyimi sağlamak için gerekli teknikleri kapsamlı bir şekilde ele almaktadır. Frame rate optimizasyonu, GPU kullanımı, animasyon performansı ve sürekli izleme konularında platform-specific çözümler sunmaktadır.

Eren Demir tarafından oluşturulmuştur.