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.

herpderp
fonte

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.

providência
fonte
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();
Günay Gültekin
fonte
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.

Angelo Angeles
fonte
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 .

bigstones
fonte
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".

Jonathan
fonte
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);
                                                        }
                                                    });
Bilal Mustafa
fonte
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);
David Dimalanta
fonte
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
}
Błażej
fonte
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);
Sankar Behera
fonte
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();
}
Hamid
fonte
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.

Uddhav Gautam
fonte
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.

Jonathan dos Santos
fonte
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...")
}
KenIchi
fonte
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);
Ifta
fonte
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

     }
Deepak Kataria
fonte
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 .

Hagbard
fonte
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.

Amit Yadav
fonte
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.

Sam
fonte
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

Nalin
fonte
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.

leitor de mentes
fonte
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.

Sruit A.Suk
fonte
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)
    }
Tarun Kumar
fonte
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);

    }
Kaushal Sachan
fonte
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
    }
Fruta
fonte
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();
Udara Kasun
fonte
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

canerkaseler
fonte
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)
}
Raheel Khan
fonte