17

I've made a simple Android music player. I want to have a TextView that shows the current time in the song in minutes:seconds format. So the first thing I tried was to make the activity Runnable and put this in run():

int position = 0;
while (MPService.getMP() != null && position<MPService.duration) {
try {
    Thread.sleep(1000);
    position = MPService.getSongPosition();
} catch (InterruptedException e) {
    return;
}

// ... convert position to formatted minutes:seconds string ...

currentTime.setText(time); // currentTime = (TextView) findViewById(R.id.current_time);

But that fails because I can only touch a TextView in the thread where it was created. So then I tried using runOnUiThread(), but that doesn't work because then Thread.sleep(1000) is called repeatedly on the main thread, so the activity just hangs at a blank screen. So any ideas how I can solve this?


new code:

private int startTime = 0;
private Handler timeHandler = new Handler();
private Runnable updateTime = new Runnable() {
    public void run() {
        final int start = startTime;
        int millis = appService.getSongPosition() - start;
        int seconds = (int) ((millis / 1000) % 60);
        int minutes = (int) ((millis / 1000) / 60);
        Log.d("seconds",Integer.toString(seconds)); // no problem here
        if (seconds < 10) {
            // this is hit, yet the text never changes from the original value of 0:00
            currentTime.setText(String.format("%d:0%d",minutes,seconds));
        } else {
            currentTime.setText(String.format("%d:%d",minutes,seconds));
        }
        timeHandler.postAtTime(this,(((minutes*60)+seconds+1)*1000));
    }

};

private ServiceConnection onService = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
        IBinder rawBinder) {
      appService = ((MPService.LocalBinder)rawBinder).getService();

    // start playing the song, etc. 

    if (startTime == 0) {
        startTime = appService.getSongPosition();
        timeHandler.removeCallbacks(updateTime);
        timeHandler.postDelayed(updateTime,1000);
    }
}
herpderp
  • 15,819
  • 12
  • 42
  • 45

5 Answers5

13

what about this:

    int delay = 5000; // delay for 5 sec.
    int period = 1000; // repeat every sec.

    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask()
        {
            public void run()
            {
                //your code
            }
        }, delay, period);
JanOlMajti
  • 1,387
  • 4
  • 22
  • 34
  • You cant access UI from timer this way, it will be undefined. You need to use Handler or Extend TimerTask, so it would accept UI element as param. – Flash Thunder Jun 23 '14 at 18:18
10

Use a Timer for this (instead of a while loop with a Thread.Sleep in it). See this article for an example of how to use a timer to update a UI element periodically:

Updating the UI from a timer

Edit: updated way-back link, thanks to Arialdo: http://web.archive.org/web/20100126090836/http://developer.android.com/intl/zh-TW/resources/articles/timed-ui-updates.html

Edit 2: non way-back link, thanks to gatoatigrado: http://android-developers.blogspot.com/2007/11/stitch-in-time.html

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
MusiGenesis
  • 74,184
  • 40
  • 190
  • 334
  • developer.android.com is a great place to start for any Android questions. :) – MusiGenesis Mar 04 '11 at 00:40
  • I know I checked your answer as correct, but I've having trouble implementing this... I don't get any exceptions, it seems like it should be working correctly, but it silently fails to set the TextView's text. I've posted the new code up there if you want to take a look. – herpderp Mar 04 '11 at 06:15
  • 1
    I refered to this article and I tried to implement, but I used mHandler.postDelayed(mUpdateTimeTask, 1000); instead of mHandler.postAtTime(this, start + (((minutes * 60) + seconds + 5) * 1000)); I found this from another blog article (see below) which clearly gives a very nice solution. Especially, if you are a background service and want to regularly update your UI from this service using a timer-like functionality. https://www.websmithing.com/2011/02/01/how-to-update-the-ui-in-an-android-activity-using-data-from-a-background-service/ – Jim C Jan 23 '15 at 07:19
4

You have to use a handler to handle the interaction with the GUI. Specifically a thread cannot touch ANYTHING on the main thread. You do something in a thread and if you NEED something to be changed in your main thread, then you call a handler and do it there.

Specifically it would look something like this:

Thread t = new Thread(new Runnable(){ ... do stuff here

Handler.postMessage(); }

Then somewhere else in your code, you do

Handler h = new Handler(){

something something... modify ui element here }

Idea its like this, thread does something, notifies the handler, the handler then takes this message and does something like update a textview on the UI thread.

JoxTraex
  • 13,423
  • 6
  • 32
  • 45
  • Just to nitpick, it's not entirely true that a thread can't touch anything in the main thread - SeekBars can be touched. but thanks :) – herpderp Mar 04 '11 at 01:17
0

This is one more Timer example and I'm using this code in my project. https://stackoverflow.com/a/18028882/1265456

Community
  • 1
  • 1
NamPham
  • 233
  • 1
  • 5
  • 13
0

I think the below blog article clearly gives a very nice solution. Especially, if you are a background service and want to regularly update your UI from this service using a timer-like functionality. It really helped me, much more than the 2007 blog link posted by MusiGenesis above.

https://www.websmithing.com/2011/02/01/how-to-update-the-ui-in-an-android-activity-using-data-from-a-background-service/

Jim C
  • 1,785
  • 1
  • 13
  • 13