← Назад к вопросам

Почему частое очищение объектов с Garbage Collector в методе onDraw может привести к тормозящему UI?

1.3 Junior🔥 221 комментариев
#UI и вёрстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм работы GC и его влияние на UI-поток

Сборщик мусора (Garbage Collector, GC) в Android выполняет автоматическое управление памятью, освобождая объекты, на которые отсутствуют ссылки. Однако каждый запуск GC требует вычислительных ресурсов и вызывает "паузы" (stop-the-world events) в работе приложения, поскольку большинство алгоритмов GC требуют приостановки всех потоков для анализа графа объектов. В контексте метода onDraw() это особенно критично.

Почему onDraw() особо чувствителен к GC

  1. Высокая частота вызовов: onDraw() может вызываться десятки раз в секунду для обеспечения плавной анимации (целевые 60 FPS требуют выполнения за ~16 мс на кадр).
  2. Строгие временные ограничения: Если выполнение onDraw() превышает 16 мс, система пропускает кадры (jank), что визуально воспринимается как "тормоза".
  3. Синхронный характер: Сборка мусора, запущенная в UI-потоке, блокирует выполнение onDraw() и всех последующих операций отрисовки.

Конкретные причины тормозов при частом GC в onDraw()

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    
    // ПЛОХО: Создание временных объектов в цикле
    for (i in 0 until 1000) {
        val tempPaint = Paint() // Новый объект каждый итерацию
        tempPaint.color = Color.RED
        canvas.drawCircle(x, y, radius, tempPaint)
    } // 1000 объектов становятся мусором после onDraw()
    
    // ПЛОХО: Создание объектов в анимации
    val gradient = LinearGradient(0f, 0f, width.toFloat(), 0f,
                                 IntArray(5) { it * 0x10 }, // Новый массив
                                 null, Shader.TileMode.CLAMP)
    
    // ХОРОШО: Переиспользование объектов
    reusablePaint.color = getNextColor()
    canvas.drawRect(rect, reusablePaint)
}

Что происходит при каждом вызове onDraw():

  1. Создаются десятки/сотни временных объектов (Paint, Path, Rect, массивы)
  2. После завершения onDraw() эти объекты становятся недостижимыми
  3. При заполнении поколения объектов система запускает GC
  4. GC приостанавливает UI-поток для:
    • Маркировки достижимых объектов (mark)
    • Очистки неиспользуемых объектов (sweep)
    • Компактизации памяти при необходимости (compact)

Типы сборок мусора в Android и их влияние

  1. Малая сборка (Young Generation GC) - быстрая (1-5 мс), но при частых вызовах накапливает задержки
  2. Полная сборка (Full GC) - может занимать 50-100 мс, гарантированно вызывает пропуск кадров
  3. Фоновые сборки - выполняются в отдельном потоке, но всё равно требуют кратковременных пауз основного потока

Практические рекомендации по оптимизации

// Оптимизированный подход с пулом объектов
public class CustomView extends View {
    private Paint reusablePaint = new Paint();
    private RectF reusableRect = new RectF();
    private Path reusablePath = new Path();
    private Paint[] paintPool = new Paint[5]; // Пул объектов
    
    @Override
    protected void onDraw(Canvas canvas) {
        // Переиспользование вместо создания
        reusablePaint.setColor(Color.RED);
        reusableRect.set(0, 0, 100, 100);
        
        // Использование пула
        Paint paint = getPaintFromPool();
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(reusableRect, paint);
        returnPaintToPool(paint);
    }
    
    private Paint getPaintFromPool() {
        // Возвращает существующий или создаёт один раз
    }
}

Ключевые принципы оптимизации:

  • Объектный пул: Переиспользование тяжелых объектов (Paint, Path, Typeface)
  • Предварительное создание: Инициализация ресурсов в onAttachedToWindow() или конструкторе
  • Избегание автобоксинг: Особенно в циклах (используйте SparseArray, избегайте HashMap<Integer, ...>)
  • Кэширование результатов: Кэширование вычислений, которые не изменяются между вызовами
  • Использование drawables: Где возможно, используйте Bitmap и Drawable вместо программной отрисовки

Мониторинг и диагностика

// Включение отслеживания сборок мусора
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    val gpuTracer = Trace.beginSection("onDraw")
    try {
        // Код отрисовки
    } finally {
        Trace.endSection()
    }
}

// Анализ через Perfetto или Android Studio Profiler
// показывает пики пауз, вызванных GC

Вывод: Частые сборки мусора в onDraw() нарушают гибкий график рендеринга (VSync), приводят к пропуску кадров и воспринимаются пользователем как "тормоза". Оптимизация сводится к минимизации создания объектов в критичных по времени методах через переиспользование, пулы объектов и архитектурные изменения. Современные средства профилирования (Android Studio Profiler, Perfetto) позволяют точно идентифицировать проблемы, связанные с GC, анализируя временные линии и логи сборок мусора.

Почему частое очищение объектов с Garbage Collector в методе onDraw может привести к тормозящему UI? | PrepBro