Secciones

jueves, 25 de febrero de 2016

Enfoque nativo en el desarrollo de aplicaciones móviles

Alternativamente podríamos titular esta entrada como hacer aplicaciones móviles multiplataforma y no morir en el intento.

Introducción


A la hora de hacer aplicaciones, el 90% de los dispositivos Android usan solo Java, y la mayoría de iOS usa Objective-C/Swift frente al uso de código C++ en ambas. Este sistema puede servir para muchos casos, pero implica en tener que codificar en 2 plataformas distintas. Por otro lado, si se separa el diseño en un SDK en C++ que contenga la lógica, y se usa un modelo vista – controlador, delegando el aspecto y la interacción a la parte móvil, conseguimos programar la lógica solo una vez, en C++, y tener separadas las interfaces en en cada plataforma. Se puede dar una vuelta de rosca más, y usar una web o framework que genere la misma interfaz para Android e iOS, aunque por el momento no hay una herramienta lo suficientemente potente o consolidada que ofrezca unos resultados de calidad.

Este enfoque no llama nada la atención si alguna vez has utilizado QT para hacer la interfaz de tu librería, en lugar de usar directamente ventanas de OpenGL o interfaces a tu S.O de escritorio. Incluso, puede usarse QT para hacer aplicaciones Android e iOS, pero aun no veo maduro el tema. Y por supuesto este diseño sirve para estos casos tradicionales en escritorio, hay muchas formas de afrontar esta problemática, pero voy a centrarme más en el caso concreto de los sistemas móviles.

Dropbox por ejemplo, utiliza este sistema porque creen que tiene muchas ventajas, puedes leer más sobre como hacen sus diseños en esta entrada.

Diseño


El diseño separa 2 grandes partes:
  • Por un lado, a nivel de aplicación, la interfaz o capa de presentación y el front-end, encargado de la comunicación al usuario con la librería encargada de la lógica.
  • Por otro lado, a nivel de motor, el back-end que procesa la entrada desde front-end para recibir y enviar todos los datos necesarios para el correcto funcionamiento a la capa de aplicación. 

Como se puede ver en la ilustración, la App Android y la App de iOS pueden diferir en lógica y elementos. Al ser 2 S.O distintos cada uno tiene sus peculiaridades, aunque ya he dicho antes que hay frameworks que pueden facilitar este desarrollo y podría ser delegada esta tarea en ellos.

Los front-end serán las capas encargadas de “traducir” el lenguaje de aplicación al nativo. En el caso de Android consistirá en una interfaz JNI que cambie funciones y valores de Java a C++ y viceversa, haciendo un uso correcto del back-end. En el caso de iOS la interfaz será entre Objective-C/Swift y C++. Aunque tengan diferente sintaxis o diferentes patrones de diseño, es deseable que sean calcados, para tener un conocimiento homogeneo del funcionamiento para todas las plataformas.

Añadir que en la parte de front-end, además de hacer llamadas a nativo, es deseable poder recibirlas. De esta forma se pueden hacer callbacks o llamadas asíncronas que notifiquen eventos en la capa de presentación. La forma más sencilla de hacer esto es proveer al inicio de la aplicación de un puntero a función, o callback que sea llamado de forma asíncrona y la aplicación pueda procesar esas peticiones usando threads o hilos coordinados.

Habitualmente uso la siguiente terminología:
  • Front-end de Android, InterfaceFromCJNI para llamadas de C++ a Java, e InterfaceToCJNI para las llamadas de Java a C++
  • Front-end de iOS, InterfaceFromONI para llamadas de C++ a Objective-C e InterfaceToCONI para el otro sentido. Usando un singleton y delegates tan comunes en iOS, se pueden unir en una sola clase.
  • Back-end, es conjunto, en C++ y lo suelo colocar como un Facade con llamadas estáticas por su versatilidad y flexibilidad si se rediseña el SDK.
  • Core o lógica serán todas las llamadas encargadas de la gestión lógica de la aplicación deseada. Algunas directamente llamadas desde la fachada, pero no tienen porque estar todas. De esta forma puedes hacer distintas fachadas en función de las configuraciones al exterior.

Diferencias


Pros:

  • Ahorras el programar la lógica en distintos lenguajes 2 veces.
  • Más fácil reciclar el código para usar también en escritorio y otros S.O
  • Separación lógica de diferentes comportamientos.
  • Con algo de código extra puedes adaptar un mismo core para aprovecharlo en distintos proyectos.
  • Permite la gestión externa de errores mediante logs y variables.

Contras:

  • Back-end vs core puede ser redundante.
  • Toda la lógica tiene que tener un puente hasta el core, muchas veces código repetitivo.
  • No es común el perfil de programador


Logs a bajo y alto nivel

 

Aunque hoy en día cualquier programa se puede debugear con muchas herramientas, gdb, eclipse, Visual Studio, Xcode... No hay que subestimar el uso de logs estratégicamente colocados en nuestro código. Por varias razones:
  • Dan información util.
  • Ayudan a aislar errores de forma rápida.
  • Son rápidos, no necesitas parar la ejecución.
  • En Release también funcionan.
Por ejemplo, podemos conseguir que los logs de warnings comenten que esperaban algún dato o estado y que se ha seguido ejecutando a pesar de ello. Se puede informar errores producidos en la ejecución, y se puede terminar o no con ella. O simplemente se puede informar de que se pasan por diversos Checkpoints o zonas, para saber que partes se han ejecutado y que otras partes no.

Los logs toman una especial importancia en plataformas móviles, porque son más dificiles de debuguear que los ejecutables normales, cuesta más tiempo hacerlo, y a veces es un verdadero quebradero de cabeza.

Por supuesto esta información no sustituye el uso de debuggers, pero es complementaria y muy útil si son colocados de forma estratégica. Además, si usas alguna macro, se pueden mostrar o dejar de mostrar según interese con el cambio de alguna definición. Una de mis headers favoritos para logs es este.


Peculiaridades de Android

 

Android es una de las plataformas móviles más extendida actualmente. Por desgracia, el ecosistema Android cambia constantemente y las herramientas de desarrollo se han ido adaptando a lo largo del tiempo. Además, hay una gran fragmentación de dispositivos, no es algo intrínsicamente malo, pero seo hace que haya que testear muchos dispositivos diferentes y el diseño de interfaz tiene ciertas peculiaridades. Por cierto, aunque lioso al principio, una vez comprendido es fácil de usar y potente, en el caso de iOS no es así, porque al principio contaban solo con 1 tipo de dispositivo, y el número ha ido creciendo a lo largo del tiempo, y los parches para adaptarse a todos ellos con el diseño de una interfaz son un desastre.

Cosas que debes saber antes de desarrollar en Android
  • Normalmente necesitas saber Java, usar C++ es complicado y engorroso al principio.
  • Para manejar una librería C++ necesitas usar JNI, es decir, traducir llamadas y variables de java a C++
  • En Java tienes que cargar la librería dinámica (*.so) de C++, Ejemplo 
  • Para realizar llamadas de C++ a Java puedes usar un puntero a función y recoger mensajes, como podeis ver aquí.
  • El verdadero reto es crear las llamadas desde C++ usando JNI
  • Una buena estrategia puede ser usar todo librerías estáticas, y crear una librería dinámica forzando al compilador que meta todo el código para evitar problemas con factorias. Para GNU en Cmake deberías TARGET_LINK_LIBRARIES(cmakeproject -Wl,--whole-archive yourcpplibrary -Wl,--no-whole-archive)
Para sabes más sobre JNI mira la documentación oficial, o busca tutoriales.


Peculiaridades de iOS

 
iOS es otra de las plataformas más extendidas. Es más dificil publicar aplicaciones en la store que en Android, debido a sus multiples restricciones en los procesos de validación. Normalmente los dispositivos son más caros, y sus usuarios están más dispuestos a pagar. Hace unos años era más fácil hacer interfaces, porque no había variedad de tamaños, pero ultimamente han salido diversos modelos, y se han sacado de la manga los constrains y auto-layouts para lidiar con diversos dispositivos. Habiendo usado los de Android e iOS, me quedo sin duda con Android. El sistema de APPLE no está nada pulido, da muchos comportamientos imprevistos, y la teoría no se corresponde para nada con la práctica. En Android no es tan complicado.

Cosas que debes saber antes de desarrollar en iOS.
  • Objective-C es una variante de C/C++, su integración es sencilla y no necesitas un JNI.
  • El standard de ficheros de *.m, pero si lo renombras a *.mm aceptará código C.
  • Solo se puede linkar librerías estáticas, por temas de seguridad (según ellos)
  • El ejecutable final debe tener por tanto todas las librerias, y su configuración es totalmente manual.
  • Es recomensable usar variables user-defined en el proyecto, combinadas con $(SRCROOT) y paths relativos puede tener proyectos multiusuario sin tener que cambiar la configuración de forma manual entre ordenadores.
Con toda esta información espero que os podais hacer una idea de como hacer unos diseños eficientes. Para cualquier sugerencia o retoque, aportación de información y bibliografía, poneos en contacto conmigo.

No hay comentarios:

Publicar un comentario