Каковы лучшие практики для тяжелого рендеринга Android
Вопросов
- Каковы лучшие практики для тяжелой визуализации в представлениях Android?
- Указывает ли предлагаемый подход в правильном направлении?
- Какие есть альтернативы?
Описание
Мое приложение для Android прокручивается
heavy rendered content
. Отрисовка одного экрана может занять до 500 мс. Окончательное изображение должно быть показано пользователю.
Heavy rendering
можно запускать несколько раз в секунду (до 20 раз) простым перетаскиванием пальца.
Обычно имеет значение только последний звонок. Промежуточные необработанные вызовы можно отбросить. Однако, если промежуточный вызов был обработан, имеет смысл отображать результат пользователю, пока выполняется рендеринг следующего / последнего вызова.
Очевидно, что если вы поместите в основной поток, пользовательский интерфейс будет заморожен, и пользовательский интерфейс пострадает.
Текущая реализация
Идея
Тяжелый рендеринг вместе с требованием «последний звонок имеет значение» позволяет подумать об использовании отдельного потока и <tcode id="55970008"></tcode>.
Идея состоит в том, чтобы иметь два растровых изображения:
- - использовать в ветке, пусть
heavy rendering
потратьте столько времени, сколько нужно, чтобы нарисовать картинку - - использовать его для сохранения окончательного изображения и отображения пользователю, если приложение вызывает функцию onDraw
Шаги:
- После запуска обработки вызов буферизируется, и обрабатывается только последний доступный обработчик.
- Рендеринг выполняется в (это долгий процесс)
- После завершения рендеринга
intermediate bitmap
передается в (это должно быть быстро) -
final bitmap
отображается на экране в любое время, когда приложение хочет обновить экран (это тоже должно быть быстрым). Шаги 3 и 4 должныsynchronized
чтобы предотвратить мерцание.
Схема
Схематично это выглядит так:
intermediate --> final --> screen
bitmap bitmap bitmap
[long] [quick] [quick]
(Rendering thread) ? (--- Main thread ---)
|---------- sync1? ----------|
|------- sync2 ------|
Код
Технически это можно сделать так (тоже схематично, некоторые детали опущены):
// trigger initialization
void init() {
mCanvasIntermediate = new Canvas(mBitmapIntermediate);
mCanvasFinal = new Canvas(mBitmapFinal);
rendering = PublishSubject.create();
rendering
.toFlowable(BackpressureStrategy.LATEST)
.onBackpressureLatest()
.observeOn(Schedulers.computation(), false, 1)
.flatMapSingle(canvas -> Single.create(emitter -> {
canvas.drawBitmap(...);
emitter.onSuccess(result);
}))
.observeOn(AndroidSchedulers.mainThread()) // <-- comment to put invalidate to the thread
.subscribe(result -> {
mCanvasIntermediate.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
mCanvasFinal.drawBitmap(mBitmapIntermediate, ...);
invalidate();
});
}
// triggering
void triggerRendering() {
rendering.onNext(mCanvasIntermediate);
}
// view ondraw
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmapFinal, ...);
}
Проблема
В целом работает, но не идеально:
- если и рендеринг, и выходной промежуточный-> финал помещаются в основной поток, экран зависает при рендеринге,
- если в основной поток помещается только output intermediate-> final, экран мерцает,
- если и рендеринг, и выходной промежуточный-> финал помещаются в поток вычислений, только часть экрана отображается во время процесса рендеринга.