Android "뷰 계층 구조를 생성 한 원래 스레드 만 뷰를 터치 할 수 있습니다."
Android에서 간단한 음악 플레이어를 만들었습니다. 각 노래의보기에는 다음과 같이 구현 된 SeekBar가 포함됩니다.
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);
}
}
이것은 잘 작동합니다. 이제 노래 진행의 초 / 분을 계산하는 타이머를 원합니다. 나는를 넣어 그래서 TextView
레이아웃에, 그것을 얻을 수 findViewById()
있는 onCreate()
, 및이를 넣어 run()
후 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);
그러나 마지막 줄은 예외를 제공합니다.
android.view.ViewRoot $ CalledFromWrongThreadException : 뷰 계층 구조를 생성 한 원래 스레드 만 뷰를 터치 할 수 있습니다.
그러나 저는 기본적으로에서 SeekBar
뷰를 생성 onCreate
한 다음 터치 하는 것과 동일한 작업을 수행하고 run()
있습니다.이 작업은 저에게이 불만을주지 않습니다.
UI를 업데이트하는 백그라운드 작업 부분을 메인 스레드로 옮겨야합니다. 이를위한 간단한 코드가 있습니다.
runOnUiThread(new Runnable() {
@Override
public void run() {
// Stuff that updates the UI
}
});
에 대한 문서 Activity.runOnUiThread
.
백그라운드에서 실행중인 메서드 내부에이를 중첩 한 다음 블록 중간에 업데이트를 구현하는 코드를 복사하여 붙여 넣습니다. 가능한 적은 양의 코드 만 포함하십시오. 그렇지 않으면 백그라운드 스레드의 목적을 무너 뜨리기 시작합니다.
나는 runOnUiThread( new Runnable(){ ..
내부 에 넣어 이것을 해결했다 run()
.
thread = new Thread(){
@Override
public void run() {
try {
synchronized (this) {
wait(5000);
runOnUiThread(new Runnable() {
@Override
public void run() {
dbloadingInfo.setVisibility(View.VISIBLE);
bar.setVisibility(View.INVISIBLE);
loadingText.setVisibility(View.INVISIBLE);
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
}
Intent mainActivity = new Intent(getApplicationContext(),MainActivity.class);
startActivity(mainActivity);
};
};
thread.start();
이것에 대한 나의 해결책 :
private void setText(final TextView text,final String value){
runOnUiThread(new Runnable() {
@Override
public void run() {
text.setText(value);
}
});
}
백그라운드 스레드에서이 메서드를 호출합니다.
일반적으로 사용자 인터페이스와 관련된 모든 작업은 onCreate()
이벤트 처리가 실행 되는 기본 또는 UI 스레드에서 수행되어야합니다 . 이를 확인하는 한 가지 방법은 runOnUiThread () 를 사용하는 것이고 다른 하나는 핸들러를 사용하는 것입니다.
ProgressBar.setProgress()
항상 메인 스레드에서 실행되는 메커니즘이 있으므로 작동했습니다.
Painless Threading을 참조하십시오 .
이 상황에 처해 있었지만 Handler Object를 사용하여 해결책을 찾았습니다.
제 경우에는 관찰자 패턴 으로 ProgressDialog를 업데이트하고 싶습니다 . 내 뷰는 관찰자를 구현하고 업데이트 메서드를 재정의합니다.
따라서 내 주 스레드는 뷰를 만들고 다른 스레드는 ProgressDialop을 업데이트하는 업데이트 메서드를 호출합니다.
보기 계층 구조를 만든 원래 스레드 만 해당보기를 터치 할 수 있습니다.
Handler Object로 문제를 해결할 수 있습니다.
아래에서 내 코드의 다른 부분 :
public class ViewExecution extends Activity implements Observer{
static final int PROGRESS_DIALOG = 0;
ProgressDialog progressDialog;
int currentNumber;
public void onCreate(Bundle savedInstanceState) {
currentNumber = 0;
final Button launchPolicyButton = ((Button) this.findViewById(R.id.launchButton));
launchPolicyButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showDialog(PROGRESS_DIALOG);
}
});
}
@Override
protected Dialog onCreateDialog(int id) {
switch(id) {
case PROGRESS_DIALOG:
progressDialog = new ProgressDialog(this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("Loading");
progressDialog.setCancelable(true);
return progressDialog;
default:
return null;
}
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch(id) {
case PROGRESS_DIALOG:
progressDialog.setProgress(0);
}
}
// Define the Handler that receives messages from the thread and update the progress
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
int current = msg.arg1;
progressDialog.setProgress(current);
if (current >= 100){
removeDialog (PROGRESS_DIALOG);
}
}
};
// The method called by the observer (the second thread)
@Override
public void update(Observable obs, Object arg1) {
Message msg = handler.obtainMessage();
msg.arg1 = ++currentPluginNumber;
handler.sendMessage(msg);
}
}
이 설명은 이 페이지 에서 찾을 수 있으며 "두 번째 스레드가있는 예제 ProgressDialog"를 읽어야합니다.
@providence의 답변을 수락하셨습니다. 혹시라도 핸들러도 사용할 수 있습니다! 먼저 int 필드를 수행하십시오.
private static final int SHOW_LOG = 1;
private static final int HIDE_LOG = 0;
다음으로 핸들러 인스턴스를 필드로 만듭니다.
//TODO __________[ Handler ]__________
@SuppressLint("HandlerLeak")
protected Handler handler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
// Put code here...
// Set a switch statement to toggle it on or off.
switch(msg.what)
{
case SHOW_LOG:
{
ads.setVisibility(View.VISIBLE);
break;
}
case HIDE_LOG:
{
ads.setVisibility(View.GONE);
break;
}
}
}
};
방법을 만드십시오.
//TODO __________[ Callbacks ]__________
@Override
public void showHandler(boolean show)
{
handler.sendEmptyMessage(show ? SHOW_LOG : HIDE_LOG);
}
마지막으로 이것을 onCreate()
method에 넣으십시오 .
showHandler(true);
비슷한 문제가 있었고 내 솔루션은 추악하지만 작동합니다.
void showCode() {
hideRegisterMessage(); // Hides view
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
showRegisterMessage(); // Shows view
}
}, 3000); // After 3 seconds
}
메인 UI 스레드를 방해하지 않고 Handler를 사용하여 뷰를 삭제할 수 있습니다. 다음은 예제 코드입니다.
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//do stuff like remove view etc
adapter.remove(selecteditem);
}
});
내가 사용 Handler
과 함께 Looper.getMainLooper()
. 그것은 나를 위해 잘 작동했습니다.
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// Any UI task, example
textView.setText("your text");
}
};
handler.sendEmptyMessage(1);
이것은 명시 적으로 오류를 던지고 있습니다. 어떤 스레드가 뷰를 생성했는지, 그 뷰를 터치 할 수있는 것만을 말합니다. 생성 된 뷰가 해당 스레드의 공간 안에 있기 때문입니다. 뷰 생성 (GUI)은 UI (기본) 스레드에서 발생합니다. 따라서 항상 UI 스레드를 사용하여 이러한 메서드에 액세스합니다.
위 그림에서 progress 변수는 UI 스레드의 공간 안에 있습니다. 따라서 UI 스레드 만이 변수에 액세스 할 수 있습니다. 여기에서 new Thread ()를 통해 진행 상황에 액세스하고 있으므로 오류가 발생합니다.
이 코드를 사용하면 runOnUiThread
작동 할 필요가 없습니다 .
private Handler handler;
private Runnable handlerTask;
void StartTimer(){
handler = new Handler();
handlerTask = new Runnable()
{
@Override
public void run() {
// do something
textView.setText("some text");
handler.postDelayed(handlerTask, 1000);
}
};
handlerTask.run();
}
나는에서 UI 변경을 요구 할 때 내 일어난 doInBackground
에서 Asynctask
사용하는 대신 onPostExecute
.
UI를 다루는 것이 onPostExecute
내 문제를 해결했습니다.
AsyncTask를 사용 하는 경우 onPostExecute 메서드 에서 UI 업데이트
@Override
protected void onPostExecute(String s) {
// Update UI here
}
나는 비슷한 문제에 직면하고 있었고 위에서 언급 한 방법 중 어느 것도 나를 위해 일하지 않았습니다. 결국 이것은 나를 위해 트릭을했습니다.
Device.BeginInvokeOnMainThread(() =>
{
myMethod();
});
이것은 언급 된 예외의 스택 추적입니다.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6149)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:843)
at android.view.View.requestLayout(View.java:16474)
at android.view.View.requestLayout(View.java:16474)
at android.view.View.requestLayout(View.java:16474)
at android.view.View.requestLayout(View.java:16474)
at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:352)
at android.view.View.requestLayout(View.java:16474)
at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:352)
at android.view.View.setFlags(View.java:8938)
at android.view.View.setVisibility(View.java:6066)
그러니 가서 땅을 파면 알게 돼
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
Where mThread is initialize in constructor like below
mThread = Thread.currentThread();
All I mean to say that when we created particular view we created it on UI Thread and later try to modifying in a Worker Thread.
We can verify it via below code snippet
Thread.currentThread().getName()
when we inflate layout and later where you are getting exception.
If you do not want to use runOnUiThread
API, you can in fact implement AsynTask
for the operations that takes some seconds to complete. But in that case, also after processing your work in doinBackground()
, you need to return the finished view in onPostExecute()
. The Android implementation allows only main UI thread to interact with views.
I was working with a class that did not contain a reference to the context. So it was not possible for me to use runOnUIThread();
I used view.post();
and it was solved.
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
final int currentPosition = mediaPlayer.getCurrentPosition();
audioMessage.seekBar.setProgress(currentPosition / 1000);
audioMessage.tvPlayDuration.post(new Runnable() {
@Override
public void run() {
audioMessage.tvPlayDuration.setText(ChatDateTimeFormatter.getDuration(currentPosition));
}
});
}
}, 0, 1000);
For me the issue was that I was calling onProgressUpdate()
explicitly from my code. This shouldn't be done. I called publishProgress()
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.
If you simply want to invalidate (call repaint/redraw function) from your non UI Thread, use postInvalidate()
myView.postInvalidate();
This will post an invalidate request on the UI-thread.
For more information : what-does-postinvalidate-do
In my case, the caller calls too many times in short time will get this error, I simply put elapsed time checking to do nothing if too short, e.g. ignore if function get called less than 0.5 second:
private long mLastClickTime = 0;
public boolean foo() {
if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
return false;
}
mLastClickTime = SystemClock.elapsedRealtime();
//... do ui update
}
Kotlin에서 어려움을 겪는 사람들을 위해 다음과 같이 작동합니다.
lateinit var runnable: Runnable //global variable
runOnUiThread { //Lambda
runnable = Runnable {
//do something here
runDelayedHandler(5000)
}
}
runnable.run()
//you need to keep the handler outside the runnable body to work in kotlin
fun runDelayedHandler(timeToWait: Long) {
//Keep it running
val handler = Handler()
handler.postDelayed(runnable, timeToWait)
}
해결 :이 메서드를 doInBackround 클래스에 넣고 메시지를 전달하십시오.
public void setProgressText(final String progressText){
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// Any UI task, example
progressDialog.setMessage(progressText);
}
};
handler.sendEmptyMessage(1);
}
'development' 카테고리의 다른 글
Unix 도구로 JSON 구문 분석 (0) | 2020.09.29 |
---|---|
현재 작업 디렉토리의 변경 사항에서 git 패치를 만듭니다. (0) | 2020.09.29 |
노드 js에서 npm 모듈을 제거하는 방법은 무엇입니까? (0) | 2020.09.28 |
Bash 함수에 매개 변수 전달 (0) | 2020.09.28 |
Ruby에서`rescue Exception => e`가 왜 나쁜 스타일입니까? (0) | 2020.09.28 |