Subprocesamiento de Android: todo lo que necesita saber

Publicado: 2022-03-11

Cada desarrollador de Android, en un momento u otro, necesita lidiar con subprocesos en su aplicación.

Cuando se inicia una aplicación en Android, crea el primer hilo de ejecución, conocido como el hilo "principal". El subproceso principal es responsable de enviar eventos a los widgets de la interfaz de usuario apropiados, así como de comunicarse con los componentes del kit de herramientas de la interfaz de usuario de Android.

Para mantener la capacidad de respuesta de su aplicación, es esencial evitar usar el hilo principal para realizar cualquier operación que pueda terminar manteniéndola bloqueada.

Las operaciones de red y las llamadas a bases de datos, así como la carga de ciertos componentes, son ejemplos comunes de operaciones que se deben evitar en el hilo principal. Cuando se les llama en el subproceso principal, se les llama sincrónicamente, lo que significa que la interfaz de usuario no responderá hasta que se complete la operación. Por esta razón, normalmente se realizan en subprocesos separados, lo que evita bloquear la interfaz de usuario mientras se realizan (es decir, se realizan de forma asíncrona desde la interfaz de usuario).

Android proporciona muchas formas de crear y administrar subprocesos, y existen muchas bibliotecas de terceros que hacen que la administración de subprocesos sea mucho más agradable. Sin embargo, con tantos enfoques diferentes disponibles, elegir el correcto puede ser bastante confuso.

En este artículo, aprenderá sobre algunos escenarios comunes en el desarrollo de Android donde la creación de subprocesos se vuelve esencial y algunas soluciones simples que se pueden aplicar a esos escenarios y más.

Enhebrar en Android

En Android, puede categorizar todos los componentes de subprocesamiento en dos categorías básicas:

  1. Subprocesos que están adjuntos a una actividad/fragmento: estos subprocesos están vinculados al ciclo de vida de la actividad/fragmento y finalizan tan pronto como se destruye la actividad/fragmento.
  2. Subprocesos que no están adjuntos a ninguna actividad/fragmento: estos subprocesos pueden continuar ejecutándose más allá de la vida útil de la actividad/fragmento (si corresponde) a partir del cual se generaron.

Enhebrar componentes que se adjuntan a una actividad/fragmento

AsyncTask

AsyncTask es el componente de Android más básico para subprocesos. Es fácil de usar y puede ser bueno para escenarios básicos.

Ejemplo de uso:

 public class ExampleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyTask().execute(url); } private class MyTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String url = params[0]; return doSomeWork(url); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // do something with result } } }

Sin embargo, AsyncTask se queda corto si necesita que su tarea diferida se ejecute más allá de la vida útil de la actividad/fragmento. Vale la pena señalar que incluso algo tan simple como la rotación de la pantalla puede hacer que la actividad se destruya.

Cargadores

Los cargadores son la solución para el problema mencionado anteriormente. Los cargadores pueden detenerse automáticamente cuando se destruye la actividad y también pueden reiniciarse después de que se vuelve a crear la actividad.

Hay principalmente dos tipos de cargadores: AsyncTaskLoader y CursorLoader . Aprenderá más sobre CursorLoader más adelante en este artículo.

AsyncTaskLoader es similar a AsyncTask , pero un poco más complicado.

Ejemplo de uso:

 public class ExampleActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(1, null, new MyLoaderCallbacks()); } private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new MyLoader(ExampleActivity.this); } @Override public void onLoadFinished(Loader loader, Object data) { } @Override public void onLoaderReset(Loader loader) { } } private class MyLoader extends AsyncTaskLoader { public MyLoader(Context context) { super(context); } @Override public Object loadInBackground() { return someWorkToDo(); } } }

Enhebrar componentes que no se adjuntan a una actividad/fragmento

Servicio

Service es un componente útil para realizar operaciones largas (o potencialmente largas) sin ninguna interfaz de usuario.

Service se ejecuta en el hilo principal de su proceso de hospedaje; el servicio no crea su propio subproceso y no se ejecuta en un proceso separado a menos que especifique lo contrario.

Ejemplo de uso:

 public class ExampleService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { doSomeLongProccesingWork(); stopSelf(); return START_NOT_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }

Con Service , es su responsabilidad detenerlo cuando su trabajo esté completo llamando al stopSelf() o stopService() .

IntentService

Al igual que Service , IntentService se ejecuta en un subproceso separado y se detiene automáticamente después de completar su trabajo.

IntentService generalmente se usa para tareas cortas que no necesitan adjuntarse a ninguna interfaz de usuario.

Ejemplo de uso:

 public class ExampleService extends IntentService { public ExampleService() { super("ExampleService"); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }

Siete patrones de enhebrado en Android

Caso de uso No. 1: Realización de una solicitud a través de la red sin requerir una respuesta del servidor

En ocasiones, es posible que desee enviar una solicitud de API a un servidor sin tener que preocuparse por su respuesta. Por ejemplo, puede enviar un token de registro de inserción al back-end de su aplicación.

Dado que esto implica realizar una solicitud a través de la red, debe hacerlo desde un subproceso que no sea el subproceso principal.

Opción 1: AsyncTask o cargadores

Puede usar AsyncTask o cargadores para realizar la llamada, y funcionará.

Sin embargo, AsyncTask y los cargadores dependen del ciclo de vida de la actividad. Esto significa que deberá esperar a que se ejecute la llamada e intentar evitar que el usuario abandone la actividad, o esperar que se ejecute antes de que se destruya la actividad.

Opción 2: Servicio

Service puede ser más adecuado para este caso de uso, ya que no está vinculado a ninguna actividad. Por lo tanto, podrá continuar con la llamada de red incluso después de que se destruya la actividad. Además, dado que no se necesita la respuesta del servidor, un servicio tampoco sería limitante aquí.

Sin embargo, dado que un servicio comenzará a ejecutarse en el subproceso de la interfaz de usuario, aún deberá administrar los subprocesos usted mismo. También deberá asegurarse de que el servicio se detenga una vez que se complete la llamada de red.

Esto requeriría más esfuerzo del que debería ser necesario para una acción tan simple.

Opción 3: IntentService

Esta, en mi opinión, sería la mejor opción.

Dado que IntentService no se adjunta a ninguna actividad y se ejecuta en un subproceso que no es de interfaz de usuario, aquí satisface perfectamente nuestras necesidades. Además, IntentService se detiene automáticamente, por lo que tampoco es necesario administrarlo manualmente.

Caso de uso n.º 2: realizar una llamada de red y obtener la respuesta del servidor

Este caso de uso es probablemente un poco más común. Por ejemplo, es posible que desee invocar una API en el back-end y usar su respuesta para completar los campos en la pantalla.

Opción 1: Servicio o IntentService

Aunque a Service o IntentService le fue bien para el caso de uso anterior, usarlos aquí no sería una buena idea. Tratar de obtener datos de un Service o un IntentService en el subproceso principal de la interfaz de usuario haría que las cosas fueran muy complejas.

Opción 2: AsyncTask o cargadores

A primera vista, AsyncTask o los cargadores parecerían ser la solución obvia aquí. Son fáciles de usar, simples y directas.

Sin embargo, al usar AsyncTask o cargadores, notará que es necesario escribir un código repetitivo. Además, el manejo de errores se convierte en una tarea importante con estos componentes. Incluso con una simple llamada de red, debe conocer las posibles excepciones, detectarlas y actuar en consecuencia. Esto nos obliga a envolver nuestra respuesta en una clase personalizada que contiene los datos, con posible información de error, y una bandera indica si la acción fue exitosa o no.

Eso es mucho trabajo por hacer para cada llamada. Afortunadamente, ahora hay disponible una solución mucho mejor y más simple: RxJava.

Opción 3: RxJava

Es posible que haya oído hablar de RxJava, la biblioteca desarrollada por Netflix. Es casi mágico en Java.

RxAndroid le permite usar RxJava en Android y hace que lidiar con tareas asincrónicas sea muy fácil. Puede obtener más información sobre RxJava en Android aquí.

RxJava proporciona dos componentes: Observer y Subscriber .

Un observador es un componente que contiene alguna acción. Realiza esa acción y devuelve el resultado si tiene éxito o un error si falla.

Un suscriptor , por otro lado, es un componente que puede recibir el resultado (o error) de un observable, suscribiéndose a él.

Con RxJava, primero crea un observable:

 Observable.create((ObservableOnSubscribe<Data>) e -> { Data data = mRestApi.getData(); e.onNext(data); })

Una vez que se ha creado el observable, puede suscribirse a él.

Con la biblioteca RxAndroid, puede controlar el hilo en el que desea ejecutar la acción en el observable y el hilo en el que desea obtener la respuesta (es decir, el resultado o el error).

Encadenas en el observable con estas dos funciones:

 .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()

Los planificadores son componentes que ejecutan la acción en un hilo determinado. AndroidSchedulers.mainThread() es el programador asociado con el subproceso principal.

Dado que nuestra llamada API es mRestApi.getData() y devuelve un objeto Data , la llamada básica puede verse así:

 Observable.create((ObservableOnSubscribe<Data>) e -> { try { Data data = mRestApi.getData(); e.onNext(data); } catch (Exception ex) { e.onError(ex); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(match -> Log.i(“rest api, "success"), throwable -> Log.e(“rest api, "error: %s" + throwable.getMessage()));

Sin siquiera entrar en otros beneficios de usar RxJava, ya puede ver cómo RxJava nos permite escribir un código más maduro al abstraer la complejidad de los subprocesos.

Caso de uso n.º 3: encadenamiento de llamadas de red

Para las llamadas de red que deben realizarse en secuencia (es decir, donde cada operación depende de la respuesta/resultado de la operación anterior), debe tener especial cuidado al generar código espagueti.

Por ejemplo, es posible que deba realizar una llamada a la API con un token que necesita obtener primero a través de otra llamada a la API.

Opción 1: AsyncTask o cargadores

Usar AsyncTask o cargadores casi definitivamente conducirá a un código de espagueti. La funcionalidad general será difícil de acertar y requerirá una gran cantidad de código repetitivo redundante a lo largo de su proyecto.

Opción 2: RxJava usando flatMap

En RxJava, el operador flatMap toma un valor emitido del observable fuente y devuelve otro observable. Puede crear un observable y luego crear otro observable utilizando el valor emitido del primero, que básicamente los encadenará.

Paso 1. Cree el observable que obtiene el token:

 public Observable<String> getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }

Paso 2. Crea el observable que obtiene los datos usando el token:

 public Observable<String> getDataObservable(String token) { return Observable.create(subscriber -> { try { Data data = mRestApi.getData(token); subscriber.onNext(data); } catch (IOException e) { subscriber.onError(e); } }); }

Paso 3. Encadena los dos observables y suscríbete:

 getTokenObservable() .flatMap(new Function<String, Observable<Data>>() { @Override public Observable<Data> apply(String token) throws Exception { return getDataObservable(token); } }) .subscribe(data -> { doSomethingWithData(data) }, error -> handleError(e));

Tenga en cuenta que el uso de este enfoque no se limita a las llamadas de red; puede funcionar con cualquier conjunto de acciones que deban ejecutarse en una secuencia pero en subprocesos separados.

Todos los casos de uso anteriores son bastante simples. El cambio entre subprocesos solo ocurría después de que cada uno terminaba su tarea. Los escenarios más avanzados, por ejemplo, donde dos o más subprocesos necesitan comunicarse activamente entre sí, también pueden ser compatibles con este enfoque.

Caso de uso n.º 4: comunicarse con el subproceso de la interfaz de usuario desde otro subproceso

Considere un escenario en el que le gustaría cargar un archivo y actualizar la interfaz de usuario una vez que esté completo.

Dado que la carga de un archivo puede llevar mucho tiempo, no es necesario hacer esperar al usuario. Podría usar un servicio, y probablemente IntentService , para implementar la funcionalidad aquí.

En este caso, sin embargo, el mayor desafío es poder invocar un método en el subproceso de la interfaz de usuario después de que se complete la carga del archivo (que se realizó en un subproceso separado).

Opción 1: RxJava dentro del servicio

RxJava, ya sea por sí solo o dentro de un IntentService , puede no ser ideal. Deberá usar un mecanismo basado en devolución de llamada cuando se suscriba a un Observable , y IntentService está diseñado para realizar llamadas síncronas simples, no devoluciones de llamada.

Por otro lado, con un Service , deberá detener manualmente el servicio, lo que requiere más trabajo.

Opción 2: BroadcastReceiver

Android proporciona este componente, que puede escuchar eventos globales (por ejemplo, eventos de batería, eventos de red, etc.), así como eventos personalizados. Puede usar este componente para crear un evento personalizado que se activa cuando se completa la carga.

Para hacer esto, debe crear una clase personalizada que amplíe BroadcastReceiver , registrarla en el manifiesto y usar Intent e IntentFilter para crear el evento personalizado. Para desencadenar el evento, necesitará el método sendBroadcast .

Manifiesto:

 <receiver android:name="UploadReceiver"> <intent-filter> <action android:name="com.example.upload"> </action> </intent-filter> </receiver>

Receptor:

 public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }

Remitente:

 Intent intent = new Intent(); intent.setAction("com.example.upload"); sendBroadcast(intent);

Este enfoque es una opción viable. Pero como habrás notado, implica algo de trabajo y demasiadas transmisiones pueden ralentizar las cosas.

Opción 3: usar el controlador

Un Handler es un componente que se puede adjuntar a un hilo y luego hacer que realice alguna acción en ese hilo a través de mensajes simples o tareas Runnable . Funciona en conjunto con otro componente, Looper , que se encarga del procesamiento de mensajes en un hilo en particular.

Cuando se Handler un controlador, puede obtener un objeto Looper en el constructor, que indica a qué subproceso está conectado el controlador. Si desea usar un controlador adjunto al subproceso principal, debe usar el looper asociado con el subproceso principal llamando a Looper.getMainLooper() .

En este caso, para actualizar la interfaz de usuario desde un subproceso en segundo plano, puede crear un controlador adjunto al subproceso de la interfaz de usuario y luego publicar una acción como Runnable :

 Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });

Este enfoque es mucho mejor que el primero, pero hay una forma aún más sencilla de hacerlo...

Opción 3: Uso de EventBus

EventBus , una biblioteca popular de GreenRobot, permite que los componentes se comuniquen de manera segura entre sí. Dado que nuestro caso de uso es uno en el que solo queremos actualizar la interfaz de usuario, esta puede ser la opción más simple y segura.

Paso 1. Cree una clase de evento. por ejemplo, UIEvent .

Paso 2. Suscríbete al evento.

 @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {/* Do something */}; register and unregister eventbus : @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }

Paso 3. Publique el evento: EventBus.getDefault().post(new UIEvent());

Con el parámetro ThreadMode en la anotación, está especificando el hilo en el que desea suscribirse para este evento. En nuestro ejemplo aquí, estamos eligiendo el hilo principal, ya que queremos que el receptor del evento pueda actualizar la interfaz de usuario.

Puede estructurar su clase UIEvent para que contenga información adicional según sea necesario.

En el servicio:

 class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }

En la actividad/fragmento:

 @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};

Usando la EventBus library , la comunicación entre subprocesos se vuelve mucho más simple.

Caso de uso n.º 5: comunicación bidireccional entre subprocesos basada en las acciones del usuario

Suponga que está construyendo un reproductor multimedia y desea que pueda continuar reproduciendo música incluso cuando la pantalla de la aplicación está cerrada. En este escenario, querrá que la interfaz de usuario pueda comunicarse con el subproceso de medios (por ejemplo, reproducir, pausar y otras acciones) y también querrá que el subproceso de medios actualice la interfaz de usuario en función de ciertos eventos (por ejemplo, error, estado de almacenamiento en búfer). , etc.).

Un ejemplo de reproductor multimedia completo está más allá del alcance de este artículo. Sin embargo, puede encontrar buenos tutoriales aquí y aquí.

Opción 1: Uso de EventBus

Puede usar EventBus aquí. Sin embargo, generalmente no es seguro publicar un evento desde el subproceso de la interfaz de usuario y recibirlo en un servicio. Esto se debe a que no tiene forma de saber si el servicio se está ejecutando cuando envió el mensaje.

Opción 2: usar BoundService

Un BoundService es un Service que está vinculado a una actividad/fragmento. Esto significa que la actividad/fragmento siempre sabe si el servicio se está ejecutando o no y, además, obtiene acceso a los métodos públicos del servicio.

Para implementarlo, debe crear un Binder personalizado dentro del servicio y crear un método que devuelva el servicio.

 public class MediaService extends Service { private final IBinder mBinder = new MediaBinder(); public class MediaBinder extends Binder { MediaService getService() { // Return this instance of LocalService so clients can call public methods return MediaService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } }

Para vincular la actividad al servicio, debe implementar ServiceConnection , que es la clase que supervisa el estado del servicio, y usar el método bindService para realizar la vinculación:

 // in the activity MediaService mService; // flag indicates the bound status boolean mBound; @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, MediaService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { MediaBinder binder = (MediaBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } };

Puede encontrar un ejemplo completo de implementación aquí.

Para comunicarse con el servicio cuando el usuario toca el botón Reproducir o Pausa, puede vincularse al servicio y luego llamar al método público relevante en el servicio.

Cuando hay un evento multimedia y desea comunicarlo a la actividad/fragmento, puede usar una de las técnicas anteriores (por ejemplo, BroadcastReceiver , Handler o EventBus ).

Caso de Uso No. 6: Ejecutar acciones en paralelo y obtener resultados

Supongamos que está creando una aplicación turística y desea mostrar atracciones en un mapa obtenido de múltiples fuentes (diferentes proveedores de datos). Dado que no todas las fuentes pueden ser confiables, es posible que desee ignorar las que han fallado y continuar representando el mapa de todos modos.

Para paralelizar el proceso, cada llamada a la API debe realizarse en un subproceso diferente.

Opción 1: usar RxJava

En RxJava, puede combinar múltiples observables en uno usando los operadores merge() o concat() . Luego puede suscribirse en el observable "combinado" y esperar todos los resultados.

Este enfoque, sin embargo, no funcionará como se esperaba. Si una llamada a la API falla, el observable fusionado informará una falla general.

Opción 2: usar componentes nativos de Java

ExecutorService en Java crea una cantidad fija (configurable) de subprocesos y ejecuta tareas en ellos simultáneamente. El servicio devuelve un objeto Future que finalmente devuelve todos los resultados a través del método invokeAll() .

Cada tarea que envíe a ExecutorService debe estar contenida en la interfaz Callable , que es una interfaz para crear una tarea que puede generar una excepción.

Una vez que obtenga los resultados de invokeAll() , puede verificar cada resultado y proceder en consecuencia.

Digamos, por ejemplo, que tiene tres tipos de atracción que llegan desde tres puntos finales diferentes y desea realizar tres llamadas paralelas:

 ExecutorService pool = Executors.newFixedThreadPool(3); List<Callable<Object>> tasks = new ArrayList<>(); tasks.add(new Callable<Object>() { @Override public Integer call() throws Exception { return mRest.getAttractionType1(); } }); // ... try { List<Future<Object>> results = pool.invokeAll(tasks); for (Future result : results) { try { Object response = result.get(); if (response instance of AttractionType1... {} if (response instance of AttractionType2... {} ... } catch (ExecutionException e) { e.printStackTrace(); } } } catch (InterruptedException e) { e.printStackTrace(); }

De esta manera, está ejecutando todas las acciones en paralelo. Por lo tanto, puede verificar si hay errores en cada acción por separado e ignorar fallas individuales según corresponda.

Este enfoque es más fácil que usar RxJava. Es más simple, más corto y no falla en todas las acciones debido a una excepción.

Caso de uso n.º 7: consulta de la base de datos SQLite local

Cuando se trata de una base de datos SQLite local, se recomienda que la base de datos se use desde un subproceso en segundo plano, ya que las llamadas a la base de datos (especialmente con bases de datos grandes o consultas complejas) pueden consumir mucho tiempo, lo que provoca que la interfaz de usuario se congele.

Al consultar datos de SQLite, obtiene un objeto Cursor que luego se puede usar para obtener los datos reales.

 Cursor cursor = getData(); String name = cursor.getString(<colum_number>);

Opción 1: Usar RxJava

Puede usar RxJava y obtener los datos de la base de datos, tal como obtenemos datos de nuestro back-end:

 public Observable<Cursor> getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }

Puede usar el observable devuelto por getLocalDataObservable() de la siguiente manera:

 getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, "error: %s" + throwable.getMessage()));

Si bien este es sin duda un buen enfoque, hay uno que es aún mejor, ya que hay un componente que se crea solo para este mismo escenario.

Opción 2: Usar CursorLoader + ContentProvider

Android proporciona CursorLoader , un componente nativo para cargar datos de SQLite y administrar el subproceso correspondiente. Es un Loader que devuelve un Cursor , que podemos usar para obtener los datos llamando a métodos simples como getString() , getLong() , etc.

 public class SimpleCursorLoader extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { public static final String TAG = SimpleCursorLoader.class.getSimpleName(); private static final int LOADER_ID = 0x01; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_cursor_loader); textView = (TextView) findViewById(R.id.text_view); getSupportLoaderManager().initLoader(LOADER_ID, null, this); } public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { return new CursorLoader(this, Uri.parse("content://com.github.browep.cursorloader.data") , new String[]{"col1"}, null, null, null); } public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { if (cursor != null && cursor.moveToFirst()) { String text = textView.getText().toString(); while (cursor.moveToNext()) { text += "<br />" + cursor.getString(1); cursor.moveToNext(); } textView.setText(Html.fromHtml(text) ); } } public void onLoaderReset(Loader<Cursor> cursorLoader) { } }

CursorLoader funciona con el componente ContentProvider . Este componente proporciona una gran cantidad de funciones de base de datos en tiempo real (p. ej., notificaciones de cambio, disparadores, etc.) que permiten a los desarrolladores implementar una mejor experiencia de usuario mucho más fácilmente.

No existe una solución bala de plata para enhebrar en Android

Android ofrece muchas formas de manejar y administrar hilos, pero ninguna de ellas es una panacea.

Elegir el enfoque de subprocesamiento correcto, según su caso de uso, puede marcar la diferencia al hacer que la solución general sea fácil de implementar y comprender. Los componentes nativos encajan bien en algunos casos, pero no en todos. Lo mismo se aplica a las sofisticadas soluciones de terceros.

Espero que este artículo le resulte útil cuando trabaje en su próximo proyecto de Android. Comparta con nosotros su experiencia de creación de subprocesos en Android o cualquier caso de uso en el que las soluciones anteriores funcionen bien, o no, en los comentarios a continuación.