Android "Apenas o segmento original que criou uma hierarquia de visualizações pode tocar suas visualizações".


940

Criei um player de música simples no Android. A visualização de cada música contém uma SeekBar, implementada assim:

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

Isso funciona bem. Agora eu quero um cronômetro contando os segundos / minutos do andamento da música. Então eu coloquei um TextViewno layout, obtê-lo com findViewById()no onCreate(), e colocar isso em run()depois 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);

Mas essa última linha me dá a exceção:

android.view.ViewRoot $ CalledFromWrongThreadException: Somente o encadeamento original que criou uma hierarquia de exibição pode tocar em suas exibições.

No entanto, estou fazendo basicamente a mesma coisa que estou fazendo com SeekBar- criando a visualização onCreatee depois tocando-a run()- e isso não me causa essa reclamação.

Respostas:


1895

Você precisa mover a parte da tarefa em segundo plano que atualiza a interface do usuário para o thread principal. Existe um trecho simples de código para isso:

runOnUiThread(new Runnable() {

    @Override
    public void run() {

        // Stuff that updates the UI

    }
});

Documentação para Activity.runOnUiThread.

Apenas aninhe isso dentro do método que está sendo executado em segundo plano e copie e cole o código que implementa as atualizações no meio do bloco. Inclua apenas a menor quantidade de código possível; caso contrário, você começará a anular o objetivo do encadeamento em segundo plano.


5
funcionou como um encanto. para mim o único problema aqui é que eu queria fazer um error.setText(res.toString());dentro do método run (), mas eu não poderia usar os res porque não era definitiva .. muito ruim
— noloman

64
Um breve comentário sobre isso. Eu tinha um thread separado que tentava modificar a interface do usuário e o código acima funcionou, mas chamei runOnUiThread do objeto Activity. Eu tive que fazer algo como myActivityObject.runOnUiThread(etc)
— Kirby

1
@ Kirby Obrigado por esta referência. Você pode simplesmente fazer 'MainActivity.this' e deve funcionar também, assim você não precisa manter a referência à sua classe de atividade.
— JRomero #

24
Demorei um pouco para descobrir que runOnUiThread()é um método de Atividade. Eu estava executando meu código em um fragmento. Acabei fazendo getActivity().runOnUiThread(etc)e funcionou. Fantástico!;
— precisa saber é

Podemos parar a tarefa que está sendo gravada no corpo do método 'runOnUiThread'?
— Karan Sharma

143

Eu resolvi isso colocando runOnUiThread( new Runnable(){ ..dentro 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();

2
Este balançou. Obrigado por informações que, isso também pode ser usado dentro de qualquer outro segmento.
— Nabin

Obrigado, é realmente triste criar um thread para voltar ao UI Thread, mas apenas essa solução salvou meu caso.
— Pierre Maoui 4/15

2
Um aspecto importante é que wait(5000);não está dentro do Runnable; caso contrário, sua interface do usuário congelará durante o período de espera. Você deve considerar o uso AsyncTaskdo Thread em vez de operações como estas.
— Martin Martin

isso é tão ruim para vazamento de memória
— Rafael Lima

Por que se preocupar com o bloco sincronizado? O código dentro dele parece razoavelmente seguro para threads (embora eu esteja totalmente preparado para comer minhas palavras).
— David

69

Minha solução para isso:

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

Chame esse método em um encadeamento em segundo plano.


Erro: erro (73, 67): o conjunto de métodos não estáticos (String) não pode ser referenciado de um contexto estático

1
Eu tenho o mesmo problema com minhas aulas de teste. Isso funcionou como um encanto para mim. No entanto, substituindo runOnUiThreadpor runTestOnUiThread. Graças
— DaddyMoe

28

Geralmente, qualquer ação que envolva a interface do usuário deve ser realizada no encadeamento principal ou da interface do usuário, que é aquele em que a onCreate()manipulação de eventos é executada. Uma maneira de ter certeza disso é usando runOnUiThread () , outra é usar Handlers.

ProgressBar.setProgress() possui um mecanismo para o qual sempre será executado no encadeamento principal, e é por isso que funcionou.

Consulte Rosqueamento indolor .


O artigo Painless Threading nesse link agora é 404. Aqui está o link para um artigo (mais antigo?) Sobre Painless Threading - android-developers.blogspot.com/2009/05/painless-threading.html
— Tony Adams

20

Estive nessa situação, mas encontrei uma solução com o objeto manipulador.

No meu caso, quero atualizar um ProgressDialog com o padrão observador . Minha visão implementa o observador e substitui o método de atualização.

Portanto, meu thread principal cria a view e outro thread chama o método update que atualiza o ProgressDialop e ....:

Somente o encadeamento original que criou uma hierarquia de visualizações pode tocar suas visualizações.

É possível resolver o problema com o objeto manipulador.

Abaixo, diferentes partes do meu código:

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

Esta explicação pode ser encontrada nesta página e você deve ler o "Exemplo ProgressDialog com um segundo thread".


10

Você pode usar o manipulador para excluir a exibição sem perturbar o principal thread da interface do usuário. Aqui está o código de exemplo

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

7

Vejo que você aceitou a resposta da providência. Apenas no caso, você também pode usar o manipulador! Primeiro, faça os campos int.

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

Em seguida, crie uma instância de manipulador como um campo.

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

Faça um método.

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

Por fim, coloque isso no onCreate()método

showHandler(true);

7

Eu tive um problema semelhante e minha solução é feia, mas funciona:

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
}

2
@ R.jzadeh, é bom ouvir isso. Desde o momento em que escrevi essa resposta, provavelmente agora você pode fazê-lo melhor :) #
— 001 Błażej

6

Eu uso Handlercom Looper.getMainLooper(). Funcionou bem para mim.

    Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
              // Any UI task, example
              textView.setText("your text");
        }
    };
    handler.sendEmptyMessage(1);

5

Use este código e não há necessidade de runOnUiThreadfuncionar:

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

5

Isso está explicitamente gerando um erro. Ele diz que qualquer segmento que criou uma visualização, apenas que pode tocar em suas visualizações. Isso ocorre porque a visualização criada está dentro do espaço desse segmento. A criação da visualização (GUI) ocorre no encadeamento da interface do usuário (principal). Portanto, você sempre usa o thread da interface do usuário para acessar esses métodos.

Digite a descrição da imagem aqui

Na imagem acima, a variável progress está dentro do espaço do thread da interface do usuário. Portanto, apenas o thread da interface do usuário pode acessar essa variável. Aqui, você está acessando o progresso por meio do novo Thread () e é por isso que ocorreu um erro.


4

Isso aconteceu quando pedi uma alteração na interface do usuário de um doInBackgroundde em Asynctaskvez de usar onPostExecute.

Lidar com a interface do usuário onPostExecuteresolveu meu problema.


1
Obrigado Jonathan. Esse também era meu problema, mas tive que ler um pouco mais para entender o que você quis dizer aqui. Para qualquer outra pessoa, onPostExecutetambém é um método de, AsyncTaskmas é executado no thread da interface do usuário. Veja aqui: blog.teamtreehouse.com/all-about-android-asynctasks
— ciaranodc:

4

As corotinas da Kotlin podem tornar seu código mais conciso e legível assim:

MainScope().launch {
    withContext(Dispatchers.Default) {
        //TODO("Background processing...")
    }
    TODO("Update UI here!")
}

Ou vice-versa:

GlobalScope.launch {
    //TODO("Background processing...")
    withContext(Dispatchers.Main) {
        // TODO("Update UI here!")
    }
    TODO("Continue background processing...")
}

3

Eu estava trabalhando com uma classe que não continha uma referência ao contexto. Portanto, não foi possível usar o que runOnUIThread();eu usei view.post();e foi resolvido.

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

Qual é a analogia audioMessagee tvPlayDurationo código das perguntas?
— gotwo 29/04

audioMessageé um objeto titular da exibição de texto. tvPlayDurationé a exibição de texto que queremos atualizar a partir de um thread que não seja da interface do usuário. Na pergunta acima, currentTimeé a exibição de texto, mas ela não possui um objeto titular.
— Ifta

3

Ao usar o AsyncTask, atualize a interface do usuário no método onPostExecute

    @Override
    protected void onPostExecute(String s) {
   // Update UI here

     }

isso aconteceu comigo. Eu estava atualizando a interface do usuário no doinbackground da tarefa assíncrona.
— #

3

Eu estava enfrentando um problema semelhante e nenhum dos métodos mencionados acima funcionou para mim. No final, isso fez o truque para mim:

Device.BeginInvokeOnMainThread(() =>
    {
        myMethod();
    });

Encontrei esta jóia aqui .


2

Este é o rastreamento de pilha da exceção mencionada

        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)

Então, se você vai cavar, então você vem a saber

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

Onde mThread é inicializado no construtor como abaixo

mThread = Thread.currentThread();

Tudo o que quero dizer é que, quando criamos uma visualização específica, a criamos no UI Thread e, posteriormente, tentamos modificar em um Worker Thread.

Podemos verificá-lo através do snippet de código abaixo

Thread.currentThread().getName()

quando inflamos o layout e mais tarde onde você está recebendo exceção.


2

Se você não deseja usar a runOnUiThreadAPI, é possível implementar AsynTaskas operações que levam alguns segundos para serem concluídas. Mas, nesse caso, também após o processamento do seu trabalho doinBackground(), é necessário retornar a exibição concluída onPostExecute(). A implementação do Android permite que apenas o principal thread da interface do usuário interaja com as visualizações.


2

Se você simplesmente deseja invalidar (função de repintar / redesenhar chamadas) do seu Thread não UI, use postInvalidate ()

myView.postInvalidate();

Isso postará uma solicitação inválida no thread da interface do usuário.

Para mais informações: what-does-postinvalidate-do


1

Para mim, o problema era que eu estava ligando onProgressUpdate()explicitamente do meu código. Isso não deve ser feito. Eu liguei publishProgress()e isso resolveu o erro.


1

No meu caso, eu tenho EditText no Adapter, e ele já está no thread da interface do usuário. No entanto, quando esta atividade é carregada, ela falha com esse erro.

Minha solução é que preciso remover <requestFocus />o EditText em XML.


1

Para as pessoas que lutam em Kotlin, funciona assim:

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

0

Resolvido: basta colocar esse método na classe doInBackround ... e passar a mensagem

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

    }

0

No meu caso, o chamador muitas vezes em pouco tempo receberá esse erro, basta colocar a verificação do tempo decorrido para não fazer nada se for muito curto, por exemplo, ignore se a função for chamada em menos de 0,5 segundo:

    private long mLastClickTime = 0;

    public boolean foo() {
        if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
            return false;
        }
        mLastClickTime = SystemClock.elapsedRealtime();

        //... do ui update
    }

Uma solução melhor seria desativar o botão ao clicar e ativá-lo novamente quando a ação for concluída.
— lsrom 12/02

@lsrom No meu caso, não é tão simples, porque o chamador é uma biblioteca de terceiros interna e está fora de meu controle.
— Fruit

0

Se você não conseguiu encontrar um UIThread, pode usar desta maneira.

se seu contexto atual significa, você precisa analisar o Contexto Atual

 new Thread(new Runnable() {
        public void run() {
            while (true) {
                (Activity) yourcurrentcontext).runOnUiThread(new Runnable() {
                    public void run() { 
                        Log.d("Thread Log","I am from UI Thread");
                    }
                });
                try {
                    Thread.sleep(1000);
                } catch (Exception ex) {

                }
            }
        }
    }).start();

0

Resposta Kotlin

Temos que usar o UI Thread para o trabalho da maneira correta. Podemos usar o Thread da interface do usuário no Kotlin:

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

@canerkaseler


0

No Kotlin, basta colocar seu código no método de atividade runOnUiThread

runOnUiThread{
    // write your code here, for example
    val task = Runnable {
            Handler().postDelayed({
                var smzHtcList = mDb?.smzHtcReferralDao()?.getAll()
                tv_showSmzHtcList.text = smzHtcList.toString()
            }, 10)

        }
    mDbWorkerThread.postTask(task)
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.