问题
- 在 Android 视图中重度渲染的最佳实践是什么?
- 建议的方法是否指向正确的方向?
- 有哪些替代方案?
描述
我的 Android 应用滚动浏览heavy rendered content
. 一个屏幕的渲染最多可能需要 500 毫秒。最终图像应显示给用户。
Heavy rendering
每秒可以触发多次(最多 20 次),只需通过手指拖动即可。
一般来说,只有最后一个电话很重要。可以丢弃中间未处理的呼叫。但是,如果中间调用已处理,则在呈现下一个/最后一个调用时向用户显示结果是有意义的。
显然,如果你heavy rendering
放入主线程,UI 将被冻结,用户体验将受到影响。
当前实施
主意
重度渲染以及“最后一次通话很重要”要求让您考虑使用单独的线程和RxJava Flowable
.
这个想法是有两个位图:
intermediate bitmap
- 在一个线程中使用它,让heavy rendering
绘制图片所需的时间尽可能多final bitmap
- 如果应用正在调用 onDraw 函数,则使用它来存储最终图片并显示给用户
脚步:
- 触发处理后,调用被缓冲,只处理最后一个可用的
- 渲染执行到
intermediate bitmap
(这是一个漫长的过程) - 渲染完成后,
intermediate bitmap
转入final 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, ...);
}
问题
一般来说,它有效,但并不完美:
- 如果渲染和输出intermediate->final都放在主线程中,则屏幕在渲染时冻结,
- 如果只将输出intermediate->final放到主线程,屏幕闪烁,
- 如果渲染和输出intermediate->final都放在计算线程中,则在渲染过程中只显示屏幕的一部分。
更新:
这条评论对我帮助很大: https ://stackoverflow.com/a/57610119/10475643
看来,这条线引起了整个小冲突:
mCanvasIntermediate.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
因为PorterDuff.Mode.CLEAR
不能很好地配合硬件加速。这会导致闪烁和部分屏幕输出。最后,我将两个drawBitmap
s 都放到了线程中。只要我删除drawColor
或关闭硬件加速,闪烁就消失了。