Android "Solo el hilo original que creó una jerarquía de vistas puede tocar sus vistas".

Construí un reproductor de música simple en Android. La vista de cada canción contiene un SeekBar, implementado así:

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

Esto funciona bien. Ahora quiero un temporizador que cuente los segundos / minutos del progreso de la canción. Así que puse un TextView en el diseño, consígalo con findViewById() in onCreate()y poner esto en run() después de 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);

Pero esa última línea me da la excepción:

android.view.ViewRoot $ CalledFromWrongThreadException: solo el hilo original que creó una jerarquía de vistas puede tocar sus vistas.

Sin embargo, estoy haciendo básicamente lo mismo aquí que con el SeekBar - creando la vista en onCreate, luego tocándolo en run() - y no me da esta queja.

preguntado el 01 de marzo de 11 a las 21:03

30 Respuestas

Tienes que mover la parte de la tarea en segundo plano que actualiza la interfaz de usuario al hilo principal. Hay un código simple para esto:

runOnUiThread(new Runnable() {

    @Override
    public void run() {

        // Stuff that updates the UI

    }
});

Documentación para Activity.runOnUiThread.

Simplemente anida esto dentro del método que se está ejecutando en segundo plano y luego copie y pegue el código que implementa las actualizaciones en el medio del bloque. Incluya solo la menor cantidad de código posible; de ​​lo contrario, comenzará a anular el propósito del hilo en segundo plano.

Respondido 22 Abr '18, 01:04

trabajado como un encanto. para mí, el único problema aquí es que quería hacer un error.setText(res.toString()); dentro del método run (), pero no pude usar la resolución porque no era final ... lástima - noloman

Un breve comentario sobre esto. Tenía un hilo separado que intentaba modificar la interfaz de usuario, y el código anterior funcionó, pero llamé a runOnUiThread desde el objeto Activity. Tuve que hacer algo como myActivityObject.runOnUiThread(etc) - Kirby

@Kirby Gracias por esta referencia. Simplemente puede hacer 'MainActivity.this' y debería funcionar también para que no tenga que mantener la referencia a su clase de actividad. - JRomero

Me tomó un tiempo darme cuenta de que runOnUiThread() es un método de actividad. Estaba ejecutando mi código en un fragmento. Terminé haciendo getActivity().runOnUiThread(etc) Y funcionó. ¡Fantástico!; - lejonl

¿Podemos detener la realización de la tarea que está escrita en el cuerpo del método 'runOnUiThread'? - Karan Sharma

Resolví esto poniendo 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();

Respondido 17 ago 16, 14:08

Este meció. Gracias por la información que, esto también se puede utilizar dentro de cualquier otro hilo. - nabín

Gracias, es muy triste crear un hilo para volver al hilo de la interfaz de usuario, pero solo esta solución salvó mi caso. - pierre maoui

Un aspecto importante es que wait(5000); no está dentro de Runnable, de lo contrario, su IU se congelará durante el período de espera. Deberías considerar usar AsyncTask en lugar de Thread para operaciones como estas. - Martin

esto es tan malo para la fuga de memoria - Rafael lima

¿Por qué molestarse con el bloque sincronizado? El código que contiene parece razonablemente seguro para subprocesos (aunque estoy completamente preparado para comerme mis palabras). - David

Mi solución a esto:

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

Llame a este método en un hilo en segundo plano.

Respondido 22 Abr '18, 02:04

Error: (73, 67) error: no se puede hacer referencia al conjunto de métodos no estáticos (String) desde un contexto estático - user3575963

Tengo el mismo problema con mis clases de prueba. Esto funcionó a las mil maravillas para mí. Sin embargo, reemplazando runOnUiThread de runTestOnUiThread. Gracias - papimoe

Por lo general, cualquier acción que involucre la interfaz de usuario debe realizarse en el hilo principal o UI, que es en el onCreate() y se ejecutan el manejo de eventos. Una forma de estar seguro es usar runOnUiThread (), otro está usando Handlers.

ProgressBar.setProgress() tiene un mecanismo para el cual siempre se ejecutará en el hilo principal, por eso funcionó.

Ver Enhebrado indoloro.

Respondido 19 ago 13, 21:08

El artículo de Hilo sin dolor en ese enlace ahora es un 404. Aquí hay un vínculo a un artículo de blog (¿más antiguo?) Sobre Hilo sin dolor: android-developers.blogspot.com/2009/05/painless-threading.html - de tony Adams

Las corrutinas de Kotlin pueden hacer que su código sea más conciso y legible de esta manera:

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

O viceversa:

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

contestado el 15 de mayo de 20 a las 11:05

¡Perfecto! ¡Gracias! - michel fernandes

Muchas gracias por salvarme el día. nik

Gracias por ahorrarme tiempo. - Dashesh

He estado en esta situación, pero encontré una solución con el objeto Handler.

En mi caso, quiero actualizar un ProgressDialog con el patrón de observador. Mi vista implementa observador y anula el método de actualización.

Entonces, mi hilo principal crea la vista y otro hilo llama al método de actualización que actualiza ProgressDialop y ....:

Solo el hilo original que creó una jerarquía de vistas puede tocar sus vistas.

Es posible resolver el problema con el objeto Handler.

A continuación, diferentes partes de mi 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 explicación se puede encontrar en esta página, y debe leer el "Ejemplo de diálogo de progreso con un segundo hilo".

Respondido 29 Abr '18, 17:04

Puede usar el controlador para eliminar la vista sin alterar el hilo principal de la interfaz de usuario. Aquí hay un código de ejemplo

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

Respondido el 04 de enero de 21 a las 15:01

Veo que has aceptado la respuesta de @ providence. Por si acaso, ¡también puedes usar el controlador! Primero, haga los campos int.

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

A continuación, cree una instancia de controlador como 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;
            }
            }
        }
    };

Haz un método.

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

Finalmente, pon esto en onCreate() método.

showHandler(true);

contestado el 20 de mayo de 13 a las 07:05

Utilice este código y no es necesario runOnUiThread función:

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

Respondido el 16 de Septiembre de 16 a las 17:09

Tuve un problema similar y mi solución es fea, pero 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
}

Respondido 22 Abr '18, 02:04

@ R.jzadeh, es bueno escuchar eso. Desde el momento en que escribí esa respuesta, probablemente ahora puedas hacerlo mejor :) - Blazej

yo suelo Handler de Looper.getMainLooper(). Funcionó bien para mí.

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

Respondido 22 Abr '18, 02:04

Esto está arrojando un error explícitamente. Dice cualquier hilo que haya creado una vista, solo que puede tocar sus vistas. Es porque la vista creada está dentro del espacio de ese hilo. La creación de la vista (GUI) ocurre en el hilo de la IU (principal). Por lo tanto, siempre usa el hilo de la interfaz de usuario para acceder a esos métodos.

Ingrese la descripción de la imagen aquí

En la imagen de arriba, la variable de progreso está dentro del espacio del hilo de la interfaz de usuario. Entonces, solo el hilo de la interfaz de usuario puede acceder a esta variable. Aquí, está accediendo al progreso a través del nuevo Thread (), y es por eso que recibió un error.

Respondido 22 Abr '18, 02:04

Estaba enfrentando un problema similar y ninguno de los métodos mencionados anteriormente funcionó para mí. Al final, esto funcionó para mí:

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

Encontré esta joya aquí.

respondido 19 nov., 18:16

Esto le sucedió a mi cuando solicité un cambio de UI de un doInBackground desde Asynctask En lugar de usar onPostExecute.

Lidiar con la interfaz de usuario en onPostExecute resolvió mi problema.

Respondido 22 Abr '18, 02:04

Gracias Jonathan. Este también era mi problema, pero tuve que leer un poco más para comprender lo que querías decir aquí. Para cualquier otra persona onPostExecute es también un método de AsyncTask pero se ejecuta en el hilo de la interfaz de usuario. Mira aquí: blog.teamtreehouse.com/all-about-android-asynctasks - ciaranodc

Estaba trabajando con una clase que no contenía una referencia al contexto. Entonces no me fue posible usar runOnUIThread(); solía view.post(); y se solucionó.

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

Respondido 22 Abr '18, 02:04

¿Cuál es la analogía de audioMessage y tvPlayDuration al código de preguntas? - tengo dos

audioMessage es un objeto titular de la vista de texto. tvPlayDuration es la vista de texto que queremos actualizar desde un hilo que no sea UI. En la pregunta anterior, currentTime es la vista de texto pero no tiene un objeto titular. - Ifta

Cuando usas Tarea asíncrona Actualiza la interfaz de usuario en enPostExecute Método

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

     }

contestado el 10 de mayo de 18 a las 14:05

esto me pasó a mí. Estaba actualizando la interfaz de usuario en doinbackground de la tarea asynk. - mehmoodnisar125

Respuesta de Kotlin

Tenemos que usar UI Thread para el trabajo de manera verdadera. Podemos usar UI Thread en Kotlin:

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

contestado el 21 de mayo de 21 a las 18:05

Si no quieres usar runOnUiThread API, de hecho puede implementar AsynTask para las operaciones que tardan algunos segundos en completarse. Pero en ese caso, también después de procesar su trabajo en doinBackground(), debe devolver la vista terminada en onPostExecute(). La implementación de Android permite que solo el hilo principal de la interfaz de usuario interactúe con las vistas.

Respondido 22 Abr '18, 02:04

Este es el seguimiento de la pila de la excepción 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)

Entonces, si vas y cavas, entonces llegarás a saber

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

Dónde hilo m se inicializa en el constructor como se muestra a continuación

mThread = Thread.currentThread();

Todo lo que quiero decir es que cuando creamos una vista particular, la creamos en UI Thread y luego intentamos modificarla en Worker Thread.

Podemos verificarlo a través del siguiente fragmento de código

Thread.currentThread().getName()

cuando inflemos el diseño y luego cuando obtenga una excepción.

Respondido el 19 de enero de 15 a las 09:01

Si simplemente desea invalidar (llamar a la función repaint / redraw) desde su subproceso que no es de la interfaz de usuario, use postInvalidate ()

myView.postInvalidate();

Esto publicará una solicitud de invalidación en el hilo de la interfaz de usuario.

Para más información : que-hace-postinvalidar

Respondido 01 Feb 19, 18:02

Bueno, puedes hacerlo así.

https://developer.android.com/reference/android/view/View#post(java.lang.Runnable)

Un enfoque simple

currentTime.post(new Runnable(){
            @Override
            public void run() {
                 currentTime.setText(time);     
            }
        }

también proporciona retraso

https://developer.android.com/reference/android/view/View#postDelayed(java.lang.Runnable,%20long)

Respondido el 28 de diciembre de 20 a las 17:12

¡Me alegra verte publicando respuestas! Tenga en cuenta que se espera que agregue algunos detalles sobre lo que arregló o por qué es correcto. Ayuda a los usuarios a comprender lo que está pasando, en lugar de simplemente recibir la respuesta. Considérelo una regla para todas sus respuestas aquí. Espero que la pases bien. - Aman

Para mi el problema era que estaba llamando onProgressUpdate() explícitamente de mi código. Esto no debería hacerse. llame publishProgress() en su lugar y eso resolvió el error.

Respondido 04 Jul 14, 09:07

En mi caso tengo EditText en Adapter, y ya está en el hilo de la interfaz de usuario. Sin embargo, cuando se carga esta actividad, se bloquea con este error.

Mi solución es que necesito eliminar <requestFocus /> desde EditText en XML.

Respondido 22 Abr '18, 02:04

Para las personas que luchan en Kotlin, funciona así:

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

contestado el 02 de mayo de 19 a las 09:05

Si está dentro de un fragmento, también debe obtener el objeto de actividad, ya que runOnUIThread es un método de la actividad.

Un ejemplo en Kotlin con algo de contexto circundante para que sea más claro: este ejemplo está navegando desde un fragmento de cámara a un fragmento de galería:

// Setup image capture listener which is triggered after photo has been taken
imageCapture.takePicture(
       outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {

           override fun onError(exc: ImageCaptureException) {
           Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
        }

        override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                        val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
                        Log.d(TAG, "Photo capture succeeded: $savedUri")
               
             //Do whatever work you do when image is saved         
             
             //Now ask navigator to move to new tab - as this
             //updates UI do on the UI thread           
             activity?.runOnUiThread( {
                 Navigation.findNavController(
                        requireActivity(), R.id.fragment_container
                 ).navigate(CameraFragmentDirections
                        .actionCameraToGallery(outputDirectory.absolutePath))
              })

Respondido 22 Oct 20, 21:10

Resuelto: simplemente coloque este método en la clase doInBackround ... y pase el mensaje

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

    }

Respondido el 28 de Septiembre de 18 a las 09:09

En mi caso, la persona que llama demasiadas veces en poco tiempo obtendrá este error, simplemente puse la verificación del tiempo transcurrido para no hacer nada si es demasiado corto, por ejemplo, ignorar si la función se llama en menos de 0.5 segundos:

    private long mLastClickTime = 0;

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

        //... do ui update
    }

respondido 11 mar '19, 08:03

Una mejor solución sería deshabilitar el botón al hacer clic y habilitarlo nuevamente cuando se complete la acción. - lsrom

@lsrom En mi caso no es tan simple porque la persona que llama es una biblioteca de terceros interna y está fuera de mi control. - Fruta

Si no pudo encontrar un UIThread, puede usarlo de esta manera.

tu contexto actual es decir, necesitas analizar el contexto actual

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

Respondido el 02 de diciembre de 19 a las 05:12

In Kotlin simplemente ponga su código en el método de actividad 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)
}

contestado el 04 de mayo de 20 a las 20:05

RunOnUIThread no pareció funcionar para mí, pero lo siguiente terminó resolviendo mis problemas.

            _ = MainThread.InvokeOnMainThreadAsync(async () =>
           {
               this.LoadApplication(Startup.Init(this.ConfigureServices));

               var authenticationService = App.ServiceProvider.GetService<AuthenticationService>();
               if (authenticationService.AuthenticationResult == null)
               {
                   await authenticationService.AuthenticateAsync(AuthenticationUserFlow.SignUpSignIn, CrossCurrentActivity.Current.Activity).ConfigureAwait(false);
               }
           });

Dentro del método Startup.Init hay un enrutamiento ReactiveUI y esto debe invocarse en el hilo principal. Este método Invoke también acepta async / await mejor que RunOnUIThread.

Entonces, en cualquier lugar donde necesite invocar métodos en el hilo principal, uso esto.

Por favor comente sobre esto si alguien sabe algo que yo no conozco y puede ayudarme a mejorar mi aplicación.

Respondido el 23 de diciembre de 20 a las 19:12

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.