1

问题

  • 在 Android 视图中重度渲染的最佳实践是什么?
  • 建议的方法是否指向正确的方向?
  • 有哪些替代方案?

描述

我的 Android 应用滚动浏览heavy rendered content. 一个屏幕的渲染最多可能需要 500 毫秒。最终图像应显示给用户。

Heavy rendering每秒可以触发多次(最多 20 次),只需通过手指拖动即可。

一般来说,只有最后一个电话很重要。可以丢弃中间未处理的呼叫。但是,如果中间调用已处理,则在呈现下一个/最后一个调用时向用户显示结果是有意义的。

显然,如果你heavy rendering放入主线程,UI 将被冻结,用户体验将受到影响。

当前实施

主意

重度渲染以及“最后一次通话很重要”要求让您考虑使用单独的线程和RxJava Flowable.

这个想法是有两个位图:

  • intermediate bitmap- 在一个线程中使用它,让heavy rendering绘制图片所需的时间尽可能多
  • final bitmap- 如果应用正在调用 onDraw 函数,则使用它来存储最终图片并显示给用户

脚步:

  1. 触发处理后,调用被缓冲,只处理最后一个可用的
  2. 渲染执行到intermediate bitmap(这是一个漫长的过程)
  3. 渲染完成后,intermediate bitmap转入final bitmap(这个应该很快)
  4. 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不能很好地配合硬件加速。这会导致闪烁和部分屏幕输出。最后,我将两个drawBitmaps 都放到了线程中。只要我删除drawColor或关闭硬件加速,闪烁就消失了。

4

0 回答 0