последовательный таймер обратного отсчета с обработчиком не будет корректно обновлять TextView

#java #android #multithreading #countdowntimer #android-handler

#java #Android #многопоточность #таймер обратного отсчета #android-обработчик

Вопрос:

Я пытался создать какой-то последовательный обратный отсчет. Это означает, что я создаю очередь «упражнений», каждое из которых содержит определенную продолжительность, которая является временем обратного отсчета. В пользовательском классе обратного отсчета я удаляю эти упражнения из очереди и использую продолжительность в качестве обратного отсчета. Я хочу, чтобы эти обратные отсчеты выполнялись один за другим. Для этого я создал класс обратного отсчета, основанный на основе кода абстрактного класса CountDownTimer .

 import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Locale;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.widget.Button;
import android.widget.TextView;

    public class ExerciseMeCountDownTimer {

    private static final int MSG_COUNTDOWN = 100;
    private static final int MSG_FINISH = 99;
    private ArrayDeque<Exercise> eq;
    private long mMillisInFuture;
    private int mCountdownInterval;
    private String name;
    private long mStopTimeInFuture;
    CountdownHandler cHandler;

    public ExerciseMeCountDownTimer(ArrayList<Exercise> elist,
            Button startStopButton, TextView countdownText,
            CountdownHandler cHandler) {

        this.cHandler = cHandler;
        eq = new ArrayDeque<Exercise>(elist);
        this.start();
    }

    public final void cancel() {
        mHandler.removeMessages(MSG);
    }

    private synchronized final ExerciseMeCountDownTimer start() {
        if (!eq.isEmpty()) {
            Exercise e = eq.pop();
            this.mMillisInFuture = Long.parseLong(e.getDuration());
            this.mCountdownInterval = 30;
            this.name = e.getName();
        } else {
            onFinish();
            return this;
        }

        if (mMillisInFuture <= 0) {
            onFinish();
            return this;
        }
        mStopTimeInFuture = SystemClock.elapsedRealtime()   mMillisInFuture;
        mHandler.sendMessage(mHandler.obtainMessage(MSG));
        return this;
    }

    public void onTick(long millisUntilFinished) {
        Message msg = cHandler.obtainMessage(MSG_COUNTDOWN);
        Bundle data = new Bundle();
        String text = String.format(Locale.GERMANY, "d:d:d",
                millisUntilFinished / 100000, millisUntilFinished / 1000,
                millisUntilFinished % 1000);
        data.putString("countdown", text);
        msg.setData(data);
        cHandler.sendMessage(msg);

    }

    public void onFinish() {
        if (!eq.isEmpty()) {
            this.start();
        }

        Message msg = cHandler.obtainMessage(MSG_FINISH);
        Bundle data = new Bundle();
        String text = String.format(Locale.GERMANY, "00:00:000");
        data.putString("finish", text);
        msg.setData(data);
        cHandler.sendMessage(msg);
    }

    private static final int MSG = 1;

    // handles counting down
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {

            final long millisLeft = mStopTimeInFuture
                    - SystemClock.elapsedRealtime();

            if (millisLeft <= 0) {
                onFinish();
            } else if (millisLeft < mCountdownInterval) {
                // no tick, just delay until done
                sendMessageDelayed(obtainMessage(MSG), millisLeft);
            } else {
                long lastTickStart = SystemClock.elapsedRealtime();
                onTick(millisLeft);

                // take into account user's onTick taking time to
                // execute
                long delay = lastTickStart   mCountdownInterval
                        - SystemClock.elapsedRealtime();

                // special case: user's onTick took more than interval
                // to
                // complete, skip to next interval
                while (delay < 0)
                    delay  = mCountdownInterval;

                sendMessageDelayed(obtainMessage(MSG), delay);
            }
        }

    };

    @Override
    public String toString() {
        return this.name;
    }
}
  

Важной частью является та sendMessage часть, где я отправляю время, оставшееся на обратный отсчет, в handler мой MainActivity , который затем должен обновить textview.

 import android.os.Handler;
import android.os.Message;

class CountdownHandler extends Handler {
private static final int MSG_COUNTDOWN = 100;
private static final int MSG_FINISH = 99;
private MainActivity mActivity;

CountdownHandler(MainActivity activity) {
    this.mActivity = activity;
}

@Override
public void handleMessage(Message msg) {
    if (msg.what == MSG_COUNTDOWN) {
        String text = msg.getData().getString("countdown");
        this.mActivity.sayLog(text);
    }
    if (msg.what == MSG_FINISH) {
        String text = msg.getData().getString("finish");
        this.mActivity.sayLog(text);
    }

}
  

И, наконец textView , обновляет MainActivty

 public void sayLog(String text) {
    countdown.setText(text);
}
  

ExerciseMeCountDownTimer вызывается

 new ExerciseMeCountDownTimer(elist, cHandler); 
  

при некоторых onClick() .

Проблема в том, что иногда (на самом деле большую часть времени) TextView не обновляется должным образом. Он перестает обновляться в случайные моменты времени, такие как 00: 05: 211 и т.д. Кто-нибудь может сказать мне, почему это продолжает происходить? Может быть, также добавить решение или хотя бы какую-то литературу (возможно, указав некоторые разделы), которые я должен прочитать, чтобы понять проблему? Я также ищу альтернативные подходы, поскольку я новичок в этом «обработчике», «потоках» в Android.

Редактировать

  • текстовое представление обновлялось, но текстовое представление было интерактивным. Всякий раз, когда я нажимал на textview, он перестал обновляться!
  • как показывает принятый ответ, я решил использовать подход directt для обновления соответствующего textview внутри onTick() метода.

Комментарии:

1. Вы знаете, что onTick() это выполняется в UI потоке, верно? Есть ли причина, по которой вы не можете обновить его там без Handler

2. да, я это знаю. Я уже пытался обновить там textview, но Android это не очень понравилось, жалобы на иерархию представлений могут быть изменены только действием, создавшим textview. Но с тех пор я многое изменил, позвольте мне попробовать еще раз.

3. Убедитесь, что вы вызываете ExerciseMeCountDownTimer из основного потока

4. хорошо, вы правы, я действительно могу использовать onTick() … странно, что раньше это не работало. Я передаю TextView ExerciseMeCountDownTimer(elist, countdownTextview, cHandler) проблеме, что зависание TextView все еще существует. Это происходит, когда я нажимаю (мышью, в эмуляторе) на textview…

5. Ну, я это понимаю, но почему вы хотите, чтобы он был интерактивным, если он обновляется чем-то другим. Сделайте его недоступным для просмотра

Ответ №1:

Использование Handler и прочее в этой ситуации делает его чрезмерно сложным.

CountDownTimer s onTick() и onFinish() оба запускаются при UI Thread обновлении so TextViews , а другие View s из любого метода можно легко выполнить, просто передав ссылку View на конструктор класса, как вы уже делаете. Затем вы просто обновляете его в необходимом методе.

 // could create a member variable for the TextView with your other member variables
...
mTV;
  

затем в вашем конструкторе назначьте ему
// удаленную ссылку на обработчик — у вас уже есть ссылка на TextView здесь
public ExerciseMeCountDownTimer(ArrayList elist,
Кнопка startStopButton, TextView countdownText) {
mTV = countdownText;

затем обновите в зависимости от того, какой метод необходим

 public void onTick(long millisUntilFinished) {

    String text = String.format(Locale.GERMANY, "d:d:d",
            millisUntilFinished / 100000, millisUntilFinished / 1000,
            millisUntilFinished % 1000);
    mTV.setText(text);  // set the text here


}

public void onFinish() {
    if (!eq.isEmpty()) {
        this.start();
    }