Systemeigenschaften als APIs implementieren

Systemeigenschaften bieten eine praktische Möglichkeit, Informationen, in der Regel Konfigurationen, systemweit zu teilen. Jede Partition kann intern eigene Systemeigenschaften verwenden. Ein Problem kann auftreten, wenn auf Properties über Partitionen hinweg zugegriffen wird, z. B. wenn /vendor auf Properties zugreift, die in /system definiert sind. Seit Android 8.0 können einige Partitionen wie /system aktualisiert werden, während /vendor unverändert bleibt. Da es sich bei Systemeigenschaften nur um ein globales Dictionary mit String-Schlüssel/Wert-Paaren ohne Schema handelt, ist es schwierig, Eigenschaften zu stabilisieren. In der /system-Partition können Eigenschaften, von denen die /vendor-Partition abhängt, ohne Vorankündigung geändert oder entfernt werden.

Ab Android 10 werden Systemattribute, auf die über Partitionen hinweg zugegriffen wird, in Sysprop-Beschreibungsdateien schematisiert. APIs für den Zugriff auf Attribute werden als konkrete Funktionen für C++ und Rust sowie als Klassen für Java generiert. Diese APIs sind einfacher zu verwenden, da für den Zugriff keine Magic Strings (wie ro.build.date) erforderlich sind und sie statisch typisiert werden können. Die ABI-Stabilität wird auch zur Build-Zeit geprüft. Der Build schlägt fehl, wenn inkompatible Änderungen vorgenommen werden. Diese Prüfung dient als explizit definierte Schnittstelle zwischen Partitionen. Diese APIs können auch für Konsistenz zwischen Rust, Java und C++ sorgen.

Systemeigenschaften als APIs definieren

Definieren Sie Systemeigenschaften als APIs mit Sysprop-Beschreibungsdateien (.sysprop), die ein TextFormat von Protobuf mit dem folgenden Schema verwenden:

// 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;
}

Eine Sysprop-Beschreibungsdatei enthält eine Properties-Nachricht, die eine Reihe von Eigenschaften beschreibt. Die Bedeutung der Felder ist wie folgt:

Feld Bedeutung
owner Wird auf die Partition festgelegt, zu der die Properties gehören: Platform, Vendor oder Odm.
module Wird verwendet, um einen Namespace (C++) oder eine statische finale Klasse (Java) zu erstellen, in der generierte APIs platziert werden. Beispiel: com.android.sysprop.BuildProperties wird in C++ zum Namespace com::android::sysprop::BuildProperties und in Java zur Klasse BuildProperties im Paket com.android.sysprop.
prop Liste der Eigenschaften.

Die Bedeutung der Property-Nachrichtenfelder ist wie folgt:

Feld Bedeutung
api_name Der Name der generierten API.
type Der Typ dieser Eigenschaft.
access Readonly: Generiert nur die Getter-API.
Writeonce, ReadWrite: Generiert Getter- und Setter-APIs.
Hinweis: Für Eigenschaften mit dem Präfix ro. darf kein ReadWrite-Zugriff verwendet werden.
scope Internal: Nur der Inhaber kann darauf zugreifen.
Public: Jeder kann darauf zugreifen, mit Ausnahme von NDK-Modulen.
prop_name Der Name der zugrunde liegenden Systemeigenschaft, z. B. ro.build.date.
enum_values (Enum, EnumList) Ein durch einen senkrechten Strich(|) getrennter String, der aus möglichen Enum-Werten besteht. Beispiel: value1|value2.
integer_as_bool (Boolean, BooleanList) Verwenden Sie in Settern 0 und 1 anstelle von false und true.
legacy_prop_name (optional, nur Readonly-Properties) Der alte Name der zugrunde liegenden System-Property. Beim Aufrufen des Getters versucht die Getter-API, prop_name zu lesen, und verwendet legacy_prop_name, wenn prop_name nicht vorhanden ist. Verwenden Sie legacy_prop_name, wenn Sie ein vorhandenes Attribut einstellen und zu einem neuen Attribut wechseln.

Jeder Property-Typ wird in C++, Java und Rust den folgenden Typen zugeordnet.

Eingeben C++ Java Rust
Boolesch std::optional<bool> Optional<Boolean> bool
Ganzzahl std::optional<std::int32_t> Optional<Integer> i32
UInt std::optional<std::uint32_t> Optional<Integer> u32
Lang std::optional<std::int64_t> Optional<Long> i64
ULong std::optional<std::uint64_t> Optional<Long> u64
Double std::optional<double> Optional<Double> f64
String std::optional<std::string> Optional<String> String
Enum std::optional<{api_name}_values> Optional<{api_name}_values> {ApiName}Values
T-Liste std::vector<std::optional<T>> List<T> Vec<T>

Hier sehen Sie ein Beispiel für eine Sysprop-Beschreibungsdatei, in der drei Properties definiert sind:

# 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
}

Bibliotheken für Systemeigenschaften definieren

Sie können jetzt sysprop_library-Module mit Sysprop-Beschreibungsdateien definieren. sysprop_library dient als API für C++, Java und Rust. Das Build-System generiert intern für jede Instanz von sysprop_library eine rust_library, eine java_library und eine cc_library.

// File: Android.bp
sysprop_library {
    name: "PlatformProperties",
    srcs: ["android/sysprop/PlatformProperties.sysprop"],
    property_owner: "Platform",
    vendor_available: true,
}

Sie müssen API-Listen-Dateien in die Quelle für API-Prüfungen einfügen. Erstellen Sie dazu API-Dateien und ein api-Verzeichnis. Legen Sie das Verzeichnis api im selben Verzeichnis wie Android.bp ab. Die API-Dateinamen sind <module_name>-current.txt und <module_name>-latest.txt. <module_name>-current.txt enthält die API-Signaturen der aktuellen Quellcodes und <module_name>-latest.txt die neuesten eingefrorenen API-Signaturen. Das Build-System prüft, ob die APIs geändert wurden, indem es diese API-Dateien zur Build-Zeit mit generierten API-Dateien vergleicht. Wenn current.txt nicht mit den Quellcodes übereinstimmt, wird eine Fehlermeldung mit einer Anleitung zum Aktualisieren der Datei current.txt ausgegeben. Hier sehen Sie ein Beispiel für die Organisation von Verzeichnissen und Dateien:

├── api
│   ├── PlatformProperties-current.txt
│   └── PlatformProperties-latest.txt
└── Android.bp

Rust-, Java- und C++-Clientmodule können mit sysprop_library verknüpft werden, um generierte APIs zu verwenden. Das Build-System erstellt Links von Clients zu generierten C++-, Java- und Rust-Bibliotheken und ermöglicht so den Zugriff auf generierte APIs.

java_library {
    name: "JavaClient",
    srcs: ["foo/bar.java"],
    libs: ["PlatformProperties"],
}

cc_binary {
    name: "cc_client",
    srcs: ["baz.cpp"],
    shared_libs: ["libPlatformProperties"],
}

rust_binary {
    name: "rust_client",
    srcs: ["src/main.rs"],
    rustlibs: ["libplatformproperties_rust"],
}

Der Name der Rust-Bibliothek wird generiert, indem der Name sysprop_library in Kleinbuchstaben konvertiert, . und - durch _ ersetzt und dann lib vorangestellt und _rust angehängt wird.

Im vorherigen Beispiel können Sie so auf definierte Eigenschaften zugreifen:

Rust-Beispiel:

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);
  }

  
}

Java-Beispiel:

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
        );
    }
    
}

C++-Beispiel:

#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);
    }
    
}