Dam M08 2112
Dam M08 2112
MULTIMEDIA
Y DISPOSITIVOS
MÓVILES
Desarrollo de aplicaciones multiplataforma
Ilerna
ILERNA, centro autorizado con código 25002775 (Lleida), 28077294 (Madrid) y 41023090 (Sevilla) www.ilerna.es
© Ilerna Online S.L., 2021
© Imágenes: Shutterstock
Reservados todos los derechos. No se permite la reproducción total o parcial de esta obra, ni su incorporación a un
sistema informático, ni su transmisión en cualquier forma o por cualquier medio (electrónico, mecánico, fotoco-
pia, grabación u otros) sin autorización previa y por escrito de los titulares del copyright. La infracción de dichos
derechos puede constituir un delito contra la propiedad intelectual.
Ilerna Online S.L. ha puesto todos los recursos necesarios para reconocer los derechos de terceros en esta obra
y se excusa con antelación por posibles errores u omisiones y queda a disposición de corregirlos en posteriores
ediciones.
Solucionario........................................................................................256
1
ANÁLISIS DE TECNOLOGÍAS PARA APLICACIONES
DISPOSITIVOS MÓVILES
EN
Programación multimedia y dispositivos móviles
Introducción
A lo largo de los temas de este libro veremos el desarrollo de aplica-
ciones móviles en entorno Android con lenguaje Kotlin. El enfoque
de este es completamente práctico desde el primer momento,
aportando los conceptos teóricos que aplicamos en el desarrollo
de una aplicación real.
Desconexión
7
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
Seguridad
Consumo de batería
Memoria y almacenamiento
A día de hoy, los dispositivos móviles cada vez incorporan más me-
moria y capacidad de almacenamiento, aunque no por ello debe-
mos desaprovecharla, ya que es un recurso limitado que no pode-
mos ampliar sin adquirir un nuevo dispositivo. Por ello, las
aplicaciones móviles deben optimizar el uso de los recursos, ya que
también debe compartirlos con otras aplicaciones.
8
Programación multimedia y dispositivos móviles
9
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
Plataformas
Plataforma Lenguaje Compañía
de destino
Android, iOS y
Xamarin C# Microsoft
Windows.
La mayoría de
Unity plataformas de
Unity C#
Technologies escritorio, móviles y
de juegos.
Android, iOS,
Fluter Dart Google Windows, Mac,
Linux, Web.
Android, iOS,
Ionic JavaScript Open source
Windows, Web.
10
Programación multimedia y dispositivos móviles
11
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
5
2 1
12
Programación multimedia y dispositivos móviles
13
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
14
Programación multimedia y dispositivos móviles
15
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
16
Programación multimedia y dispositivos móviles
17
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
Por último, indicaremos el API Level mínimo con que será compa-
tible la aplicación. Veremos este concepto en los siguientes temas
con más detalle y la importancia de su selección.
18
Programación multimedia y dispositivos móviles
1.5. Emuladores
En el ciclo de desarrollo de cualquier tipo de aplicación ne-
cesitaremos probar el funcionamiento y corregir los posibles
errores que surjan. Cuando desarrollamos aplicaciones de es-
critorio o web, estas pruebas las podemos realizar en nuestro
propio ordenador.
¡RECUERDA!
19
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
20
Programación multimedia y dispositivos móviles
21
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
22
Programación multimedia y dispositivos móviles
ponte a prueba
23
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
24
Programación multimedia y dispositivos móviles
• Dispositivo ligero.
• Que podamos usar en el exterior mientras realizamos una
actividad.
• Acceso rápido y fácil.
• Sensores de tipo ritmo cardiaco, GPS, brújula.
Para este tipo de aplicaciones, los dispositivos idóneos son los de
tipo smartwatch, que disponen de las características indicadas y
tienen ventajas sobre los otros dispositivos para cumplir con este
tipo de aplicaciones.
25
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
26
Programación multimedia y dispositivos móviles
1. C
omponent Tree o árbol de vistas: muestra la lista jerárquica
de vistas y componentes gráficos que componen el layout.
2. L
ayout Display o visor de pantalla: renderiza el layout tal
como se ve en la pantalla del terminal o emulador, señalando
además los límites de cada componente. Es una especie de
captura de pantalla de lo que muestra el dispositivo en un mo-
mento dado.
3. L
ayout Inspector toolbar o barra de herramientas del
inspector: presenta algunas herramientas disponibles para
analizar el layout.
4. A
ttributes o tabla de propiedades: muestra las propiedades
gráficas del componente seleccionado actualmente.
En cualquier momento puedes cambiar la aplicación que estás
depurando, mediante el selector Select Process. Recuerda que el
proceso debe ser depurable, es decir, tener activada la depuración.
Muy posiblemente, una aplicación descargada desde el store no te
va a permitir que la depures.
27
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
28
Programación multimedia y dispositivos móviles
PARA + INFO
29
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
Created
Bajo o nula Primer plano, tiene el foco Started
Resumed
Stoped
Alta Segundo plano, no visible
Destroyed
30
Programación multimedia y dispositivos móviles
CONCEPTO
Actividad
creada
onCreate0
onStart0 onRestart0
Usuario toma
la actividad onResume0
El proceso de la Actividad
aplicación se termina en marcha
La actividad ya
no es visible Usuario toma
la actividad
onStop0
onDestroy0
Actividad
finalizada
31
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
32
Programación multimedia y dispositivos móviles
companion object {
private const val TAG = “MainActivity”
}
33
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
34
Programación multimedia y dispositivos móviles
ponte a prueba
35
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
36
Programación multimedia y dispositivos móviles
Historia de usuario
Tarea
Test
37
Tema 1: Análisis de tecnologías para aplicaciones en dispositivos móviles
BUSCA EN LA WEB
38
Programación multimedia y dispositivos móviles
Recapitulando
39
2
PROGRAMACIÓN DE APLICACIONES PARA
DISPOSITIVOS MÓVILES
Programación multimedia y dispositivos móviles
2.1.1. Herramientas
En realidad, para empezar a programar aplicaciones Android, solo
necesitamos una herramienta, que, además, es gratuita: el entorno
de desarrollo Android Studio. El instalador de Android Studio se
asegurará de que nuestro sistema dispone de una versión adecua-
da del JDK de Java y el SDK de Android. Podríamos elegir otros IDE,
pero Android Studio es el estándar de Google, y quizá el mejor. Tiene
tantas opciones que es posible que al principio solo necesitemos
una pequeña parte. Ya vimos algunas de sus funcionalidades en el
tema anterior, y, mientras lo usamos, aprenderemos otras tantas.
41
Tema 2: Programación de aplicaciones para dispositivos móviles
42
Programación multimedia y dispositivos móviles
43
Tema 2: Programación de aplicaciones para dispositivos móviles
Planificación
44
Programación multimedia y dispositivos móviles
45
Tema 2: Programación de aplicaciones para dispositivos móviles
Despliegue y mantenimiento
46
Programación multimedia y dispositivos móviles
47
Tema 2: Programación de aplicaciones para dispositivos móviles
48
Programación multimedia y dispositivos móviles
Patrones de diseño
49
Tema 2: Programación de aplicaciones para dispositivos móviles
object FileUtil {
// Las funciones que realmente solucionan nuestras tareas
fun funcionesUtiles() {
//...
}
}
Clean Code
Controllers
i
eb
ev
Interface Adapters
D
Entities
Use Case
Presenter Output Port
Ga
Use Case
r
DB UI
te
te
es
s
Pr
Use Case
External Controller Input Port
interfaces
Fuente: https://blog.cleancoder.com/uncle-
bob/2012/08/13/the-clean-architecture.html
50
Programación multimedia y dispositivos móviles
SOLID
Son las siglas de cinco principios Clean que pueden ayudarnos a mejorar
la calidad de nuestro código. En la práctica, quizá no conviene esforzarse BUSCA EN LA WEB
en llevarlos todos a cabo, pero algunos son imprescindibles en cualquier
Uncle Bob sigue siendo
proyecto, por simple que sea. Vamos a enumerarlos rápidamente. un referente, podemos
aprender mucho en su
S: single responsibility. Cada clase, cada método y cada variable de-
blog:
ben tener un único objetivo, no pueden valer para dos cosas distintas,
porque de hacerlo estarías complicando el código innecesariamente. https://blog.
cleancoder.com/
O: open-closed. Las entidades deben estar abiertas para ser extendi- uncle-bob/2012/08/13/
das con más funcionalidad, pero no debe permitirse su modificación the-clean-architecture.
una vez diseñadas, de modo que la funcionalidad primordial siga sien- html
do igual.
L: Liskov substitution. Precisamente por el principio anterior, este
principio asegura que una clase puede ser sustituida en el código por
cualquiera de sus subclases sin modificar el funcionamiento, porque
hemos asegurado que llevará a cabo las mismas operaciones básicas,
aparte de otras añadidas.
I: interface segregation. No debemos mezclar métodos de casos de
uso diferentes en la misma interfaz, porque causará problemas. Es
mejor crear diferentes interfaces, cada una justo con los métodos que
la definan.
D: dependency inversion. La funcionalidad de una clase o método
debe depender solo de abstracciones, no de código concreto. Es de-
cir, una clase no debería estar compuesta en su interior de objetos de
otras clases, porque, de cambiar esas clases, la clase principal fallaría.
La clase debe depender de interfaces, y las clases reales que ejecuten
las funciones cumplirán esas interfaces.
51
Tema 2: Programación de aplicaciones para dispositivos móviles
2.3.1. Compilación
Antes de que nuestro código en Kotlin pueda ser ejecutado en el
procesador virtual del emulador, o en el real de un terminal físico,
debe pasar por varios filtros, conocidos en conjunto como compila-
dor. Cuando seleccionamos la opción Run o el icono de flecha verde
del menú de Android Studio, el compilador analizará el código y
comprobará que no tenga errores, realizará algunas mejoras en
él para aumentar su eficiencia y lo transformará en un lenguaje
comprensible para el procesador del móvil. Si es la primera vez
que ejecutamos nuestra app, Android Studio creará algunas nue-
vas carpetas dentro del directorio de proyecto. Por ejemplo, bajo
la carpeta “app”, creará “build”, “outputs”, “apk” y, dentro, “debug”.
En esa última carpeta, el entorno de desarrollo guardará el archivo
app-debug.apk, que es un APK especialmente diseñado para ser
depurado en el emulador o terminal que tengamos conectado. Es
importante recordar que ese APK no vale para ser distribuido, es
solo un APK de pruebas.
52
Programación multimedia y dispositivos móviles
53
Tema 2: Programación de aplicaciones para dispositivos móviles
Los archivos de configuración de Gradle utilizan un lenguaje llamado Groovy, y aunque no es nece-
sario que lo dominemos, será conveniente que tengamos una idea de cómo funciona. Si abrimos
el archivo de módulo build.gradle que ha generado Android Studio, podremos observar un código
similar a este:
No es necesario que lo entendamos todo. Aquí nos interesa comprender las partes defaultConfig
y buildTypes. En la primera van las configuraciones generales que tendrá por defecto nuestra app.
ApplicacionId es el nombre completo de nuestra app, con nuestro dominio: “com.tudominio” y el
nombre de la app: “tuaplicacion”. Debe ser único en el Play Store, o Google no te dejará subir la app.
En defaultConfig se define también la versión de nuestra app, el SDK mínimo con el que es compa-
tible y la versión del SDK para la que está desarrollada. En buildTypes está la opción release, para
definir cómo será compilada la app en modo producción. En este caso, está desactivada la opción
minify. Esta opción permite indicar si queremos reducir, optimizar y ofuscar nuestro código dentro
del APK para que sea menos pesado, más rápido y más difícil de decompilar por un hacker. Así que,
cuando terminemos de depurar, deberíamos activar esta opción usando true en lugar de false para la
variable minifyEnabled.
54
Programación multimedia y dispositivos móviles
Veamos un ejemplo de cómo modificar el build.grade por defecto de Android Studio para definir
nuestras propias variantes de compilación. Primero definamos nuestros tipos de compilación:
signingConfigs {
release {
storeFile file(“archivo_de_firma.ks”)
storePassword “mipassword”
keyAlias “mialias”
keyPassword “mipassword”
}
}
buildTypes {
debug {
applicationIdSuffix “.debug”
debuggable true
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(‘proguard-android-optimize.txt’),
‘proguard-rules.pro’
signingConfig signingConfigs.release
}
}
Observemos cómo hemos creado la configuración de firma signingConfigs válida únicamente para
release, ya que en debug nos basta con la firma de depuración por defecto del compilador. Dentro
de buildTypes, hemos definido debug para añadir al nombre de aplicación el sufijo .debug de modo
que podamos distinguirla del APK release. Al tener diferente nombre de app, podríamos tener la
aplicación instalada dos veces en nuestro terminal de pruebas, una en modo debug y otra en modo
release. Hemos especificado que debug sea depurable, y que en release se lleven a cabo las funciones
de minify.
Veamos ahora cómo crear un par de variantes de producto, una para la versión demo y otra para la
versión completa:
flavorDimensions “version”
productFlavors {
demo {
dimension “version”
applicationIdSuffix “.demo”
versionNameSuffix “-demo”
}
completa {
dimension “version”
applicationIdSuffix “.completa”
versionNameSuffix “-completa”
}
}
55
Tema 2: Programación de aplicaciones para dispositivos móviles
Creamos la dimensión version, que tendrá dos opciones, demo y completa. Cada una añadirá un su-
fijo al nombre de la aplicación, así como a la versión. Una vez sincronizado el proyecto con los nuevos
cambios, podríamos seleccionar la variante de compilación que deseamos ejecutar mediante la pes-
taña Build Variants que podemos encontrar en la barra izquierda de Android Studio. Veamos una
imagen de las opciones que tendríamos:
Manejar todas las opciones que permite Gradle es muy difícil, pero a medida que lo utilicemos
iremos aprendiendo, en cualquier caso, siempre es buen momento para echarle una ojeada a la
documentación oficial.
BUSCA EN LA WEB
2.3.2. Preverificación
Mientras codificamos, podemos desarrollar pruebas unitarias y pruebas instrumentales para ase-
gurarnos de que cada parte del código hace lo que se supone que tiene que hacer. Además, cuando
compilamos, cualquier error de sintaxis hará que el compilador se detenga, mostrándonos la línea
fallida con un mensaje descriptivo.
Pero aún podemos ir más allá: Android Studio nos brinda una herramienta de revisión de código llama-
da Lint, que puede ayudarnos a identificar y corregir problemas estructurales en nuestra app. Al pasar el
escáner de Lint, la herramienta nos hará un listado de problemas y recomendaciones, cada uno con un
mensaje descriptivo y con un nivel de gravedad, para mejorar el código de nuestra aplicación. Podemos
configurar Lint para que solo nos avise de errores de cierto calibre, si no nos interesan algunas de las
mejoras menos importantes que pueda sugerirnos. La herramienta Lint comprueba nuestro proyecto
Android para buscar posibles errores y posibles optimizaciones de código, de seguridad y rendimiento,
de usabilidad y de accesibilidad. Mientras desarrollas en Android Studio, Lint escanea el proyecto para
recomendarte cambios en el código. Pero también puedes inspeccionar el código manualmente desde
el menú: Analyze > Inspect Code, o lanzar Lint desde la línea de comandos: ./gradlew lint
56
Programación multimedia y dispositivos móviles
2.3.3. Empaquetado
Ya hemos hablado anteriormente de los paquetes de instalación de Android: del muy conocido APK y
del más reciente Bundle. Veamos detalladamente cómo generar estos paquetes de instalación para
la distribución de nuestra app.
En el menú Build de Android Studio disponemos de dos opciones para generar el empaquetado: Build
Bundle(s) / APK(s), que generará un Bundle o APK con el build variant activo actualmente; y Generate
Signed Bundle / APK, que nos llevará por una serie de pasos a obtener el paquete que deseamos. Esta
última opción requiere un archivo Key Store para firmar el Bundle o APK que se genere. Para que un
paquete de instalación Android pueda instalarse, debe estar firmado con un certificado digital, que
estará almacenado en el archivo Key Store. Podríamos crear el archivo en el proceso de empaque-
tado, o tenerlo ya creado y usarlo cada vez que firmamos una de nuestras apps para subirla al store.
57
Tema 2: Programación de aplicaciones para dispositivos móviles
Una vez que tenemos nuestro APK o Bundle, podremos publicarlo en el Play Store de Google o en
otros repositorios. Aunque el estándar es el Play Store, y a Google le gustaría que fuese el único, exis-
ten otros como F-Droid, Aptoide, Getjar, Uptodown o Amazon, pero tengamos en cuenta que, para
poder instalar apps de estas fuentes, tendremos que activar la opción del móvil: Ajustes > Seguridad
> Orígenes desconocidos.
Estas plataformas son gratuitas y no piden nada por subir las aplicaciones. Google tampoco pide mu-
cho más, pero debemos registrarnos como desarrolladores antes de que podamos subir nuestra pri-
mera app. En el proceso de registro se nos pedirá un único pago de 25 dólares. Merece la pena, como
programador, para poder acceder a la consola de desarrollador del Play Store y a sus herramientas.
Para los desarrolladores de iOS sale bastante más caro, está en 99 dólares anuales, y también son
mucho más estrictos con el estilo de las apps que permiten subir.
58
Programación multimedia y dispositivos móviles
59
Tema 2: Programación de aplicaciones para dispositivos móviles
BUSCA EN LA WEB Si no hubiésemos creado los build variant, el comando podría haber
sido más simple: adb shell am start -n com.ilernaonline.miapp/.
Documentación oficial de
la utilidad ADB: MainActivity.
https://bit.ly/3cQu7G3 El ADB es imprescindible para el programador Android. Si necesitas
más información sobre sus comandos, puedes escribir adb --help
en la línea de comandos o leer la documentación oficial sobre la
herramienta en la web.
60
Programación multimedia y dispositivos móviles
2.3.5. Depuración
En muchas ocasiones, veremos que la aplicación que estamos desarro-
llando tiene algún comportamiento extraño que no podemos explicar. La
mejor manera de conocer la causa del problema y arreglar el error es de-
purar la app. Tenemos diferentes alternativas. La más evidente es utilizar
la opción depuración que tiene Android Studio. Podemos ejecutar la op-
ción del menú: Run > Debug app o hacer clic en el icono debug, que es un
bichito verde. Al seleccionar la opción debug, la aplicación será instalada
en el dispositivo seleccionado en modo depuración:
61
Tema 2: Programación de aplicaciones para dispositivos móviles
El Logcat es una gran herramienta que nos permitirá ver y filtrar los
mensajes de depuración de todas las aplicaciones arrancadas en
el terminal o emulador. Debemos tener en cuenta que las aplica-
ciones instaladas en release, muy probablemente, no podrán de-
purarse de este modo, pues se desactiva la opción de depurado.
ponte a prueba Sí podrán verse, no obstante, los mensajes de las excepciones que
generen un crash durante la ejecución incluso en release.
¿Cómo se llama el encapsu-
lado que podemos generar en Otro caso que pudiera darse sería la necesidad de depurar una
Android Studio para instalar app desde un terminal que no esté conectado a nuestro ordenador
nuestras aplicaciones en mediante un cable USB. Si el dispositivo móvil está conectado a la
dispositivos sin necesidad de misma red que nuestro ordenador, por ejemplo, compartiendo la
conectarlos mediante cable?
misma red wifi, podremos depurar la app con comandos del ADB.
a) Bundle Primero tendremos que apuntar la dirección IP del terminal en la
b) APK red wifi, por ejemplo, accediendo a Ajustes > Información del te-
c) AVD léfono > Estado > Dirección IP. En nuestro caso, el móvil está en la
d) Build File 192.168.0.18. Ahora debemos conectar el móvil a nuestro ordena-
dor con el cable USB y lanzar el comando adb tcpip 5555, que defi-
ne el puerto del ADB como el 5555. Ahora podemos desconectar el
62
Programación multimedia y dispositivos móviles
2.4.1. Actividad
La actividad es la más común de las clases del SDK de Android para
crear una pantalla en nuestra app. Dentro de cada actividad, colo-
caremos los diferentes componentes de la interfaz gráfica, como
campos de introducción de texto, botones, etcétera. Toda actividad
debe declararse en el manifiesto de nuestra app (AndroidManifest.
xml), pues, de otro modo, obtendremos un error de compilación.
Normalmente, además, crearemos un recurso layout que defina los
elementos que componen la pantalla, así como su posición dentro
de ella. De lo contrario, tendríamos que crear los componentes en
tiempo de ejecución, lo que resulta más difícil y tendrá sentido solo
en algunos casos particulares.
63
Tema 2: Programación de aplicaciones para dispositivos móviles
64
Programación multimedia y dispositivos móviles
65
Tema 2: Programación de aplicaciones para dispositivos móviles
BUSCA EN LA WEB
BUSCA EN LA WEB
66
Programación multimedia y dispositivos móviles
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
67
Tema 2: Programación de aplicaciones para dispositivos móviles
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Hello World!”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />
</androidx.constraintlayout.widget.ConstraintLayout>
68
Programación multimedia y dispositivos móviles
69
Tema 2: Programación de aplicaciones para dispositivos móviles
Recuerda establecer un id en el botón para que podamos acceder a él desde el código. Volvamos al
editor de código, tendremos algo como:
<TextView
android:id=”@+id/tvSaludo”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Hello World!”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />
<Button
android:id=”@+id/btnCambiarSaludo”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Button”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/tvSaludo” />
</androidx.constraintlayout.widget.ConstraintLayout>
El editor visual ha generado el código para nuestro botón por sí solo. Volvamos al código de la acti-
vidad, y añadamos el código:
Ahora podemos ejecutar la app en un terminal o emulador y pulsar el botón. Veremos que el campo
de texto cambia su contenido original por nuestra frase.
70
Programación multimedia y dispositivos móviles
Quizá lo más importante de las clases del SDK de Android, y no solo hablamos de las actividades, es
el ciclo de vida. Ya vimos un esquema sobre el ciclo de vida de las actividades en el tema anterior,
pero merece la pena recalcarlo, pues muchos errores de programación Android son debidos a un
descuido en el ciclo de vida tanto de actividades como de fragmentos, servicios y otros componentes
del sistema operativo. Estos componentes tienen lo que se denomina un contexto, que es una especie
de identificador del recurso. De hecho, una clase Activity hereda de la clase Context a través de una
larga cadena de subclases. Vamos a comprobar cómo un descuido con los contextos puede ser un
gran problema. Añadamos un código que se ejecutará siempre que la actividad vuelva a ser visible,
sobrescribiendo la función onResume:
Este código no hace más que cambiar el texto del TextView y escribir un mensaje de error en el Logcat.
Lancemos la aplicación al terminal. Ahora rotemos el terminal. Observaremos que el identificador ha
cambiado. Esto se debe a que, cada vez que rotamos el móvil, el giroscopio o acelerómetro integrado
en el terminal avisarán al sistema operativo de que la orientación de la pantalla ha cambiado. El
sistema preguntará a la app cómo desea adaptarse al cambio de formato de portrait a landscape, o
viceversa. Por defecto, la actividad será destruida y recreada con la nueva configuración de pantalla.
Lo mismo ocurriría con otros cambios del sistema, podemos adaptarnos a ellos o dejar que el sistema
operativo destruya y vuelva a construir la actividad.
Esto no ocurre cuando llevamos la aplicación a background y la volvemos a activar. Por ejemplo, pro-
bemos a pulsar el botón de home del terminal. Nos aparece el escritorio. Si volvemos a abrir la app, el
context seguirá siendo el mismo, aunque veremos otra línea en el Logcat, porque al activarse se llama
a onResume. ¿Qué ocurriría si almacenásemos el contexto en una variable e intentásemos utilizarla
más tarde sin tener en cuenta el ciclo de vida de la actividad? Pues que estaríamos utilizando un
contexto desfasado:
71
Tema 2: Programación de aplicaciones para dispositivos móviles
btnLanzarSegundaActividad.setOnClickListener {
val intent = Intent(this, SecondActivity::class.java)
startActivityForResult(intent, REQUEST_SEGUNDA_ACTIVIDAD)
}
72
Programación multimedia y dispositivos móviles
• data: un objeto intent con los datos requeridos por la activity que
lanzamos. Podremos obtener los datos mediante diferentes mé-
todos en función al tipo de datos, como getStringExtra,
getIntExtra, getBooleanExtra, getByteExtra, etcétera.
73
Tema 2: Programación de aplicaciones para dispositivos móviles
Simplemente, un botón con id btnVolver y un TextView con un texto estático. El código de la segunda
actividad responsable de devolver los datos sería:
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_second.*
74
Programación multimedia y dispositivos móviles
<Button
android:id=”@+id/btnVolver”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:onClick=”onClick”
android:text=”Volver”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/lblMensaje” />
75
Tema 2: Programación de aplicaciones para dispositivos móviles
76
Programación multimedia y dispositivos móviles
Android Studio creará el proyecto, que tiene una clase activity lla-
mada MainActivity con un layout llamado activity_main. Vamos a
editar el layout para que tenga todos los campos que necesitamos.
Primero creamos seis campos TextView, unos servirán de etiquetas
y otros de valores. Los dispondremos así:
77
Tema 2: Programación de aplicaciones para dispositivos móviles
78
Programación multimedia y dispositivos móviles
<TextView
android:id=”@+id/tvId”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”50dp”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent”
tools:text=”6969” />
<TextView
android:id=”@+id/tvName”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”16dp”
app:layout_constraintStart_toStartOf=”@+id/tvId”
app:layout_constraintTop_toBottomOf=”@+id/tvId”
tools:text=”John Smith” />
<TextView
android:id=”@+id/tvPhone”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”16dp”
app:layout_constraintStart_toStartOf=”@+id/tvName”
app:layout_constraintTop_toBottomOf=”@+id/tvName”
tools:text=”555 123 123” />
<TextView
android:id=”@+id/lblId”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginEnd=”50dp”
android:text=”ID:”
app:layout_constraintEnd_toStartOf=”@+id/tvId”
app:layout_constraintTop_toTopOf=”@+id/tvId” />
<TextView
android:id=”@+id/lblName”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Nombre:”
app:layout_constraintEnd_toEndOf=”@+id/lblId”
79
Tema 2: Programación de aplicaciones para dispositivos móviles
app:layout_constraintTop_toTopOf=”@+id/tvName” />
<TextView
android:id=”@+id/lblPhone”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Teléfono:”
app:layout_constraintEnd_toEndOf=”@+id/lblName”
app:layout_constraintTop_toTopOf=”@+id/tvPhone” />
<Button
android:id=”@+id/btnContacto”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”32dp”
android:text=”Contacto...”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/tvPhone” />
<Button
android:id=”@+id/btnLlamar”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”24dp”
android:text=”Llamar”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintHorizontal_bias=”0.498”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/btnContacto” />
</androidx.constraintlayout.widget.ConstraintLayout>
Editemos ahora el código de MainActivity. Vamos a darles una utilidad a los dos botones que
tenemos:
btnContacto.setOnClickListener {
obtenerContacto()
}
btnLlamar.setOnClickListener {
llamar()
}
}
80
Programación multimedia y dispositivos móviles
El botón Contacto llamará a la función obtenerContacto y el botón Llamar a llamar. Veamos qué hace
la primera función:
Lo realmente importante aquí es que creamos un intent de forma diferente a como hemos visto hasta
ahora, y es porque estamos accediendo a una activity que está fuera de nuestra app. Como vemos
por los parámetros, queremos acceder a la lista de contactos que guarda el terminal en la app de
Contactos. Luego llamamos a startActivityForResult para que nos devuelva el contacto seleccionado
por el usuario. ¿Para qué sirven las demás líneas? Para pasar los controles de seguridad de Android.
Los contactos de nuestro teléfono son datos personales, y si cualquier app pudiese acceder a ellos sin
pedirle permiso al usuario, estaríamos violando su privacidad. De modo que, para acceder a cierta
información como los contactos o dispositivos como la cámara, el sistema se asegurará primero de
que el usuario nos da su consentimiento. Algunos permisos son menos importantes que otros. Los
que no son tan importantes basta con pedirlos en el manifest para que, al instalar, el usuario sepa
que la app accederá a esos datos. Los permisos más importantes deberán especificarse tanto en el
manifest como a la hora de ejecutar la app. En nuestro caso, los contactos son considerados muy
importantes, por lo que tendremos que pedir permisos tanto en el manifest:
<manifest
xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.ilernaonline.actividades”>
<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher” ...
81
Tema 2: Programación de aplicaciones para dispositivos móviles
companion object {
private const val TAG = “MainActivity”
private const val PERMISO = android.Manifest.permission.READ_CONTACTS
private const val REQUEST_PERMISOS = 1
private const val REQUEST_CONTACTO = 1
private const val REQUEST_SEGUNDA_ACTIVIDAD = 2
}
82
Programación multimedia y dispositivos móviles
83
Tema 2: Programación de aplicaciones para dispositivos móviles
2.4.2. Fragment
Aunque no necesariamente, las actividades suelen entenderse
como una pantalla completa en la que distribuir elementos gráfi-
cos como imágenes, botones y demás controles. Los fragmentos
llegaron para dar mayor flexibilidad a la composición de las pan-
tallas. Pueden ocupar toda o solo una parte de ella. El fragment
podría entenderse como una unidad de presentación, como una
subactividad. En sí mismo, sería como un componente gráfico más.
Imaginemos un fragment llamado DetallesClienteFragment que
muestre los datos de un cliente. Podríamos utilizar ese fragment,
solo o en composición con otros, en varias actividades de la apli-
cación, sin ninguna repetición de código. El paso de un fragment a
otro es más eficiente que el paso entre actividades y, además, no
es necesario declarar los fragmentos en el manifest. Los fragments,
como las activities, tienen un ciclo de vida que debemos compren-
der y respetar si queremos que todo funcione como deseamos.
Además, un fragment siempre irá embebido en una activity, que
será la responsable de dirigir sus fragments.
84
Programación multimedia y dispositivos móviles
<fragment
android:id=”@+id/nav_host_fragment”
android:name=”androidx.navigation.fragment.NavHostFragment”
android:layout_width=”0dp”
android:layout_height=”0dp”
app:defaultNavHost=”true”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:layout_constraintTop_toTopOf=”parent”
app:navGraph=”@navigation/nav_graph” />
</androidx.constraintlayout.widget.ConstraintLayout>
85
Tema 2: Programación de aplicaciones para dispositivos móviles
<fragment
android:id=”@+id/FirstFragment”
android:name=”com.ilernaonline.miapp.FirstFragment”
android:label=”@string/first_fragment_label”
tools:layout=”@layout/fragment_first”>
<action
android:id=”@+id/action_FirstFragment_to_SecondFragment”
app:destination=”@id/SecondFragment” />
</fragment>
<fragment
android:id=”@+id/SecondFragment”
android:name=”com.ilernaonline.miapp.SecondFragment”
android:label=”@string/second_fragment_label”
tools:layout=”@layout/fragment_second”>
<action
android:id=”@+id/action_SecondFragment_to_FirstFragment”
app:destination=”@id/FirstFragment” />
</fragment>
</navigation>
86
Programación multimedia y dispositivos móviles
...
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
87
Tema 2: Programación de aplicaciones para dispositivos móviles
AlertDialog.Builder(context)
.setTitle(“Borrar elemento”)
.setMessage(“¿Está seguro de que desea eliminar el elemento?”)
.setPositiveButton(android.R.string.yes) { dialog, which ->
Log.d(“Dialog”, “---------------- YES ----------------”)
//TODO: borrar el elemento
}
//Si utilizamos null como listener, el diálogo simplemente se cierra
.setNegativeButton(android.R.string.no, null)
.setIcon(android.R.drawable.ic_dialog_alert)
.show()
88
Programación multimedia y dispositivos móviles
DatePickerDialog(requireContext(),
OnDateSetListener { view, year, monthOfYear, dayOfMonth ->
Log.d(“Dialog”, “Fecha seleccionada: $dayOfMonth/${monthOfYear+1}/$-
year”)
}, 2020, 7, 16
)
.show()
89
Tema 2: Programación de aplicaciones para dispositivos móviles
TimePickerDialog(requireContext(),
OnTimeSetListener { view, hourOfDay, minute ->
Log.d(“Dialog”, “Hora seleccionada: $hourOfDay:$minute”)
}, 12, 5, false
)
.show()
90
Programación multimedia y dispositivos móviles
2.5.1. Drawables
Ya conocemos la carpeta de recursos “res” que los proyectos An-
droid tienen dentro del directorio “app”. Dentro de “res” podemos
encontrar el directorio “layout” que guarda los diseños de nuestras
pantallas, values, navigation, menu, etcétera. Encontraremos
también la carpeta “drawable”. En esa carpeta, el proyecto guar-
dará las imágenes como recursos drawables que la app utilizará en
layouts, gráficos, diálogos e iconos. Un recurso drawable es una abs-
tracción del SDK para manejar imágenes que puedan ser mostradas
en pantalla. Los drawables pueden cargarse mediante funciones
de la API como getDrawable o ser incluidas en otros recursos XML,
como layouts, con algún atributo como android:drawable o androi-
d:icon. Imaginemos que queremos mostrar una imagen en uno de
nuestros layouts: podemos utilizar el componente ImageView, que
es un contenedor de imágenes. El código sería algo como:
<ImageView
android:layout_height=”wrap_content”
android:layout_width=”wrap_content”
android:src=”@drawable/imagen” />
91
Tema 2: Programación de aplicaciones para dispositivos móviles
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.OvalShape
import android.util.AttributeSet
import android.view.View
92
Programación multimedia y dispositivos móviles
En este caso, la vista ocupará todo el fragment, pero también podríamos utilizar la vista dentro de un
layout junto con otros componentes gráficos:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”.NuestroFragment”>
<com.ilernaonline.miapp.CustomDrawableView
android:id=”@+id/view”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/button_second” />
...
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import kotlin.math.min
93
Tema 2: Programación de aplicaciones para dispositivos móviles
redPaint)
}
override fun setAlpha(alpha: Int) {
// Sobrescribir este método es obligatorio aunque no lo uses
}
override fun setColorFilter(colorFilter: ColorFilter?) {
// Sobrescribir este método es obligatorio aunque no lo uses
}
override fun getOpacity(): Int =
// Puede ser PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
PixelFormat.OPAQUE
}
NuestroDrawable hereda de Drawable. Crea un objeto Pain de color rojo, que utilizará en el método
draw que ya conocemos. En el método draw utilizamos el objeto Canvas que nos pasan por paráme-
tro para dibujar un círculo con el método drawCircle. La clase Canvas es la responsable de dibujar
todas las primitivas de imagen, se trata de un lienzo virtual que contiene las llamadas a métodos de
dibujo para finalmente presentar una imagen. Los objetos drawables pueden utilizar un bitmap, es
decir, una imagen corriente para definir su apariencia, y/o utilizar la clase Canvas para definir formas
básicas como óvalos y rectángulos. La clase ShapeDrawable que vimos anteriormente no hace más
que utilizar las funciones del Canvas para definir su aspecto.
<ImageView
android:id=”@+id/imageView”
android:layout_width=”100dp”
android:layout_height=”100dp”
app:layout_constraintBottom_toTopOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent”
android:contentDescription=”Nuestra imagen” />
94
Programación multimedia y dispositivos móviles
Para utilizar cualquier librería, lo primero que tenemos que hacer es añadirla a la lista de dependen-
cias de nuestro archivo de compilación build.gradle:
dependencies {
implementation ‘com.github.bumptech.glide:glide:4.11.0’
annotationProcessor ‘com.github.bumptech.glide:compiler:4.11.0’
}
Utilizar sus funciones es muy sencillo. Imaginemos que en nuestro layout tenemos un elemento
ImageView:
<ImageView
android:id=”@+id/imageView”
android:layout_width=”200dp”
android:layout_height=”200dp”
Glide
.with(this)
.load(“https://www.ilerna.es/themes/classic/assets/img/ilerna-online-foo-
ter.png”)
.into(imageView)
Debemos recordar, en este caso, que para acceder a internet necesitamos declarar el permiso en el
manifest de nuestro proyecto. De otro modo, el permiso nos será denegado y la app fallará:
La librería Glide es ampliamente usada en apps Android no solo por su sencillez y eficacia, sino por
la gran cantidad de opciones que presenta. Por ello, es buena idea leer la documentación oficial y
estudiar ejemplos con ella.
BUSCA EN LA WEB
95
Tema 2: Programación de aplicaciones para dispositivos móviles
96
Programación multimedia y dispositivos móviles
97
Tema 2: Programación de aplicaciones para dispositivos móviles
2.7.1. Animaciones
En este punto estudiaremos animaciones sencillas de elemen-
tos de nuestras pantallas, lo que dará una ilusión de mayor
responsividad al usuario. En el siguiente ejemplo, mostraremos
cómo animar un drawable mediante la clase AnimatedVector-
DrawableCompat.
<vector xmlns:android=”http://schemas.android.com/apk/res/android”
android:width=”107dp”
android:height=”57dp”
android:viewportWidth=”107”
android:viewportHeight=”57”>
<path
android:name=”one”
android:fillAlpha=”1”
android:fillColor=”#F00”
android:pathData=”M53,11.6c-28.9,4.6 -52.6,8.3 -52.7,8.4 -0.2,-
0 -0.3,7.7 -0.3,17l0,17 3.7,-0.6c2.1,-0.3 26.1,-4 53.3,-8.3l49.5,-
7.7 0.3,-17.2c0.1,-9.5 -0.1,-17.1 -0.5,-17.1 -0.5,0.1 -24.4,3.9
-53.3,8.5zM96.9,21.2c1.8,4 3.5,7.9 3.8,8.5 0.3,0.8 -0.3,1.3 -1.6,1.3
-1.2,-0 -2.4,-0.7 -2.7,-1.5 -1.1,-2.8 -9.4,-1.1 -9.4,1.9 0,0.8 -0.9,1.6
-2,1.9 -1.1,0.3 -2,0.1 -2,-0.3 0,-0.8 6.5,-16.4 7.6,-18.1 1.4,-2.3 3.4,-
0.3 6.3,6.3zM80,24.9c0,7.5 -0.3,9 -1.7,9.6 -1.3,0.4 -2.5,-0.3 -4.3,-
2.6 -1.4,-1.8 -3.5,-4.4 -4.7,-5.8l-2.2,-2.6 -0.1,6.1c0,4.6 -0.4,6.3
-1.5,6.8 -1.4,0.5 -1.5,-0.7 -1.3,-8.7 0.3,-8.3 0.5,-9.2 2.3,-9.5 1.4,-
0.1 3.1,1.4 6,5.4l4,5.6 0.3,-5.3c0.3,-6.3 0.8,-7.9 2.2,-7.9 0.6,-0 1,3.5
1,8.9zM58.8,21.2c1.7,1.7 1.5,6.7 -0.3,7.4 -2,0.8 -1.9,2 0.5,5.4 1.9,2.6
1.9,2.8 0.3,3.5 -1.7,0.6 -3.3,-0.7 -5.9,-5.1 -1.5,-2.6 -3.4,-0.9 -3.4,3.2
0,2.4 -0.5,3.4 -1.5,3.4 -1.2,-0 -1.5,-1.7 -1.5,-8.9l0,-9 2.8,-0.4c5.4,-0.8
7.8,-0.7 9,0.5zM43,23.5c0,1.4 -1.4,2 -6.5,2.7 -1.1,0.2 -2.1,1.1 -2.3,2.2
-0.3,1.5 0.2,1.7 4,1.4 3.3,-0.2 4.3,-0 4.3,1.2 0,1 -1.3,1.7 -4,2 -3.2,0.4
-4.1,0.9 -4.3,2.8 -0.3,2.2 -0.2,2.3 4.9,1.6 4.1,-0.5 5.1,-0.4 4.7,0.6
-0.5,1.5 -2.7,2.3 -9,3.3l-3.8,0.6 0,-8.9c0,-9.8 -0.6,-8.8 6.5,-10.3 4,-
0.9 5.5,-0.7 5.5,0.8zM20.8,33.1l-0.3,7.1 3.8,-0.6c2.8,-0.5 3.7,-0.3 3.7,0.8
0,1.5 -1.2,2 -7.2,3.1l-3.8,0.6 0,-9.1c0,-8.5 0.1,-9 2.1,-9 1.9,-0 2,0.5
1.7,7.1zM13,36c0,8.3 -0.1,9 -2,9 -1.9,-0 -2,-0.7 -2,-9 0,-8.3 0.1,-9 2,-9
1.9,-0 2,0.7 2,9z”
/>
</vector>
98
Programación multimedia y dispositivos móviles
Luego editamos el código para añadir algunos campos más. Estos nos ser-
virán para modificar en el animador los valores transparencia y color del
drawable:
android:name=”path_ilerna”
android:fillAlpha=”1”
android:fillColor=”#F00”
Una vez que tenemos el drawable, crearemos una animación. Pulsamos con
el botón derecho sobre la carpeta “res” y elegimos New > Directory. Esta-
blecemos su nombre como “animator”. Ahora pulsamos sobre la carpeta
“animator”, elegimos New > Animator Resource File y usamos el nombre
“animacion”, con el código:
99
Tema 2: Programación de aplicaciones para dispositivos móviles
BUSCA EN LA WEB
100
Programación multimedia y dispositivos móviles
2.7.2. Sonidos
Los sonidos de nuestra app pueden alertar al usuario de un mensa-
je o un estado especial. Una forma sencilla de generar un sonido es
un generador de tonos. Veamos un ejemplo:
101
Tema 2: Programación de aplicaciones para dispositivos móviles
btnSound1.setOnClickListener {
playSound(sound1)
}
btnSound2.setOnClickListener {
playSound(sound2)
}
}
}
102
Programación multimedia y dispositivos móviles
103
Tema 2: Programación de aplicaciones para dispositivos móviles
104
Programación multimedia y dispositivos móviles
105
Tema 2: Programación de aplicaciones para dispositivos móviles
android:id=”@+id/txtHasta”
android:layout_width=”100dp”
android:layout_height=”wrap_content”
android:layout_marginStart=”8dp”
android:layout_marginEnd=”8dp”
android:ems=”10”
android:hint=”Hasta”
android:inputType=”number”
android:text=”90000”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toEndOf=”@+id/txtDesde”
app:layout_constraintTop_toTopOf=”@+id/txtDesde” />
<Button
android:id=”@+id/btnCalcular”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”8dp”
android:layout_marginTop=”8dp”
android:layout_marginEnd=”8dp”
android:text=”Calcular”
android:focusedByDefault=”true”
app:layout_constraintEnd_toStartOf=”@id/btnCancelar”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/txtDesde” />
<Button
android:id=”@+id/btnCancelar”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”8dp”
android:layout_marginEnd=”8dp”
android:text=”Cancelar”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toEndOf=”@+id/btnCalcular”
app:layout_constraintTop_toTopOf=”@+id/btnCalcular” />
<TextView
android:id=”@+id/txtContenido”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginLeft=”8dp”
android:layout_marginTop=”32dp”
android:layout_marginRight=”32dp”
android:scrollbars=”vertical”
android:text=””
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/btnCalcular” />
</androidx.constraintlayout.widget.ConstraintLayout>
106
Programación multimedia y dispositivos móviles
Ahora crearemos un objeto que será el cerebro del cálculo de números primos:
object Primos {
private const val TAG = “Primos”
107
Tema 2: Programación de aplicaciones para dispositivos móviles
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
btnCalcular.setOnClickListener {
txtContenido.movementMethod = ScrollingMovementMethod()
txtContenido.text = “”
txtContenido.scrollY = 0
btnCancelar.setOnClickListener {
Primos.cancelar = true
}
}
108
Programación multimedia y dispositivos móviles
EventBus.getDefault().register(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventBusEvent(res: String) {
txtContenido.text = res
}
}
<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:theme=”@style/AppTheme”>
<activity android:name=”.MainActivity”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
</application>
</manifest>
109
Tema 2: Programación de aplicaciones para dispositivos móviles
Pero sigamos con la activity. Creamos el intent para llamar al servicio PrimosForegroundService y
le pasamos dos argumentos: los enteros desde y hasta. Después arrancamos el servicio llamando a
startService. Veamos cómo crear el servicio en primer plano:
import android.app.*
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import org.greenrobot.eventbus.EventBus
createNotificationChannel()
startForeground(1, notification)
Thread {
run {
val res = Primos.formateaPrimos(desde, hasta)
EventBus.getDefault().post(“PrimosService: $res”)
stopSelf()
}
}.start()
return START_NOT_STICKY
}
110
Programación multimedia y dispositivos móviles
manager.createNotificationChannel(serviceChannel)
}
}
companion object {
const val CHANNEL_ID = “PrimosChannel”
}
}
111
Tema 2: Programación de aplicaciones para dispositivos móviles
dependencies {
...
implementation ‘org.greenrobot:eventbus:3.2.0’
}
BUSCA EN LA WEB
BUSCA EN LA WEB
https://developer.android.com/guide/components/
broadcasts
112
Programación multimedia y dispositivos móviles
2.9.1. Persistencia
Si lo que necesitamos guardar no es más que un pequeño
conjunto de pares clave-valor, deberíamos utilizar la clase Sha-
redPreferences. Un objeto de este tipo utilizará un archivo para
escribir y leer pares clave-valor. Pero nosotros solo veremos
una simple interfaz con algunos pocos métodos con los que
podremos hacer persistentes nuestros datos. Si necesitamos el
objeto desde un objeto de nuestra app que no sea una activity,
tendríamos que crearlo con:
113
Tema 2: Programación de aplicaciones para dispositivos móviles
114
Programación multimedia y dispositivos móviles
null) {
stringBuilder.append(“\n”).append(receiveString)
}
inputStream.close()
ret = stringBuilder.toString()
}
}
catch (e: FileNotFoundException) {
Log.e(“login activity”, “File not found: “, e)
}
catch (e: IOException) {
Log.e(“login activity”, “Can not read file: “, e)
}
return ret
}
115
Tema 2: Programación de aplicaciones para dispositivos móviles
116
Programación multimedia y dispositivos móviles
117
Tema 2: Programación de aplicaciones para dispositivos móviles
118
Programación multimedia y dispositivos móviles
Para no complicar el código, y puesto que solo tenemos una versión de nuestras tablas, hacemos lo más
sencillo, que es eliminar las tablas antiguas y volverlas a crear con la nueva estructura, sin tener en cuen-
ta que perderemos los datos de la versión previa en caso de que los hubiera. El código completo sería:
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
119
Tema 2: Programación de aplicaciones para dispositivos móviles
Primero creamos nuestra clase de ayuda para BBDD. Con ese objeto
creamos nuestra base de datos db. Luego creamos el Jugador, y
llamamos a su método insertarEnBBDD con el objeto db.
Como podemos observar, utilizar las funciones de bajo nivel para
manejar nuestra base de datos es posible, pero requiere mucho có-
digo para cada tabla. Pasar un objeto de Kotlin a una fila de base de
datos SQL es incómodo y repetitivo, entre otras cosas porque para
cada acción de BBDD necesitamos desgranar los campos del objeto
en pares clave-valor. Además, necesitamos conocer el lenguaje SQL
para definir las tablas y sus relaciones, y para codificar las consultas
de selección, actualización y borrado.
120
Programación multimedia y dispositivos móviles
Por fortuna, existe Room, una librería con la que crear y acceder a los
datos de nuestra BBDD SQLite de forma fácil, eficiente y orientada
a objetos. Veamos cómo utilizar este ORM. Lo primero es añadir la
librería en Gradle para que esté disponible en nuestro proyecto:
implementation “androidx.room:room-ktx:$room_version”
kapt “androidx.room:room-compiler:$room_version”
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = “jugadores”)
data class JugadorEntity(
@PrimaryKey val id: Int,
val nombre: String,
val avatar: String?,
val cinturon: Int?
)
@Entity(tableName = “partidas”)
data class PartidaEntity(
@PrimaryKey val id: Int,
val jugador_id: Int,
val fecha: Long,
val puntos: Int?,
val nivel: Int?
)
121
Tema 2: Programación de aplicaciones para dispositivos móviles
import androidx.room.*
@Dao
interface JuegoDao {
//SELECT
@Query(“SELECT * FROM jugadores ORDER BY nombre”)
suspend fun getJugadores(): List<JugadorEntity>
@Query(“SELECT * FROM partidas ORDER BY fecha DESC”)
suspend fun getPartidas(): List<PartidaEntity>
@Query(“SELECT * FROM jugadores WHERE id = :idJugador”)
suspend fun getJugadorById(idJugador: Int): JugadirEntity
//INSERT
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addPartida(partida: PartidaEntity)
//DELETE
@Query(“DELETE FROM partidas”)
suspend fun deletePartidas()
}
122
Programación multimedia y dispositivos móviles
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(
entities = [JugadorEntity::class, PartidaEntity::class],
version = 1,
exportSchema = false)
abstract class JuegoDb : RoomDatabase() {
abstract val dao: JuegoDao
companion object {
private const val NAME = “juego.db”
fun buildDefault(context: Context) =
Room.databaseBuilder(context, JuegoDb::class.java, NAME)
.fallbackToDestructiveMigration()
.build()
}
}
BUSCA EN LA WEB
123
Tema 2: Programación de aplicaciones para dispositivos móviles
Utilizando el ORM Room, hemos creado una interfaz que nos aísla
de toda la complejidad de usar una BBDD relacional en Android.
Para pasar a producción, nuestro ejemplo aún necesitaría añadir al-
gunos mecanismos, como la actualización de los datos a una nueva
versión, el control de excepciones, las llamadas asíncronas, etcéte-
ra, pero gracias a la funcionalidad y a la abstracción de Room todo
será sencillo y ordenado. Para una mejor comprensión y dominio,
la documentación de Android es muy interesante, echa un vistazo
tanto al núcleo de SQLite en Android como al ORM Room:
BUSCA EN LA WEB
124
Programación multimedia y dispositivos móviles
125
Tema 2: Programación de aplicaciones para dispositivos móviles
// Firebase
implementation “com.google.firebase:firebase-core:$core_version”
implementation “com.google.firebase:firebase-auth:$auth_version”
implementation “com.google.firebase:firebase-database:$database_version”
implementation “com.google.firebase:firebase-storage:$storage_version”
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
class Fire {
private val CHATS: String = “ListaChats”
private val CONTACTOS: String = “ListaContactos”
data class Chat(val id: String, val otrosDatos: String)
data class Contacto(val id: String, val otrosDatos: String)
private var fbdb: FirebaseDatabase? = null
126
Programación multimedia y dispositivos móviles
127
Tema 2: Programación de aplicaciones para dispositivos móviles
• Java Thread API: la más básica y antigua en Java, que, por su-
puesto, también podremos utilizar en Kotlin. La vimos en un
ejemplo anterior, con la creación de un Thread y su ejecución.
Es potente y bien conocida, sin embargo, no es la mejor que po-
128
Programación multimedia y dispositivos móviles
Thread {
run {
val res = Primos.formateaPrimos(desde, hasta)
EventBus.getDefault().post(“PrimosService: $res”)
stopSelf()
}
}.start()
import android.os.AsyncTask
import org.greenrobot.eventbus.EventBus
129
Tema 2: Programación de aplicaciones para dispositivos móviles
130
Programación multimedia y dispositivos móviles
object Primos {
var cancelar = false
131
Tema 2: Programación de aplicaciones para dispositivos móviles
Nada de particular, ya lo hemos visto antes. Simplemente, le ofrecemos un par de enteros y nos
devolverá una cadena de caracteres con todos los números primos en ese rango. Vamos a crear la
interfaz gráfica mediante la definición del layout:
<EditText
android:id=”@+id/txtDesde”
android:layout_width=”100dp”
android:layout_height=”wrap_content”
android:layout_marginStart=”16dp”
android:layout_marginTop=”32dp”
android:ems=”10”
android:inputType=”number”
android:text=”1”
app:layout_constraintEnd_toStartOf=”@+id/txtHasta”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />
<EditText
android:id=”@+id/txtHasta”
android:layout_width=”100dp”
android:layout_height=”wrap_content”
android:layout_marginEnd=”16dp”
android:ems=”10”
android:inputType=”number”
android:text=”90000”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toEndOf=”@+id/txtDesde”
app:layout_constraintTop_toTopOf=”@+id/txtDesde” />
<Button
android:id=”@+id/btnCalcular”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”24dp”
android:text=”Calcular”
app:layout_constraintEnd_toStartOf=”@id/btnCancelar”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/txtDesde” />
<Button
android:id=”@+id/btnCancelar”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”24dp”
android:text=”Cancelar”
app:layout_constraintEnd_toEndOf=”parent”
132
Programación multimedia y dispositivos móviles
app:layout_constraintStart_toEndOf=”@+id/btnCalcular”
app:layout_constraintTop_toBottomOf=”@+id/txtDesde” />
<ProgressBar
android:id=”@+id/progressBar”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />
<TextView
android:id=”@+id/txtResultado”
android:layout_width=”0dp”
android:layout_height=”0dp”
android:layout_marginLeft=”16dp”
android:layout_marginTop=”16dp”
android:layout_marginRight=”16dp”
android:layout_marginBottom=”16dp”
android:scrollbars=”vertical”
android:scrollbarSize=”50dp”
android:text=””
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/btnCalcular” />
</androidx.constraintlayout.widget.ConstraintLayout>
Veamos cómo llamar desde nuestra activity a la función formateaPrimos sin congelar el hilo princi-
pal. El código de la actividad será:
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
…
progressBar.visibility = View.GONE
btnCalcular.setOnClickListener {
//GlobalScope.launch {
lifecycleScope.launch {
progressBar.visibility = View.VISIBLE
133
Tema 2: Programación de aplicaciones para dispositivos móviles
progressBar.visibility = View.GONE
}
}
btnCancelar.setOnClickListener {
Primos.cancelar = true
progressBar.visibility = View.GONE
}
}
BUSCA EN LA WEB
Hay muchos detalles implementation “org.jetbrains.kotlinx:kotlinx-co-
que aprender para routines-core:$coroutines_version”
usar correctamente las implementation “org.jetbrains.kotlinx:kotlinx-co-
coroutines:
routines-android:$coroutines_version”
https://developer.android. implementation “androidx.lifecycle:lifecycle-run-
com/kotlin/coroutines
time-ktx:$lifecycle_version”
134
Programación multimedia y dispositivos móviles
135
Tema 2: Programación de aplicaciones para dispositivos móviles
136
Programación multimedia y dispositivos móviles
// Coroutines
implementation “org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_
version”
implementation “org.jetbrains.kotlinx:kotlinx-coroutines-android:$corouti-
nes_version”
// EventBus
implementation ‘org.greenrobot:eventbus:3.2.0’
// RecyclerView
implementation ‘androidx.recyclerview:recyclerview:1.1.0’
137
Tema 2: Programación de aplicaciones para dispositivos móviles
<application
android:name=”.App”
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:theme=”@style/AppTheme”>
<activity android:name=”.MainActivity”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
</application>
</manifest>
import android.app.Application
companion object {
private var _instance: App? = null
val instance: App
get() = _instance!!
}
}
138
Programación multimedia y dispositivos móviles
<Button
android:id=”@+id/btnEscanear”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”16dp”
android:layout_marginTop=”16dp”
android:text=”Escanear”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />
<Button
android:id=”@+id/btnVisible”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginEnd=”16dp”
android:text=”VISIBLE”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintTop_toTopOf=”@+id/btnEscanear” />
<androidx.recyclerview.widget.RecyclerView
android:id=”@+id/lstEscaner”
android:layout_width=”0dp”
android:layout_height=”200dp”
android:layout_marginStart=”16dp”
android:layout_marginTop=”16dp”
android:layout_marginEnd=”16dp”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/btnEscanear” />
<ProgressBar
android:id=”@+id/progressBarEscaneo”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
app:layout_constraintBottom_toBottomOf=”@+id/lstEscaner”
app:layout_constraintEnd_toEndOf=”@+id/lstEscaner”
app:layout_constraintStart_toStartOf=”@+id/lstEscaner”
139
Tema 2: Programación de aplicaciones para dispositivos móviles
app:layout_constraintTop_toTopOf=”@+id/lstEscaner” />
<Button
android:id=”@+id/btnPareados”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”32dp”
android:text=”Pareados”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/lstEscaner” />
<androidx.recyclerview.widget.RecyclerView
android:id=”@+id/lstPareados”
android:layout_width=”0dp”
android:layout_height=”0dp”
android:layout_marginStart=”16dp”
android:layout_marginTop=”16dp”
android:layout_marginEnd=”16dp”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/btnPareados” />
</androidx.constraintlayout.widget.ConstraintLayout>
140
Programación multimedia y dispositivos móviles
import android.app.Activity
import android.bluetooth.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import androidx.core.content.ContextCompat.startActivity
import org.greenrobot.eventbus.EventBus
object MiBluetooth {
fun activar() {
adapter.enable()
}
141
Tema 2: Programación de aplicaciones para dispositivos móviles
142
Programación multimedia y dispositivos móviles
import android.app.Activity
import android.bluetooth.BluetoothDevice
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
btnVisible.setOnClickListener {
MiBluetooth.visibilizar(this, CINCO_MINUTOS)
}
btnEscanear.setOnClickListener {
btnEscanear.isEnabled = false
progressBarEscaneo.visibility = View.VISIBLE
MiBluetooth.comenzarEscaner(applicationContext)
}
btnPareados.setOnClickListener {
btnPareados.isEnabled = false
lstPareados.adapter = BluetoothListAdapter(MiBluetooth.listarPa-
reados())
btnPareados.isEnabled = true
}
143
Tema 2: Programación de aplicaciones para dispositivos móviles
progressBarEscaneo.visibility = View.GONE
val layoutManager1 = LinearLayoutManager(this)
lstEscaner.layoutManager = layoutManager1
val layoutManager2 = LinearLayoutManager(this)
lstPareados.layoutManager = layoutManager2
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onBluetoothDevice(device: BluetoothDevice) {
lstEscaner.adapter = BluetoothListAdapter(MiBluetooth.escaneados)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onBluetoothEscaneoTerminado(escaneo: MiBluetooth.EscaneoTerminado) {
btnEscanear.isEnabled = true
progressBarEscaneo.visibility = View.GONE
lstEscaner.adapter =
BluetoothListAdapter(MiBluetooth.escaneados)
}
companion object {
private const val CINCO_MINUTOS = 300
}
}
Tras establecer las acciones para los botones, vemos cómo creamos
un layout que estableceremos en sendos RecyclerView. Esto es así
debido a la gran capacidad de adaptación que tiene este control.
En nuestro caso, nos basta con el layout más sencillo, pues solo le
pedimos a cada elemento de la lista que muestre un texto plano.
144
Programación multimedia y dispositivos móviles
import android.bluetooth.BluetoothDevice
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class BluetoothListAdapter(
private val dataSet: List<BluetoothDevice>)
: RecyclerView.Adapter<BluetoothListAdapter.ViewHolder>() {
145
Tema 2: Programación de aplicaciones para dispositivos móviles
Esta clase recibe una lista de objetos, en nuestro caso, una lista de
BluetoothDevices. La eficiencia de los RecyclerView reside en que,
para mostrar una lista de cientos de elementos, el componente
solo tiene en memoria la representación gráfica de los objetos que
pueden verse, y no más. Para ello, define la clase ViewHolder, que
es la vista de un elemento. Cuando el usuario haga scroll sobre la
lista, el RecyclerView irá reutilizando (reciclando) los ViewHolder
con los datos de los nuevos elementos ahora visibles. Vemos cómo
en onCreateViewHolder utilizamos otro layout para definir qué as-
pecto tendrán los elementos de la lista. En nuestro caso, el layout
del elemento es:
ponte a prueba
146
Programación multimedia y dispositivos móviles
<uses-feature
android:name=”android.hardware.bluetooth_le”
android:required=”false” />
147
Tema 2: Programación de aplicaciones para dispositivos móviles
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.le.*
...
object MiBluetoothLE {
private const val tag = “MiBluetoothLE”
private const val REPORT_DELAY = 10000L
148
Programación multimedia y dispositivos móviles
fun stopScan() {
adapter.bluetoothLeScanner?.flushPendingScanResults(scanCallback)
adapter.bluetoothLeScanner?.stopScan(scanCallback)
}
149
Tema 2: Programación de aplicaciones para dispositivos móviles
<Button
android:id=”@+id/btnEscanear”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”16dp”
android:layout_marginTop=”16dp”
android:text=”@string/escanear”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />
<androidx.recyclerview.widget.RecyclerView
android:id=”@+id/lstEscaner”
android:layout_width=”0dp”
android:layout_height=”200dp”
android:layout_marginStart=”16dp”
android:layout_marginTop=”16dp”
android:layout_marginEnd=”16dp”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/btnEscanear” />
<ProgressBar
android:id=”@+id/progressBarEscaneo”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
app:layout_constraintBottom_toBottomOf=”@+id/lstEscaner”
app:layout_constraintEnd_toEndOf=”@+id/lstEscaner”
app:layout_constraintStart_toStartOf=”@+id/lstEscaner”
app:layout_constraintTop_toTopOf=”@+id/lstEscaner” />
</androidx.constraintlayout.widget.ConstraintLayout>
150
Programación multimedia y dispositivos móviles
import android.Manifest
import android.app.AlertDialog
import android.bluetooth.BluetoothDevice
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_ble.btnEscanear
import kotlinx.android.synthetic.main.activity_ble.lstEscaner
import kotlinx.android.synthetic.main.activity_ble.progressBarEscaneo
...
151
Tema 2: Programación de aplicaciones para dispositivos móviles
bluetoothGatt = device.connectGatt(
appContext,
true,
gattCallback,
BluetoothDevice.TRANSPORT_LE)
152
Programación multimedia y dispositivos móviles
153
Tema 2: Programación de aplicaciones para dispositivos móviles
Y en la activity:
154
Programación multimedia y dispositivos móviles
SHORT).show()
SmsManager.RESULT_ERROR_NO_SERVICE ->
Toast.makeText(baseContext, “Sin servicio”, Toast.LENGTH_
SHORT).show()
SmsManager.RESULT_ERROR_NULL_PDU ->
Toast.makeText(baseContext, “Null PDU”, Toast.LENGTH_SHORT).
show()
SmsManager.RESULT_ERROR_RADIO_OFF ->
Toast.makeText(baseContext, “Radio off”, Toast.LENGTH_SHORT).
show()
}
}
}
private val brEntregado = object : BroadcastReceiver() {
override fun onReceive(arg0: Context?, arg1: Intent?) {
when (resultCode) {
Activity.RESULT_OK ->
Toast.makeText(baseContext,”SMS delivered”, Toast.LENGTH_SHORT).
show()
Activity.RESULT_CANCELED ->
Toast.makeText(baseContext,”SMS not delivered”, Toast.LENGTH_
SHORT).show()
}
}
}
155
Tema 2: Programación de aplicaciones para dispositivos móviles
<receiver
android:name=”.ReceptorSMS”
android:enabled=”true”
android:exported=”true”>
<intent-filter android:priority=”1000”>
<action android:name=”android.provider.Telephony.SMS_RECEIVED” />
</intent-filter>
</receiver>
if(pdus != null) {
val isVersionM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
msgs = arrayOfNulls(pdus.size)
for(i in msgs.indices) {
msgs[i] = SmsMessage.createFromPdu(pdus[i] as ByteArray, format)
strMessage += “SMS desde “ + msgs[i]?.originatingAddress
strMessage += “ :${msgs[i]?.messageBody}”
EventBus.getDefault().post(strMessage)
}
}
}
156
Programación multimedia y dispositivos móviles
BUSCA EN LA WEB
157
Tema 2: Programación de aplicaciones para dispositivos móviles
La razón por la que el protocolo HTTP es tan usado en las apps móviles es
debido a la potencia de los actuales servicios web. Mediante servicios web,
por ejemplo, una app puede informar al usuario sobre la temperatura que
hace en su zona: la app pide al sistema que le informe sobre la posición GPS
del terminal; entonces la app se conecta a internet y hace una petición a un
servidor web meteorológico; este consulta sus datos y transforma la latitud y
longitud en una temperatura, la formatea y la devuelve a la app; finalmente,
la app formatea y muestra al usuario la información. Existen millones de po-
sibilidades, y millones de apps que utilizan millones de servicios web. Incluso
las entidades financieras se unieron a esta tecnología. Con el surgimiento del
HTTP seguro o HTTPS, los dispositivos pueden validar la autenticidad de los
mensajes y encriptar las comunicaciones con los servidores web.
Android nos permite comunicarnos a través de internet con los protocolos más
básicos: sockets de TCP/IP o UDP. Pero como no queremos reinventar la rueda,
utilizaremos alguna de las muchas librerías disponibles que construyen por
nosotros la pila de protocolos necesarios para llegar a utilizar servicios web.
En la práctica, al menos de momento, las librerías más utilizadas son OkHttp
como capa base y, sobre ella, Retrofit o Volley, que nos ayudarán aún más a
convertir peticiones HTTP en meras funciones que podremos llamar cómo-
damente desde nuestro código. Para transformar la información, podremos
utilizar librerías como GSON, que traduce JSON a un objeto en Kotlin.
Pero mejor veamos un ejemplo. Crearemos un nuevo proyecto de Android Stu-
dio File > New > New project… > Empty Activity. Primero, vamos a añadir las
librerías que necesitamos en build.gradle:
// Coroutines
implementation “org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_
version”
implementation “org.jetbrains.kotlinx:kotlinx-coroutines-android:$corouti-
nes_version”
implementation “androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha05”
// Network
implementation “com.squareup.okhttp3:okhttp:$okhttp_version”
implementation(“com.squareup.okhttp3:logging-interceptor:$okhttp_version”)
implementation “com.squareup.retrofit2:retrofit:$retrofit_version”
implementation “com.squareup.retrofit2:converter-gson:$gson_version”
158
Programación multimedia y dispositivos móviles
Hemos añadido las librerías para las coroutines, porque las lla-
madas a internet pueden tardar y no queremos paralizar el main
thread. Después añadimos las librerías para acceso a internet OkH-
ttp y Retrofit. Como haremos uso de internet, debemos declarar el
permiso en el manifest:
import retrofit2.Response
import retrofit2.http.*
interface GithubApi {
@GET(“/repositories”)
suspend fun getRepoList(): Response<List<RepoEntity>>
}
159
Tema 2: Programación de aplicaciones para dispositivos móviles
Ahora veamos cómo utilizar esta interfaz, una vez que hemos informado a Retrofit sobre el servicio
que utilizar:
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object GithubApiImpl {
private const val GITHUB_API_URL = “https://api.github.com/”
160
Programación multimedia y dispositivos móviles
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class RepoEntity(
@SerializedName(“id”)
val id: String?,
@SerializedName(“name”)
val name: String?,
@SerializedName(“description”)
val description: String?
)
161
Tema 2: Programación de aplicaciones para dispositivos móviles
No tenemos por qué utilizar todos los campos que nos de-
vuelve el servicio, solo los que nos interesen. El parseador
GSON olvidará el resto. Veamos cómo utilizar esta clase desde
nuestra activity:
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.launch
162
Programación multimedia y dispositivos móviles
BUSCA EN LA WEB
163
Tema 2: Programación de aplicaciones para dispositivos móviles
class ExampleUnitTest {
@Test fun primos_isCorrect() {
val primos = Primos.calcularPrimos(1,15)//1,2,3,5,7,11,13
assertEquals(7, primos.size)
assertEquals(13, primos[primos.size-1])
}
}
164
Programación multimedia y dispositivos móviles
165
Tema 2: Programación de aplicaciones para dispositivos móviles
// Tests
androidTestImplementation ‘androidx.test:core-ktx:1.2.0’
2.16.3. Documentación
BUSCA EN LA WEB Documentar el código es una tarea importante. Aunque una es-
tructura y un código Clean es la mejor documentación, no está de
La documentación
completa de cómo utilizar más añadir toda la información que podamos a nuestro proyecto.
este documentador: En programación Java, existieron múltiples herramientas para la
generación automática de documentación. Kotlin lleva menos
https://github.com/Kotlin/
dokka tiempo, pero ya disponemos de algunas buenas herramientas tam-
bién, por ejemplo, Dokka, que es el generador de documentación
para Kotlin desarrollado por JetBrains. La documentación automá-
tica suele depender de comentarios formateados adecuadamente
dentro de nuestro código. La herramienta parseará el código en
busca de estos comentarios y generará la documentación. Veamos
cómo podría ser nuestro código para que la herramienta de docu-
mentación hiciese su trabajo:
/**
* Esta sería la documentación de la clase NuestraActivity,
* podríamos explicar para que vale, etc.
*
* @param layout: Esta sería la explicación del parámetro layout.
*/
class NuestraActivity(layout: Int) : Activity {
/**
* Este sería un mensaje explicando la utilidad de una propiedad callback.
*/
private val callback: Callback()
/**
* Este sería un mensaje explicando cómo y para qué tenemos este método.
*
* @return Application: Mensaje explicando el objeto devuelto por el méto-
do.
*/
fun app() = getApplication<Application>()
...
}
166
Programación multimedia y dispositivos móviles
ponte a prueba
167
3
UTILIZACIÓN DE LIBRERÍAS MULTIMEDIA
INTEGRADAS
Programación multimedia y dispositivos móviles
169
Tema 3: Utilización de librerías multimedia integradas
170
Programación multimedia y dispositivos móviles
<Cliente> <Servidor>
UI Player
(Actividad)
(Notificación) (Servicio)
171
Tema 3: Utilización de librerías multimedia integradas
<VideoView
android:id=”@+id/videoView”
android:layout_width=”match_parent”
android:layout_height=”match_parent” />
172
Programación multimedia y dispositivos móviles
Esta sería la opción más sencilla, pero no permitiría tanto control como
utilizando directamente los objetos MediaPlayer o ExoPlayer. Más ade-
lante veremos arquitecturas más complejas que nos permitirán un ma-
yor control sobre la reproducción de vídeo.
3.2.3. A
pp captadora de audio o vídeo. Ejemplos
Si nuestra app no tiene como objetivo la reproducción sino la captación
de media, su arquitectura no tendrá por qué guardar relación directa
con las arquitecturas vistas hasta ahora. Deberemos tener en cuenta,
sin embargo, otros factores, por ejemplo, si nuestra app utilizará otras
aplicaciones como interfaz hacia el hardware o si, por el contrario, ac-
cederá directamente a la cámara o al micro. Si nuestra app no pretende
ser lo último en grabación, quizá sea conveniente llamar directamente
a la grabadora instalada por defecto en nuestro móvil, con lo que aho-
rraremos esfuerzo y tiempo de desarrollo. Este podría ser el caso de una
app que gestione tareas y permita además grabar notas de voz o vídeo.
Quizá resultaría innecesario para una tarea tan sencilla meternos a pro-
gramar el hardware de la cámara y el sonido. No obstante, si realmente
nuestra app se basa en la captación de media, querremos tener total
control sobre el proceso y tendremos que estar al tanto de las interfaces
y arquitectura de la cámara o el micro.
<provider
android:name=”androidx.core.content.FileProvider”
android:authorities=”${applicationId}.provider”
android:exported=”false”
android:grantUriPermissions=”true”>
<meta-data
android:name=”android.support.FILE_PROVIDER_PATHS”
android:resource=”@xml/provider_paths”/>
</provider>
173
Tema 3: Utilización de librerías multimedia integradas
El FileProvider hace referencia a un documento XML que definirá nuestras rutas. Crearemos una carpeta
“xml” dentro de la carpeta “res” y añadiremos el archivo provider_paths.xml con el siguiente código:
Ahora podremos llamar a una aplicación que permita la grabación de vídeo, es decir, a una aplicación
preinstalada que responda a una acción MediaStore.ACTION_VIDEO_CAPTURE. Nuestro código sería:
import android.content.Intent
import android.net.Uri
import android.provider.MediaStore
import androidx.core.content.FileProvider
import java.io.File
174
Programación multimedia y dispositivos móviles
import android.content.Intent
import android.provider.MediaStore
companion object {
private const val REQUEST_ID = 1337
}
}
175
Tema 3: Utilización de librerías multimedia integradas
176
Programación multimedia y dispositivos móviles
177
Tema 3: Utilización de librerías multimedia integradas
178
Programación multimedia y dispositivos móviles
MIDI No Sí MIDI tipo 0 y 1. DLS versiones 1 y 2. XMF y Tipo 0 y 1 (.mid, .xmf, .mxmf)
Mobile XMF. Formatos RTTTL/RTX, OTA, y RTTTL/RTX (.rtttl, .rtx)
iMelody OTA (.ota) iMelody (.imy)
179
Tema 3: Utilización de librerías multimedia integradas
H.265 HEVC No Sí Main Profile Level 3 para móviles y Main MPEG-4 (.mp4)
Profile Level 4.1 para Android TV
VP9 No Sí
3.4.2. Hardware
El paquete android.hardware proporciona al programador las cla-
ses necesarias para el manejo del hardware del dispositivo. Una
de las más utilizada en las app multimedia de toma de imágenes
será la clase Camera2, si no se han actualizado a CameraX. Debe-
mos tener en cuenta que no todos los dispositivos dispondrán de
las características de hardware que soporta el sistema operativo
Android, por ello es necesario que declaremos en el manifest de
nuestra app cuáles de ellas usaremos y cuáles son indispensables
para su funcionamiento. De esta manera, nos aseguraremos de que
las aplicaciones se ejecuten en el entorno adecuado y no se rompa
la ejecución por falta de hardware. Por ejemplo, si en nuestra app
usamos la cámara pero no es fundamental, deberemos declararlo
en el manifest de este modo:
180
Programación multimedia y dispositivos móviles
181
Tema 3: Utilización de librerías multimedia integradas
videoView.requestFocus()
videoView.start()
182
Programación multimedia y dispositivos móviles
<uses-permission android:name=”android.permission.RECORD_AUDIO”/>
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
<uses-feature android:name=”android.hardware.microphone” android:required=”true”/>
183
Tema 3: Utilización de librerías multimedia integradas
import android.Manifest
import android.media.MediaPlayer
import android.media.MediaRecorder
import kotlinx.android.synthetic.main.activity_main.*
import java.io.*
184
Programación multimedia y dispositivos móviles
btnGrabar.setOnClickListener {
onGrabar()
}
btnReproducir.isEnabled = false
btnReproducir.setOnClickListener {
val file = getArchivoSalida()
val reproductor = MediaPlayer()
reproductor.setDataSource(file?.path)
reproductor.prepare()
reproductor.start()
}
}
185
Tema 3: Utilización de librerías multimedia integradas
when(what) {
MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED -> {
showMensaje(“Se alcanzó la duración máxima!”)
onGrabar()
}
MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED -> {
showMensaje(“Se alcanzó el tamaño máximo!”)
onGrabar()
}
}
}
}
186
Programación multimedia y dispositivos móviles
companion object {
private const val tag = “Activity”
private const val archivo = “grabacion.3gp”
}
}
187
Tema 3: Utilización de librerías multimedia integradas
188
Programación multimedia y dispositivos móviles
En Android Studio, elegimos la opción File > New > New Project…
> Empty Activity. Diseñaremos una interfaz gráfica sencilla del si-
guiente modo:
<TextView
android:id=”@+id/lblAudio”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”16dp”
android:layout_marginTop=”8dp”
android:text=”@string/audio_clip”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”@+id/btnPlayAudio” />
<Button
189
Tema 3: Utilización de librerías multimedia integradas
android:id=”@+id/btnPlayAudio”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”16dp”
android:layout_marginTop=”32dp”
android:text=”@string/play”
app:layout_constraintStart_toEndOf=”@+id/textView2”
app:layout_constraintTop_toTopOf=”parent” />
<Button
android:id=”@+id/btnStopAudio”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”16dp”
android:text=”@string/stop”
app:layout_constraintStart_toEndOf=”@+id/btnPlayAudio”
app:layout_constraintTop_toTopOf=”@+id/btnPlayAudio” />
<TextView
android:id=”@+id/lblVideo”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”16dp”
android:layout_marginTop=”8dp”
android:text=”@string/video_clip”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”@+id/btnPlayVideo” />
<Button
android:id=”@+id/btnPlayVideo”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”16dp”
android:layout_marginTop=”24dp”
android:text=”@string/play”
app:layout_constraintStart_toEndOf=”@+id/lblVideo”
app:layout_constraintTop_toBottomOf=”@+id/btnPlayAudio” />
<Button
android:id=”@+id/btnStopVideo”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginStart=”16dp”
android:text=”@string/stop”
app:layout_constraintStart_toEndOf=”@+id/btnPlayVideo”
app:layout_constraintTop_toTopOf=”@+id/btnPlayVideo” />
<VideoView
android:id=”@+id/videoView”
android:layout_width=”0dp”
android:layout_height=”0dp”
android:layout_marginTop=”8dp”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
190
Programación multimedia y dispositivos móviles
app:layout_constraintTop_toBottomOf=”@+id/btnPlayVideo” />
</androidx.constraintlayout.widget.ConstraintLayout>
<resources>
<string name=”app_name”>AudioVideoClips</string>
<string name=”play”>Play</string>
<string name=”pause”>Pause</string>
<string name=”stop”>Stop</string>
<string name=”audio_finish”>Audio clip terminado</string>
<string name=”video_finish”>Video clip terminado</string>
<string name=”video_clip”>Vídeo Clip:</string>
<string name=”audio_clip”>Audio Clip:</string>
</resources>
import android.media.MediaPlayer
import android.net.Uri
import android.os.Bundle
import android.widget.MediaController
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
/////////////////////////////////////////////////////////////////////////
///////////
/// AUDIO
private fun initAudio() {
mp = MediaPlayer.create(this, R.raw.audio_midi)
//mp = MediaPlayer.create(this, R.raw.audio_ogg)
mp.setOnCompletionListener { mp ->
stopAudio()
Toast
.makeText(this, getString(R.string.audio_finish), Toast.LENG-
191
Tema 3: Utilización de librerías multimedia integradas
TH_LONG)
.show()
}
btnPlayAudio.setOnClickListener {
if(mp.isPlaying)
pauseAudio()
else
playAudio()
}
btnStopAudio.setOnClickListener {
stopAudio()
}
btnPlayAudio.text = getString(R.string.play)
btnStopAudio.isEnabled = false
}
private fun playAudio() {
mp.start()
//
btnPlayAudio.text = getString(R.string.pause)
btnStopAudio.isEnabled = true
}
private fun stopAudio() {
mp.stop()
mp.prepare()
mp.seekTo(0)
//
btnPlayAudio.text = getString(R.string.play)
btnStopAudio.isEnabled = false
}
private fun pauseAudio() {
mp.pause()
//
btnPlayAudio.text = getString(R.string.play)
}
/////////////////////////////////////////////////////////////////////////
///////////
/// VIDEO
private fun initVideo() {
videoView.setOnCompletionListener {
stopVideo()
Toast
.makeText(this, getString(R.string.video_finish), Toast.LENGTH_LONG)
.show()
}
btnPlayVideo.setOnClickListener {
when {
videoView.isPlaying -> pauseVideo()
videoView.currentPosition != 0 -> resumeVideo()
else -> playVideo()
}
192
Programación multimedia y dispositivos móviles
}
btnStopVideo.setOnClickListener {
stopVideo()
}
btnPlayVideo.text = getString(R.string.play)
btnStopVideo.isEnabled = false
}
private fun playVideo() {
// Archivo en directorio res/raw (Nota: no debemos escribir la extensión)
//videoView.setVideoURI(Uri.parse(“android.resource://$packageName/raw/
video_mp4”))
//videoView.setVideoURI(Uri.parse(“android.resource://$packageName/raw/
video_ogv”))
videoView.setVideoURI(Uri.parse(“android.resource://$packageName/raw/
video_3gp”))
val mediaController = MediaController(this)
mediaController.setMediaPlayer(videoView)
videoView.setMediaController(mediaController)
videoView.requestFocus()
videoView.start()
//
btnPlayVideo.text = getString(R.string.pause)
btnStopVideo.isEnabled = true
}
private fun resumeVideo() {
videoView.start()
//
btnPlayVideo.text = getString(R.string.pause)
}
private fun pauseVideo() {
videoView.pause()
//
btnPlayVideo.text = getString(R.string.play)
}
private fun stopVideo() {
videoView.stopPlayback()
//
btnPlayVideo.text = getString(R.string.play)
btnStopVideo.isEnabled = false
}
}
Primero definimos una variable de tipo MediaPlayer que nos servirá para reproducir el audio. Para el
vídeo, utilizaremos el componente VideoView que ya está compuesto de una instancia de MediaPla-
yer en su interior. En onCreate llamamos a dos funciones, una para iniciar el audio y otra para el vídeo.
El método initAudio inicia la variable MediaPlayer mediante un contexto y el identificador del archivo
de audio que deseamos reproducir. Como vemos, tenemos dos líneas iguales, una para un clip de
audio en formato MIDI y otra en formato OGG. Podemos comentar una de ellas, y será lo único que
necesitemos reproducir un tipo u otro: la clase MediaPlayer utilizará el códec correspondiente a cada
uno cuando lo reproduzca.
193
Tema 3: Utilización de librerías multimedia integradas
194
Programación multimedia y dispositivos móviles
3.6.2. MIDI
Ya hemos visto cómo podemos reproducir clips de audio, incluidos
clips MIDI, pero merece especial atención, además, la capacidad
del sistema Android para controlar dispositivos MIDI conectados a
él mediante USB o Bluetooth LE. Observemos cómo, cuando conec-
tamos nuestro terminal Android a un ordenador de sobremesa me-
diante un cable USB, en las opciones de conexión aparecerá MIDI:
BUSCA EN LA WEB
BUSCA EN LA WEB
195
Tema 3: Utilización de librerías multimedia integradas
196
Programación multimedia y dispositivos móviles
BUSCA EN LA WEB
197
Tema 3: Utilización de librerías multimedia integradas
Ejemplo 1: SoundPool
198
Programación multimedia y dispositivos móviles
app:layout_constraintTop_toBottomOf=”@+id/button1” />
<Button
android:id=”@+id/button4”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Sonido4”
android:tag=”4”
android:onClick=”onClick”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toEndOf=”@+id/button3”
app:layout_constraintTop_toTopOf=”@+id/button3” />
<Button
android:id=”@+id/button5”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”32dp”
android:text=”Sonido5”
android:tag=”5”
android:onClick=”onClick”
app:layout_constraintStart_toStartOf=”@+id/button3”
app:layout_constraintTop_toBottomOf=”@+id/button3” />
<Button
android:id=”@+id/button6”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Sonido6”
android:tag=”6”
android:onClick=”onClick”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toEndOf=”@+id/button5”
app:layout_constraintTop_toTopOf=”@+id/button5” />
<Button
android:id=”@+id/button7”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”32dp”
android:text=”Sonido7”
android:tag=”7”
android:onClick=”onClick”
app:layout_constraintStart_toStartOf=”@+id/button5”
app:layout_constraintTop_toBottomOf=”@+id/button5” />
<Button
android:id=”@+id/button8”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Sonido8”
android:tag=”8”
android:onClick=”onClick”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toEndOf=”@+id/button7”
app:layout_constraintTop_toTopOf=”@+id/button7” />
199
Tema 3: Utilización de librerías multimedia integradas
<Switch
android:id=”@+id/swtBucle”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”32dp”
android:text=”En bucle”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/button7” />
<EditText
android:id=”@+id/txtCanales”
android:layout_width=”100dp”
android:layout_height=”wrap_content”
android:layout_marginTop=”32dp”
android:ems=”10”
android:hint=”Núm. Canales”
android:text=”8”
android:inputType=”number”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/swtBucle” />
</androidx.constraintlayout.widget.ConstraintLayout>
No son más que ocho botones, un interruptor y una entrada de texto. Todos los botones llaman a la
misma acción cuando son presionados: onClick. Hemos añadido el atributo tag para diferenciar los
distintos botones en el código. El interruptor hará que los sonidos se reproduzcan una sola vez o en
bucle. La caja de texto permitirá al usuario crear un SoundPool con un número de canales diferen-
te: por ejemplo, si introduce 3, solo se escucharán tres sonidos al mismo tiempo, aunque se pulsen
cuatro botones consecutivamente. Estudiemos el código de la activity, donde se lleva a cabo toda la
lógica:
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.SoundPool
import android.widget.Button
import kotlinx.android.synthetic.main.activity_main.*
data class Sonido(val id: Int, var boton: Button?=null, var cargado: Boo-
lean=false)
private val sonidos = HashMap<Int, Sonido>()
private val sonidosRes = arrayListOf(
R.raw.animal_bark_and_growl,
R.raw.animal_hiss_and_rattle,
R.raw.crow_call,
R.raw.distant_dog_barking,
R.raw.dog_barking,
200
Programación multimedia y dispositivos móviles
R.raw.dog_growling,
R.raw.dog_snarling,
R.raw.mouse_squeaking
)
private lateinit var botonesRes: ArrayList<Button>
private var actVolume = 0f
private var maxVolume = 0f
private var volume = 0f
201
Tema 3: Utilización de librerías multimedia integradas
SIC).toFloat()
volume = actVolume / maxVolume
volumeControlStream = AudioManager.STREAM_MUSIC
202
Programación multimedia y dispositivos móviles
203
Tema 3: Utilización de librerías multimedia integradas
204
Programación multimedia y dispositivos móviles
205
Tema 3: Utilización de librerías multimedia integradas
mediaPlayer?.apply {
setDataSource()
setOnDrmConfigHelper() // opcional, para otras configuraciones
prepare()
drmInfo?.also {
prepareDrm() //Prepara el DRM para la fuente actual
getKeyRequest() //Pide una prueba de clave al servidor de licencias
provideKeyResponse() //Procesa la prueba de clave para iniciar sesión
}
// MediaPlayer listo para reproducir
start()
// ...play/pause/resume...
stop()
releaseDrm()
}
import android.net.Uri
import com.google.android.exoplayer2.ExoPlayerFactory
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
206
Programación multimedia y dispositivos móviles
exoPlayer = ExoPlayerFactory.newSimpleInstance(this)
dsf = DefaultDataSourceFactory(this, Util.getUserAgent(this, “ExoPla-
yer”))
dependencies {
implementation ‘com.google.android.exoplayer:exoplayer:2.10.4’
...
207
Tema 3: Utilización de librerías multimedia integradas
208
Programación multimedia y dispositivos móviles
209
4
MOTORES DE JUEGOS: TIPOS Y UTILIZACIÓN
Programación multimedia y dispositivos móviles
211
Tema 4: Motores de juegos: tipos y utilización
• Según la licencia:
212
Programación multimedia y dispositivos móviles
CONCEPTO
213
Tema 4: Motores de juegos: tipos y utilización
Dicha estructura estará formada por una serie de bloques con una
función específica dentro del juego:
214
Programación multimedia y dispositivos móviles
215
Tema 4: Motores de juegos: tipos y utilización
216
Programación multimedia y dispositivos móviles
EJEMPLO
217
Tema 4: Motores de juegos: tipos y utilización
218
Programación multimedia y dispositivos móviles
219
Tema 4: Motores de juegos: tipos y utilización
220
Programación multimedia y dispositivos móviles
Animación 2D
Para esto nos hará falta nuestro personaje 2D con las imágenes de
la animación separadas en poses clave.
Por cada pose se diseña una imagen del personaje: una imagen
para cuando salte, otra para cuando duerma, otra para cuando
hable… y así tendremos una o varias imágenes para cada acción
que realice. No obstante, habrá acciones que requieran más de una
imagen, ya que en cada movimiento de un salto, por ejemplo, el
personaje mantendrá una postura distinta.
221
Tema 4: Motores de juegos: tipos y utilización
222
Programación multimedia y dispositivos móviles
223
Tema 4: Motores de juegos: tipos y utilización
224
Programación multimedia y dispositivos móviles
225
Tema 4: Motores de juegos: tipos y utilización
226
Programación multimedia y dispositivos móviles
227
Tema 4: Motores de juegos: tipos y utilización
228
Programación multimedia y dispositivos móviles
229
Tema 4: Motores de juegos: tipos y utilización
230
Programación multimedia y dispositivos móviles
231
Tema 4: Motores de juegos: tipos y utilización
232
Programación multimedia y dispositivos móviles
233
Tema 4: Motores de juegos: tipos y utilización
234
Programación multimedia y dispositivos móviles
235
5
DESARROLLO DE JUEGOS 2D Y 3D
Programación multimedia y dispositivos móviles
237
Tema 5: Desarrollo de juegos 2D y 3D
• Unreal Engine: junto con Unity 3D, es uno de los entornos más
conocidos y valorados dentro del mundo del desarrollo de juegos.
Permite la configuración y diseño de recursos gráficos avanzados
de la misma forma que Unity.
Ambos entornos requieren de un nivel significativo de pro-
gramación.
238
• Versión de gráficos utilizados: lugar de instalación de la aplica-
ción por defecto en el dispositivo.
Por último, una vez completados todos estos apartados, se ge-
nerará en el equipo un .apk (extensión de las aplicaciones en
Android), que será el archivo ejecutable que se instalará en el dis-
positivo deseado.
239
Tema 5: Desarrollo de juegos 2D y 3D
240
Programación multimedia y dispositivos móviles
5.3.2. Modelos 3D
Para mejorar la calidad de nuestro videojuego, podemos utilizar
objetos tridimensionales previamente modelados con alguna
aplicación de diseño 3D. Por ejemplo, si nuestro videojuego es un
simulador de vuelo, podríamos utilizar como enemigos simples
esferas asesinas. Sin embargo, obtendríamos mejor resultado si
pudiésemos utilizar modelos 3D profesionales de aviones, buques
y camiones, los cuales enriquecerían la sensación de realidad de la
escena.
241
Tema 5: Desarrollo de juegos 2D y 3D
242
Programación multimedia y dispositivos móviles
5.3.3. Formas 3D
Los modelos 3D creados con complejas aplicaciones de diseño
aportan gran valor a nuestro juego. A pesar de ello, su uso puede
ser ineficiente en algunos casos.
Dependiendo de lo complejo que sea el modelo, de la cantidad de
vértices y mapeo de texturas o de la cantidad de animaciones, entre
otros, un modelo 3D puede ser muy pesado y requerir demasiados
recursos del motor gráfico. Ello podría traducirse en que nuestra
app final se cuelgue por falta de memoria o por no conseguir los
frames por segundo suficientes (es decir, que las animaciones
funcionen a tirones). Si, además, dicho objeto no aporta gran im-
portancia dentro del juego, muy probablemente deberíamos pensar
en sustituirlo por una forma tridimensional básica, como un cubo,
una espera, una pirámide o un cilindro. Por ejemplo, en el caso un
simulador de vuelo, imaginemos que deseamos dibujar una ciudad
a nuestros pies mientras la sobrevolamos con nuestro caza. Si mo-
delamos los edificios como prismas cuadrados, en lugar de utilizar
modelos perfectos de la torre Chrysler para componer nuestra ciu-
dad, ahorraremos vértices y datos, los cuales colapsarían el motor
de renderizado. Con unas buenas texturas, un cubo alargado pasará
perfectamente por un rascacielos o un edificio cualquiera. Además,
los motores gráficos nos permiten el uso de estas formas 3D pri-
mitivas con gran facilidad. De este modo aliviaremos la carga del
procesador, a la vez que reduciremos el consumo de memoria.
Veamos un ejemplo. En Unity, selecciona el menú GameObject > 3D
Object > Cube. En la escena aparecerá un nuevo objeto 3D en forma
de cubo. Más tarde podremos añadir texturas, materiales, propie-
dades físicas, colliders o cambiar sus dimensiones desde el
Inspector y sus atributos.
243
Tema 5: Desarrollo de juegos 2D y 3D
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
244
Programación multimedia y dispositivos móviles
245
Tema 5: Desarrollo de juegos 2D y 3D
246
Programación multimedia y dispositivos móviles
Efectos de posprocesamiento
247
Tema 5: Desarrollo de juegos 2D y 3D
248
Programación multimedia y dispositivos móviles
Una vez pasada la fase del testeo del videojuego, este debe
obtener la certification testing, la cual es necesaria para poder
comercializarlo. Actualmente, ser game tester es un empleo que
requiere experiencia y conocimientos avanzados en el sector.
249
Tema 5: Desarrollo de juegos 2D y 3D
Asimismo, para crear un nuevo material deberemos ir a Assets > Create >
Material. Los nuevos materiales se asignan al Standard shader y, una
vez aplicados a un objeto (para aplicarlo podemos arrastrarlo desde
el Project view hasta la scene o la jerarquía), podremos cambiar las
propiedades en el Inspector.
250
Programación multimedia y dispositivos móviles
Efectos de posprocesamiento
251
Tema 5: Desarrollo de juegos 2D y 3D
2. Cuando esté listo, nos saldrá una pestaña de Importar, la cual se-
leccionaremos. Ya estaremos listos para crear un posprocesado.
252
Programación multimedia y dispositivos móviles
253
Tema 5: Desarrollo de juegos 2D y 3D
Cada uno de estos nodos será la representación de una acción del persona-
je. Estos reflejarán las transiciones entre los estados más básicos.
254
Programación multimedia y dispositivos móviles
Otra herramienta que es útil dentro del IDE de Unity es el Unity test
runner. Esta herramienta comprueba el código de programación en
busca de errores antes de realizar una compilación. Esto puede ser
útil para corregir errores de sintaxis, por ejemplo.
ponte a prueba
255
solucionario
1.3. Entornos de trabajo integrado 2.9. Bases de datos y almacenamiento
Una de las herramientas de que dispone Las SharedPreferences son una forma mucho
Android Studio para verificar errores y así poder más segura para proteger los datos que
solucionarlos es: queremos almacenar en nuestra aplicación en
d) Logcat. comparación con una base de datos embebida
como SQLite.
b) Falso.
1.6. Integración en el entorno de desarrollo
Cuando creamos un proyecto seleccionando
2.12. Búsqueda de dispositivos
una versión de API de nivel 29, ¿en qué
dispositivos podremos usarla? Indica cuál de las siguientes opciones es la más
a) En los de nivel 29 y anteriores. correcta sobre la conexión de dos dispositivos a
través del puerto Bluetooth:
d) Todas las opciones anteriores son correctas.
1.9. Modelo de estados de una aplicación
para dispositivos móviles. Activo, pausa y
destruido 2.16. Pruebas y documentación
Cuando la actividad está en un estado en el ¿Cuántos tipos de pruebas podemos distinguir
que Activity ya es visible al usuario y coincide en Android?
con el último método que se ejecuta mientras c) 2.
que dicha Activity está en primer plano, ¿a qué
función se accede?
3.3. Descripción e instalación de las librerías
b) onResume().
multimedia
Se conoce así a la librería que nos permite
1.11. Utilización de entornos de ejecución
solapar muchas fuentes de sonido, asignando
del administrador de aplicaciones
prioridades diferentes a cada una de ellas, y así
En los dispositivos actuales, la función de poder reproducirlas al mismo tiempo.
depuración de aplicaciones que usa el puerto b) SoundPool.
USB, más conocido como depuración USB, ha
sido eliminada porque vulnera la seguridad de
los datos almacenados en el mismo. 3.7. Procesamiento de objetos multimedia.
Clases. Estados, métodos y eventos
b) Falso.
¿Cuál de las siguientes no es una clase de bajo
nivel?
2.3. Compilación, preverificación,
empaquetado y ejecución c) Camera2.
256
solucionario
4.9. Aplicación de modificaciones sobre
juegos existentes
La gran inversión que requiere el desarrollo de
un juego tanto en recursos económicos como
humanos compensa, porque una vez lanzado
al mercado pasarán muchos años hasta que
sea necesario modificarlo y mejorarlo para
adaptarse a los posibles cambios que se hayan
producido en tecnología.
b) Falso.
257