Почему частое очищение объектов с Garbage Collector в методе onDraw может привести к тормозящему UI?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Механизм работы GC и его влияние на UI-поток
Сборщик мусора (Garbage Collector, GC) в Android выполняет автоматическое управление памятью, освобождая объекты, на которые отсутствуют ссылки. Однако каждый запуск GC требует вычислительных ресурсов и вызывает "паузы" (stop-the-world events) в работе приложения, поскольку большинство алгоритмов GC требуют приостановки всех потоков для анализа графа объектов. В контексте метода onDraw() это особенно критично.
Почему onDraw() особо чувствителен к GC
- Высокая частота вызовов:
onDraw()может вызываться десятки раз в секунду для обеспечения плавной анимации (целевые 60 FPS требуют выполнения за ~16 мс на кадр). - Строгие временные ограничения: Если выполнение
onDraw()превышает 16 мс, система пропускает кадры (jank), что визуально воспринимается как "тормоза". - Синхронный характер: Сборка мусора, запущенная в 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():
- Создаются десятки/сотни временных объектов (
Paint,Path,Rect, массивы) - После завершения
onDraw()эти объекты становятся недостижимыми - При заполнении поколения объектов система запускает GC
- GC приостанавливает UI-поток для:
- Маркировки достижимых объектов (mark)
- Очистки неиспользуемых объектов (sweep)
- Компактизации памяти при необходимости (compact)
Типы сборок мусора в Android и их влияние
- Малая сборка (Young Generation GC) - быстрая (1-5 мс), но при частых вызовах накапливает задержки
- Полная сборка (Full GC) - может занимать 50-100 мс, гарантированно вызывает пропуск кадров
- Фоновые сборки - выполняются в отдельном потоке, но всё равно требуют кратковременных пауз основного потока
Практические рекомендации по оптимизации
// Оптимизированный подход с пулом объектов
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, анализируя временные линии и логи сборок мусора.