Ответ
Частые вызовы GC в onDraw() приводят к пропускам кадров (jank), т.к. сборка мусора требует приостановки UI-потока.
Причины:
- Создание временных объектов (например,
new Paint(),new Rect()) в циклеonDraw() - Автоупаковка примитивов (например,
Integer.valueOf())
Решение:
- Выносите создание объектов в инициализацию (поля класса)
- Используйте
canvas.save()/restore()вместо создания новых матриц - Для сложных вычислений - кешируйте результаты
// Плохо:
override fun onDraw(canvas: Canvas) {
val paint = Paint() // Создается каждый кадр
canvas.drawText("Hello", 0f, 0f, paint)
}
// Хорошо:
private val paint = Paint() // Инициализируется один раз
override fun onDraw(canvas: Canvas) {
canvas.drawText("Hello", 0f, 0f, paint)
} Ответ 18+ 🔞
А, слушай, вот тебе история про то, как твой красивый интерфейс превращается в слайд-шоу из-за одной тупой, но вечной ошибки. Ёпта, это же классика, как будто на дворе 2002-й год и мы только учимся не создавать объекты в цикле.
Короче, суть в чём. Твой метод onDraw() вызывается дохуя раз в секунду, чтобы всё плавно рисовалось. И если ты внутри него начинаешь плодить временные объекты — новые Paint, Rect, Path — то ты, сам того не желая, устраиваешь мусорную вечеринку. А потом приходит уборщик — Garbage Collector (GC), — и ему, этому пидарасу шерстяному, нужно всё за собой прибрать. А чтобы подмести, он вынужден остановить весь UI-поток. Представь: ты смотришь кино, а его каждые пять секунд ставят на паузу, чтобы дворник прошёлся с метлой. Вот это и есть твой «jank» — эти дерганые, пропущенные кадры. Волнение ебать, когда смотришь на это.
Основные причины, из-за которых это происходит — просто пиздопроебибна своей очевидностью:
- Создание объектов прямо в
onDraw(). Самый частый грех —val paint = Paint()внутри метода. Это же, блядь, создаётся каждый раз, когда нужно что-то нарисовать! Через пару секунд у тебя овердохуища мусора, и GC начинает свою пляску. - Автоупаковка (autoboxing). Когда ты, например, в список
List<Int>пихаешь обычныйint, а система тихонечко создаёт для тебя объектInteger. Делает это она черезInteger.valueOf(). В циклеonDraw()это тоже смерть.
Что делать, чтобы не было хиросимы?
- Выноси всё, что можно, в поля класса. Кисти (
Paint), прямоугольники (Rect), пути (Path) — инициализируй их один раз, при создании вьюхи, а вonDraw()только используй. Это же логично, ёклмн! - Вместо того чтобы создавать новые трансформации (
Matrix), используйcanvas.save()иcanvas.restore(). Экономия — мать порядка. - Сложные вычисления, которые не меняются каждый кадр, — кешируй. Не вычисляй одно и то же по сто раз, будь умнее.
Смотри, вот наглядный пример, чтобы в сердцу мать дошло:
// Плохо: делаем так — и получаем слайд-шоу. Каждый кадр новая кисть. Манда с ушами.
override fun onDraw(canvas: Canvas) {
val paint = Paint() // Создается КАЖДЫЙ КАДР! Пиздец.
canvas.drawText("Hello", 0f, 0f, paint)
}
// Хорошо: делаем так — и живём спокойно. Кисть одна на всю жизнь вьюхи.
private val paint = Paint() // Инициализируется ОДИН РАЗ, когда создаётся вьюха
override fun onDraw(canvas: Canvas) {
canvas.drawText("Hello", 0f, 0f, paint) // Просто рисуем готовым инструментом
}
Вот и вся магия. Не устраивай мусорный апокалипсис в методе, который должен летать, и всё будет охуенно. А то будешь потом сидеть и думать: «Э, бошка, думай, почему же у меня всё тормозит?». А причина-то — хуй в пальто, который ты сам себе и создал.