Nivel de API: 19
Android 4.4 (KITKAT
) es una nueva versión de la plataforma de Android que ofrece nuevas funciones para los usuarios y los desarrolladores de apps. En este documento se ofrece una introducción a las API nuevas más distinguidas.
Como desarrollador de apps, debes descargar la imagen del sistema Android 4.4 y la plataforma de SDK de SDK Manager lo antes posible. Si no tienes un dispositivo con Android 4.4 en el que puedas probar la app, usa la imagen del sistema de Android 4.4 para probarla en el emulador de Android. Luego, compila tus apps con la plataforma Android 4.4 para comenzar a usar las APIs más recientes.
Actualiza el nivel de tu API de destino
A fin de optimizar mejor tu app para dispositivos que ejecutan Android 4.4, debes configurar targetSdkVersion
como "19"
, instalarla en una imagen del sistema de Android 4.4, probarla y, luego, publicar una actualización con este cambio.
Puedes usar las APIs en Android 4.4 y, al mismo tiempo, admitir versiones anteriores. Para ello, debes agregar condiciones a tu código que comprueben el nivel de API del sistema antes de ejecutar APIs no compatibles con tu minSdkVersion
.
Si deseas obtener más información sobre cómo mantener la retrocompatibilidad, consulta Cómo brindar compatibilidad con diferentes versiones de la plataforma.
Para obtener más información sobre cómo funcionan los niveles de API, consulta ¿Qué es el nivel de API?
Importantes cambios en los comportamientos
Si publicaste anteriormente una app para Android, ten en cuenta que esta podría verse afectada por los cambios en Android 4.4.
Si tu app realiza operaciones de lectura desde un medio de almacenamiento externo...
Tu app no puede leer archivos compartidos en el almacenamiento externo cuando se ejecuta en Android 4.4, a menos que tenga el permiso READ_EXTERNAL_STORAGE
. Es decir, ya no se puede acceder a los archivos dentro del directorio que muestra getExternalStoragePublicDirectory()
sin el permiso. Sin embargo, si necesitas acceder solo a los directorios específicos de tu app que proporciona getExternalFilesDir()
, no necesitas el permiso READ_EXTERNAL_STORAGE
.
Si tu app usa WebView...
Es posible que tu app se comporte de manera diferente cuando se ejecuta en Android 4.4, en especial cuando actualizas targetSdkVersion
de la app a "19" o una versión posterior.
El código subyacente de la clase WebView
y las APIs relacionadas se actualizó para basarse en una instantánea moderna del código fuente de Chromium. Esto aporta una variedad de mejoras en el rendimiento, compatibilidad con nuevas funciones de HTML5 y compatibilidad con la depuración remota de tu contenido de WebView
. El alcance de esta actualización implica que, si tu app usa WebView
, su comportamiento puede verse afectado en algunos casos. Si bien los cambios de comportamiento conocidos están documentados y afectan principalmente a tu app solo cuando actualizas la targetSdkVersion
de tu app a "19" o una versión posterior, el nuevo WebView
funciona en "modo no estándar" para proporcionar algunas funcionalidades heredadas en apps orientadas al nivel de API 18 o versiones anteriores, es posible que tu app dependa de comportamientos desconocidos de la versión anterior de WebView
.
Por lo tanto, si tu app existente usa WebView
, es importante que pruebes en Android 4.4 lo antes posible y consultes Cómo migrar a WebView en Android 4.4 para obtener información sobre cómo podría verse afectada tu app cuando actualices tu targetSdkVersion
a "19" o una versión posterior.
Si tu app usa AlarmManager...
Cuando estableces la targetSdkVersion
de tu app en "19" o un valor superior, las alarmas que crees con set()
o setRepeating()
no serán exactas.
Para mejorar la eficiencia energética, Android ahora agrupa las alarmas de todas las apps que tienen lugar en horarios razonablemente similares para que el sistema active el dispositivo una sola vez, en lugar de varias veces, para controlar cada alarma.
Si la alarma no está asociada con una hora exacta, pero es importante que se invoque durante un intervalo de tiempo específico (como entre las 2 p.m. y las 4 p.m.), puedes usar el nuevo método setWindow()
, que acepta un horario "más antiguo" para la alarma y un "período" posterior a la hora más temprana en la que el sistema debe invocar la alarma.
Si necesitas fijar la alarma a una hora de reloj exacta (por ejemplo, para un recordatorio de un evento del calendario), puedes usar el nuevo método setExact()
.
Este comportamiento de agrupamiento inexacto se aplica únicamente a apps actualizadas. Si configuraste targetSdkVersion
en "18" o un valor inferior, las alarmas se seguirán comportando como lo hacían en las versiones anteriores cuando se ejecuten en Android 4.4.
Si tu app sincroniza datos con ContentResolver...
Cuando configures la targetSdkVersion
de tu app en "19" o un valor superior, la creación de una sincronización con addPeriodicSync()
realizará tus operaciones de sincronización dentro de un intervalo flexible predeterminado de aproximadamente el 4% del período que especifiques. Por ejemplo, si tu frecuencia de sondeo es de 24 horas, la operación de sincronización puede ocurrir dentro de un plazo aproximado de una hora cada día, en lugar de producirse exactamente a la misma hora cada día.
Para especificar tu propio intervalo flexible para las operaciones de sincronización, debes comenzar a usar el nuevo método requestSync()
. Para obtener más información, consulta la siguiente sección sobre adaptadores de sincronización.
Este comportamiento de intervalo flexible se aplica solamente a apps actualizadas. Si configuraste targetSdkVersion
en "18" o versiones anteriores, tus solicitudes de sincronización existentes se seguirán comportando como lo hacían en versiones anteriores cuando se ejecuten en Android 4.4.
Framework de impresión
Android ahora incluye un framework completo que permite a los usuarios imprimir documentos con una impresora conectada por Wi-Fi, Bluetooth u otros servicios. El sistema maneja las transacciones entre una app que intenta imprimir un documento y los servicios que envían los trabajos de impresión a una impresora. El framework android.print
proporciona todas las APIs necesarias para especificar un documento de impresión y entregarlo al sistema para que lo imprima. El contenido determinará la API necesitarás para un trabajo de impresión.
Impresión de contenido genérico
Si deseas imprimir contenido desde tu IU como un documento, primero debes crear una subclase de PrintDocumentAdapter
. Dentro de esta clase, debes implementar algunos métodos de devolución de llamada, como onLayout()
para establecer tu diseño en función de las propiedades de impresión proporcionadas y onWrite()
para serializar tu contenido imprimible en una ParcelFileDescriptor
.
Para escribir tu contenido en el ParcelFileDescriptor
, debes pasarle un PDF. Las nuevas APIs de PdfDocument
ofrecen una manera conveniente de hacerlo, ya que proporcionan un Canvas
de getCanvas()
, en el que puedes dibujar tu contenido imprimible. Luego, escribe el PdfDocument
en ParcelFileDescriptor
con el método writeTo()
.
Una vez que hayas definido tu implementación para PrintDocumentAdapter
, podrás ejecutar trabajos de impresión a pedido del usuario con el método PrintManager
, print()
, que toma PrintDocumentAdapter
como uno de sus argumentos.
Impresión de imágenes
Si deseas imprimir una fotografía u otra imagen de mapa de bits, las API del asistente de la biblioteca de compatibilidad hacen todo el trabajo por ti. Simplemente crea una instancia nueva de PrintHelper
, establece el modo de escala con setScaleMode()
y pasa tu Bitmap
a printBitmap()
. Eso es todo. La biblioteca se encargará de toda la interacción restante con el sistema para enviar el mapa de bits a la impresora.
Compilación de servicios de impresión
Como OEM de impresoras, puedes usar el framework android.printservice
para proporcionar interoperabilidad con tus impresoras desde dispositivos Android. Puedes compilar y distribuir servicios de impresión como APK, que los usuarios pueden instalar en sus dispositivos. Una app de servicios de impresión funciona principalmente como un servicio sin periféricos creando subclases de la clase PrintService
, que recibe trabajos de impresión del sistema y los comunica a las impresoras usando los protocolos correspondientes.
Para obtener más información sobre cómo imprimir el contenido de tu app, consulta Cómo imprimir contenido.
Proveedor de SMS
El proveedor de contenido Telephony
(el "proveedor de SMS") permite que las apps lean y escriban mensajes SMS y MMS en el dispositivo. Incluye tablas para los mensajes SMS y MMS recibidos, en borrador, enviados y pendientes, entre otros elementos.
A partir de Android 4.4, la configuración del sistema les permite a los usuarios seleccionar una "app de SMS predeterminada". Una vez seleccionada, solo la app de SMS predeterminada podrá escribir en el proveedor de SMS, y solo la app de SMS predeterminada recibirá la transmisión SMS_DELIVER_ACTION
cuando el usuario reciba un SMS o WAP_PUSH_DELIVER_ACTION
cuando reciba un MMS. La app de SMS predeterminada se encarga de la escritura de los detalles en el proveedor de SMS cuando recibe o envía un mensaje nuevo.
Otras apps que no se hayan seleccionado como app de SMS predeterminada solo podrán leer el proveedor de SMS. Sin embargo, también es posible que reciban una notificación cuando llegue un SMS nuevo escuchando la transmisión de SMS_RECEIVED_ACTION
, que es una transmisión no anulable que se puede enviar a varias apps. Esta transmisión está destinada a apps que, si bien no fueron seleccionadas como la app de SMS predeterminada, deben leer mensajes entrantes especiales; por ejemplo, al realizar la verificación de un número de teléfono.
Para obtener más información, lee la entrada de blog Cómo preparar tus apps de SMS para KitKat.
Redes inalámbricas y conectividad
Emulación de tarjeta de host
Las apps de Android ahora emulan tarjetas NFC del protocolo ISO14443-4 (ISO-DEP) que usan APDU para el intercambio de datos (como se especifica en ISO7816-4). Esto permite que un dispositivo habilitado para NFC que ejecuta Android 4.4 emule varias tarjetas NFC al mismo tiempo. También permite que una terminal de pago NFC u otro lector NFC inicie una transacción con la tarjeta NFC correspondiente según el identificador de la aplicación (AID).
Si deseas emular una tarjeta NFC que usa estos protocolos en tu app, crea un componente de servicio basado en la clase HostApduService
. Por otro lado, si tu app usa un Elemento seguro para emular tarjetas, debes crear un servicio basado en la clase OffHostApduService
, que no estará involucrado directamente en las transacciones, pero es necesario para registrar los AID que debe controlar el Elemento seguro.
Para obtener más información, lee la guía Emulación de tarjetas NFC.
Modo de lector NFC
Un nuevo modo de lector NFC permite que una actividad restrinja toda la actividad de NFC a la lectura exclusiva de los tipos de etiquetas que la actividad busca mientras funciona en segundo plano. Puedes habilitar el modo de lectura para tu actividad con enableReaderMode()
, que proporciona una implementación de NfcAdapter.ReaderCallback
que recibe una devolución de llamada cuando se detectan etiquetas nuevas.
Esta nueva capacidad, junto con la emulación de tarjeta de host, permite que Android funcione en ambos extremos de una interfaz de pago móvil: un dispositivo funciona como la terminal de pago (un dispositivo que ejecuta una actividad en modo de lector) y otro dispositivo como el cliente de pago (un dispositivo que emula una tarjeta NFC).
Transmisores infrarrojos
Cuando usas un dispositivo que incluye un transmisor infrarrojo (IR), puedes transmitir señales IR con las APIs de ConsumerIrManager
. Para obtener una instancia de ConsumerIrManager
, llama a getSystemService()
con CONSUMER_IR_SERVICE
como argumento. Luego, puedes consultar las frecuencias IR admitidas del dispositivo con getCarrierFrequencies()
y transmitir señales pasando el patrón de frecuencia y señal deseado con transmit()
.
Siempre debes verificar primero si un dispositivo incluye un transmisor IR llamando a hasIrEmitter()
. Sin embargo, si tu app es compatible solo con dispositivos que tienen uno, debes incluir un elemento <uses-feature>
en tu manifiesto para "android.hardware.consumerir"
(FEATURE_CONSUMER_IR
).
Multimedia
Reproducción adaptable
La compatibilidad con la reproducción de video adaptable ahora está disponible con las APIs de MediaCodec
, lo que permite un cambio fluido de la resolución durante la reproducción en un Surface
. Puedes proporcionar los fotogramas de entrada del decodificador de una nueva resolución y la resolución de los búferes de salida cambian sin una brecha importante.
Para habilitar la reproducción adaptativa, agrega dos teclas a MediaFormat
que especifiquen la resolución máxima que tu app requiere del códec: KEY_MAX_WIDTH
y KEY_MAX_HEIGHT
. Con estos agregados a tu MediaFormat
, pasa MediaFormat
a tu instancia de MediaCodec
con configure()
.
El códec realizará una transición fluida entre las resoluciones que sean iguales o inferiores a estos valores. El códec también puede admitir resoluciones superiores a los máximos especificados (siempre y cuando estén dentro de los límites de los perfiles admitidos). Sin embargo, es posible que las transiciones a resoluciones más grandes no sean fluidas.
Para cambiar la resolución al decodificar video H.264, sigue poniendo en cola los fotogramas con MediaCodec.queueInputBuffer(), pero asegúrate de proporcionar los valores nuevos del conjunto de parámetros de secuencia (SPS) y el conjunto de parámetros de imagen (PPS) junto con el fotograma de actualización instantánea del decodificador (IDR) en un solo búfer.
Sin embargo, antes de intentar configurar el códec para la reproducción adaptable, debes verificar que el dispositivo admita esta función llamando a isFeatureSupported(String)
con FEATURE_AdaptivePlayback
.
Nota: La compatibilidad con la reproducción adaptable depende del proveedor. Algunos códecs pueden requerir más memoria para sugerencias de resolución más grandes. Por lo tanto, debes fijar los máximos de resolución según el material de fuente que decodifiques.
Marcas de tiempo de audio a pedido
Para facilitar la sincronización de audio y video, la nueva clase AudioTimestamp
proporciona detalles del cronograma sobre un "fotograma" específico en una transmisión de audio controlada por AudioTrack
. Para obtener la marca de tiempo más reciente disponible, crea una instancia de un objeto AudioTimestamp
y pásalo a getTimestamp()
. Si la solicitud de la marca de tiempo se realiza correctamente, la instancia AudioTrack
se completa con una posición en unidades de fotogramas, junto con el tiempo estimado en el que ese fotograma se presentó o se confirmó.
Puedes usar el valor de nanoTime
en AudioTimestamp
(que es monotónico) para encontrar el fotograma del video asociado más cercano en comparación con framePosition
, de modo que puedas soltar, duplicar o interpolar fotogramas de video para que coincidan con el audio. Como alternativa, puedes determinar el tiempo delta entre el valor de nanoTime
y el tiempo esperado de un fotograma de video futuro (teniendo en cuenta la tasa de muestreo) para predecir qué fotograma de audio se espera en el mismo momento que un fotograma de video.
Lector de imágenes de superficie
La nueva API de ImageReader
te proporciona acceso directo a los búferes de imagen a medida que se renderizan en Surface
. Puedes adquirir un ImageReader
con el método estático newInstance()
. Luego, llama a getSurface()
para crear un Surface
nuevo y entregar los datos de la imagen con un productor, como MediaPlayer
o MediaCodec
. Si quieres recibir notificaciones cuando haya imágenes nuevas disponibles desde la superficie, implementa la interfaz ImageReader.OnImageAvailableListener
y regístrala con setOnImageAvailableListener()
.
Ahora, a medida que dibujas contenido en tu Surface
, ImageReader.OnImageAvailableListener
recibe una llamada a onImageAvailable()
a medida que cada marco de imagen nuevo está disponible, lo que te proporciona el ImageReader
correspondiente. Puedes usar el ImageReader
para adquirir los datos de imagen del marco como un objeto Image
llamando a acquireLatestImage()
o acquireNextImage()
.
El objeto Image
proporciona acceso directo a los datos de la marca de tiempo, el formato, las dimensiones y los píxeles de la imagen en un ByteBuffer
. Sin embargo, para que la clase Image
interprete tus imágenes, estas deben tener un formato acorde a uno de los tipos definidos por las constantes en ImageFormat
o PixelFormat
.
Medición de picos y RMS
Ahora, puedes consultar el pico y la RMS de la transmisión de audio actual desde Visualizer
creando una nueva instancia de Visualizer.MeasurementPeakRms
y pasándola a getMeasurementPeakRms()
. Cuando llamas a este método, los valores máximos y RMS de la Visualizer.MeasurementPeakRms
determinada se establecen en los últimos valores medidos.
Amplificador de volumen
LoudnessEnhancer
es una subclase nueva de AudioEffect
que te permite aumentar el volumen audible de MediaPlayer
o AudioTrack
. Esto puede ser especialmente útil en conjunto con el nuevo método getMeasurementPeakRms()
, mencionado anteriormente, para aumentar el volumen de las pistas de audio habladas mientras se reproduce otro contenido multimedia.
Controladores remotos
Android 4.0 (nivel de API 14) introdujo las APIs de RemoteControlClient
que permiten que las apps de música consuman eventos de controladores multimedia desde clientes remotos, como controles multimedia en la pantalla de bloqueo. Ahora, las nuevas APIs de RemoteController
te permiten compilar tu propio control remoto, lo que posibilita la creación de apps y periféricos nuevos e innovadores que pueden controlar la reproducción de cualquier app de música que se integre con RemoteControlClient
.
Para compilar un controlador remoto, puedes implementar la interfaz de usuario de la manera que desees, pero para enviar los eventos de botones de medios a la app de música del usuario, debes crear un servicio que extienda la clase NotificationListenerService
e implemente la interfaz RemoteController.OnClientUpdateListener
. Es importante usar NotificationListenerService
como base porque proporciona las restricciones de privacidad adecuadas, que requieren que los usuarios habiliten tu app como objeto de escucha de notificaciones en la configuración de seguridad del sistema.
La clase NotificationListenerService
incluye un par de métodos abstractos que debes implementar, pero si solo te preocupan los eventos del controlador multimedia para controlar la reproducción de contenido multimedia, puedes dejar tu implementación vacía y, en su lugar, enfocarte en los métodos RemoteController.OnClientUpdateListener
.
Calificaciones desde controladores remotos
Android 4.4 se basa en las capacidades existentes para clientes de control remoto (apps que reciben eventos de control de medios con RemoteControlClient
) y agrega la capacidad de que los usuarios califiquen la pista actual desde el control remoto.
La nueva clase Rating
encapsula información sobre la calificación de un usuario. Una calificación se define en función de su estilo de calificación (RATING_HEART
, RATING_THUMB_UP_DOWN
, RATING_3_STARS
, RATING_4_STARS
, RATING_5_STARS
o RATING_PERCENTAGE
) y el valor de la calificación que corresponde a ese estilo.
Para permitir que los usuarios califiquen tus pistas desde un controlador remoto:
- Para indicar que deseas exponer la IU de calificación al usuario (si corresponde), agrega la marca
FLAG_KEY_MEDIA_RATING
ensetTransportControlFlags()
. - Llama a
editMetadata()
para recuperar unRemoteControlClient.MetadataEditor
y pásaloRATING_KEY_BY_USER
conaddEditableKey()
. - Luego, especifica el estilo de calificación. Para ello, llama a
putObject()
y pásaleRATING_KEY_BY_USER
como la clave y uno de los estilos de calificación anteriores como el valor.
Para recibir una devolución de llamada cuando el usuario cambie la calificación desde el control remoto, implementa la nueva interfaz RemoteControlClient.OnMetadataUpdateListener
y pasa una instancia a setMetadataUpdateListener()
. Cuando el usuario cambia la calificación, tu RemoteControlClient.OnMetadataUpdateListener
recibe una llamada a onMetadataUpdate()
y pasa RATING_KEY_BY_USER
como la clave y un objeto Rating
como el valor.
Subtítulos
VideoView
ahora admite pistas de subtítulos WebVTT cuando se reproducen videos de transmisión en vivo HTTP (HLS) y las muestra según las preferencias de subtítulos que definió el usuario en la configuración del sistema.
También puedes proporcionar a VideoView
las pistas de subtítulos de WebVTT con el método addSubtitleSource()
. Este método acepta un InputStream
que lleva los datos de subtítulos y un objeto MediaFormat
que especifica el formato de los datos de subtítulos, que puedes especificar con createSubtitleFormat()
. Estos subtítulos también aparecen sobre el video según las preferencias del usuario.
Si no usas VideoView
para mostrar el contenido del video, debes lograr que la superposición de subtítulos coincida con las preferencias de subtítulos del usuario de la manera más precisa posible. Una nueva API de CaptioningManager
te permite consultar las preferencias de subtítulos del usuario, incluidos los estilos definidos por CaptioningManager.CaptionStyle
, como el tipo de letra y el color. En caso de que el usuario ajuste algunas preferencias una vez que el video ya se haya iniciado, debes escuchar los cambios en las preferencias registrando una instancia de CaptioningManager.CaptioningChangeListener
para recibir una devolución de llamada cuando alguna de las preferencias cambie y luego actualizar los subtítulos según sea necesario.
Animación y gráficos
Escenas y transiciones
El nuevo framework android.transition
proporciona APIs que facilitan las animaciones entre diferentes estados de tu interfaz de usuario. Una función clave es la capacidad de definir estados distintos de tu IU, conocidos como "escenas", creando un diseño separado para cada uno. Cuando quieras animar de una escena a otra, ejecuta una "transición", que calcula la animación necesaria para cambiar el diseño de la escena actual a la siguiente.
Generalmente, para realizar una transición entre dos escenas tienes que realizar lo siguiente:
- Especifica el
ViewGroup
que contiene los componentes de la IU que deseas cambiar. - Especifica el diseño que representa el resultado final del cambio (la siguiente escena).
- Especifica el tipo de transición que debe animar el cambio de diseño.
- Ejecuta la transición.
Puedes usar un objeto Scene
para completar los pasos 1 y 2. Un Scene
contiene metadatos que describen las propiedades de un diseño que son necesarias para realizar una transición, incluida la vista superior de la escena y el diseño de la escena. Puedes crear un Scene
con un constructor de clase o el método estático getSceneForLayout()
.
Luego, debes usar TransitionManager
para completar los pasos 3 y 4. Una forma es pasar tu Scene
al método estático go()
. De esta manera, se encuentra la vista superior de la escena en el diseño actual y se realiza una transición en las vistas secundarias para alcanzar el diseño definido por Scene
.
Como alternativa, no necesitas crear un objeto Scene
. En su lugar, puedes llamar a beginDelayedTransition()
especificando un ViewGroup
que contenga las vistas que desees cambiar. Luego, agrega, quita o reconfigura las vistas objetivo. Una vez que el sistema disponga los cambios según sea necesario, se iniciará una transición para animar todas las vistas afectadas.
Para obtener un control adicional, puedes definir conjuntos de transiciones que deben ocurrir entre escenas predefinidas; para ello, usa un archivo en formato XML en el directorio res/transition/
de tu proyecto. Dentro de un elemento <transitionManager>
, especifica una o más etiquetas <transition>
que indiquen una escena (una referencia a un archivo de diseño) y la transición que se aplicará cuando ingreses a esa escena o salgas de ella. Luego, aumenta este conjunto de transiciones con inflateTransitionManager()
. Usa el TransitionManager
que se muestra para ejecutar cada transición con transitionTo()
y pasa una Scene
representada por una de las etiquetas <transition>
. También puedes definir conjuntos de transiciones de manera programática con las APIs de TransitionManager
.
Cuando especificas una transición, puedes usar varios tipos predefinidos definidos por subclases de Transition
, como Fade
y ChangeBounds
. Si no especificas un tipo de transición, el sistema usa AutoTransition
de forma predeterminada, que automáticamente atenúa, mueve y cambia el tamaño de las vistas según sea necesario. Además, puedes crear transiciones personalizadas extendiendo cualquiera de estas clases para realizar las animaciones como lo desees. Una transición personalizada puede hacer un seguimiento de los cambios de propiedades que desees y crear cualquier animación que desees en función de esos cambios. Por ejemplo, podrías proporcionar una subclase de Transition
que escuche los cambios en la propiedad de "rotación" de una vista y, luego, anime cualquier cambio.
Para obtener más información, consulta la documentación de TransitionManager
.
Pausa del animador
Las APIs de Animator
ahora te permiten pausar y reanudar una animación en curso con los métodos pause()
y resume()
.
Para realizar un seguimiento del estado de una animación, puedes implementar la interfaz Animator.AnimatorPauseListener
, que proporciona devoluciones de llamada cuando se pausa y reanuda una animación: pause()
y resume()
. Luego, agrega el objeto de escucha a un objeto Animator
con addPauseListener()
.
Como alternativa, puedes crear una subclase de la clase abstracta AnimatorListenerAdapter
, que ahora incluye implementaciones vacías para las devoluciones de llamada de pausa y reanudación definidas por Animator.AnimatorPauseListener
.
Mapas de bits reutilizables
Ahora puedes reutilizar cualquier mapa de bits mutable en BitmapFactory
para decodificar cualquier otro mapa de bits, incluso cuando el mapa de bits nuevo tenga un tamaño diferente, siempre que el recuento de bytes resultante del mapa de bits decodificado (disponible desde getByteCount()
) sea menor o igual que el recuento de bytes asignado del mapa de bits reutilizado (disponible desde getAllocationByteCount()
). Para obtener más información, consulta inBitmap
.
Las nuevas APIs para Bitmap
permiten una reconfiguración similar para reutilizarla fuera de BitmapFactory
(para la generación manual de mapas de bits o la lógica de decodificación personalizada). Ahora puedes establecer las dimensiones de un mapa de bits con los métodos setHeight()
y setWidth()
, y especificar una nueva Bitmap.Config
con setConfig()
sin afectar la asignación del mapa de bits subyacente. El método reconfigure()
también proporciona una forma conveniente de combinar estos cambios con una llamada.
Sin embargo, no debes reconfigurar un mapa de bits que el sistema de vista use actualmente, ya que el búfer de píxeles subyacente no se reasignará de manera predecible.
Contenido del usuario
Framework de acceso a almacenamiento
En versiones anteriores de Android, si quieres que tu app recupere un tipo específico de archivo de otra app, esta debe invocar un intent con la acción ACTION_GET_CONTENT
. Esta acción sigue siendo la forma adecuada de solicitar un archivo que deseas importar a tu app. Sin embargo, Android 4.4 presenta la acción ACTION_OPEN_DOCUMENT
, que permite al usuario seleccionar un archivo de un tipo específico y otorgar a tu app acceso de lectura a largo plazo (posiblemente con acceso de escritura) sin importar el archivo a tu app.
Si estás desarrollando una app que proporciona servicios de almacenamiento para archivos (como un servicio de almacenamiento en la nube), puedes participar en esta IU unificada para elegir archivos implementando un proveedor de contenido como una subclase de la nueva clase DocumentsProvider
. Tu subclase de DocumentsProvider
debe incluir un filtro de intents que acepte la acción PROVIDER_INTERFACE
("android.content.action.DOCUMENTS_PROVIDER"
). Luego, debes implementar los cuatro métodos abstractos en DocumentsProvider
:
queryRoots()
- Debe mostrar un
Cursor
que describa todos los directorios raíz del almacenamiento de documentos con las columnas definidas enDocumentsContract.Root
. queryChildDocuments()
- Debe mostrar un
Cursor
que describa todos los archivos en el directorio especificado, con las columnas definidas enDocumentsContract.Document
. queryDocument()
- Debe mostrar un
Cursor
que describa el archivo especificado con las columnas definidas enDocumentsContract.Document
. openDocument()
- Debe mostrar un
ParcelFileDescriptor
que represente el archivo especificado. El sistema llama a este método una vez que el usuario selecciona un archivo y la app cliente solicita acceso a él llamando aopenFileDescriptor()
.
Para obtener más información, consulta la guía Framework de acceso al almacenamiento.
Acceso a almacenamiento externo
Ahora puedes leer archivos específicos de la app y escribir en ellos en medios de almacenamiento externo secundario, como en el caso de un dispositivo que brinda almacenamiento emulado y en una tarjeta SD. El nuevo método getExternalFilesDirs()
funciona igual que el método getExternalFilesDir()
existente, excepto que muestra un array de objetos File
. Antes de leer cualquiera de las rutas de acceso que muestra este método o escribir en ellas, pasa el objeto File
al nuevo método getStorageState()
para verificar que el almacenamiento esté disponible en ese momento.
Otros métodos para acceder al directorio de caché específico de la app y al directorio OBB ahora también tienen versiones correspondientes que proporcionan acceso a dispositivos de almacenamiento secundario: getExternalCacheDirs()
y getObbDirs()
, respectivamente.
La primera entrada en el array de File
que se muestra se considera el almacenamiento externo principal del dispositivo, que es la misma que la File
que muestran los métodos existentes, como getExternalFilesDir()
.
Nota: A partir de Android 4.4, la plataforma ya no requiere que tu app adquiera WRITE_EXTERNAL_STORAGE
o READ_EXTERNAL_STORAGE
cuando necesitas acceder, con los métodos anteriores, solo a regiones específicas de tu app del almacenamiento externo. Sin embargo, se requieren permisos si quieres acceder a las regiones que se pueden compartir del almacenamiento externo, que proporciona getExternalStoragePublicDirectory()
.
Adaptadores de sincronización
El nuevo método requestSync()
en ContentResolver
simplifica algunos de los procedimientos para definir una solicitud de sincronización para tu ContentProvider
mediante el encapsulamiento de solicitudes en el nuevo objeto SyncRequest
, que puedes crear con SyncRequest.Builder
. Las propiedades de SyncRequest
proporcionan la misma funcionalidad que las llamadas de sincronización de ContentProvider
existentes, pero agregan la capacidad de especificar que una sincronización se debe interrumpir si la red es de uso medido, para ello, se habilita setDisallowMetered()
.
Entrada del usuario
Tipos de sensores nuevos
El nuevo sensor TYPE_GEOMAGNETIC_ROTATION_VECTOR
proporciona datos del vector de rotación basados en un magnetómetro, que es una alternativa útil al sensor TYPE_ROTATION_VECTOR
cuando no hay un giroscopio disponible o cuando se usa con eventos de sensor en lotes para registrar la orientación del dispositivo mientras el teléfono está suspendido. Este sensor requiere menos energía que TYPE_ROTATION_VECTOR
, pero puede ser propenso a datos de eventos ruidosos y es más eficaz cuando el usuario está al aire libre.
Android ahora también es compatible con sensores de pasos incorporados en el hardware:
TYPE_STEP_DETECTOR
- Este sensor activa un evento cada vez que el usuario da un paso. Con cada paso del usuario, este sensor genera un evento con un valor de 1.0 y una marca de tiempo que indica cuándo se produjo el paso.
TYPE_STEP_COUNTER
- Este sensor también activa un evento con cada paso detectado, pero proporciona la cantidad total de pasos acumulados desde que la app registró este sensor por primera vez.
Ten en cuenta que estos dos pasos no siempre arrojan los mismos resultados. Los eventos TYPE_STEP_COUNTER
ocurren con una latencia más alta que los de TYPE_STEP_DETECTOR
, pero eso se debe a que el algoritmo TYPE_STEP_COUNTER
realiza más procesamiento para eliminar los falsos positivos. Por lo tanto, TYPE_STEP_COUNTER
puede ser más lento para entregar eventos, pero los resultados deberían ser más precisos.
Ambos sensores de pasos dependen del hardware (el dispositivo Nexus 5 es el primer dispositivo que los admite), por lo que debes verificar la disponibilidad con hasSystemFeature()
usando las constantes FEATURE_SENSOR_STEP_DETECTOR
y FEATURE_SENSOR_STEP_COUNTER
.
Eventos de sensores por lotes
Para administrar mejor la energía del dispositivo, las APIs de SensorManager
ahora te permiten especificar la frecuencia con la que deseas que el sistema entregue lotes de eventos de sensores a tu app. Esto no reduce la cantidad de eventos reales de sensores disponibles para tu app durante un período determinado, sino la frecuencia con la que el sistema llama a tu SensorEventListener
con actualizaciones del sensor. Es decir, en lugar de proporcionar cada evento a tu app en el momento en el que se produce, el sistema acumula todos los eventos que tienen lugar en un período y luego los entrega a tu app de una sola vez.
Para proporcionar el procesamiento por lotes, la clase SensorManager
agrega dos versiones nuevas del método registerListener()
que te permiten especificar la "latencia máxima de informe". Este nuevo parámetro especifica el retraso máximo que tu SensorEventListener
tolerará para la entrega de nuevos eventos de sensores. Por ejemplo, si especificas una latencia de lote de un minuto, el sistema entregará el conjunto reciente de eventos por lotes en un intervalo de no más de un minuto mediante llamadas consecutivas al método onSensorChanged()
, una vez por cada evento que se agrupó en lotes. El retraso de los eventos de sensores nunca superará el valor de la latencia de informe máxima, pero pueden presentarse antes si otras apps solicitan una latencia más corta para el mismo sensor.
Sin embargo, ten en cuenta que el sensor entregará a la app los eventos por lotes según la latencia de tu informe solo mientras la CPU esté activa. Si bien un sensor de hardware compatible con los lotes seguirá recolectando los eventos de sensores mientras la CPU está suspendida, no la activará para proporcionar a la app los eventos por lotes. Cuando se acabe la memoria para eventos del sensor, este comenzará a dejar de lado los eventos más antiguos para guardar los más nuevos. Para evitar perder eventos, activa el dispositivo antes de que el sensor llene su memoria y, luego, llama a flush()
para capturar el último lote de eventos. Para calcular cuándo estará llena la memoria y cuándo se debe vaciar, llama a getFifoMaxEventCount()
para obtener la cantidad máxima de eventos de sensor que puede guardar y divide esa cantidad por la velocidad a la que tu app desea cada evento. Usa ese cálculo para configurar alarmas de activación con AlarmManager
que invoquen tu Service
(que implementa SensorEventListener
) para vaciar el sensor.
Nota: No todos los dispositivos admiten el procesamiento por lotes de eventos de sensores, ya que requieren compatibilidad con el sensor de hardware. Sin embargo, a partir de Android 4.4, siempre debes usar los nuevos métodos registerListener()
, ya que, si el dispositivo no admite el procesamiento por lotes, el sistema ignorará correctamente el argumento de latencia de lotes y entregará eventos de sensores en tiempo real.
Identidades de los controladores
Android ahora identifica cada control conectado con un número entero único que puedes consultar con getControllerNumber()
, lo que facilita la asociación de cada control a un jugador diferente en un juego. El número de cada control puede cambiar si el usuario lo desconecta, conecta o reconfigura, por lo que debes registrar una instancia de InputManager.InputDeviceListener
para hacer un seguimiento del número de controlador que corresponde a cada dispositivo de entrada. Luego, llama a getControllerNumber()
para cada InputDevice
cuando se produzca un cambio.
Los dispositivos conectados ahora también proporcionan los IDs de productos y proveedores que están disponibles en getProductId()
y getVendorId()
. Si necesitas modificar las asignaciones de claves según el conjunto de claves disponible en un dispositivo, puedes consultarlo para verificar si determinadas claves están disponibles con hasKeys(int...)
.
Interfaz de usuario
Modo de pantalla completa envolvente
Para proporcionar a tu app un diseño que ocupe toda la pantalla, la nueva marca SYSTEM_UI_FLAG_IMMERSIVE
para setSystemUiVisibility()
(cuando se combina con SYSTEM_UI_FLAG_HIDE_NAVIGATION
) habilita un nuevo modo de pantalla completa envolvente. Mientras el modo de pantalla completa envolvente está habilitado, la actividad continúa recibiendo todos los eventos táctiles. El usuario puede revelar las barras del sistema con un deslizamiento hacia adentro en la región donde normalmente aparecen las barras del sistema. Esto borra la marca SYSTEM_UI_FLAG_HIDE_NAVIGATION
(y la marca SYSTEM_UI_FLAG_FULLSCREEN
, si se aplica) para que las barras del sistema permanezcan visibles. Sin embargo, si deseas que las barras del sistema se vuelvan a ocultar después de unos momentos, puedes usar la marca SYSTEM_UI_FLAG_IMMERSIVE_STICKY
.
Barras de sistema translúcidas
Ahora puedes hacer que las barras del sistema sean parcialmente traslúcidas con temas nuevos, Theme.Holo.NoActionBar.TranslucentDecor
y Theme.Holo.Light.NoActionBar.TranslucentDecor
. Si habilitas las barras de sistema translúcidas, tu diseño llenará el área detrás de las barras del sistema, por lo que también debes habilitar fitsSystemWindows
para la parte del diseño que las barras del sistema no debería cubrir.
Si creas un tema personalizado, establece uno de estos temas como tema superior o incluye las propiedades de estilo windowTranslucentNavigation
y windowTranslucentStatus
en tu tema.
Receptor de notificaciones mejorado
En Android 4.3, se agregaron las API de NotificationListenerService
, lo que permite que las apps reciban información sobre notificaciones nuevas a medida que el sistema las publica. En Android 4.4, los objetos de escucha de notificaciones pueden recuperar metadatos adicionales para la notificación y detalles completos sobre las acciones de la notificación:
El nuevo campo Notification.extras
incluye un Bundle
para entregar metadatos adicionales al compilador de notificaciones, como EXTRA_TITLE
y EXTRA_PICTURE
.
La nueva clase Notification.Action
define las características de una acción adjunta a la notificación, que puedes recuperar desde el nuevo campo actions
.
Duplicación de elementos de diseño para diseños RTL
En versiones anteriores de Android, si tu app incluye imágenes que deben invertir su orientación horizontal para diseños de derecha a izquierda, debes incluir la imagen duplicada en un directorio de recursos drawables-ldrtl/
. Ahora, el sistema puede duplicar imágenes automáticamente habilitando el atributo autoMirrored
en un recurso de elementos de diseño o llamando a setAutoMirrored()
. Cuando se habilita, Drawable
se duplica automáticamente cuando la dirección del diseño es de derecha a izquierda.
Accesibilidad
La clase View
ahora te permite declarar "regiones activas" para las partes de tu IU que se actualizan de forma dinámica con nuevo contenido de texto. Para ello, agrega el nuevo atributo accessibilityLiveRegion
a tu diseño XML o llama a setAccessibilityLiveRegion()
. Por ejemplo, una pantalla de acceso con un campo de texto que muestra una notificación de “contraseña incorrecta” se debe marcar como región activa para que el lector de pantalla recite el mensaje cuando cambie.
Las apps que ofrecen un servicio de accesibilidad ahora también pueden mejorar sus capacidades con nuevas APIs que brindan información sobre colecciones de vistas, como vistas de lista o cuadrícula, con AccessibilityNodeInfo.CollectionInfo
y AccessibilityNodeInfo.CollectionItemInfo
.
Permisos de la app
Tu app debe solicitar los siguientes permisos nuevos con la etiqueta <uses-permission>
para usar determinadas APIs nuevas:
INSTALL_SHORTCUT
- Permite que una aplicación instale un acceso directo en el Selector.
UNINSTALL_SHORTCUT
- Permite que una aplicación desinstale un acceso directo en el Selector.
TRANSMIT_IR
- Permite que una aplicación use el transmisor IR del dispositivo, si está disponible.
Nota: A partir de Android 4.4, la plataforma ya no requiere que tu app adquiera WRITE_EXTERNAL_STORAGE
o READ_EXTERNAL_STORAGE
cuando quieras acceder a regiones específicas del almacenamiento externo con métodos como getExternalFilesDir()
. Sin embargo, los permisos siguen siendo obligatorios si quieres acceder a las regiones que se pueden compartir del almacenamiento externo, que proporciona getExternalStoragePublicDirectory()
.
Funciones del dispositivo
A continuación, se incluyen las nuevas funciones del dispositivo que puedes declarar con la etiqueta <uses-feature>
para declarar los requisitos de tu app y habilitar el filtrado en Google Play o verificarlo durante el tiempo de ejecución:
FEATURE_CONSUMER_IR
- El dispositivo puede comunicarse con los dispositivos IR del consumidor.
FEATURE_DEVICE_ADMIN
- El dispositivo admite la aplicación de políticas de dispositivo por medio de administradores de dispositivos.
FEATURE_NFC_HOST_CARD_EMULATION
- El dispositivo admite la emulación de tarjetas NFC basada en el host.
FEATURE_SENSOR_STEP_COUNTER
- El dispositivo incluye un contador de pasos de hardware.
FEATURE_SENSOR_STEP_DETECTOR
- El dispositivo incluye un detector de pasos de hardware.
Para obtener una vista detallada de todos los cambios de la API en Android 4.4, consulta el Informe de diferencias de las APIs.