Partial Invalidation in Custom Android View with Hardware Acceleration

Android partial invalidation draws the entire view while hardware accelerated

Unfortunately this is a limitation of hardware acceleration in Android, at least as of Android 5. The invalidate rect is ignored and the entire view is always redrawn, requiring you to draw the entire area. If you attempt to draw only part of the view, anything else that was drawn in the view before will disappear.

I've read posts where it is claimed that Android does not re-render the whole view but only the part that changed, but this seems wrong because when I try to only render the area that was in the rect that I passed to invalidate, the rest of the view disappears. If it was true that Android only re-rendered the changed area, then I would expect the rest of the custom drawing in the view to stay visible.

iOS has much better performance with it's drawRect method and setNeedsDisplayInRect. I would have expected Android to work the same way, but alas it does not.

Android partial invalidation doesn't work as expected with hardware acceleration

I have post my answer in your given link. To understand the problem , you should know the difference between software rendering and hardware rendering. That's really a big topic which I won't cover here. I think the best approach is to read the source code(I did the same thing a few days ago). The key question is "what is DisplayList and when it is used"
. Repeat my answer here, the Canvas in onDraw is simply used to rebuild the DisplayList. That doesn't mean everything in your View will be redrawn.

Why does partial invalidate span the entire View?

The behavior changed with hardware accelaration, the default after Honeycomb. Now when you invalidate() a view providing a dirty region, the clip bound is not set (canvas.getClipBounds()). However, only objects intersecting the dirty region will be redrawn.

See for reference this answer by Romain Guy, the author of the rendering pipeline.

How to partially redraw a non-rect shaped part of the canvas

The recommended way is to redraw your view's entire canvas. The doc for the deprecated method invalidate(Rect dirty) mentions

The switch to hardware accelerated rendering in API 14 reduced
the importance of the dirty rectangle. In API 21 the given rectangle is
ignored entirely in favor of an internally-calculated area instead.

Since hardware acceleration is enabled by default (even at view level) for API >=14, you can simply invalidate the entire view.

Because of Hardware Acceleration there is very little performance impact.
Hardware Acceleration utilizes Display Lists which further improves performance.

In fact instead of worrying about using invalidate(), you can actually take care not to perform other things that can impact performance while drawing, refer:
https://developer.android.com/guide/topics/graphics/hardware-accel#tips

Android: How to get a custom view to redraw partially?

Current nice workaround is to manually cache the full canvas to a bitmap:

 private void onDraw(Canvas canvas)
{
if (!initialDrawingIsPerformed)
{
this.cachedBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Config.ARGB_8888); //Change to lower bitmap config if possible.
Canvas cacheCanvas = new Canvas(this.cachedBitmap);
doInitialDrawing(cacheCanvas);
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
initialDrawingIsPerformed = true;
}
else
{
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
doPartialRedraws(canvas);
}
}

Ofcourse, you need to store the info about what to redraw yourself and preferably not use a new Paint everytime, but that are details.

Also note: Bitmaps are quite heavy on the memory usage of your app. I had crashes when I cached a View that was used with a scroller and that was like 5 times the height of the device, since it used > 10MB memory!



Related Topics



Leave a reply



Submit