Count for 45 seconds, pause for 20 then repeat wit

2019-02-15 21:26发布

问题:

In trying to limit battery usage I need to not have busy loops, so I am not certain how to solve this problem.

If I have a program that will allow someone to sing for 45 seconds, then they pause for 20 seconds to get a drink, then repeat, for some number of songs.

My timer is at Problem getting timer to work after cancelling one iteration and starting another, but in order for this to work, when someone clicks on a button it starts the first countdown, but the times were queued up, and the title were displayed, then when the time is done it will show Rest, then after 20 seconds it will show a new title.

So use sendMessageDelayed is a problem, though it works for the first part.

Ideally I would like to have the Runnable return when the countdown is reached, to some controller function that would then use an if statement.

void Controller(int curCount) {
  if(curCount % 2 == 0) {
     // call thread that uses runnable with next song time limit and title
  } else {
     // call thread with 20 and "REST"
  }
}

But, this would only work if

Thread t = new Thread(runnable);
... // Go to controller, which will count down
t.join();

blocked until done. I could do t.wait(time * 1000); instead of t.join(), but that may be a bit late or early as the timer won't be perfect at keeping time, so I don't think it is correct.

So what is the best way to do something like this, without needless polling or busy loops.

for(SongModel m : songList) {
  countdownTime(m.getSongTime(), m.getTitle);
  countdownTime(10, "REST");
}

Edit:

I think I was unclear about the problem with my current timer. Since the messages are queued up it will change to Rest while still counting down the time left for the first song, since the Runnable finishes much more quickly than the Handler is processing. So, there is a flaw in this approach that leads me to think for this design using a sendMessageDelayed won't work as I had hoped, though, if I just need to count down once/button click it will be fine, but my needs are to have this all happen with one button click, it starts the process, and then should simulate the for loop at the end of my question.

回答1:

Here we are again :)

I wrote this and tested it in Eclipse with an Eclair emulator, and running fine. One click counts from 54 to 0, then from 20 to 0, then 45 to 0, etc., until another click cancels it. Let me know if it's up to your needs. Of course the UI is just the bare minimum:

TimerTest.java

package com.aleadam.timertest;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class TimerTest extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Button button = (Button) findViewById(R.id.btn);
        button.setOnClickListener(new MyListener ());
    }

    private class MyListener implements OnClickListener {
        private long mStartTime = 0L;
        private boolean started = false;
        private boolean singing = true;
        private final Handler mHandler = new Handler();
        private Runnable mUpdateTimeTask;
        TextView label, timer;

        public MyListener() {
            super();
            label = (TextView) findViewById (R.id.lbl);
            timer = (TextView) findViewById (R.id.timer);
        }
        public void onClick(View v) {
            if (!started) {
                started = true;
                ((Button) v).setText(R.string.button_label_stop);
                mUpdateTimeTask = new Runnable() {
                    public void run() {
                        long millis = SystemClock.elapsedRealtime() - mStartTime;
                        if (singing && millis >= 45000L) {
                            label.setText(R.string.text_label_rest);
                            mStartTime = SystemClock.uptimeMillis();
                            timer.setText("20");
                            singing = false;
                        } else if (!singing && millis >= 20000L) {
                            label.setText(R.string.text_label_sing);
                            mStartTime = SystemClock.uptimeMillis();
                            timer.setText("45");
                            singing = true;
                        } else {
                            if (singing)
                                timer.setText(Long.toString(45-millis/1000));
                            else
                                timer.setText(Long.toString(20-millis/1000));
                        }
                        mHandler.postDelayed (mUpdateTimeTask, 1000);
                    }
                };    
                singing = true;
                mStartTime = SystemClock.uptimeMillis();
                label.setText(R.string.text_label_sing);
                timer.setText("45");
                mHandler.removeCallbacks(mUpdateTimeTask);
                mHandler.postDelayed(mUpdateTimeTask, 1000);
            } else {
                started = false;
                mHandler.removeCallbacks(mUpdateTimeTask);
                label.setText("");
                timer.setText("");
                ((Button) v).setText(R.string.button_label_start);
            }
        }
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/title_label"
    />
<Button
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/button_label_start"
    android:id="@+id/btn"
    />
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:id="@+id/lbl"
    />
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:id="@+id/timer"
    android:textSize="24dp"
    android:textStyle="bold" 
    android:typeface="monospace"/>
</LinearLayout>

strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="title_label">Timer test - 45 + 20 sec</string>
    <string name="app_name">Timer Test</string>
    <string name="button_label_start">Click to start</string>
    <string name="text_label_sing">Sing for 45 seconds</string>
    <string name="text_label_rest">20 seconds to drink!</string>
    <string name="button_label_stop">Click to cancel</string>
</resources>


回答2:

I would do this with a Timer and a pair of TimerTasks.

Timer scheduler = new Timer("Song Scheduler");
TimerTask restTask = new TimerTask() {
  public void run() {
    // call rest code
    scheduler.schedule(songTask, 20*1000);      
  }
};
TimerTask songTask = new TimerTask() {
  public void run() {
    // call song code
    scheduler.schedule(restTask, 45*1000);      
  }
};

The important thing is that I don't schedule the next timer until after the last one expires. You could adjust the times dynamically if you want the songTask to last for the length of the current song (as your code suggests) or 45 seconds (as your prose suggests). Either way, the basics are the same.



标签: android timer