Les propriétés système permettent de partager facilement des informations, généralement des configurations, à l'échelle du système. Chaque partition peut utiliser ses propres propriétés système en interne. Un problème peut se produire lorsque des propriétés sont accessibles dans plusieurs partitions, par exemple lorsque /vendor
accède à des propriétés définies par /system
. Depuis Android 8.0, certaines partitions, telles que /system
, peuvent être mises à niveau, tandis que /vendor
reste inchangé. Étant donné que les propriétés système ne sont qu'un dictionnaire global de paires clé-valeur de chaîne sans schéma, il est difficile de stabiliser les propriétés. La partition /system
peut modifier ou supprimer les propriétés dont dépend la partition /vendor
sans aucun préavis.
À partir de la version 10 d'Android, les propriétés système auxquelles on accède dans les partitions sont schématisées dans des fichiers de description Sysprop, et les API permettant d'accéder aux propriétés sont générées en tant que fonctions concrètes pour C++ et Rust, et en tant que classes pour Java. Ces API sont plus pratiques à utiliser, car aucune chaîne magique (telle que ro.build.date
) n'est nécessaire pour y accéder et parce qu'elles peuvent être typées de manière statique. La stabilité de l'ABI est également vérifiée au moment de la compilation, et la compilation cesse de fonctionner si des modifications incompatibles se produisent. Cette vérification agit comme des interfaces définies explicitement entre les partitions. Ces API peuvent également assurer la cohérence entre Rust, Java et C++.
Définir des propriétés système en tant qu'API
Définissez les propriétés système en tant qu'API avec des fichiers de description Sysprop (.sysprop
), qui utilisent un TextFormat de protobuf, avec le schéma suivant:
// File: sysprop.proto
syntax = "proto3";
package sysprop;
enum Access {
Readonly = 0;
Writeonce = 1;
ReadWrite = 2;
}
enum Owner {
Platform = 0;
Vendor = 1;
Odm = 2;
}
enum Scope {
Public = 0;
Internal = 2;
}
enum Type {
Boolean = 0;
Integer = 1;
Long = 2;
Double = 3;
String = 4;
Enum = 5;
UInt = 6;
ULong = 7;
BooleanList = 20;
IntegerList = 21;
LongList = 22;
DoubleList = 23;
StringList = 24;
EnumList = 25;
UIntList = 26;
ULongList = 27;
}
message Property {
string api_name = 1;
Type type = 2;
Access access = 3;
Scope scope = 4;
string prop_name = 5;
string enum_values = 6;
bool integer_as_bool = 7;
string legacy_prop_name = 8;
}
message Properties {
Owner owner = 1;
string module = 2;
repeated Property prop = 3;
}
Un fichier de description Sysprop contient un message de propriétés, qui décrit un ensemble de propriétés. La signification de ses champs est la suivante :
Champ | Signification |
---|---|
owner
|
Définit la partition propriétaire des propriétés: Platform , Vendor ou Odm .
|
module
|
Permet de créer un espace de noms (C++) ou une classe finale statique (Java) dans lequel les API générées sont placées. Par exemple, com.android.sysprop.BuildProperties correspond à l'espace de noms com::android::sysprop::BuildProperties en C++, et la classe BuildProperties du package dans com.android.sysprop en Java.
|
prop
|
Liste des propriétés. |
Voici la signification des champs de message Property
.
Champ | Signification |
---|---|
api_name
|
Nom de l'API générée. |
type
|
Type de cette propriété. |
access
|
Readonly : génère uniquement l'API getter
Writeonce , ReadWrite : génère les API getter et setter
Remarque: Les propriétés avec le préfixe ro. ne peuvent pas utiliser l'accès ReadWrite .
|
scope
|
Internal : seul le propriétaire peut y accéder.
Public : tout le monde peut y accéder, à l'exception des modules NDK.
|
prop_name
|
Nom de la propriété système sous-jacente, par exemple ro.build.date .
|
enum_values
|
(Enum , EnumList uniquement) Chaîne séparée par une barre(|) composée de valeurs d'énumération possibles. Exemple : value1|value2 .
|
integer_as_bool
|
(Boolean , BooleanList uniquement) Les setters doivent utiliser 0 et 1 au lieu de false et true .
|
legacy_prop_name
|
(Facultatif, propriétés Readonly uniquement) Ancien nom de la propriété système sous-jacente. Lors de l'appel du getter, l'API getter tente de lire prop_name et utilise legacy_prop_name si prop_name n'existe pas. Utilisez legacy_prop_name lorsque vous supprimez une propriété existante et que vous passez à une nouvelle propriété.
|
Chaque type de propriété correspond aux types suivants en C++, Java et Rust.
Type | C++ | Java | Rust |
---|---|---|---|
Booléen | std::optional<bool>
|
Optional<Boolean>
|
bool
|
Nombre entier | std::optional<std::int32_t>
|
Optional<Integer>
|
i32
|
UInt | std::optional<std::uint32_t>
|
Optional<Integer>
|
u32
|
Longue | std::optional<std::int64_t>
|
Optional<Long>
|
i64
|
ULong | std::optional<std::uint64_t>
|
Optional<Long>
|
u64
|
Double | std::optional<double>
|
Optional<Double>
|
f64
|
Chaîne | std::optional<std::string>
|
Optional<String>
|
String
|
Enum | std::optional<{api_name}_values>
|
Optional<{api_name}_values>
|
{ApiName}Values
|
Liste T | std::vector<std::optional<T>>
|
List<T>
|
Vec<T>
|
Voici un exemple de fichier de description Sysprop qui définit trois propriétés:
# File: android/sysprop/PlatformProperties.sysprop
owner: Platform
module: "android.sysprop.PlatformProperties"
prop {
api_name: "build_date"
type: String
prop_name: "ro.build.date"
scope: Public
access: Readonly
}
prop {
api_name: "date_utc"
type: Integer
prop_name: "ro.build.date_utc"
scope: Internal
access: Readonly
}
prop {
api_name: "device_status"
type: Enum
enum_values: "on|off|unknown"
prop_name: "device.status"
scope: Public
access: ReadWrite
}
Définir des bibliothèques de propriétés système
Vous pouvez désormais définir des modules sysprop_library
avec des fichiers de description Sysprop.
sysprop_library
sert d'API pour C++, Java et Rust. Le système de compilation génère en interne un élément rust_library
, un élément java_library
et un élément cc_library
pour chaque instance de sysprop_library
.
// File: Android.bp
sysprop_library {
name: "PlatformProperties",
srcs: ["android/sysprop/PlatformProperties.sysprop"],
property_owner: "Platform",
vendor_available: true,
}
Vous devez inclure des fichiers de liste d'API dans la source pour les vérifications d'API. Pour ce faire, créez des fichiers d'API et un répertoire api
. Placez le répertoire api
dans le même répertoire que Android.bp
. Les noms de fichiers de l'API sont <module_name>-current.txt
et <module_name>-latest.txt
. <module_name>-current.txt
contient les signatures d'API des codes sources actuels, tandis que <module_name>-latest.txt
contient les dernières signatures d'API figées. Le système de compilation vérifie si les API sont modifiées en comparant ces fichiers d'API avec les fichiers d'API générés au moment de la compilation, et émet un message d'erreur et des instructions pour mettre à jour le fichier current.txt
si current.txt
ne correspond pas aux codes sources. Voici un exemple d'organisation de répertoires et de fichiers:
├── api
│ ├── PlatformProperties-current.txt
│ └── PlatformProperties-latest.txt
└── Android.bp
Les modules clients Rust, Java et C++ peuvent être liés à sysprop_library
pour utiliser les API générées. Le système de compilation crée des liens entre les clients et les bibliothèques C++, Java et Rust générées, ce qui leur permet d'accéder aux API générées.
java_library {
name: "JavaClient",
srcs: ["foo/bar.java"],
libs: ["PlatformProperties"],
}
cc_binary {
name: "cc_client",
srcs: ["baz.cpp"],
shared_libs: ["PlatformProperties"],
}
rust_binary {
name: "rust_client",
srcs: ["src/main.rs"],
rustlibs: ["libplatformproperties_rust"],
}
Notez que le nom de la bibliothèque Rust est généré en convertissant le nom sysprop_library
en minuscules, en remplaçant .
et -
par _
, puis en ajoutant lib
et _rust
.
Dans l'exemple précédent, vous pouvez accéder aux propriétés définies comme suit.
Exemple Rust:
use platformproperties::DeviceStatusValues;
fn foo() -> Result<(), Error> {
// Read "ro.build.date_utc". default value is -1.
let date_utc = platformproperties::date_utc()?.unwrap_or_else(-1);
// set "device.status" to "unknown" if "ro.build.date" is not set.
if platformproperties::build_date()?.is_none() {
platformproperties::set_device_status(DeviceStatusValues::UNKNOWN);
}
…
}
Exemple Java:
import android.sysprop.PlatformProperties;
…
static void foo() {
…
// read "ro.build.date_utc". default value is -1
Integer dateUtc = PlatformProperties.date_utc().orElse(-1);
// set "device.status" to "unknown" if "ro.build.date" is not set
if (!PlatformProperties.build_date().isPresent()) {
PlatformProperties.device_status(
PlatformProperties.device_status_values.UNKNOWN
);
}
…
}
…
Exemple en C++:
#include <android/sysprop/PlatformProperties.sysprop.h>
using namespace android::sysprop;
…
void bar() {
…
// read "ro.build.date". default value is "(unknown)"
std::string build_date = PlatformProperties::build_date().value_or("(unknown)");
// set "device.status" to "on" if it's "unknown" or not set
using PlatformProperties::device_status_values;
auto status = PlatformProperties::device_status();
if (!status.has_value() || status.value() == device_status_values::UNKNOWN) {
PlatformProperties::device_status(device_status_values::ON);
}
…
}
…