Saturday 17 October 2020

Android canvas.draw leaks graphic memory

I've have a custom view which overrides onDraw and I've noticed that Graphics memory keeps increasing overtime, until my app crashes with OOM (it ranges anywhere from 4h to 12h, based on device).

Graphics

I'm doing a bit complex drawing but for reproducing purposes, this code does the trick:

package com.example.testdrawing;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

import java.util.Random;

public class CustomView extends View {
    private Random rand;
    private Paint paint;

    public CustomView(Context context) {
        super(context);
        init(context);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context) {
        rand = new Random();
        paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(10);
        
        // Simulate invalidation loop
        final Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    // I invoke postInvalidate() when the rendering data change.
                    postInvalidate();
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        thread.start();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 1) This one leaks memory
        canvas.drawOval(0, 0, 500 + (rand.nextInt(100)), 900 + (rand.nextInt(100)), paint);
        
        // 2) This one keeps graphic memory at constant
        //canvas.drawOval(0, 0, 500, 900, paint);

    }
}

Basically, the memory is retained whenever a drawing location is dynamic. If location is static, the memory remains at constant. In both cases, the graphic memory doesn't go down. Here is the profiler output after ~12 minutes for the CustomView: profiler

Full sample here

EDIT(@PerracoLabs): I don't believe that Random is the culprit. This reproduces by just drawing to dynamic coordinates. I.e:

 canvas.drawOval(x++ % 500, y++ % 500, w++ % 1080, h++ % 1000, paint);

Also, if these are just allocations stats, why are they accounted into total memory? If not released, it's a leak, right?

It is also strange that memory increase rate is ~100kb regardless of what's drawn.

EDIT 2:

I've attached full sample app that produces this (On Pixel 4, Android 10): enter image description here

I've stopped profiling since the profiler slowed down to the point it became unusable. Note that occasional drops where some memory is indeed freed.

Again, to me it doesn't make sense that for few draw calls, the overhead is ~200MB of allocated graphic memory. I'd really like to understand what's going on here. Obviously, there is a difference when drawing on dynamic locations on the canvas compared when the location is fixed, at which time the memory consumption stabilises.



from Android canvas.draw leaks graphic memory

No comments:

Post a Comment