tutorial - tareas asincronas android
Tarea asíncrona de Android (2)
Esto fue preguntado en una de las entrevistas de Android. Me preguntaron si es posible iniciar otra tarea asíncrona (deje que sea Tarea2) desde el método doInBackground () de la tarea asíncrona 1 (sea Tarea1). He pasado por los documentos que dicen lo siguiente:
La instancia de tarea debe crearse en el subproceso de la interfaz de usuario.
execute (Params ...) debe invocarse en el subproceso de la interfaz de usuario.
Según estas afirmaciones, creo que no debería ser posible iniciar una tarea desde el método en segundo plano de otra tarea. Además, la tarea asíncrona tiene métodos de UI (que no se pueden usar en un hilo de fondo), por lo que fortaleció mi argumento y respondí que no era posible.
Al verificar una aplicación de demostración simple, vi que sí es posible hacerlo. Algún código de demostración:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
init();
Log.v ("gaurav", "Thread is : " + Thread.currentThread().getName());
Task1 task = new Task1();
task.execute();
}
class Task1 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
// TODO Auto-generated method stub
Log.v ("gaurav", "Thread task 1 is : " + Thread.currentThread().getName());
Task2 task = new Task2();
task.execute();
return null;
}
}
class Task2 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
// TODO Auto-generated method stub
Log.v ("gaurav", "Thread task 2 is : " + Thread.currentThread().getName());
Log.v ("gaurav", "Task 2 started");
return null;
}
}
Obtengo los siguientes registros que indican la ejecución exitosa:
> 08-07 09:46:25.564: V/gaurav(2100): Thread is : main 08-07
> 09:46:25.564: V/gaurav(2100): Thread task 1 is : AsyncTask #3 08-07
> 09:46:25.564: V/gaurav(2100): Thread task 2 is : AsyncTask #4 08-07
> 09:46:25.564: V/gaurav(2100): Task 2 started
He comprobado esto en el dispositivo ICS, KK y L y funciona bien para todos.
Una razón por la que podría pensar es que no estoy anulando ningún método de UI y haciendo ninguna actualización de UI en mi segunda tarea, por lo que no causa ningún problema, pero no estoy seguro. Incluso si ese es el caso, viola las reglas de subprocesos mencionadas en la guía del desarrollador.
Como referencia, también verifiqué este enlace: inicie AsyncTask desde otro AsyncTask doInBackground () pero la respuesta indica que se inicie la segunda tarea utilizando el método runOnUiThread () dentro de doInBackground (). Me gustaría algo de ayuda sobre lo que está pasando aquí. Gracias.
Cambiemos tu código a lo siguiente:
class Task1 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
// TODO Auto-generated method stub
Log.v ("gaurav", "Thread task 1 is : " + Thread.currentThread().getName());
Task2 task = new Task2();
task.execute();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.v ("gaurav", "Log after sleeping");
return null;
}
}
class Task2 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
// TODO Auto-generated method stub
Log.v ("gaurav", "Thread task 2 is : " + Thread.currentThread().getName());
Log.v ("gaurav", "Task 2 Started");
return null;
}
}
Ahora el LogCat devuelve:
08-07 06:13:44.208 3073-3073/testapplication V/gaurav﹕ Thread is : main
08-07 06:13:44.209 3073-3091/testapplication V/gaurav﹕ Thread task 1 is : AsyncTask #1
08-07 06:13:49.211 3073-3091/testapplication V/gaurav﹕ Log after sleeping
08-07 06:13:49.213 3073-3095/testapplication V/gaurav﹕ Thread task 2 is : AsyncTask #2
08-07 06:13:49.213 3073-3095/testapplication V/gaurav﹕ Task 2 Started
Como puede ver, la Task 2
se ejecuta después del final de la ejecución de la Task 1
(incluso después de dormir durante 5 segundos). Esto significa que la segunda tarea no se iniciará hasta que se complete la primera.
¿Por qué? La razón está detrás del código fuente de AsyncTask . Por favor considere el método de execute()
:
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
y el método scheduleNext()
:
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
La palabra clave más importante en estos métodos está synchronized
que garantiza que estos métodos se ejecutarán solo en un subproceso al mismo tiempo. Cuando llama al método de execute
, ofrece un nuevo Runnable
to mTask
que es una instancia de la ArrayDeque<Runnable>
que funciona como un serializador de las diferentes solicitudes en diferentes subprocesos [más información] . Si no hubiera Runnable
ejecutado (es decir, if (mActive == null)
), se llamaría a scheduleNext()
, de lo contrario, se llamaría a scheduleNext()
en el bloque finally
después del final (por cualquier motivo) del final ejecutable de Runnable
. Todos los Runnable
s se ejecutan en un hilo separado por THREAD_POOL_EXECUTOR
.
¿Qué hay de malo en la ejecución de AsyncTask desde otros hilos? Comenzando con Jelly Bean, un AsyncTask
se carga en clase al inicio de la aplicación en el subproceso de la interfaz de usuario, por lo que se garantiza que las devoluciones de llamada se produzcan en el subproceso de la interfaz de usuario, sin embargo, antes de la versión de Jelly Bean, si otro hilo crea el AsyncTask
las devoluciones de llamada No se produce en el hilo correcto.
Por lo tanto, AsyncTask
implementaciones de AsyncTask
deben llamarse desde el subproceso de la interfaz de usuario solo en plataformas anteriores a Jelly Bean ( + y + ).
Aclaración: tenga en cuenta el siguiente ejemplo que simplemente aclara las diferencias entre las diferentes versiones de plataforma de Android:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
new Thread() {
@Override
public void run() {
Task1 task = new Task1();
task.execute();
}
}.start();
}
class Task1 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
return null;
}
}
Funciona bien en Android 5.1, pero se bloquea con la siguiente excepción en Android 2.3:
08-07 12:05:20.736 584-591/github.yaa110.testapplication E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-8
java.lang.ExceptionInInitializerError
at github.yaa110.testapplication.Main2Activity$1.run(Main2Activity.java:21)
Caused by: java.lang.RuntimeException: Can''t create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421)
at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421)
at android.os.AsyncTask.<clinit>(AsyncTask.java:152)
at github.yaa110.testapplication.Main2Activity$1.run(Main2Activity.java:21)
public class MainActivity extends Activity {
private final static String TAG = "ThreadingAsyncTask";
private ImageView mImageView;
private ProgressBar mProgressBar;
private int mDelay = 500;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.imageView);;
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
final Button button = (Button) findViewById(R.id.loadButton);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
new LoadIconTask().execute(R.drawable.cheetah);
}
});
final Button otherButton = (Button) findViewById(R.id.otherButton);
otherButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "I''m Working",
Toast.LENGTH_SHORT).show();
}
});
}
class LoadIconTask extends AsyncTask<Integer, Integer, Bitmap> {
@Override
protected void onPreExecute() {
mProgressBar.setVisibility(ProgressBar.VISIBLE);
}
@Override
protected Bitmap doInBackground(Integer... resId) {
Bitmap tmp = BitmapFactory.decodeResource(getResources(), resId[0]);
// simulating long-running operation
for (int i = 1; i < 11; i++) {
sleep();
publishProgress(i * 10);
}
return tmp;
}
@Override
protected void onProgressUpdate(Integer... values) {
mProgressBar.setProgress(values[0]);
}
@Override
protected void onPostExecute(Bitmap result) {
mProgressBar.setVisibility(ProgressBar.INVISIBLE);
mImageView.setImageBitmap(result);
}
private void sleep() {
try {
Thread.sleep(mDelay);
} catch (InterruptedException e) {
Log.e(TAG, e.toString());
}
}
}
}