1208 Stimmen

Android "Nur der ursprüngliche Thread, der eine Ansichtshierarchie erstellt hat, kann seine Ansichten berühren."

Ich habe einen einfachen Musik-Player in Android gebaut. Die Ansicht für jeden Song enthält eine SeekBar, wie folgt implementiert:

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

Das funktioniert gut. Jetzt möchte ich einen Timer, der die Sekunden/Minuten des Songfortschritts zählt. Also habe ich eine TextView im Layout, erhalten Sie es mit findViewById() en onCreate() und setzen Sie dies ein run() nach 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);

Aber diese letzte Zeile ist die Ausnahme:

Android.view.ViewRoot$CalledFromWrongThreadException: Nur der ursprüngliche Thread, der eine View-Hierarchie erstellt hat, kann seine Views berühren.

Dennoch tue ich hier im Grunde das Gleiche wie bei der SeekBar - die Erstellung der Ansicht in onCreate und berührt sie dann in run() - und ich bekomme diese Beschwerde nicht.

28voto

DevPolarBear Punkte 4036

Kotlin Antwort

Wir müssen UI Thread für die Arbeit mit wahrer Weise zu verwenden. Wir können UI Thread in Kotlin verwenden, wie zum Beispiel:

runOnUiThread(Runnable {
   //TODO: Your job is here..!
})

27voto

Bilal Mustafa Punkte 700

Sie können Handler verwenden, um die Ansicht zu löschen, ohne den Haupt-UI-Thread zu stören. Hier ein Beispielcode

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

22voto

Jonathan Punkte 454

Ich war auch schon in dieser Situation, aber ich habe eine Lösung mit dem Handler-Objekt gefunden.

In meinem Fall möchte ich einen ProgressDialog mit dem Beobachtungsmuster . Meine Ansicht implementiert Observer und überschreibt die Update-Methode.

Also, mein Hauptthread erstellt die Ansicht und ein anderer Thread ruft die Aktualisierungsmethode auf, die das ProgressDialop und .... aktualisiert:

Nur der ursprüngliche Thread, der eine Ansichtshierarchie erstellt hat, kann ihre Ansichten berühren.

Es ist möglich, das Problem mit dem Handler-Objekt zu lösen.

Nachfolgend finden Sie verschiedene Teile meines Codes:

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

Diese Erklärung ist zu finden unter diese Seite und Sie müssen das "Beispiel ProgressDialog mit einem zweiten Thread" lesen.

8voto

David Dimalanta Punkte 538

Ich sehe, dass Sie die Antwort von @providence akzeptiert haben. Nur für den Fall, dass Sie den Handler auch verwenden können! Machen Sie zuerst die int-Felder.

    private static final int SHOW_LOG = 1;
    private static final int HIDE_LOG = 0;

Als nächstes erstellen Sie eine Handler-Instanz als Feld.

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

Erstellen Sie eine Methode.

//TODO __________[ Callbacks ]__________
@Override
public void showHandler(boolean show)
{
    handler.sendEmptyMessage(show ? SHOW_LOG : HIDE_LOG);
}

Setzen Sie dies schließlich unter onCreate() Methode.

showHandler(true);

8voto

Hamid Punkte 1364

Verwenden Sie diesen Code, und Sie müssen nicht runOnUiThread Funktion:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X