Question Android "Seul le thread d'origine qui a créé une hiérarchie de vues peut toucher ses vues."


J'ai construit un simple lecteur de musique sous Android. La vue de chaque chanson contient un SeekBar, implémenté comme ceci:

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

Cela fonctionne bien. Maintenant, je veux un compte à rebours les secondes / minutes de la progression de la chanson. Alors j'ai mis un TextView dans la mise en page, obtenez-le avec findViewById() dans onCreate()et mettez cela dans run() après 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);

Mais cette dernière ligne me donne l'exception:

android.view.ViewRoot $ CalledFromWrongThreadException: Seul le thread d'origine qui a créé une hiérarchie de vues peut toucher ses vues.

Pourtant, je fais essentiellement la même chose ici que je fais avec le SeekBar - créer la vue dans onCreate, puis en le touchant run() - Et ça ne me donne pas cette plainte.


707
2018-03-02 00:07


origine


Réponses:


Vous devez déplacer la partie de la tâche d'arrière-plan qui met à jour l'interface utilisateur sur le thread principal. Il y a un simple code pour cela:

runOnUiThread(new Runnable() {

    @Override
    public void run() {

        // Stuff that updates the UI

    }
});

Documentation pour Activity.runOnUiThread.

Il suffit de l'imbriquer dans la méthode en cours d'exécution en arrière-plan, puis de copier le code qui implémente les mises à jour au milieu du bloc. Incluez seulement la plus petite quantité de code possible, sinon vous commencerez à vaincre le but du thread d'arrière-plan.


1504
2018-03-02 00:29



J'ai résolu cela en mettant runOnUiThread( new Runnable(){ .. à l'intérieur 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();

110
2018-01-01 21:50



Ma solution à ceci:

private void setText(final TextView text,final String value){
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            text.setText(value);
        }
    });
}

Appelez cette méthode sur un thread d'arrière-plan.


44
2017-08-25 14:34



Habituellement, toute action impliquant l'interface utilisateur doit être effectuée dans le thread principal ou de l'interface utilisateur, c'est-à-dire celui dans lequel onCreate() et la gestion des événements sont exécutés. Une façon d'être sûr de cela est d'utiliser runOnUiThread (), un autre utilise des gestionnaires.

ProgressBar.setProgress() a un mécanisme pour lequel il sera toujours exécuté sur le thread principal, c'est pourquoi cela a fonctionné.

Voir Filetage indolore.


22
2018-03-02 00:29



J'ai été dans cette situation, mais j'ai trouvé une solution avec l'objet Handler.

Dans mon cas, je veux mettre à jour un ProgressDialog avec le modèle d'observateur. Ma vue implémente observateur et remplace la méthode de mise à jour.

Ainsi, mon thread principal crée la vue et un autre thread appelle la méthode de mise à jour qui met à jour le ProgressDialop et ....:

Seul le thread d'origine qui a créé une hiérarchie de vues peut toucher   vues.

Il est possible de résoudre le problème avec l'objet Handler.

Ci-dessous, différentes parties de mon code:

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

Cette explication peut être trouvée sur cette page, et vous devez lire le "Example ProgressDialog avec un deuxième thread".


19
2018-04-07 12:03



Je vois que vous avez accepté la réponse de @ providence. Juste au cas où, vous pouvez également utiliser le gestionnaire aussi! D'abord, faites les champs int.

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

Ensuite, créez une instance de gestionnaire en tant que champ.

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

Faire une méthode.

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

Enfin, mettez cela à onCreate() méthode.

showHandler(true);

6
2018-05-20 06:55



J'ai eu un problème similaire, et ma solution est moche, mais cela fonctionne:

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
}

6
2017-09-14 14:17



Ceci lance explicitement une erreur. Il dit quel fil a créé une vue, seulement cela peut toucher ses points de vue. C'est parce que la vue créée est à l'intérieur de l'espace de ce thread. La création de vue (GUI) se produit dans le thread UI (principal). Ainsi, vous utilisez toujours le thread d'interface utilisateur pour accéder à ces méthodes.

Enter image description here

Dans l'image ci-dessus, la variable de progression se trouve dans l'espace du thread de l'interface utilisateur. Ainsi, seul le thread UI peut accéder à cette variable. Ici, vous accédez à la progression via le nouveau Thread (), et c'est pourquoi vous avez une erreur.


4
2017-08-05 02:04



Utilisez ce code, et pas besoin de runOnUiThread fonction:

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

3
2017-07-24 20:06