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);
        }
    });
}

백그라운드 스레드에서이 메소드를 호출하십시오.


답변

일반적으로 사용자 인터페이스와 관련된 모든 작업은 기본 또는 UI 스레드에서 수행해야합니다. 즉 onCreate(), 이벤트 처리가 실행됩니다. 이를 확인하는 한 가지 방법은 runOnUiThread ()를 사용하는 것입니다. 하고 다른 방법은 핸들러를 사용하는 것입니다.

ProgressBar.setProgress() 메인 스레드에서 항상 실행되는 메커니즘이 있으므로 작동합니다.

무통 스레딩을 참조하십시오 .


답변

나는이 상황에 있었지만 핸들러 객체로 해결책을 찾았습니다.

제 경우에는 관찰자 패턴 으로 ProgressDialog를 업데이트하고 싶습니다 . 내보기는 관찰자를 구현하고 업데이트 방법을 재정의합니다.

따라서 내 주요 스레드는 뷰를 만들고 다른 스레드는 ProgressDialop 및 ….을 업데이트하는 update 메서드를 호출합니다.

뷰 계층 구조를 만든 원래 스레드 만 해당 뷰를 만질 수 있습니다.

핸들러 오브젝트의 문제점을 해결할 수 있습니다.

아래 코드의 다른 부분 :

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”를 읽어야합니다.


답변

기본 UI 스레드를 방해하지 않고 핸들러를 사용하여보기를 삭제할 수 있습니다. 예제 코드는 다음과 같습니다

new Handler(Looper.getMainLooper()).post(new Runnable() {
                                                        @Override
                                                        public void run() {
                                                           //do stuff like remove view etc
                                                            adapter.remove(selecteditem);
                                                        }
                                                    });


답변

@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()방법에 넣으십시오 .

showHandler(true);