Использование потока в пользовательском представлении

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);
    }
}

Я видел это решение для потоков в пользовательских представлениях, но оно не сработало. Несмотря на то, что мой цикл продолжает вызывать invalidate(), он не продолжает вызывать onDraw(). Я также видел решение, в котором они реализовали Runnable и переопределили run(). Это лучший способ сделать это?


person Derek Dawson    schedule 10.06.2015    source источник
comment
Возможно, потому что метод onDraw не вызывается внутри вашего потока. Вы только увеличиваете переменную count. Может быть, вы можете попробовать вызвать метод drawText из вашего Runnable.   -  person Thyen Hong Guedes Chang    schedule 11.06.2015
comment
@ThyenHongGuedesChang: он вызывает invalidate(), который по существу помечает всю клиентскую область представления как требующую перерисовки. Рисование происходит асинхронно с этим. Настоящая проблема заключается в бесконечном цикле, работающем в потоке пользовательского интерфейса. Это лишает пользовательский интерфейс возможности что-либо делать.   -  person Michael Krause    schedule 11.06.2015


Ответы (2)


Для всех представлений уже настроен обработчик, поэтому создавать еще один неэффективно. Вместо этого вы должны использовать методы post(Runnable action) или postDelayed(Runnable action, long delayMillis) класса View.

Что касается того, чего вы пытаетесь достичь, я предполагаю, что предоставленный вами код служит просто примером. Если бы вы действительно просто пытались обновить какой-то текст, я бы сказал, используйте TextView и вызовите для него setText. TextView позаботится о том, чтобы аннулировать себя, когда узнает, что его текст изменился.

Но вернемся к вашему вопросу. Многое зависит от скорости, с которой вы хотите обновить рендеринг вашей переменной count. В настоящее время вы будете работать в бесконечном цикле в потоке пользовательского интерфейса. Это, безусловно, вызовет проблемы, поскольку вы будете спамить очередь обработчиков/событий недействительными вызовами, в то же время предотвращая запуск чего-либо еще в потоке пользовательского интерфейса.

Вместо этого я бы предложил ввести настраиваемую временную задержку и способ остановить обновление представления.

Например, вы можете настроить так, чтобы ваше представление аннулировалось раз в 10 секунд, и счетчик обновлялся только тогда, когда ваше представление было прикреплено к окну (обратите внимание, что Runnable сам отвечает за то, следует ли запускать его снова на основе значение флага updateView):

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();
    }
}
person Michael Krause    schedule 10.06.2015

Прежде всего, в предоставленном вами фрагменте обработчик вообще не нужен, вы можете просто использовать метод View.post, поскольку конструктор уже вызывается в потоке пользовательского интерфейса.

Во-вторых, причина, по которой аннулирование не приводит к обновлению пользовательского интерфейса, заключается в том, что

1 метод View.invalide помещает задачу в очередь потока пользовательского интерфейса и возвращает результат. Обычно, когда поток пользовательского интерфейса получает свою долю процессорного времени для выполнения, он будет выполнять эти задачи последовательно, а затем представление будет перерисовано.

2, однако Runnable, который вы публикуете, выполняет бесконечный цикл в потоке пользовательского интерфейса, а задача перерисовки, созданная недействительными вызовами, ставится в очередь, но никогда не обрабатывается, приложение, скорее всего, зависнет.

person ssynhtn    schedule 30.04.2017