问题
I was trying to build some sort of sequential countdown. Meaning, that I build up a queue of "exercises", each one containing a specific duration, which is the countdown time. In a custom Countdown class, I pop these exercises off the queue and use the duration as countdown.
I want these countdowns to run one after another. For this I built a Countdown class, based on the code basis of the abstract class 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, "%02d:%02d:%03d",
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;
}
}
The important part is the sendMessage
part, where I send the time left on the countdown to a handler
of my MainActivity
, which then, should update a 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);
}
}
And finally updates the textView
in MainActivty
public void sayLog(String text) {
countdown.setText(text);
}
ExerciseMeCountDownTimer is called by
new ExerciseMeCountDownTimer(elist, cHandler);
on some onClick().
The problem is, that sometimes (actually most of the time) the textView is not updated properly. It stops updating on random times like 00:05:211 etc. Would anyone mind telling me why this is keeps happening? Maybe also adding a solution or at least some literature (maybe pointing out some sections) which I should read to understand the problem? I am also upen for alternative approaches, as I am new to this "handler", "threads" thing in android.
EDIT
- the textview was updating, but the textview was clickable. Whenever I clicked on the textview it stopped updating!
- as the accepted answer shows, I decided to use the direkt approach of updating the appropriate textview inside the
onTick()
method.
回答1:
Using Handler
and things in this situation is making it overly complicated.
CountDownTimer
s onTick()
and onFinish()
both run on the UI Thread
so updating TextViews
and other View
s from either method can be done easily just by passing a reference of the View
to the constructor of the class, as you are already doing. Then you simply update it in the method needed.
// could create a member variable for the TextView with your other member variables
...
mTV;
then in your constructor assign it // removed reference to Handler--you already have reference to TextView here public ExerciseMeCountDownTimer(ArrayList elist, Button startStopButton, TextView countdownText) { mTV = countdownText;
then update in whichever method is needed
public void onTick(long millisUntilFinished) {
String text = String.format(Locale.GERMANY, "%02d:%02d:%03d",
millisUntilFinished / 100000, millisUntilFinished / 1000,
millisUntilFinished % 1000);
mTV.setText(text); // set the text here
}
public void onFinish() {
if (!eq.isEmpty()) {
this.start();
}
来源:https://stackoverflow.com/questions/24251800/sequential-countdowntimer-with-handler-wont-update-textview-correctly