Cómo migrar un juego desarrollado con SDL2 en Windows a Android
“El hombre, en su miserable condición, levanta con la mente complicadas arquitecturas y cree que aplicándolas con rigor conseguirá poner orden al tumultuoso y caótico latido de su sangre” Álvaro Mutis
Nunca digas no
Durante los últimos 18 años, que he estado trabajando como freelancer, me han preguntado al menos una vez al mes si trabajo mobile. Es decir, si hago aplicaciones para el play store. Mi respuesta indubitable siempre ha sido “No“. A veces “Yo no trabajo eso” con énfasis en “eso“. Y cuando estoy realmente de mal humor “Que no chico, que no“.
Hoy, estoy descargando Android Studio (AS), el jdk actualizado, y otras librerías para hacer mi primera aplicación “comercial” en el play store. He trabajado algunos proyectos formando parte de un equipo y mi trabajo ha consistido en resolver “detalles“. En estos casos hemos usado Qt. Esta es la primera vez que hago un proyecto 100% utilizando Android Studio, yo, solo.
Lo que pasa es que yo aprendo lentamente: las primeras 3 reglas que debemos aprender en la vida de los negocios son:
- No permitas que nadie dirija tu vida (“Claim your life“)
- Aprende a ser despiadado (“Learn to be an asshole“)
- Nunca digas no. (“two are enough!!!”)
Tardé 41 años para aprender la primera regla, 37 segundos para aprender la segunda y 18 años para aprender la tercera. Nunca es tarde para aprender, ¿cierto?
Si eres programador freelance, tienes sólidos conocimientos de negocios y quieres expandir tus horizontes (¿quién no?), mobile es una apuesta inevitable. Además, no se trata de hacer un mmo con temática espacial y que requiere 3 años de desarrollo (como khpx). Los juegos mobile son, casi por definición, fáciles de jugar y “fáciles” de programar (¿cierto?)
Los juegos en el play store tienen que ser de bajo nivel, nivel idiota por decirlo de alguna forma. Estilo flappy bird. Y por lo que se puede ver con khpx, a mi me gustan los juegos complicados, y tardar años en ellos. Así que voy a tratar de migrar mis juegos cortos (que tengo unos cuantos) y luego paso a juegos complicados.
“¿Tardar años en ellos?” ¿Qué clase de loco soy? ¿Cuál es el problema? Si 3 años te parece demasiado, piensalo de nuevo. Por ejemplo, este señor dedicó 11 años de su vida a hacer un Line Rider Track!!!
A continuación mis anotaciones sobre el proceso de migrar tilemovers, un juego de tiles, que se mueven, y son tiles, (y se mueven) y la idea es mover uno de ellos desde la posición inicial, hasta la posición final. Listo. Esa es toda la documentación. The end.
Esta decisión de colocar khpx en suspenso y trabajar juegos pequeños no es al azar. Hubo mucha intelectualización (una palabra que acabo de inventar). Baste decir que a veces, un resultado temprano es necesario. Hay otras consideraciones pero este post está demasiado largo, así que no va a pasar la edición final. Aquí algunos comentarios sobre juegos pequeños:
“Hacer juegos cortos e intensos:
piensa en haiku, no en épico.
Piensa en poesía, no en prosa.”
Más adelante estaré migrando otros: un juego de memoria, un juego tipo asteroides y un juego tipo counter strike pero muy simple y por supuesto monojugador.
Voy a trabajar en:
- C++ (no hay sorpresas aquí)
- SDL2 (podría ser Qt pero la parafernalia es demasiado aparatosa (y parafernálica), lo cual no digo que la de SDL2 no lo sea, pero es más por el tema de AS)
Retos
SDL2. El problema con SDL2 es SDL. Hay dos versiones y en todas partes (StackOverflow e inclusive algunos sitios de tutoriales sobre SDL*) se confunden las filosofias de los dos. El enfoque es ligeramente diferente y las llamadas son diferentes. SDL2 se parece a SDL pero es diferente. De hecho, el mencionado juego de memoria está implementado en SDL, y lo he estado viendo de reojo, o sea, abro el archivo, lo veo, salgo corriendo, me tomo nerviosamente un whisky, lo vuelvo a ver, me tomo otro whisky. Etcétera. Creo que lo voy a reescribir en SDL2 sin siquiera verlo otra vez. De hecho voy a borrar todo el directorio que lo contiene:
format c: \ memorybreaker
Listo lo borreddddddddddddd
Es infinitamente repetitivo. Después de 50 juegos realizados comenzar a migrar/adaptar un juego a una nueva plataforma es extremadamente aburrido y repetitivo. Los tiempos en que algo nuevo me entusiasmaba ya pasaron así que tengo que armarme de entusiasmo y optimismo.
Android. Ya he hecho algunas cosas, nada del otro mundo así que esto es nuevo, o casi nuevo. No conozco Android Studio así que veremos.
Ahora, el problema desde el punto de vista de negocios es que este proyecto (publicar una aplicación en el play store y ganar dinero con eso) se supone que debe ser algo que no debe durar más de 2 semanas de comienzo a fin: voy ya por la segunda y apenas la aplicación está lista para comenzar la migración. Además de la migración, falta el marketing, otras muchas cosas. (NOTA: desde que escribí eso hasta hoy ya han transcurrido 1 2 3 4 5 meses).
Es como el proceso de ingresar en Tiragarde Sound (World of Warcraft) un alt, debería ser rápido (10, 15 minutos?) Pero en realidad son 2-3 horas. Finalmente yo soy un programador como Arthur Bishop (el personaje interpretado por Charles Bronson en Asesino a precio fijo (ves, otra película que en español tiene un mejor nombre (en inglés es The mechanic (los que han leído otros post en este sitio sabrán de qué estoy hablando ))))
Arthur Bishop recibe contratos para asesinar a funcionarios rivales, millonarios etc. Pero él es un asesino metódico e infalible: él estudia a su objetivo durante semanas para descubrir los puntos débiles de seguridad en su rutina diaria. Una vez identificado el momento perfecto cuándo su victima es más vulnerable, procede a ejecutar a su objetivo.
Yo procedo de una forma similar. Tengo 3 días pensando un algoritmo para generar los niveles del juego de una forma automática. Pronto estará liquidado. Pero va a tomar su tiempo, necesito verificar todos sus ángulos.
Nota del editor: lo anterior fue escrito alrededor del 19 de julio, y los planes segun se puede entender, es que tilemovers mobile esté listo en 1-2 semanas. Hoy es 16 de octubre.
El proyecto se demoró un poco porque se me ocurrió hacer un algoritmo para resolver los puzzles. Resulta ser que este es un problema complicado. Pasé un mes haciendo un programa no recursivo que algunas veces resuelve el puzzle, otra veces comienza a generar soluciones con muchos pasos innecesarios. Entonces pasé 2 semanas haciendo un programa para eliminar los pasos inncesarios. Finalmente decidi probar los puzzles a mano. El programa quedará como un caso de estudio. Si alguna vez lo termino colocaré el resultado en github como una contribución a la humanidad.
Tratar de hacer un programa para resolver uno de estos puzzles es algo bien difícil de justificar. Es como Kurz (Apocalipsis ahora) entrenando para ser paracaidista a los 38 años. “¿Por qué hizo algo así?”
Yo tengo una buena razón. Lo prometo. Es una muy buena razón. Estaba tratando de invertir la búsqueda de buenos puzzles. Al diseñar los puzzles se llega a un momento en que las ideas se agotan. Así que quería hacer un algoritmo capaz de resolverlos, y de esa forma encontrar puzzles con soluciones difíciles. Es decir, diseñar el puzzle basado en soluciones difíciles de encontrar. Este camino no funcionó.
El juego tiene 5 4 3 niveles de dificultal. El primer nivel Fácil Normal tiene 512 256 128 puzzles (todos probados a mano. En algún sitio tengo notas sobre algunos de ellos, los publicaré en un próximo post). El segundo nivel Difficult Imposible tiene 64 32 28 y el tercer nivel Kill me now Apocalipse tiene 12 puzzles. En futuras versiones a estar disponibles pronto agregaré más puzzles.
Versión Windows
Como está programado en SDL2 potencialmente se puede publicar además de Windows en Linux, Ios y Android. La versión windows se puede descargar aquí:
https://agnasg.itch.io/tilemovers
Es gratis. Si usted lo desea puede contribuir con $2 o más, lo cual me permitirá hacer más juegos similares (más rápidamente).
También puede contribuir en mi página de patreon que me ayudará a terminar mi mmo, khpx5 y otros juegos del baúl de los zombies.
Versión Android
Nota: esta sección discute mis vicisitudes migrando tilemovers a Android. Es una aventura con mucha acción, intriga, emboscadas, romance, suspenso y un desenlace feliz.
Versión Android en el Google Store.
Protagonistas:
Android Studio: el ambiente de desarrollo (algo así como Visual Studio)
Gradle: el build system, permite organizar y ejecutar la compilación y linkeo con todos los componentes involucrados.
ndk: Native Development Kit, un conjunto de herramientas que permiten usar C++ con Android.
Para la instalación de android, cosa que que hago por primera vez (como dije al comienzo yo nunca he trabajado con app mobile) seguí esta guía. https://lazyfoo.net/tutorials/SDL/52_hello_mobile/android_windows/index.php. Hay otras guías en google, pero esta me pareció la mejor.
Nota (04-01-2021): otra guía es esta que está ligeramente actualizada y tiene explicaciones pormenorizadas. Lamentablemente la encontré muy tarde. Como dice el autor, “estás entrando en el reino de desarrollo Android… respira profundo…“.
Como suele suceder en estos casos la guía está desactualizada y en el paso 5 las cosas comenzaron a aparecer diferentes. En el momento de la instalación, no aparece la pregunta sobre la ubicación del Android SDK (que debe ser inicializado a c:\androidsdk). Esa pregunta aparece luego de instalar y ejecutar por primera vez la aplicación.
La guía está tan desactualizada que indica instrucciones para Android SDK 16 (la versión que estoy instalando en octubre de 2020 dice Android 30. Hay errores que no me sucedieron a mi, y otros que sí: por ejemplo, el paso 15 Error:(688) Android NDK: Module main depends on undefined modules: SDL2.
Problemas encontrados: muchos, incluyendo algunos inexplicables. Uno de los tantos escoyos (algo que particularmente sucede con aplicaciones Java) es que hay múltiples versiones de multiples paquetes y tablas de compatibilidades entre paquetes y multiples errores debido (quizás) a esas incompatibilidades. Por ejemplo, aqui aparece las compatibilidades entre versiones entre Android Gradle Plugin y Android Gradle: trabajando este proyecto tuve que revisar innumerables veces stackoverflow, y encontrar tablas como esta con mucha frecuencia.
En resumen, el error que no se encuentra SDL2 se resuelve tal como se explica en la guía. El error “fatal error: ‘string’ file not found” se presta para confusión porque el archivo makefile “Android.mk” no tiene la línea:
#APP_STL := stlport_static
Simplemente se agrega y el error desaparece. (esta línea no hace falta, ver más abajo)
Otro error que apareció no está incluído en la guía:
Unsupported method: TaskExecutionResult.getExecutionReasons()
La consola indica que hay una incompatibilidad con Gradle sin dar mayores detalles. Un paso que me salté en la guía es el paso 12 porque a mi no me apareció el error:
Minimum supported Gradle version is 4.1. Current version is 2.14.1.
Pues bien, en la nueva versión, o debido a que yo tenía la versión 3.1.x el mensaje de error es diferente (después de varios tropiezos, resbalones, caidas, risas, gritos desconsolados a la media noche, alaridos y otras manifestaciones de frustración apareció sin explicación el mensaje ” Minimum supported Gradle version“. Si siempre estuvo ahí, no estoy seguro ahora) . Para resolverlo se abre el archivo build.gradle del proyecto y se modifica la línea
classpath ‘com.android.tools.build:gradle:3.1.4’
a
classpath ‘com.android.tools.build:gradle:4.0.2’
y en el archivo gradle-wrapper.properties la línea distributionUrl debe decir:
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
(Aquí 6.1.1 se debe sustituir por la última versión).
La ventana va a desplegar un enlace para hacer Sync, lo que va a permitir descargar la actualización.
Salir del proyecto y volver a entrar, a veces funcionó para restaurar valores y hacer desaparecer errores. Como estoy trabajando como trabajamos todos los programadores, es decir, sin leer el manual, la mayor parte del tiempo estoy en un modo de descubrimiento. En stackoverflow y en otros sitios se habla de cosas como “Edit Configurations“, en Android Studio eso podría sea cualquier cosa, si no sabes exactamente dónde está (está dentro del menu de “Run”, ¿cómo es que no se me ocurrió?)
Android Studio es un caballo difícil de domar. ¿Cómo se definen las arquitecturas que se deben compilar? Por defecto, las compila todas, "armeabi-v7a", "armeabi"
, “x86”, “x86-64”, “arm64-v8a” (respuesta). Si voy a cambiar un asset, qué debo hacer? Si presiono “rebuild”, comienza a compilar todo de nuevo, eso no es lo que quiero, sino que el apk final incluya los nuevos assets. ¿Cómo se crea un nuevo proyecto basado en uno viejo? Me sorprende que “Import” de un proyecto viejo es lo mismo que “Abrir el proyecto“, aparte de abrirlo no hace nada más (esto puede generar problemas, ¿cierto?) (al final simplemente se copia la carpeta y se abre el proyecto desde la nueva carpeta).
Eventualmente conseguí las respuestas a estas y otras preguntas, pero hay que estar preparado para dedicarle tiempo (es decir, la curva de aprendizaje tiene subidas bien pronunciadas).
Mi impresión es que el proceso de instalación y ajustes de la aplicación tiene muchos detalles y cambian a medida que cambian las versiones de los plugins y paquetes. Lo mejor es descargar la aplicación y comenzar a trabajar paso por paso. Eventualmente todo funciona.
SDL es de temer
Hay que tener cuidado porque SDL no necesariamente es inofensivo. Puede causar dolor, y mucho. Por ejemplo perdí 1 hora tratando de descubrir por qué mi programa al ejecutar decía que no encontraba el SDL_main en la librería. Resulta que este define
#define SDL_MAIN_HANDLED
funciona en windows pero no en Android (es decir, en Android debe estar deshabilitado)
Usualmente esto debería resolverse rápidamente con una visita a la documentación, pero tenía chrome cerrado porque
mi máquina tiene solo 8gb y Visual Studio, Android Studio y el emulador mobile se lo devoran todo.
Luego para resolver este problema temporalmente dupliqué mi memoria agregando a mi artillería
mi laptop, ahora chrome tenía 4gb para él solito.
¿Otras emboscadas?
La variable abiFilters que es la que indica cuales arquitecturas van a ser soportadas por el ejecutable de Android (las posibilidades son ‘armeabi-v7a’, ‘arm64-v8a’, ‘x86’, ‘x86_64’) solamente debe modificarse en el archivo build.gradle de la aplicación ( el que está en la carpeta app) también se puede ajustar en Application.mk a través de APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 pero o es ignorado o el efecto es desconocido (al menos para mi).
Como un truco de productividad hay que tener a la mano el archivo build.gradle(:app) y cambiar la variable ‘abiFilters’ a la arquitectura del dispositivo que estás usando. Por ejemplo, pescando el problema con la base de datos sqlite corrupta, tuve que provar altenativamente entre el emulador Pixel 2 API 30 (abiFilters ‘x86’) y mi dispositivo celular (un antiguo BLU R1 HD) (abiFilters ‘armeabi-v7a’)
Otra nota curiosa: el emulador de Pixel 2 API 30 no es tan lento como dice se puede trabajar en él. Tuve que pasar varios minutos (más de los que desearía admitir) porque no encontraba cómo llegar a Settings->Applications para borrar ek juego e instalarlo de nuevo (también se puede hacer en AS -> Tools -> AVD Manager). Resulta que en google pixel el menú con todos los iconos se abre deslizando desde abajo en el borde inferior hacia arriba (en todos los dispositivos androides que he conocido es de arriba hacia abajo).
Todavía otra nota mucho más curiosa: implementar el movimiento deslizando los tiles require una combinación SDL_FINGERMOTION y SDL_FINGERUP. Ya yo había implementado todas las combinaciones y validaciones para el movimiento de los tiles, pero todavía tuve que crear nuevas funciones especialmente para SDL_FINGERMOTION. La forma correcta es olvidar la cantidad de movimiento reportado, al menos las 2-3 horas que estuve tradando de convertir esos deltas en la resolución correcta, en windows y en todas las combinaciones de dispositivos en android, determiné que es inútil. Igualmente inútil es tratar de convertir el valor float 0…1.0 que arroja SDL_FINGERUP a la resolución correcta. Mi implementación final es determinar la dirección del movimiento con SDL_FINGERMOTION y hacer los cálculos cuando se dispara SDL_FINGERUP .
En los últimos meses de 2020 estuve trabajando simultáneamente Python, Java, PHP, C++ y C#. Este último me tiene sorprendido, le huía como al Covid 19, pero ahora me gusta, me entusiasma, y siento una felicidad tan grande cuando cruzo el puente y entrego una flamante aplicación funcionando, que me sorprende que he hecho tan pocos desarrollos en C#.
Java. Las energías en el universo se compensan, y como Java sabe que yo lo odio, porque tenemos historia, él me paga con odio. Por ejemplo, el código para abrir una página web en android. Al parecer hay una forma de hacerlo desde ndk, pero como había tantas preguntas huí por la derecha como el león Melquíades. Además la versión java era tan simple, mjummmmm…
public static void openWebPage(String url) { Uri webpage = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, webpage); if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } }
Ok, al tratar de compilar “eso” comenzó a decir “cannot find symbol”. Uri.parse fue fácil simplemente hay que agregar
import android.net.Uri;
y lo mismo con Intent intent = new Intent(Intent.ACTION_VIEW, webpage); hay que agregar:
import android.content.Intent;
El resto fue otra historia. ¿por qué sistemáticamente toda la documentación ignora/soslaya/oculta cuál es el archivo que hay que importar? Inclusive las respuestas en stackoverflow no incluyen los “import” respectivos, por razones que desconozco. Yo creo que todos los 200 programadores java en el mundo saben algo que yo no sé. O en alguna documentación hay una clave de como deducir el “import” basado en el nombre de la función.
Lo ignoro.
Que la documentación es imprecisa, incompleta o sospechosamente ambigua es algo que se ve por todas partes. Tengo múltiples ejemplos, pero veamos el de agregar el icono a la aplicación. Hayq eu olvidarse de stackoverflow porque esto ha cambiado mucho durante los años, y hay respuestas que corresponden a 2013, 2016 y 2018, ninguna sirve para la versión actual (Android Studio 4.0).
El manual dice que hay que usar un tool llamado Image Asset Studio. No dice que este tool se invoca directamente dentro de Android Studio, debemos suponer eso (aunque la forma que es descrito parece un tool aparte/externo). Pero más abajo nos dan las instrucciones (incompletas) de cómo invocarlo desde AS. Dice que en la ventana del proyecto se debe seleccionar la vista Android, tal como se muestra en la siguiente imagen:
Luego dice como paso 2 ” Right-click the res folder ” es decir hacer click derecho sobre la “carpeta res” pero ahi solamente está Gradle Scripts y “External Build Files”. ¿Donde está la carpeta res? Oh, sí, aparece otra vez app pero eso contiene los fuentes del sistema eso no debe ser, ¿verdad?. Pues sí es, dentro de app está la carpeta res. Pero eso lo omite el manual porque sí.
INSTALL_PARSE_FAILED_NO_CERTIFICATES
Este error aparece al tratar de instalar la version release, y, al parecer, no se ha completado correctamente el proceso de signature del apk. Vamos a ver que dice stackoverflow. Esto me dejó con un verdadero overflow.
- Rebuild la aplicación a mi no me funcionó. Y eso que esperé los 10 minutos que tarda en mi equipo para generar el paquete para las 4 arquitecturas, release, etc.
- Puesto que dice “APK signature verification failed.” debe ser algo con la firma. Cuando hago build dice Generate Signed Bundle… App bundle(s) generated successfully… así que el proceso Falló exitosamente. No entiendo.
- Es algo con la firma pero “v2SigningEnabled true” en build.gradle no funciona.
- Tampoco parece que tenga que ver con las versión de la firma v1 o v2 (full APK Signature), porque AS ya no tiene opción para indicar cuál versión se va a usar (no la tiene cuando generas un Bundle, pero cuando generas un APK, si aparece)
- Si en vez de usar “Generate Signed Bundle” seleccionamos “Generate Signed APK” el error es el mismo.
- Finalmente el problema se resolvió validando que en “Project Structure” > “Modules” > “Signing Configs” esté seleccionado una configuración de firma correcta. Por defecto “Signing Configs” no está seleccionado. Esto debe ser un bug.
- Jugué un extra tiempo aquí tratando de encontrar “Project Structure” . Es una opción en el primer menú, File, está entre “Settings” y “Other Settings“. Ya había estado ahí hace un mes y todavía se me pierde.
- La primera vez que intenté esto me generó un error “Keystore file not set for signing config dev“, así que probé con la otra configuración “$signingConfigs.debug” y funcionó sin errores. El problema es que estaba trabajando con build release así que no quería nada “debug” involucrado.
- Edité el resto porque ya este post ha crecido desmesuradamente. Aquí tenía al menos otras 3 notas adicionales con otras escaramuzas. Sigamos.
SQLite no tiene la culpa
La compilación de tilemovers avanzó sin problemas hasta que apareció este error al momento de ejecutar el linker:
../toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include/c++/v1/ios:547: error: undefined reference to ‘std::__ndk1::ios_base::clear(unsigned int)’
Android Studio no indica por ninguna parte cuál código en mi aplicación está generando el problema, simplemente lanza ese error sin anestesia. Supuse que podía ser SQLite ya que yo estaba usando el código fuente directamente y no la librería que hay en la página de descargas:
sqlite-android-3330000.aar
Así que seguí este procedimiento para agregar directamente la librería. Pero eso no quitó el error.
Luego el error se hizo viral, y cada cambio generaba errores similares pero con diferentes funciones:
undefined reference to ‘std::__ndk1::__next_prime
undefined reference to ‘std::__ndk1::locale::locale
Aquí encontré una sugerencia, que tengo una mezcla entre compilación con el parámetro c++_static y el parámetro gnustl_static, es decir unas partes las estoy compilando con c++_static y otras (por ejemplo una librería) con gnustl_static, pero yo no estoy usando librerías externas más allá de SQLite.
Es muy gracioso cuando se presentan situaciones como estas (porque entre otras cosas no debería suceder). En mis pruebas, traté de utilizar SQLite librería (el archivo .aaa que se encuentra en la página de descargas) y removí el código fuente sqlite.c. Importé el archivo .aaa, quedó agregado como un nuevo módulo en el proyecto. Hice mis pruebas y no hubo cambios, el error continuó. Entonces traté de remover el módulo: ups, eso no es tan fácil como hacer RIGHT-CLICK Remove. No señor, eso en sí es una epopeya.
Aparentemente es suficiente removerlo del archivo y en la sección dependencies quitarlo de ahí (de build.gradle ). Pero eso no lo remueve del árbol del proyecto. Alrededor de stackoverflow se indica que basta con:
Removerlo en Build -> Edit Libraries And Dependencies (sigue en el árbol)
Removerlo del archivo settings.gradle (no está ahí)
Right click en el módulo y seleccionar “Select Open Module Settings ” (eso no existe, tampoco “Module Settings”, debe ser en una versión vieja)
No, “Module Settings” está en Right click sobre Project (no, no y no)
En settings.gradle buscar la línea':modulenameyouchooseduringimport'
y borrarlo de ahi (no).
Al final como no parecía haber forma de borrarlo, comencé hacer cosas sin sentido. Por ejemplo, salir y entrar de Android Studio: el módulo aparece, y luego de unos 20-30 segundos desaparece (Android Studio tarda en mi máquina 2 minutos para cargar completamente). Parece que al borrarlo de build.gradle no se refleja el cambio en el árbol del proyecto a menos que salgas de la aplicación y entres de nuevo (eso es un bug, ¿correcto?)
En diversos sitios dice (por ejemplo) que ese error (undefined reference to ‘std::__ndk1::locale::locale ) se corrige agregando en el archivo build.gradle lo siguiente:
externalNativeBuild {
cmake {
cppFlags "-DANDROID_STL=c++_shared"
}
}
Lamentablemente nada de eso funcionó probando todas las combinaciones.
Así que comencé a eliminar archivos en el juego con la esperanza de conseguir información adicional, y efectivamente así fue. Al ir eliminando módulos los errores cambiaban, pero siempre eran del tipo undefined reference to ‘std::__ndk1:: xx (algún método). Hasta que apareció
undefined reference to ‘std::__ndk1:: to_string ()
Eso encendió el bombillo sobre mi cabeza. ¿ndk/gradle/android Studio no está aceptando C++11, solamente C99? Porque std::to_string () es una típica llamada agregada en C++11. Pero ya había revisado eso casi desde el comienzo, en el Android.mk estaba el APP_STL := c++_shared que supuestamente (aquí lo confirman) habilita el soporte hasta C++17.
Cualquier combinación de esto falló:
APP_STL := c++_shared APP_STL := c++_static # Enable c++11 extentions in source code APP_CPPFLAGS += -std=c++11 LOCAL_CFLAGS := -std=gnu++11 LOCAL_FLAGS := -std=c++11
Lo cual me llevó a un dead-end, un cul-de-sac, un callejón sin salida. Investigando cómo viajar en el tiempo usando gradle, descubrí que en la carpeta
\tilemovers\app.cxx\ndkBuild\debug\armeabi-v7a
Está el archivo donde se muestran todos los flags que clang está usando para cada uno de los archivos del proyecto. La línea que corresponde a mi archivo de prueba no tenía el flag:
–std:c++11
Curiosamente otros archivos sí tienen este flag, por ejemplo el archivo de SDL2
SDL2\src\hidapi\android\hid.cpp
Así que simplemente fui a esa carpeta y revisé el archivo Android.mk
La instrucción que se debe colocar es esta:
LOCAL_CPPFLAGS := -std=c++11
(eso no aparece en ninguna parte hasta donde me alcanzó la paciencia para revisar la documentación. Más adelante veremos que esto tampoco es necesario)
Al agregarlo la línea de comandos de mi archivo con std::to_string () apareció con el flag correcto (-std=c++11) pero el error siguió apareciendo.
Mi siguiente prueba fue determinar si estaba usando ese flag, así que elimine y agregué algo típico de c++11. Lambda expressions. Algo como esto (tomado de aquí):
auto unhex = [](char c) -> int {
return c >= ‘0’ && c <= ‘9’ ? c – ‘0’ : c >= ‘A’ && c <= ‘Z’ ? c – ‘A’ + 10 : c >= ‘a’ && c <= ‘f’ ? c – ‘a’ + 10 : -1;
};
Funcionó. Así que si estaba compilando tomando en cuenta las extensiones de c++11. ¿Y entonces? ¿Otro cul-de-sac?
Recordé que en algún sitio alguien decía que en alguna parte se estaba deshabilitando o asignando en forma incorrecta el uso de ndk. Comencé a buscar y revisar todos los archivos dentro de app y ahí estaba:
Application.mk
dentro de app\jni.
# Uncomment this if you're using STL in your project # APP_STL := c++_shared
Funcionó (el error undefined std::to_string() desapareció)
Es decir, Android Studio ignora cualquier cosa que coloquemos en build.gradle, Android.mk y utiliza la opción en Application.mk
Lamentablemente las secciones dedicadas a C++ en la documentación no mencionan esto (por ejemplo aquí), pero sí en la sección dedicada a ndk. que por supuesto tiene sentido, dado que ndk es específico para C++. Como siempre, una vez resuelto el problema, resulta raro cómo es que no encontré la solución al comienzo.
Este error undefined std::to_string() representó unas 2 horas de escaramuzas e intrigas, pero fue divertido. Me recordó algo que siempre he criticado, la proliferación de archivos de configuración, algo muy común en sistemas java. En el caso de Android Studio hay que estar pendiente de 5 archivos de configuración. Afortunadamente, esto es algo que se hace una sola vez la primera vez que se instala/crea un proyecto, y más nunca en la vida. De aquí en adelante es un copy-paste de los proyectos.
¿Leer el manual? ¿Quién? ¿yo?
¿Por qué no leo la documentación? Porque es inútil. Por ejemplo, en el trabajo de programación diario, (edición-compilación-decepción-frustración-aceptación-repetición) en Android Studio vamos a ver con mucha frecuencia esto abajo en el UI:
Si usted va a trabajar con Android Studio prepárese a ver esa imágen dando vueltas durante minutos y más minutos. Mi record personal es 15 minutos. Algunas veces se queda atascado ahi y no te deja hacer nada más. No es que está procesando, está actualizando alguna librería de alguna parte de internet (eso no está en la documentación, eso lo leí en stackoverflow) Claro que eso se puede deshabilitar, simplemente hay que ir a View-Tool Windows-Gradle. No aparece una ventana popup, sino una ventana en el UI. En el borde superior hay un icono con dos rayas cruzadas por una línea: ese ícono significa trabajar offline. Si está presionado (en negrillas) significa que Gradle está trabajando offline, de lo contrario está online. ¿Conseguir todo eso en la documentación? Ni lo sueñes.
Historias similares ocurrieron con otras actividades Cómo haces para actualizar la aplicación en el dispositivo? si la ejecutas del Android Studio, no se actualiza sino que se ejecuta la versión instalada, no importa si AS sabe que acabas de hacer dramáticas modificaciones al código fuente, por defecto, ejecuta la versión en el dispositivo. what the fuck!. (Simply uninstall the application from your mobile device and then run your app, no eso no funciona= Al final, resulta ser que es un bug. Hay que hace clean y rebuild. Y AS es muy gracioso: Luego de tardar varios minutos haciendo varias cosas termina diciendo:
CONFIGURE SUCCESSFUL in 16s
AS tiene serios problemas de conducta
AS tiene sus peculiaridades. En internet dicen con frecuencia que es una inmensa montaña de !#@%. (AS is complete joke and bs. I am just tired of this nonsense ) Y a veces pienso que es posible. Por ejemplo, yo no he leido el manual, pero qué tan difícil puede ser hacer build de la aplicación. Inspeccionando el UI no hay ningún icono que diga “Build”, pero si hay un menu “Build”. Dentro del menú hay un “Make Project” que no me suena a “Build“, eso debe hacer otras cosas aparte de “Build” luego hay un “Make module app” eso si se acerca bastante a lo que quiero hacer, porque la otra opción, “Run generate sources Gradle Task” nuevamente se aleja demasiado de “Build“. Pero hay un problema: “Make module app” está deshabilitado. Luego de buscar todo el UI observamos que en la ventana del proyecto “app” no está seleccionado. Si hacemos click se torna azul (seleccionado) y ahora “Make module app” está habilitado. Nuestro siguiente problema es que no vemos qué está pasando y eso se debe a que la ventana de “Build” está oculta: tenemos que hacer click sobre el tab “Build” en el sector del UI de ventanas de outputs. Si tenemos suerte y el “Build” resulta exitoso, ahora podemos hacer “Run” (aleluya: si hay un ícono “Run”). Nuevamente tenemos que hacer click sobre el tab “Run” en el sector del UI de ventanas de outputs. ¿Eso no debería ser automático? Bien, ejecutamos el juego, el cambio que tratamos de hacer no funcionó así que tenemos que ir al editor, corregir y repetir el ciclo. ¿Saben qué? Nuevamente hay que hacer click en “app” en la ventana del proyecto porque nuevamente se deshabilitó, hacer click en la ventana de output de “Build”, y así horas y horas haciendo cosas inútiles como si no tuviéramos otras cosas que hacer. Hmm sí, AS tiene serios problemas de conducta.
Nota final: al publicar la aplicación en el Play Store (también conocido como Google Store), el Play Console me arrojó este error “Debes utilizar otro nombre de paquete porque “org.libsdl.app” ya existe en Google Play.”. En toda la explicación precedente hay que incluir que se debe colocar un nombre nuevo en Android Studio. Yo sabía eso hace 6 meses e intenté hacerlo, pero al parecer, a pesar de que el nombre “tilemovers” aparece en todas partes, desde el punto de vista muy personal de Android, la aplicación se llama “app”.
Ok respira profundo:
- Hay que modificar en res/values/string the string name=”app_name” y colocarle el nombre de la aplicación. Lo hice desde el comienzo, No es suficiente.
- …
- …
- Bueno etc. La solución (como explico aquí) es cambiar en build.gradle applicationId “org.libsdl.app” a applicationId “org.libsdl.tilemovers”
Nota sopotocientos: Si al publicar en Play Store o Google Play o como se llame te aparece este error: ” Tienes que usar un código de versión diferente para el APK o Android App Bundle porque ya cuentas con un archivo con el código de versión 1. “, tienes que corregir el valor en build.gradle(:app) versionCode 1 => versionCode 2 o el siguiente valor. También aparece en el manifiesto (AndroidManifest.xml) pero no tiene efecto (es decir, lo que cuenta es lo que aparece en build.gradle(:app) versionCode .)
Cómo Actualizar (crear un nuevo Release) la aplicación (actualizado 14-07-24)
Al entrar en play console y seleccionar la aplicación (se debe mostrar el nombre y logo de la aplicación en la esquina superior derecha), aparece al lado izquierdo un sidemenu, se debe seleccionar Production y luego presionar “Crear un nuevo Release” ( Create New Release), subir la aplicación en formato Google Bundle, por ejemplo app.release.aab (no en un formato apk), colocar la descripción del nuevo release y seguir las instrucciones (Next, Save, Send to review).
09-07-2024: Para generar el bundle en Android Studio se debe hacer Build => Generate Signed Bundle / APK.
El archivo para hacer el upload al play console está en tilemovers\app\release. Sustituya tilemovers por el nombre de su aplicación.
También puede estar en tilemovers\app\outputs\bundle\release todavía no he podido descubrir la diferencia.
09-07-2024: Nuevo error: Your Android App Bundle is signed with the wrong key. Ensure that your app bundle is signed with the correct signing key and try again
Si yo no he hecho nada sino actualizar el úmero de versión esto no debería pasar. Vi en Google que hay que hacer Build ->Rebuild Project y se debe resolver. A mi me funcionó pero dejé a un gentío en google diciendo que eso no funciona. Pobrecitos.
09-07-2024: Nuevo error: You uploaded an APK or Android App Bundle which has an activity, activity alias, service or broadcast receiver with intent filter, but without ‘android:exported’ property set
Para resolver esto, hay que ir a AndroidManifest.xml, buscar <activity android:name=”SDLActivity”> y en esa subseccion agregar al final
android:exported="true"
Por ejemplo, en esa subsección, aparece al final (en mi caso): android:screenOrientation="portrait"
Hay que agregarlo en la línea siguiente. Eso debe resolver el problema (mucha suerte)
24-07-24: A pesar de todos los cambios y actualizaciones tratando de satisfacer el apetito de la play console, sigue apareciendo este mensaje:
We will soon take action because your app does not adhere to Google Play Developer Program policies.
[Pronto tomaremos medidas porque tu aplicación no cumple las políticas del programa de desarrolladores de Google Play.]
Esto se puede deber a que tienes una versión en alguna parte (producción o testing) que no has actualizado a la última version del API (hoy es 34). La respuesta es que no hay forma de remover este mensaje hasta que el alto concilio de Trento, el Sacro Imperio Google y demás instancias apropiadas lo decreten. Tanto en los foros de Google como en Stackoverflow se indica que no hay forma de hacer "delete" a las versiones viejas, solo se indica que puedes deshabilitar el "track" (a veces tampoco se puede deshabilitar el "track", yo no puedo hacerlo).
Adios, me despido de ti
Mi cuaderno de notas está lleno de anotaciones sobre las peripecias con el manejador de bases de datos sqlite, pero ya eso es tema de otro post. Se cansa uno.