Xamarin Only the original thread that created a view hierarchy can touch its views

데브팜

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.


Toplist

Neuester Beitrag

Stichworte