I've built a simple music player in Android. The view for each song contains a SeekBar, implemented like this:
public class Song extends Activity implements OnClickListener,Runnable {
private SeekBar progress;
private MediaPlayer mp;
// ...
private ServiceConnection onService = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder rawBinder) {
appService = ((MPService.LocalBinder)rawBinder).getService(); // service that handles the MediaPlayer
progress.setVisibility(SeekBar.VISIBLE);
progress.setProgress(0);
mp = appService.getMP();
appService.playSong(title);
progress.setMax(mp.getDuration());
new Thread(Song.this).start();
}
public void onServiceDisconnected(ComponentName classname) {
appService = null;
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.song);
// ...
progress = (SeekBar) findViewById(R.id.progress);
// ...
}
public void run() {
int pos = 0;
int total = mp.getDuration();
while (mp != null && pos<total) {
try {
Thread.sleep(1000);
pos = appService.getSongPosition();
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
progress.setProgress(pos);
}
}
This works fine. Now I want a timer counting the seconds/minutes of the progress of the song. So I put a TextView
in the layout, get it with findViewById()
in onCreate()
, and put this in run()
after progress.setProgress(pos)
:
String time = String.format("%d:%d",
TimeUnit.MILLISECONDS.toMinutes(pos),
TimeUnit.MILLISECONDS.toSeconds(pos),
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(
pos))
);
currentTime.setText(time); // currentTime = (TextView) findViewById(R.id.current_time);
But that last line gives me the exception:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Yet I'm doing basically the same thing here as I'm doing with the SeekBar
- creating the view in onCreate
, then touching it in run()
- and it doesn't give me this complaint.
I had a similar issue, and my solution is ugly, but it works:
For me the issue was that I was calling
onProgressUpdate()
explicitly from my code. This shouldn't be done. I calledpublishProgress()
instead and that resolved the error.In my case, I have
EditText
in Adaptor, and it's already in the UI thread. However, when this Activity loads, it's crashes with this error.My solution is I need to remove
<requestFocus />
out from EditText in XML.In my case, the caller calls too many times in short time will get this error, I simply put elpased time checking to do nothing if too short, e.g. ignore if function get called less than 0.5 second:
If you do not want to use
runOnUiThread
API, you can in fact implementAsynTask
for the operations that takes some seconds to complete. But in that case, also after processing your work indoinBackground()
, you need to return the finished view inonPostExecute()
. The Android implementation allows only main UI thread to interact with views.I've been in this situation, but I found a solution with the Handler Object.
In my case, I want to update a ProgressDialog with the observer pattern. My view implements observer and overrides the update method.
So, my main thread create the view and another thread call the update method that update the ProgressDialop and....:
It's possible to solve the problem with the Handler Object.
Below, different parts of my code:
This explanation can be found on this page, and you must read the "Example ProgressDialog with a second thread".