HIDL se compila en torno a interfaces, un tipo abstracto que se usa en lenguajes orientados a objetos para definir comportamientos. Cada interfaz forma parte de un paquete.
Paquetes
Los nombres de los paquetes pueden tener subniveles, como package.subpackage
. El directorio raíz de los paquetes HIDL publicados es hardware/interfaces
o vendor/vendorName
(por ejemplo, vendor/google
para dispositivos Pixel). El nombre del paquete forma uno o más subdirectorios en el directorio raíz. Todos los archivos que definen un paquete se encuentran en el mismo directorio. Por ejemplo, package android.hardware.example.extension.light@2.0
podría encontrarse en hardware/interfaces/example/extension/light/2.0
.
En la siguiente tabla, se enumeran los prefijos y las ubicaciones de los paquetes:
Prefijo del paquete | Ubicación | Tipos de interfaz |
---|---|---|
android.hardware.* |
hardware/interfaces/* |
HAL |
android.frameworks.* |
frameworks/hardware/interfaces/* |
frameworks/ related |
android.system.* |
system/hardware/interfaces/* |
system/ related |
android.hidl.* |
system/libhidl/transport/* |
core |
El directorio del paquete contiene archivos con la extensión .hal
. Cada archivo debe contener una sentencia package
que asigne un nombre al paquete y a la versión de la que forma parte. Si está presente, el archivo types.hal
no define una interfaz, sino que define los tipos de datos a los que puede acceder cada interfaz del paquete.
Definición de la interfaz
Además de types.hal
, todos los demás archivos .hal
definen una interfaz. Por lo general, una interfaz se define de la siguiente manera:
interface IBar extends IFoo { // IFoo is another interface // embedded types struct MyStruct {/*...*/}; // interface methods create(int32_t id) generates (MyStruct s); close(); };
Una interfaz sin una declaración extends
explícita se extiende de forma implícita desde android.hidl.base@1.0::IBase
(similar a java.lang.Object
en Java). La interfaz IBase, que se importa de forma implícita, declara varios métodos reservados que no deben volver a declararse en interfaces definidas por el usuario ni usarse de otra manera. Estos métodos incluyen los siguientes:
ping
interfaceChain
interfaceDescriptor
notifySyspropsChanged
linkToDeath
unlinkToDeath
setHALInstrumentation
getDebugInfo
debug
getHashChain
Proceso de importación
La sentencia import
es un mecanismo de HIDL para acceder a las interfaces y los tipos de paquetes en otro paquete. Una sentencia import
se ocupa de dos entidades:
- La entidad que realiza la importación, que puede ser un paquete o una interfaz
- La entidad importada, que puede ser un paquete o una interfaz
La entidad importadora se determina según la ubicación de la sentencia import
. Cuando la sentencia está dentro del types.hal
de un paquete, todo el paquete puede ver lo que se importa. Esta es una importación a nivel del paquete. Cuando la sentencia está dentro de un archivo de interfaz, la entidad de importación es la interfaz en sí, es decir, una importación a nivel de interfaz.
La entidad importada se determina según el valor después de la palabra clave import
. El valor no tiene que ser un nombre completamente calificado. Si se omite un componente, se completa automáticamente con información del paquete actual.
Para los valores completamente calificados, se admiten los siguientes casos de importación:
- Importaciones de paquetes completos. Si el valor es un nombre de paquete y una versión (se describe el sintaxis a continuación), se importa todo el paquete a la entidad de importación.
- Importaciones parciales: Si el valor es:
- Se importa una interfaz, el
types.hal
del paquete y esa interfaz a la entidad de importación. - Un UDT definido en
types.hal
, luego solo se importa ese UDT a la entidad de importación (no se importan otros tipos entypes.hal
).
- Se importa una interfaz, el
- Importaciones de solo tipos. Si el valor usa la sintaxis de una importar parcial que se describió anteriormente, pero con la palabra clave
types
en lugar de un nombre de interfaz, solo se importan las UDT entypes.hal
del paquete designado.
La entidad que realiza la importación obtiene acceso a una combinación de los siguientes elementos:
- Las UDT comunes del paquete importado definidas en
types.hal
- Las interfaces del paquete importado (para una importación de paquete completo) o la interfaz especificada (para una importación parcial) con el fin de invocarlas, pasarles controladores o heredarlos
La sentencia de importación usa la sintaxis de nombre de tipo completamente calificado para proporcionar el nombre y la versión del paquete o la interfaz que se importa:
import android.hardware.nfc@1.0; // import a whole package import android.hardware.example@1.0::IQuux; // import an interface and types.hal import android.hardware.example@1.0::types; // import just types.hal
Herencia de interfaz
Una interfaz puede ser una extensión de una interfaz definida anteriormente. Las extensiones pueden ser de uno de los siguientes tres tipos:
- La interfaz puede agregar funcionalidad a otra, incorporando su API sin cambios.
- El paquete puede agregar funcionalidad a otro, incorporando su API sin cambios.
- La interfaz puede importar tipos de un paquete o de una interfaz específica.
Una interfaz puede extender solo otra interfaz (no se permite la herencia múltiple).
Cada interfaz de un paquete con un número de versión secundaria distinto de cero debe extender una interfaz en la versión anterior del paquete. Por ejemplo, si una interfaz IBar
en la versión 4.0 del paquete derivative
se basa en (extiende) una interfaz IFoo
en la versión 1.2 del paquete original
y se crea una versión 1.3 del paquete original
, la versión 4.1 de IBar
no puede extender la versión 1.3 de IFoo
. En su lugar, la versión 4.1 de IBar
debe extender la versión 4.0 de IBar
, que está vinculada a la versión 1.2 de IFoo
.
La versión 5.0 de IBar
podría extender la versión 1.3 de IFoo
, si así lo deseas.
Las extensiones de interfaz no implican dependencia de bibliotecas ni inclusión de HAL en el código generado, sino que simplemente importan la estructura de datos y las definiciones de métodos a nivel de HIDL. Cada método de un HAL debe implementarse en ese HAL.
Extensiones de proveedores
En algunos casos, las extensiones de proveedores se implementan como una subclase del objeto base que representa la interfaz principal que extienden. El mismo objeto se registra con el nombre y la versión de HAL de la base, y con el nombre y la versión de HAL de la extensión (proveedor).
Control de versiones
Los paquetes tienen control de versiones, y las interfaces tienen la versión de su paquete. Las versiones se expresan en dos números enteros, principal.secundaria.
- Las versiones principales no son retrocompatibles. Si aumentas el número de versión principal, se restablece el número de versión secundaria a 0.
- Las versiones secundarias son retrocompatibles. El aumento del número secundario indica que la versión más reciente es totalmente retrocompatible con la versión anterior. Se pueden agregar estructuras de datos y métodos nuevos, pero no se pueden cambiar las estructuras de datos ni las firmas de métodos existentes.
Se pueden incluir varias versiones principales o secundarias de un HAL en un dispositivo de forma simultánea. Sin embargo, se debe preferir una versión secundaria a una versión principal, ya que el código del cliente que funciona con una interfaz de versión secundaria anterior también funciona con versiones secundarias posteriores de esa misma interfaz. Para obtener más detalles sobre el control de versiones y las extensiones de proveedores, consulta Control de versiones de HIDL.
Resumen del diseño de la interfaz
En esta sección, se resume cómo administrar un paquete de interfaz HIDL (como hardware/interfaces
) y se consolida la información que se presenta en toda la sección de HIDL. Antes de leer, asegúrate de estar familiarizado con los conceptos de control de versiones de HIDL, creación de hash con hidl-gen, los detalles de cómo trabajar con HIDL en general y las siguientes definiciones:
Término | Definición |
---|---|
interfaz binaria de la aplicación (ABI) | La interfaz de programación de aplicaciones, además de cualquier vinculación binaria necesaria |
nombre completamente calificado (fqName) | Es el nombre para distinguir un tipo de hidl. Ejemplo: android.hardware.foo@1.0::IFoo . |
paquete | Es un paquete que contiene una interfaz y tipos de HIDL. Ejemplo: android.hardware.foo@1.0 . |
raíz del paquete | Es el paquete raíz que contiene las interfaces HIDL. Ejemplo: La interfaz HIDL android.hardware está en la raíz del paquete android.hardware.foo@1.0 . |
ruta de acceso raíz del paquete | Es la ubicación en el árbol de fuentes de Android a la que se asigna la raíz de un paquete. |
Para obtener más definiciones, consulta la terminología de HIDL.
Todos los archivos se pueden encontrar desde la asignación raíz del paquete y su nombre completamente calificado.
Las raíces de los paquetes se especifican en hidl-gen
como el argumento -r android.hardware:hardware/interfaces
. Por ejemplo, si el paquete es vendor.awesome.foo@1.0::IFoo
y hidl-gen
se envía a -r vendor.awesome:some/device/independent/path/interfaces
, el archivo de interfaz debe ubicarse en $ANDROID_BUILD_TOP/some/device/independent/path/interfaces/foo/1.0/IFoo.hal
.
En la práctica, se recomienda que un proveedor o OEM llamado awesome
coloque sus interfaces estándar en vendor.awesome
. Una vez que se selecciona una ruta de acceso del paquete, no se debe cambiar, ya que está integrada en la ABI de la interfaz.
La asignación de rutas de paquetes debe ser única
Por ejemplo, si tienes -rsome.package:$PATH_A
y -rsome.package:$PATH_B
, $PATH_A
debe ser igual a $PATH_B
para obtener un directorio de interfaz coherente (esto también facilita mucho las interfaces de control de versiones).
La raíz del paquete debe tener un archivo de control de versiones
Si creas una ruta de acceso de paquete, como -r vendor.awesome:vendor/awesome/interfaces
, también debes crear el archivo $ANDROID_BUILD_TOP/vendor/awesome/interfaces/current.txt
, que debe contener valores hash de interfaces creados con la opción -Lhash
en hidl-gen
(esto se analiza en detalle en Hashing con hidl-gen).
Las interfaces se ubican en ubicaciones independientes del dispositivo.
En la práctica, recomendamos compartir interfaces entre ramas. Esto permite el máximo reuso y la máxima prueba de código en diferentes dispositivos y casos de uso.