Using a thread in a custom view

3.2k views Asked by At
public class ThreadView extends View {

    Paint paint = new Paint();
    int count;
    Handler uiThread = new Handler();

    public ThreadView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint.setColor(Color.BLACK);
        paint.setTextSize(80);

        uiThread.post(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    count++;
                    invalidate();
                }
            }
        });
    }

    @Override
    public void onDraw(Canvas canvas) {
        canvas.drawText(count + "", 100, 100, paint);
    }
}

I saw this solution for threads in custom views, but it did not work. Even though my loop continues to call invalidate(), it does not continue to call onDraw() I also saw a solution where they implemented Runnable and overrode run() is this a better way to do it?

2

There are 2 answers

0
Michael Krause On

All Views have a handler already set up for them, so it is inefficient to create another one. Instead, you should make use of the View class' post(Runnable action) or postDelayed(Runnable action, long delayMillis) methods.

As far as what you are trying to accomplish, I am making the assumption that the code you provided is just to serve as an example. If you were really just trying to update some text, I'd say go with a TextView and call setText on it. The TextView takes care of invalidating itself when it knows its text has changed.

But back to your question. A lot depends on the rate at which you want to update the rendering of your count variable. You currently will run in an endless loop on the UI Thread. This most certainly will cause problems as you will spam the handler/event queue with invalidate calls while at the same time preventing anything else from running on the UI Thread.

Instead, I'd propose introducing a configurable timing delay, and a way to stop updating the view.

For example, you could set things up to invalidate your view once every 10th of a second and only have the counter update while your view is attached to the window like so (note that the Runnable is itself responsible for whether it should run again based on the value of the updateView flag):

public class ThreadView extends View {

    private class UpdateViewRunnable implements Runnable {
        public void run() {
            count++;
            invalidate();

            if (updateView) {
                postDelayed(this, DELAY_TIME_MILLIS);
            }
        }
    }

    private static final long DELAY_TIME_MILLIS = 100L;
    private boolean updateView = false;
    private UpdateViewRunnable updateViewRunnable = new UpdateViewRunnable();
    private Paint paint = new Paint();
    private int count;

    public ThreadView(Context context) {
        super(context);
        init(context, null);
    }

    public ThreadView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        paint.setColor(Color.BLACK);
        paint.setTextSize(80);
    }

    @Override
    public void onDraw(Canvas canvas) {
        canvas.drawText(count + "", 100, 100, paint);
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        updateView = true;
        postDelayed(updateViewRunnable, DELAY_TIME_MILLIS);
    }

    @Override
    public void onDetachedFromWindow() {
        updateView = false;
        super.onDetachedFromWindow();
    }
}
0
ssynhtn On

First of all, in the snippet you provided the handler is not needed at all, you can just use the View.post method since the constructor is already called in the UI thread.

Secondly, the reason invalidate does not cause the ui to update is because

1, the method View.invalide puts a task in the UI thread's queue and returns. Normally when UI thread gets its share of CPU time to execute, it will execute these tasks sequentially and then the view would be redrawn.

2, however, the Runnable you post is doing an infinite loop, in the UI thread, and the redraw task created by the invalidate calls are queueing up, but never gets dealt with, the app is most likely going to freeze.