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 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 y la plataforma de SDK de Android 4.4 desde SDK Manager lo antes posible. Si no tienes un dispositivo con Android 4.4 en el que probar tu app, usa la imagen del sistema de Android 4.4 para probarla en el emulador de Android. Luego, compila tus apps para la plataforma de Android 4.4 para comenzar a usar las APIs más recientes.
Actualiza el nivel de tu API de destino
Para optimizar mejor tu app para dispositivos que ejecutan Android 4.4, debes configurar tu targetSdkVersion
en "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 si agregas condiciones a tu código que verifiquen el nivel de API del sistema antes de ejecutar las APIs que no son compatibles con tu minSdkVersion
.
Para obtener más información sobre cómo mantener la retrocompatibilidad, consulta Compatibilidad con versiones de plataforma diferentes.
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 ya publicaste 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 del directorio que muestra getExternalStoragePublicDirectory()
sin el permiso. Sin embargo, si solo necesitas acceder 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 forma diferente cuando se ejecute en Android 4.4, en especial cuando actualices el targetSdkVersion
de tu 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 ofrece una variedad de mejoras de 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 significa que, si tu app usa WebView
, su comportamiento puede verse afectado en algunos casos. Aunque los cambios de comportamiento conocidos están documentados y, en su mayoría, afectan a tu app solo cuando actualizas su targetSdkVersion
a "19" o versiones posteriores (el nuevo WebView
funciona en "modo no estándar" para proporcionar algunas funciones heredadas en apps que se orientan al nivel de API 18 y 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 la 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 configuras el atributo targetSdkVersion
de tu app en "19" o un valor superior, las alarmas que crees con set()
o setRepeating()
serán inexactas.
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 aún es importante que se invoque durante un período específico (por ejemplo, entre las 2 p.m. y las 4 p.m.), puedes usar el nuevo método setWindow()
, que acepta una hora "más temprana" para la alarma y una "ventana" de tiempo después de la hora más temprana en la que el sistema debe invocar la alarma.
Si la alarma debe fijarse a una hora exacta (como para un recordatorio de evento de 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 una versión anterior, las alarmas seguirán comportándose como lo hacían en versiones anteriores cuando se ejecutan en Android 4.4.
Si tu app sincroniza datos con ContentResolver...
Cuando establezcas el targetSdkVersion
de tu app en "19" o un valor superior, crear una sincronización con addPeriodicSync()
realizará las 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 detalles, 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 una versión anterior, tus solicitudes de sincronización existentes seguirán comportándose como lo hacían en versiones anteriores cuando se ejecutan 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 enviarlo al sistema para que se imprima. El contenido determinará la API necesitarás para un trabajo de impresión.
Impresión de contenido genérico
Si quieres imprimir contenido de 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, incluido onLayout()
para establecer tu diseño en función de las propiedades de impresión proporcionadas y onWrite()
para serializar tu contenido imprimible en un ParcelFileDescriptor
.
Para escribir tu contenido en ParcelFileDescriptor
, debes pasarle un PDF. Las nuevas APIs de PdfDocument
ofrecen una forma conveniente de hacerlo, ya que proporcionan un Canvas
de getCanvas()
, en el que puedes dibujar tu contenido imprimible. Luego, escribe el PdfDocument
en el 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, luego, 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 servicio de impresión funciona principalmente como un servicio sin cabeza, ya que crea una subclase de la clase PrintService
, que recibe trabajos de impresión del sistema y los comunica a sus impresoras con los protocolos adecuados.
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 permite que los usuarios seleccionen una "app de SMS predeterminada". Una vez que se selecciona, solo la app de SMS predeterminada puede escribir en el proveedor de SMS y solo la app de SMS predeterminada recibe la transmisión SMS_DELIVER_ACTION
cuando el usuario recibe un SMS o la transmisión WAP_PUSH_DELIVER_ACTION
cuando el usuario recibe 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 seleccionan como la app de SMS predeterminada solo pueden leer el proveedor de SMS, pero también pueden recibir una notificación cuando llega un SMS nuevo escuchando la transmisión SMS_RECEIVED_ACTION
, que es una transmisión no abortable que se puede entregar 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, consulta 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
. En cambio, si tu app usa un elemento seguro para la emulación de tarjetas, debes crear un servicio basado en la clase OffHostApduService
, que no participará 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 lector para tu actividad con enableReaderMode()
, lo que proporciona una implementación de NfcAdapter.ReaderCallback
que recibe una devolución de llamada cuando se detectan etiquetas nuevas.
Esta nueva función, junto con la emulación de tarjetas 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 de modo de lector) y otro dispositivo funciona como el cliente de pago (un dispositivo que emula una tarjeta NFC).
Transmisores infrarrojos
Cuando se ejecuta en un dispositivo que incluye un transmisor infrarrojo (IR), ahora 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 de IR compatibles del dispositivo con getCarrierFrequencies()
y transmitir señales pasando la frecuencia y el patrón de señal deseados con transmit()
.
Siempre debes verificar primero si un dispositivo incluye un transmisor de IR llamando a hasIrEmitter()
, pero si tu app solo es compatible 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 cambiar la resolución sin problemas durante la reproducción en un Surface
. Puedes alimentar los fotogramas de entrada del decodificador de una resolución nueva y la resolución de los búferes de salida cambia sin un intervalo significativo.
Para habilitar la reproducción adaptativa, agrega dos claves 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 elementos agregados a tu MediaFormat
, pasa el 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 adaptativa, debes verificar que el dispositivo admita la reproducción adaptativa llamando a isFeatureSupported(String)
con FEATURE_AdaptivePlayback
.
Nota: La compatibilidad con la reproducción adaptativa es específica de cada 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 de la línea de tiempo sobre un "fotograma" específico en una transmisión de audio que controla AudioTrack
. Para obtener la marca de tiempo más reciente disponible, crea una instancia de un objeto AudioTimestamp
y pásala a getTimestamp()
. Si la solicitud de la marca de tiempo se realiza correctamente, la instancia de AudioTrack
se completa con una posición en unidades de fotogramas, junto con la hora estimada en la que se presentó o se comprometió a presentar ese fotograma.
Puedes usar el valor de nanoTime
en AudioTimestamp
(que es monotónico) para encontrar el fotograma de 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 (con consideración de 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 brinda acceso directo a los búferes de imágenes a medida que se renderizan en un Surface
. Puedes adquirir un ImageReader
con el método estático newInstance()
. Luego, llama a getSurface()
para crear un Surface
nuevo y entrega tus datos de imagen con un productor, como MediaPlayer
o MediaCodec
. Para recibir notificaciones cuando haya imágenes nuevas disponibles desde la plataforma, implementa la interfaz ImageReader.OnImageAvailableListener
y regístrala con setOnImageAvailableListener()
.
Ahora, a medida que dibujas contenido en tu Surface
, tu ImageReader.OnImageAvailableListener
recibe una llamada a onImageAvailable()
a medida que cada fotograma de imagen nuevo está disponible, lo que te proporciona el ImageReader
correspondiente. Puedes usar ImageReader
para adquirir los datos de imagen de la trama como un objeto Image
llamando a acquireLatestImage()
o acquireNextImage()
.
El objeto Image
proporciona acceso directo a la marca de tiempo, el formato, las dimensiones y los datos de píxeles de la imagen en un ByteBuffer
. Sin embargo, para que la clase Image
interprete tus imágenes, estas deben tener el formato de uno de los tipos definidos por constantes en ImageFormat
o PixelFormat
.
Medición de picos y RMS
Ahora puedes consultar el pico y el RMS de la transmisión de audio actual desde Visualizer
. Para ello, crea una instancia nueva de Visualizer.MeasurementPeakRms
y pásala a getMeasurementPeakRms()
. Cuando llamas a este método, los valores máximos y RMS de la Visualizer.MeasurementPeakRms
determinada se establecen en los valores medidos más recientes.
Amplificador de volumen
LoudnessEnhancer
es una nueva subclase de AudioEffect
que te permite aumentar el volumen audible de tu MediaPlayer
o AudioTrack
. Esto puede ser especialmente útil junto 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 multimedia consuman eventos del controlador multimedia desde clientes remotos, como los controles multimedia en la pantalla de bloqueo. Ahora, las nuevas APIs de RemoteController
te permiten crear tu propio control remoto, lo que permite la creación de apps y periféricos nuevos e innovadores que pueden controlar la reproducción de cualquier app multimedia que se integre con RemoteControlClient
.
Para compilar un control remoto, puedes implementar tu interfaz de usuario de la forma que quieras, pero para entregar los eventos del botón multimedia a la app multimedia del usuario, debes crear un servicio que extienda la clase NotificationListenerService
y que implemente la interfaz RemoteController.OnClientUpdateListener
. Usar NotificationListenerService
como base es importante 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 interesan los eventos del controlador multimedia para controlar la reproducción de contenido multimedia, puedes dejar tu implementación para esos eventos vacía y, en su lugar, enfocarte en los métodos RemoteController.OnClientUpdateListener
.
Calificaciones desde controladores remotos
Android 4.4 se basa en las funciones existentes para clientes de control remoto (apps que reciben eventos de control multimedia con RemoteControlClient
) y agrega la capacidad para 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 por 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 calificación que es apropiado para ese estilo.
Para permitir que los usuarios califiquen tus pistas desde un controlador remoto:
- Si 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 pasarleRATING_KEY_BY_USER
conaddEditableKey()
. - Luego, especifica el estilo de calificación llamando a
putObject()
y pasándoleRATING_KEY_BY_USER
como clave y uno de los estilos de calificación anteriores como 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()
, que pasa RATING_KEY_BY_USER
como clave y un objeto Rating
como valor.
Subtítulos
VideoView
ahora admite pistas de subtítulos WebVTT cuando se reproducen videos de HTTP Live Stream (HLS) y muestra la pista de subtítulos según las preferencias de subtítulos que el usuario definió en la configuración del sistema.
También puedes proporcionar VideoView
con tus pistas de subtítulos WebVTT con el método addSubtitleSource()
. Este método acepta un InputStream
que contiene 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 tu contenido de video, debes hacer que la superposición de subtítulos coincida con la preferencia de subtítulos del usuario lo más cerca 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 haya comenzado, debes detectar los cambios en las preferencias. Para ello, registra una instancia de CaptioningManager.CaptioningChangeListener
para recibir una devolución de llamada cuando cambie alguna de las preferencias y, luego, actualiza 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 independiente 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 realizar los pasos 1 y 2. Un Scene
contiene metadatos que describen las propiedades de un diseño que son necesarios 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()
. Esto encuentra la vista superior de la escena en el diseño actual y realiza una transición en las vistas secundarias para llegar al diseño definido por Scene
.
Como alternativa, no es necesario que crees un objeto Scene
, sino que puedes llamar a beginDelayedTransition()
y especificar un ViewGroup
que contenga las vistas que deseas 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 con 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 especifiquen una escena (una referencia a un archivo de diseño) y la transición que se aplicará cuando se ingrese o salga de esa escena. Luego, aumenta este conjunto de transiciones con inflateTransitionManager()
. Usa el TransitionManager
que se muestra para ejecutar cada transición con transitionTo()
y pasar un Scene
que esté representado por una de las etiquetas <transition>
. También puedes definir conjuntos de transiciones de forma programática con las APIs de TransitionManager
.
Cuando especifiques 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 desvanece, mueve y cambia el tamaño de las vistas automáticamente según sea necesario. Además, puedes crear transiciones personalizadas extendiendo cualquiera de estas clases para realizar las animaciones como desees. Una transición personalizada puede hacer un seguimiento de cualquier cambio de propiedad que desees y crear cualquier animación que quieras en función de esos cambios. Por ejemplo, puedes proporcionar una subclase de Transition
que detecte cambios en la propiedad "rotación" de una vista y, luego, animar los cambios.
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 hacer un seguimiento del estado de una animación, puedes implementar la interfaz Animator.AnimatorPauseListener
, que proporciona devoluciones de llamada cuando se pausa y se 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 volver a usar cualquier mapa de bits mutable en BitmapFactory
para decodificar cualquier otro mapa de bits, incluso cuando el mapa de bits nuevo tiene un tamaño diferente, siempre y cuando el recuento de bytes resultante del mapa de bits decodificado (disponible en getByteCount()
) sea menor o igual al recuento de bytes asignado del mapa de bits reutilizado (disponible en getAllocationByteCount()
). Para obtener más información, consulta inBitmap
.
Las nuevas APIs de Bitmap
permiten una reconfiguración similar para su reutilización 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 un nuevo Bitmap.Config
con setConfig()
sin afectar la asignación de 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 volver a configurar un mapa de bits que el sistema de vistas use actualmente, ya que el búfer de píxeles subyacente no se volverá a asignar de una 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, 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 otorgarle a tu app acceso de lectura a largo plazo a ese archivo (posiblemente con acceso de escritura) sin importarlo a tu app.
Si estás desarrollando una app que ofrece 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 de tu almacenamiento de documentos usando las columnas definidas enDocumentsContract.Root
. queryChildDocuments()
- Debe mostrar un
Cursor
que describa todos los archivos del directorio especificado usando las columnas definidas enDocumentsContract.Document
. queryDocument()
- Debe mostrar un
Cursor
que describa el archivo especificado, usando 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 del marco de trabajo 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 de la misma manera que el método getExternalFilesDir()
existente, excepto que muestra un array de objetos File
. Antes de leer o escribir en cualquiera de las rutas de acceso que muestra este método, pasa el objeto File
al nuevo método getStorageState()
para verificar que el almacenamiento esté disponible en este momento.
Otros métodos para acceder al directorio de caché y al directorio OBB específicos de tu app también tienen las versiones correspondientes que proporcionan acceso a dispositivos de almacenamiento secundarios: getExternalCacheDirs()
y getObbDirs()
, respectivamente.
La primera entrada del array File
que se muestra se considera el almacenamiento externo principal del dispositivo, que es el mismo que el 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 necesites acceder solo a las regiones específicas de tu app del almacenamiento externo con los métodos anteriores. Sin embargo, los permisos son obligatorios si deseas acceder a las regiones compartibles del almacenamiento externo que proporciona getExternalStoragePublicDirectory()
.
Adaptadores de sincronización
El nuevo método requestSync()
en ContentResolver
simplifica parte del procedimiento para definir una solicitud de sincronización para tu ContentProvider
, ya que encapsula las 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 ContentProvider
existentes, pero agregan la capacidad de especificar que se debe descartar una sincronización si la red está medida habilitando setDisallowMetered()
.
Entrada del usuario
Tipos de sensores nuevos
El nuevo sensor TYPE_GEOMAGNETIC_ROTATION_VECTOR
proporciona datos de vectores 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 por lotes para registrar la orientación del dispositivo mientras el teléfono está en modo de suspensión. Este sensor requiere menos energía que TYPE_ROTATION_VECTOR
, pero puede ser propenso a datos de eventos con ruido 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 envía un evento con un valor de 1.0 y una marca de tiempo que indica cuándo ocurrió el paso.
TYPE_STEP_COUNTER
- Este sensor también activa un evento en cada paso detectado, pero, en su lugar, proporciona la cantidad total de pasos acumulados desde que una app lo registró por primera vez.
Ten en cuenta que estos dos sensores de paso no siempre proporcionan 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, es posible que TYPE_STEP_COUNTER
sea más lento para entregar eventos, pero sus resultados deberían ser más precisos.
Ambos sensores de pasos dependen del hardware (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 envíe lotes de eventos del sensor a tu app. Esto no reduce la cantidad de eventos del sensor reales disponibles para tu app durante un período determinado, sino que reduce 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 del informe". Este nuevo parámetro especifica la demora máxima que tolerará tu SensorEventListener
para la entrega de eventos de sensores nuevos. Por ejemplo, si especificas una latencia por lotes de un minuto, el sistema publicará el conjunto reciente de eventos por lotes en un intervalo de no más de un minuto realizando llamadas consecutivas a tu método onSensorChanged()
, una vez por cada evento que se agrupó. 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 enviará a tu 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 lote más reciente de eventos. Para estimar cuándo la memoria estará llena y se debe borrar, llama a getFifoMaxEventCount()
para obtener la cantidad máxima de eventos del 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 limpiar el sensor.
Nota: No todos los dispositivos admiten la agrupación de eventos del sensor porque requiere 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 ignora de forma elegante el argumento de latencia por lotes y entrega eventos del sensor en tiempo real.
Identidades de los controladores
Android ahora identifica cada controlador conectado con un número entero único que puedes consultar con getControllerNumber()
, lo que te permite asociar cada controlador a un jugador diferente en un juego. El número de cada controlador puede cambiar debido a que el usuario desconecta, conecta o vuelve a configurar los controladores, por lo que debes hacer un seguimiento del número de controlador que corresponde a cada dispositivo de entrada registrando una instancia de InputManager.InputDeviceListener
. Luego, llama a getControllerNumber()
para cada InputDevice
cuando se produzca un cambio.
Los dispositivos conectados también proporcionan IDs de productos y proveedores disponibles en getProductId()
y getVendorId()
. Si necesitas modificar las asignaciones de teclas según el conjunto de teclas disponible en un dispositivo, puedes consultar el dispositivo para verificar si ciertas teclas están disponibles con hasKeys(int...)
.
Interfaz de usuario
Modo de pantalla completa envolvente
Para proporcionarle 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 inmersivo. 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 vuelvan a ocultarse 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 translúcidas con los temas nuevos Theme.Holo.NoActionBar.TranslucentDecor
y Theme.Holo.Light.NoActionBar.TranslucentDecor
. Si habilitas las barras del sistema translúcidas, tu diseño ocupará el área detrás de ellas, por lo que también debes habilitar fitsSystemWindows
para la parte del diseño que no debe estar cubierta por las barras del sistema.
Si creas un tema personalizado, establece uno de estos temas como el tema superior o incluye las propiedades de estilo windowTranslucentNavigation
y windowTranslucentStatus
en tu tema.
Receptor de notificaciones mejorado
Android 4.3 agregó las APIs 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 completar los detalles sobre sus acciones:
El nuevo campo Notification.extras
incluye un Bundle
para proporcionarle al compilador de notificaciones metadatos adicionales, 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 del 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 revertir su orientación horizontal para diseños de derecha a izquierda, debes incluir la imagen reflejada en un directorio de recursos drawables-ldrtl/
. Ahora, el sistema puede duplicar imágenes automáticamente si habilitas el atributo autoMirrored
en un recurso de diseño o si llamas a setAutoMirrored()
. Cuando se habilita, el Drawable
se refleja 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 partes de tu IU que se actualizan de forma dinámica con contenido de texto nuevo. 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 proporcionan un servicio de accesibilidad ahora también pueden mejorar sus capacidades con nuevas APIs que proporcionan información sobre colecciones de vistas, como vistas de lista o cuadrícula, con AccessibilityNodeInfo.CollectionInfo
y AccessibilityNodeInfo.CollectionItemInfo
.
Permisos de la app
Los siguientes son permisos nuevos que tu app debe solicitar con la etiqueta <uses-permission>
para usar ciertas APIs nuevas:
INSTALL_SHORTCUT
- Permite que una aplicación instale un atajo en el selector
UNINSTALL_SHORTCUT
- Permite que una aplicación desinstale un atajo en el selector
TRANSMIT_IR
- Permite que una aplicación use el transmisor de 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 las regiones específicas de tu app del almacenamiento externo con métodos como getExternalFilesDir()
. Sin embargo, los permisos siguen siendo necesarios si quieres acceder a las regiones compartibles del almacenamiento externo que proporciona getExternalStoragePublicDirectory()
.
Funciones del dispositivo
A continuación, se muestran 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 dispositivos infrarrojos de consumo.
FEATURE_DEVICE_ADMIN
- El dispositivo admite la aplicación forzosa de políticas de dispositivos a través de administradores de dispositivos.
FEATURE_NFC_HOST_CARD_EMULATION
- El dispositivo admite la emulación de tarjetas NFC basada en 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 en la API de Android 4.4, consulta el Informe de diferencias de API.