데브팜
Android
[Android] Thread 관련 에러 (Only the original thread that created a view hierarchy can touch its views.)
퓨새 2017. 6. 1. 20:07
<Error Code>
android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
<해석>
Original Thread으로만 UI(view heirarchy: 화면 체계)를 변경시킬 수 있어.
<원인>
Main Thread 외의 새로 생성한 Thread를 이용하여 임의로 UI를 변경시키려고 했기 때문이에요.
<해결 방법>
Handler를 이용하여 Main Thread를 간접적으로 사용하면 해결할 수 있습니다.
저같은 경우 동적으로 일정한 시간마다 화면을 변환시켜야해서
Handler와 Timer 같이 이용한 코드를 작성했습니다.
final Handler handler = new Handler(){
public void handleMessage(Message msg){
// 원래 하려던 동작 (UI변경 작업 등)
}
};
Timer timer = new Timer(true);
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
Log.v(TAG,"timer run");
Message msg = handler.obtainMessage();
handler.sendMessage(msg);
}
@Override
public boolean cancel() {
Log.v(TAG,"timer cancel");
return super.cancel();
}
};
timer.schedule(timerTask, 0, 1000);
위의 코드를 보면 알 수 있듯,
Handler를 생성해서 원하는 동작을 선언해 놓고,
Timer를 카운트 할 때마다 handler를 작동시키는 것을 볼 수 있습니다!
좀 더 간결하게 표현하면
Handler handler = new Handler(){
public void handleMessage(Messaage msg){
// 내가 원하는 UI 변경 작업!!
}
}
.
.
.
코드 Run하는 중 UI 동작 변경하고 싶을 때
Message msg = handler.obtainMessage();
handler.sendMessage(msg);
해당 동작의 handler를 호출하면 됩니다!!
와웅!!!
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.