Android "Solo el hilo original que creó una jerarquía de vistas puede tocar sus vistas".
Frecuentes
Visto 654,978 equipos
1058
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.
30 Respuestas
2102
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
152
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
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
78
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
30
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
25
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
21
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
19
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
8
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
8
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
8
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
7
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
7
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.
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
6
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
4
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
4
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
4
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
3
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
2
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
2
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
2
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
1
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
1
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
1
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
1
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
0
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
0
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
0
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
0
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
0
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 android multithreading or haz tu propia pregunta.
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 - nolomanUn 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é haciendogetActivity().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