Una guía demasiado completa para las bibliotecas de Android infrautilizadas

Publicado: 2022-03-11

Cualquier desarrollador experimentado le dirá que su mejor código no es el código que escribió. Es un código que tomaron del trabajo de otra persona.

Sí, los desarrolladores somos innovadores en la resolución de problemas, pero muchos de los problemas que encontramos ya se han resuelto y los remedios están empaquetados en bibliotecas disponibles para todos. ¿Por qué reinventar la rueda cuando las ruedas libres están en todas partes?

Android no es una excepción. La fuente definitiva para la reutilización de código es el propio SDK de Android, que viene con excelentes construcciones y servicios que harán gran parte del trabajo por usted.

Pero donde el SDK se queda corto, la comunidad de Android ha creado algunas bibliotecas de primera línea que pueden ahorrarle toneladas de trabajo de codificación, reemplazándolas con implementaciones altamente ajustadas, examinadas y probadas. No me refiero a las bibliotecas obvias: biblioteca de soporte de Android, biblioteca de soporte de diseño de Android, Gson. Me refiero a herramientas que quizás no conozcas. E incluso si lo hace, probablemente aún no los esté usando.

Una de las principales diferencias entre un desarrollador estándar y un maestro es el uso correcto de bibliotecas de terceros. Un desarrollador maestro realizará aproximadamente la misma tarea tres veces más rápido que un novato y, por lo general, con un código más corto. Gran parte de esto proviene de saber qué bibliotecas de terceros usar y cómo integrarlas correctamente en su proyecto.

He estado desarrollando, asesorando y liderando equipos de Android durante años, y he estudiado y usado docenas de herramientas y bibliotecas externas. (Incluso se sabe que leí su código de implementación y discutí su funcionamiento interno con el desarrollador). Muchos han sido muy efectivos para ayudarme a hacer el trabajo, pero la verdad es que la mayoría no lo fue.

Es por eso que he elaborado esta guía. Apóyese en mi experiencia, así como en la de otros desarrolladores móviles, para asegurarse de que está utilizando las mejores bibliotecas. He elegido siete. Sospecho que muy pronto también serán algunos de tus favoritos.

Selección de la biblioteca de Android adecuada

Al elegir una biblioteca, busco cuatro características clave:

  • Proporciona una solución consistente y de alta calidad para un problema real y no trivial.
  • Utiliza una API tan simple como sea posible.
  • No fuerza ningún cambio en mi arquitectura general.
  • Tiene una gran base de usuarios y, preferiblemente, una comunidad activa de desarrolladores.

Las tres primeras características son factores decisivos. Si no están presentes, sigo adelante o empiezo a codificar manualmente.

Bibliotecas de Android

Las bibliotecas que cubro a continuación pasan las cuatro pruebas. También resuelven algunos de los aspectos más desafiantes del desarrollo móvil.

  • Dos bibliotecas para inyección de dependencia, enlace de diseño a Java, objetos simulados.
  • Modelo de mensajería pub/sub en la aplicación.
  • Capa de comunicación HTTP segura, eficiente y autorrecuperable.
  • Manipulación de imágenes: descarga, almacenamiento en caché, cambio de tamaño y carga en RAM.
  • Transmisión de video en tiempo real.
  • Detección de fugas de memoria.

ButterKnife: la herramienta de inyección de dependencia definitiva

Esta es la última biblioteca de inyección de dependencia para Android. Simple, robusto, súper rápido (¡sin reflejos!) y capaz de eliminar gran parte del código repetitivo de su aplicación.

Algunos dirían que ButterKnife es lo que debería haber sido el diseño de Android para el mapeo de Java en primer lugar.

Inyección de dependencia de Android Butterknife

Se acabó la necesidad de vincular directamente cada una de sus vistas a través de una llamada a findViewById() ; en su lugar, hay una vista anotada que le brinda acceso directo al código. ButterKnife también elimina la necesidad de eventos de interfaz de usuario repetitivos como onClick , onTouch , etc., y los reemplaza con código autoinyectado.

Pero basta de cháchara, veamos el código.

Ver enlace de campo:

 class MyButterKnifeActivity extends Activity { @BindView(R.id.name) TextView name; @BindView(R.id.address) TextView address; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // MUST BE CALLED BEFORE ACCESSING UI FIELDS name.setText(“etc etc”); } }

Enlace de recursos:

 class ExampleActivity extends Activity { @BindString(R.string.username) String username; @BindDrawable(R.drawable.graphic) Drawable graphic; @BindColor(R.color.bg_color) int bgColor; @BindDimen(R.dimen.lower_padding) Float lowerPadding; // and no need for getResources().getString()/getDrawable/getColor() }

Enlace de evento de interfaz de usuario:

 @OnClick(R.id.my_button) public void clickHandler(View view) { // onClick logic goes here }

AndroidAnnotations: llevando la inyección de dependencia al siguiente nivel

En segundo lugar cercano a ButterKnife cuando se trata de inyección de dependencia, AndroidAnnotations utiliza un enfoque ligeramente diferente: clases generadas automáticamente, que, una vez que aprendes a usarlas, son extremadamente simples. Aún más útil es que le permite la inyección de dependencia "basada en el nombre". Por ejemplo, @ViewById ListView myUserList; indica a la biblioteca que asigne este campo con un layoutListView con el mismo nombre.

AndroidAnnotations también es increíblemente rápido, pero lo logra de una manera algo diferente a ButterKnife. En lugar de la inyección de dependencia de vinculación en tiempo de ejecución, AndroidAnnotations crea una duplicación de tiempo de compilación de todas las actividades afectadas e inserta su lógica de conexión en ellas, lo que le permite obtener el mismo rendimiento que obtendría con la lógica codificada a mano.

Pero las capacidades de inyección de AndroidAnnotations van más allá. Puede inyectar tanto el estado como el diseño en una actividad.

Implementación de anotaciones de Android:

 @NoTitle @Fullscreen @EActivity(R.layout.my_layout) public class MyActivity extends Activity { @ViewById ListView customerList; // auto-binded to R.id.customerList @App MyApplication app; // auto-binded to app object @AminationRes Animation fadeoutAnimation; @UiThread void updateUI() { // main thread action } }

La última anotación requiere un poco más de explicación: una tarea común para una aplicación de Android de subprocesos múltiples es cambiar de subprocesos de fondo (o de trabajo) al subproceso de avance (o principal o de IU), que es el único que permite el acceso a los componentes de la IU. . Esta tarea, aunque no es compleja, a menudo es necesaria e implica una codificación desordenada:

 new Handler(Looper.getMainLooper()).post(new Runnable() { logic goes here } ); // NO ANNOTATIONS

En AndroidAnnotations, todo lo que necesita hacer es anotar su función con @UiThread, y ahora se garantiza que siempre se ejecutará:

 @UiThread void updateUI() {..} // WITH ANNOTATIONS

Tenga en cuenta que esta anotación se aplica a las clases de componentes estándar de Android (actividades, servicios, etc.). Pero, ¿qué sucede cuando también quiero anotar mis propias clases?

Aquí, AndroidAnnotations presenta un nuevo concepto, el de EBean . Todo lo que tiene que hacer es marcar su clase como tal usando @EBean y listo:

 @EBean public class MyNonComponentClass { @SystemService NotificationManager notifManager; @Bean MyOtherClass dependency; @UiThread void updateUI() { // main thread work goes here } }

EventBus: comunicación entre componentes simplificada

La biblioteca EventBus convierte un problema que ha perseguido a los desarrolladores de Android durante años en un paseo por el parque. La comunicación entre componentes nunca ha sido más sencilla: utilice un modelo pub/sub simple para comunicarse entre dos partes de su sistema.

Animación de EventBus

El uso del bus de eventos dará como resultado un código más robusto, ya que lo obliga a desacoplar sus componentes entre sí.

Su servicio de sondeo en segundo plano ya no necesita estar al tanto de sus fragmentos para alimentarlos con eventos de cambio.

El uso de EventBus es sencillo.

un. Crear clases de eventos. Trabajar con POJO aquí es lo mejor:

 class NewUserEvent { String fullname; String address; String role; // add getters and setters }

B. Cree métodos de manejo de eventos en la clase, cualquier clase a la que desee suscribirse para estos eventos:

 class MySubscriber { @Subscribe public void newUserHandler(NewUserEvent event) { // handle NewUserEvent } @Subscribe public void newUserHandler(AnotherEvent event) { // handle AnotherEvent } }

Pero bueno, cualquier desarrollador de Android con poca experiencia se detendría y preguntaría en este punto: ¿Cuál es el modelo de subprocesamiento de estos controladores? ¿Y puedo obligar a un controlador a que se salga del hilo principal si, por ejemplo, implica el acceso a los componentes de la interfaz de usuario? Buena pregunta…

De forma predeterminada, todos los métodos de controlador se ejecutan en un subproceso de trabajo tomado de un grupo de subprocesos que EventBus asigna y mantiene. Si necesita un método de controlador para ejecutar en el hilo principal, expanda sus anotaciones de suscripción de la siguiente manera:

 @Subscribe(threadMode = ThreadMode.MAIN) public void runOnMainThreadHandler(AnotherEvent event) { … }

Advertencia: ¡No abuses de esta función! Las operaciones de ejecución prolongada nunca deben ejecutarse en el subproceso principal , e incluso con operaciones rápidas, tenga cuidado. Abrumar el hilo principal es la forma más segura de hacer que su aplicación sea lenta, nerviosa y básicamente menos divertida para sus usuarios.

C. Administre el ciclo de vida de registro de EventBus de su clase de suscriptor, es decir, ¿cuándo se conecta y cuándo se desconecta del bus? Un flujo de registro razonable para una actividad sería:

 class MySubscriberActivity extends Activity { @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); // START RECEIVING EVENTS HERE } @Override public void onStop() { EventBus.getDefault().unregister(this); // NO MORE EVENTS super.onStop(); } }

Lo anterior es, por supuesto, solo un ejemplo. Puedes realizar el (des)registro en cualquier lugar que elijas.

D. Y finalmente, disparar un evento:

 EventBus.getDefault().post(new MyEvent(“I'm here”));

Hay mucho más que saber sobre el uso de EventBus: eventos de multidifusión (el comportamiento predeterminado), uso de eventos fijos, hilos de entrega, prioridades y más. Pero lo anterior es suficiente para que pueda comenzar con esta tecnología simple pero poderosa.

OkHttp: HttpClient de Android con esteroides

Esta es la forma en que debería haberse escrito HttpClient de Android. Muy simple, muy inteligente. La biblioteca OkHttp se ocupa internamente del bucle de reintento, la compresión automática de carga útil, la compatibilidad con Http/2, la agrupación de conexiones y el almacenamiento en caché de respuestas para que pueda evitar el acceso innecesario a la red.

Animación de la biblioteca OkHttp

El uso de OkHttp es obvio.

PUBLICACIÓN Http:

 OkHttpClient client = new OkHttpClient(); MediaType JSON = MediaType.parse("application/json; charset=utf-8"); RequestBody body = RequestBody.create(JSON, json_str); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); return response.body().string();

Http OBTENER:

 OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(urls[0]) .build(); Response responses = client.newCall(request).execute(); String jsonData = responses.body().string();

OkHttp también es compatible con funciones tan útiles como redes asíncronas, solicitud de consulta de ruta de redireccionamiento, consulta de caché local y más. Siéntase libre de usarlos donde sea necesario. La mayoría de los desarrolladores usan OkHttp como un reemplazo más inteligente para el cliente HTTP predeterminado de Android, HttpURLConnection. De hecho, todo este proyecto comenzó como una bifurcación privada para HttpURLConnection.

Me encanta su robustez: se suma inmediatamente a su capa de red.

Con solo unas pocas líneas de código, OkHttp hace que su aplicación se comporte como si pasara noches depurando y optimizando su capa de red.

Picasso: ¡Hay una buena razón por la que Google también lo usa!

Picasso es la forma más simple y sólida de administrar la descarga, el almacenamiento en caché, el cambio de tamaño y el recorte de imágenes.

Esta declaración:

 Picasso.with(context).load(url).resize(50,50).centerCrop().into(imageView)

Hará esto por ti:

  • Conéctese a una URL remota.
  • Descarga una imagen.
  • Guárdelo en un caché LRU local que también administrará por usted.
  • Cambie el tamaño de la imagen original antes de cargarla en la memoria.
  • Ejecute todo lo anterior en un grupo de subprocesos administrado por Picasso.
  • Use la imagen redimensionada para completar su imageView.
  • Antes de cualquier ejecución futura, verifique el caché local para asegurarse de que realmente sea necesario un viaje de ida y vuelta a la red.

Crear el conjunto de tareas anterior requeriría muchas horas de trabajo, incluso para un desarrollador experto. Y eso supone que recuerdas todo. ¿Qué pasa si olvidaste, digamos, la parte de cambio de tamaño?

Animación de la biblioteca Android de Picasso

Bueno, en el dispositivo Android promedio, una aplicación no obtiene más de 50 a 60 Megabytes de RAM, y el factor de píxeles a bytes para la mayoría de los dispositivos Android es 4. Esto significa intentar cargar una imagen de 13 megapíxeles desde la tarjeta SD. requeriría 52 Megabytes de RAM. En otras palabras, su aplicación colapsaría inmediatamente.

Este es solo un ejemplo de la fuerza de Picasso. Una de las primeras cosas que hago al optimizar/depurar un proyecto heredado con uso intensivo de medios es cambiar toda la carga de imágenes a Picasso. Te sorprendería el impacto que este simple paso tiene en la calidad de la aplicación.

Uno de los testimonios más sólidos del poder de esta biblioteca: muchas de las muestras de código de Android de Google de los últimos dos años emplean a Picasso para cargar imágenes.

ActiveAndroid: ORM sin sobrecarga de rendimiento

ORM, abreviatura de mapeo relacional de objetos, se hizo popular en los días de J2EE. Le permite almacenar sus POJO y recuperarlos de una base de datos sin tener que convertirlos en campos separados.

ActiveAndroid ORM

¿Es útil? En gran medida, ya que le permite escribir una gran parte de su aplicación sin codificar ninguna instrucción SQL.

También es muy eficiente. En los viejos tiempos, las plataformas ORM dependían en gran medida de la reflexión y eran conocidas por ser lentas. Las plataformas modernas, incluido ActiveAndroid, son mucho más rápidas y, para la mayoría de los requisitos prácticos, no sufrirán sobrecargas de rendimiento por la codificación SQL sin formato.

¡Sin declaraciones SQL codificadas a mano, sin sobrecarga de rendimiento!

Uso:

un. Inicialice en el objeto de la aplicación extendiendo una clase de aplicación personalizada:

 public class MyApplication extends extends com.activeandroid.app.Application { … }

B. Cree POJO, derivado de una clase modelo, con clases para cada uno de los registros que planea almacenar en la base de datos. Cada uno de estos POJO puede residir en su propia tabla. Se deben usar anotaciones para especificar el nombre de los campos de la base de datos para cada miembro almacenado:

 @Table(name = "Categories") public class UserDetails extends Model { @Column(name = "Name") public String name; @Column(name = "Address") public String address; @Column(name = "Age") public int age; }

Si desea establecer un índice para un miembro, utilice la siguiente anotación:

 @Column(name = "ID", index = true) public String userID;

C. Para evitar que la biblioteca itere durante todo el tiempo de inicio con más clase, que es el comportamiento predeterminado, se recomienda encarecidamente que especifique todas las clases de su modelo en la siguiente sección de manifiesto:

 <meta-data android:name="AA_MODELS" android:value=“com.myapp.MyModelA, com.myapp.MyModelB" />

Nota: ActiveAndroid no reconocerá las clases de modelo que no aparezcan en esta lista.

D. Escribir en la base de datos:

 UserDetails usr = new UserDetails(); usr.save(); // RUNS ON A BACKGROUND THREAD

Si se necesitan varias escrituras, una forma más eficiente sería agruparlas en lotes en una sola transacción:

 ActiveAndroid.beginTransaction(); try { for (UserDetails u: userList) item.save(); ActiveAndroid.setTransactionSuccessful(); } finally { ActiveAndroid.endTransaction(); }

mi. Leer POJOs de la base de datos:

 new Select() .from(UserDetails.class) .where("name = ?", usr.getName()) .orderBy("Age") .executeSingle();

ORM fue una herramienta imprescindible durante mis días como desarrollador del lado del servidor. Tuvo una entrada algo tardía en el dominio de Android. Pero, por fin, aquí está: la programación de bases de datos es tan simple como parece. Disfrútala.

LibStreaming: transmisión de video sin dolor

La transmisión de video en tiempo real solía ser un gran dolor debido a las API no documentadas, las diferencias de versión entre SDK, el uso de reflexión y más.

Animación de la biblioteca libStreaming

Afortunadamente, libStreaming cambió todo esto al encapsular la mayoría de las complejidades de transmisión y exponer una API simple y amigable que le permite escribir una aplicación de transmisión básica en cuestión de horas.

En pocas palabras, agiliza la transmisión de video.

Para usarlo para H.264 y AAC, debe hacer lo siguiente:

un. Inicialice un objeto de sesión en el método onCreate de su actividad principal. Los objetos de sesión representan la transmisión de medios a un par:

 protected void onCreate(Bundle savedInstanceState) { mSession = SessionBuilder.getInstance() .setCallback(this) .setSurfaceView(mSurfaceView) .setPreviewOrientation(90) .setContext(getApplicationContext()) .setAudioEncoder(SessionBuilder.AUDIO_NONE) .setAudioQuality(new AudioQuality(16000, 32000)) .setVideoEncoder(SessionBuilder.VIDEO_H264) .setVideoQuality(new VideoQuality(320,240,20,500000)) .build(); mSurfaceView.getHolder().addCallback(this); }

B. En realidad, comience la sesión:

 mSession.setDestination(destination_server_url); mSession.start();

C. Detener la sesión cuando haya terminado:

 mSession.stop();

Ahora, por favor, no lo malinterprete. La transmisión en tiempo real es complicada por naturaleza y libStreaming no elimina esta complejidad. Sin embargo, hace un muy buen trabajo escondiéndolo la mayor parte del tiempo. En algunos casos, deberá lidiar con la complejidad, como al seleccionar la política de señalización entre pares, elegir la codificación de la cámara (por lo general, querrá usar MediaCodec/surface-to-buffer) o lidiar con la paquetización.

Aún así, encontrará que los buenos chicos detrás de libStreaming hicieron un esfuerzo adicional al fusionar estas complejidades sin problemas en una API fácil de usar.

LibStreaming es compatible con la mayoría de los codificadores utilizados por las aplicaciones de Android, incluidos H.264, H.263, AAC y AMR.

He cosechado grandes resultados con esta biblioteca. Varias de las aplicaciones de transmisión más populares lo usan como parte de su infraestructura. Si alguna vez encuentra la necesidad, estoy seguro de que hará que su experiencia de transmisión de medios sea mucho más fluida.

LeakCanary: detecta fugas de memoria en una línea de código

Comencemos con la motivación detrás de esta biblioteca: pérdidas de memoria . Las aplicaciones de Android son propensas a ellos, especialmente si no tiene cuidado con su codificación. De hecho, crear fugas de memoria es muy simple. Todo lo que necesita hacer es almacenar una referencia de actividad fuera de su contexto. De hecho, incluso almacenar una referencia a un único objeto de vista fuera del contexto de su actividad creará una fuga.

¿Por qué? Porque una vista (todas las vistas, de hecho) almacena internamente una referencia de contexto a su actividad contenedora. Siempre que se mantenga una referencia a la vista, el recolector de elementos no utilizados no podrá reclamar la actividad que la contiene, junto con su contenido, incluidos los elementos de diseño, la jerarquía de vistas y los recursos.

Mantener una referencia a una actividad de fuga no siempre es obvio como parámetro estático. Cada vez que cree una clase interna o genere un hilo dentro de una actividad, se creará una referencia a esa actividad y la actividad no podrá recuperarse hasta que se complete esa clase interna o hilo.

Las filtraciones, por supuesto, no son exclusivas de Android, pero al ser un sistema móvil con recursos de memoria limitados, el impacto es más inmediato.

La filtración de una referencia a una sola actividad que consume muchos recursos a veces es suficiente para bloquear su aplicación con una excepción de "memoria insuficiente".

Biblioteca LeakCanary

¿Cómo puedes protegerte contra ellos? Comience con prácticas de codificación rigurosas , por supuesto. Pero no todos somos desarrolladores de Android experimentados, e incluso los desarrolladores experimentados a veces olvidan las reglas.

Las revisiones periódicas del código con énfasis en las fugas de memoria pueden ser útiles, pero llevan tiempo. Además, algunas fugas son realmente astutas y difíciles de detectar con una mera revisión del código.

Usar la herramienta de memoria de DDMS es una excelente manera de saber, con el tiempo, si su aplicación tiene fugas. Definitivamente deberías usarlo. Sin embargo, no le dirá qué está causando la fuga.

Aquí viene leakCanary al rescate. Es el mejor detector de fugas de memoria que existe y proporciona una detección automática de fugas para todas sus actividades, como en una o dos líneas de código.

Para usarlo, simplemente inicialice leakCanary con el objeto de su aplicación onCreate() :

 public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); // more initialisations } }

Y tu estas listo. LeakCanary controlará las fugas de memoria y enviará una notificación si detecta una.

LeakCanary logra esta magia inyectando automáticamente un objeto llamado ActivityRefWatcher en todas sus actividades y monitoreando su conteo de referencias después de que se haya llamado onDestroy() . Un conteo de ref > 0 en una actividad destruida solo puede significar una fuga.

Importante: la detección de fugas solo funciona para aplicaciones en modo de depuración. Nunca realice pruebas de fugas (bueno, no con LeakCanary) en un APK en modo de lanzamiento.

Pero, ¿qué pasa si quiero probar otras partes de mi sistema en busca de fugas? Aquí, LeakCanary ofrece un objeto llamado refWatcher, que de hecho es el valor de retorno de la llamada de inicialización:

 refWatcher = LeakCanary.install(this);

Se puede utilizar para observar valores que pronto se recuperarán. Más precisamente, valores que creo que pronto serán reivindicados. Para ello llama al:

 refWatcher.watch(my_soon_to_be_reclaimed_obj);

La biblioteca le informará si este objeto no se ha liberado poco tiempo después de la llamada de vigilancia.

No pude encontrar el valor de este “poco tiempo” en ninguna parte, pero probablemente no sea tan importante. Con leakCanary, las cosas simplemente funcionan. No tiene precio.

Resumen

Los desarrolladores experimentados acortan días y semanas de sus fases de codificación y depuración utilizando estas bibliotecas, por lo que no hay razón para que usted no pueda hacer lo mismo.

En resumen, esto es lo que mi selección de bibliotecas de Android puede hacer por usted:

  • ButterKnife : el código autoinyectado lo ayudará a eliminar gran parte del código repetitivo de su aplicación. Es la última inyección de código para Android. ¿Necesito decir mas?

  • Anotaciones de Android: use clases generadas automáticamente increíblemente rápidas e inyección de código basada en nombres para ahorrar tiempo sin penalizar el rendimiento por la lógica codificada a mano.

  • EventBus : desacople los componentes para obtener un código más robusto, la comunicación entre componentes nunca ha sido tan simple.

  • OkHttp : un reemplazo inteligente para HttpURLConnection, con soporte para redes asincrónicas, solicitud de consulta de ruta de redireccionamiento, consulta de caché local y más.

  • Picasso : manipulación de imágenes optimizada que es tan buena que ahora Google la usa. Es un gran ahorro de tiempo en proyectos pesados ​​de medios y ciertos proyectos heredados.

  • ActiveAndroid : ORM simplificado sin sobrecarga de rendimiento.

  • LibStreaming : transmisión de video en tiempo real, utilizada por las principales aplicaciones de transmisión.

¿Son estas las únicas bibliotecas de Android que valen la pena? Ciertamente no. Pero te prometo esto: usar cualquiera de ellos en tu próximo proyecto te hará un desarrollador mucho mejor. Si quieres verlos en acción, echa un vistazo a mi GitHub.

Si ya está utilizando algunos o todos ellos, o si está utilizando bibliotecas alternativas, le insto a que comparta sus experiencias en los comentarios a continuación.

Relacionado: Android 7.0 para desarrolladores: nuevas funciones, mejoras de rendimiento y otras cosas que no le interesarán