#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()
… странно, что раньше это не работало. Я передаю TextViewExerciseMeCountDownTimer(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();
}