Systemeigenschaften als APIs implementieren

Systemeigenschaften bieten eine bequeme Möglichkeit, Informationen (in der Regel Konfigurationen) systemweit zu teilen. Jede Partition kann intern ihre eigenen Systemeigenschaften verwenden. Ein Problem kann auftreten, wenn partitionierter Zugriff auf Attribute wie /vendor erfolgt, die auf /system-definierte Attribute zugreifen. Seit Android 8.0 können einige Partitionen, z. B. /system, aktualisiert werden, während /vendor unverändert bleibt. Da Systemattribute nur ein globales Wörterbuch von String-Schlüssel/Wert-Paaren ohne Schema sind, ist es schwierig, Attribute zu stabilisieren. Die Partition /system kann Attribute, von denen die Partition /vendor abhängt, ohne Vorankündigung ändern oder entfernen.

Ab Android 10 werden Partitionsübergreifende Systemeigenschaften, auf die über Partitionen 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 praktischer, da für den Zugriff keine magischen Strings wie ro.build.date erforderlich sind und sie statisch eingegeben werden können. Die ABI-Stabilität wird auch zum Zeitpunkt der Build-Erstellung geprüft. Der Build wird unterbrochen, wenn inkompatible Änderungen auftreten. Diese Prüfung fungiert als explizit definierte Schnittstellen zwischen Partitionen. Diese APIs können auch für Konsistenz zwischen Rost, Java und C++ sorgen.

Systemeigenschaften als APIs definieren

Definieren Sie Systemeigenschaften als APIs mit Sysprop-Beschreibungsdateien (.sysprop), die als TextFormat von protobuf mit dem folgenden Schema verwendet werden:

// 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 Attributnachricht, die eine Reihe von Attributen beschreibt. Die Felder haben folgende Bedeutung.

Feld Bedeutung
owner Legen Sie als Wert die Partition fest, zu der die Attribute gehören: Platform, Vendor oder Odm.
module Wird zum Erstellen eines Namespace (C++) oder einer statischen finalen Klasse (Java) verwendet, in dem generierte APIs platziert werden. Zum Beispiel ist com.android.sysprop.BuildProperties der Namespace com::android::sysprop::BuildProperties in C++ und die Klasse BuildProperties im Paket in com.android.sysprop in Java.
prop Liste der Properties.

Die Property-Nachrichtenfelder haben folgende Bedeutung:

Feld Bedeutung
api_name Der Name der generierten API.
type Der Typ dieser Eigenschaft.
access Readonly: Generiert nur Getter-API
Writeonce, ReadWrite: Generiert Getter- und Setter-APIs
Hinweis: Properties mit dem Präfix ro. dürfen keinen ReadWrite-Zugriff verwenden.
scope Internal: Nur der Eigentümer hat Zugriff.
Public: Alle Personen haben Zugriff, mit Ausnahme von NDK-Modulen.
prop_name Der Name der zugrunde liegenden Systemeigenschaft, z. B. ro.build.date.
enum_values (Nur Enum, EnumList) Ein durch Balken(|) getrennter String, der aus möglichen Aufzählungswerten besteht. Beispiel: value1|value2.
integer_as_bool (Nur Boolean, BooleanList) Setter müssen 0 und 1 anstelle von false und true verwenden.
legacy_prop_name (optional, nur Readonly-Attribute) Der alte Name des zugrunde liegenden Systemattributs. Beim Aufrufen eines Getters versucht die Getter API, prop_name zu lesen. Ist prop_name nicht vorhanden, verwendet sie legacy_prop_name. Verwende legacy_prop_name, wenn du eine vorhandene Property verwerfen und zu einer neuen Property verschiebst.

Jeder Eigenschaftstyp ist den folgenden Typen in C++, Java und Rust zugeordnet.

Typ 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
Lang std::optional<std::uint64_t> Optional<Long> u64
Double std::optional<double> Optional<Double> f64
String std::optional<std::string> Optional<String> String
Aufzählung std::optional<{api_name}_values> Optional<{api_name}_values> {ApiName}Values
T-Liste std::vector<std::optional<T>> List<T> Vec<T>

Hier ist ein Beispiel für eine Sysprop-Beschreibungsdatei, die drei Eigenschaften definiert:

# 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 einen rust_library, einen java_library und einen cc_library für jede Instanz von sysprop_library.

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

Sie müssen Dateien mit API-Listen in die Quelle für API-Prüfungen aufnehmen. Dazu erstellen Sie 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, <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. Dazu werden diese API-Dateien zum Zeitpunkt der Erstellung mit den generierten API-Dateien verglichen. Eine Fehlermeldung und eine Anleitung zum Aktualisieren der Datei current.txt werden ausgegeben, wenn current.txt nicht mit den Quellcodes übereinstimmt. Hier ist ein Beispiel für eine Verzeichnis- und Dateiorganisation:

├── 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, sodass Clients Zugriff auf generierte APIs erhalten.

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"],
}

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

Im vorherigen Beispiel könnten Sie folgendermaßen auf definierte Properties 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);
    }
    …
}
…