TextView.setText() high-frequency usage: memory leaks

This article present why Android’s TextView is responsible for memory leaks when it comes to high-frequency updating displayed text, and how to do it by other means.Disclaimer: I share in this article my experience as a novice Java Android developer as I would have appreciated seeing such an article before. I may miss something.

The problem: oversized objects creation

I display every 50ms all sensors new values from my smartphone: accelerometer, gyroscope, and a few others based on hardware or from sensors fusion. Here is what’s happening in memory:

RAM state screenshot showing memory going up quickly
Every 15 seconds, garbage collector frees garbage memory

Without talking about the annoying milliseconds of pauses dues to memory freeing, the garbage collector is not supposed to be that much solicited. After reducing as much as I could every object construction (especially String) in the main repeated frame, I put myself in interest over TextView and launch a memory analysis:

Memory analysis screenshot

Every setText() call implies a new StaticLayout creation and then computes the exact proportion of new layout taking into account the content. But every StaticLayout creation implies itself a lot of objects creation into memory, which is especially what we want to avoid.

In my case, I ridiculously use setText() as I have about ten layouts to update on every frame. Unfortunately, reducing all layouts to only one won’t change anything since that on high-frequency even one StaticLayout creation consume too much memory. It has to be done avoiding objects creation as much as possible.

One solution : Avoid using TextView

To avoid this, I made a new type of View which can react as a specialized layout, with precise dimensions (because content is always about the same height, and mostly always the same width) to which I modify its onDraw() event (the one called for drawing myself all text using Canvas.drawText()), goal in mind to bypass the basic behavior of TextView.

public class SensorView extends View {

    ...

    private final char[] buffer = new char[BUFFER_SIZE];
    private final Paint paint = new Paint();

    public SensorView(Context context) {
        super(context);
        this.initPaint();
    }

    public SensorView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // Get back some values from xml data
        this.padding = attrs.getAttributeIntValue("http://schemas.android.com/apk/res/android", "padding", this.padding);
        this.textSize = attrs.getAttributeIntValue("http://schemas.android.com/apk/res/android", "padding", this.textSize);
        this.initPaint();
    }

    public void initPaint() {

        // Setting the paint
        this.padding = getPixels(TypedValue.COMPLEX_UNIT_DIP, this.padding);
        this.textSize = getPixels(TypedValue.COMPLEX_UNIT_SP, this.textSize);

        this.paint.setAntiAlias(true);
        this.paint.setColor(Color.DKGRAY);
        this.paint.setTextSize(this.textSize);
    }

    @Override
    public void onDraw(Canvas canvas) {

        int start = 0, line = 0;

        // Display multi-lines content
        for (int i = 0; i < BUFFER_SIZE; i++)
        {
            if (buffer[i] == '\n' || buffer[i] == '\0') {
                canvas.drawText(buffer, start, i-start, this.padding, line++ * this.textSize * LINE_HEIGHT + this.padding, paint);
                start = i+1;
            }
        }
    }

    public void setText(StringBuilder source) {
        source.getChars(0, source.length(), buffer, 0);
    }

    private int getPixels(int unit, float size) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        return (int)TypedValue.applyDimension(unit, size, metrics);
    }
}

I call SensorView.postInvalidate() on every frame which invalidates my View and calls back onDraw(). For sure, I don’t take advantage of the many optimizations related to TextView, but as long as I’m sure that my content will never be the same, it makes sense that I’ll never make the most of any cache system. Here is the new memory state:

Memory state screenshot after correction
After correction, the memory is not going up anymore

Problem solved!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.