Systemeigenschaften als APIs implementieren

Systemeigenschaften bieten eine bequeme Möglichkeit, Informationen, normalerweise Konfigurationen, systemweit auszutauschen. Jede Partition kann intern ihre eigenen Systemeigenschaften verwenden. Ein Problem kann auftreten, wenn auf Eigenschaften über Partitionen hinweg zugegriffen wird, z. B. wenn /vendor auf /system -definierte Eigenschaften zugreift. Seit Android 8.0 können einige Partitionen, z. B. /system , aktualisiert werden, während /vendor unverändert bleibt. Da es sich bei Systemeigenschaften nur um ein globales Wörterbuch mit Zeichenfolgen-Schlüssel/Wert-Paaren ohne Schema handelt, ist es schwierig, Eigenschaften zu stabilisieren. Die /system Partition könnte Eigenschaften, von denen die /vendor Partition abhängt, ohne Vorankündigung ändern oder entfernen.

Ab der Version Android 10 werden Systemeigenschaften, auf die über Partitionen hinweg zugegriffen wird, in Sysprop-Beschreibungsdateien schematisiert, und APIs für den Zugriff auf Eigenschaften werden als konkrete Funktionen für C++ und Rust sowie Klassen für Java generiert. Diese APIs sind bequemer zu verwenden, da für den Zugriff keine magischen Zeichenfolgen (z. B. ro.build.date ) erforderlich sind und weil sie statisch typisiert werden können. Die ABI-Stabilität wird auch zur Build-Zeit überprüft und der Build bricht ab, wenn inkompatible Änderungen vorgenommen werden. Diese Prüfung fungiert 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 verwenden, mit dem folgenden Schema:

// 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 Eigenschaftennachricht, die eine Reihe von Eigenschaften beschreibt. Die Bedeutung seiner Felder ist wie folgt.

Feld Bedeutung
owner Legen Sie die Partition fest, die die Eigenschaften besitzt: Platform , Vendor oder Odm .
module Wird zum Erstellen eines Namespace (C++) oder einer statischen Endklasse (Java) verwendet, in der generierte APIs platziert werden. Beispielsweise ist com.android.sysprop.BuildProperties der Namespace com::android::sysprop::BuildProperties in C++ und die BuildProperties Klasse im Paket in com.android.sysprop in Java.
prop Liste der Eigenschaften.

Die Bedeutung der Property 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 : Erzeugt Getter- und Setter-APIs

Hinweis: Eigenschaften mit dem Präfix ro. darf keinen ReadWrite Zugriff verwenden.

scope Internal : Nur der Eigentümer hat Zugriff.

Public : Jeder kann darauf zugreifen, außer auf NDK-Module.

prop_name Der Name der zugrunde liegenden Systemeigenschaft, zum Beispiel ro.build.date .
enum_values (Nur Enum , EnumList ) Eine durch Striche (|) getrennte Zeichenfolge, die aus möglichen Enum-Werten besteht. Beispiel: value1|value2 .
integer_as_bool (Nur Boolean , BooleanList ) Lassen Sie Setter 0 und 1 anstelle von false und true verwenden.
legacy_prop_name (optional, nur Readonly Eigenschaften) Der alte Name der zugrunde liegenden Systemeigenschaft. Beim Aufruf von Getter 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 eine vorhandene Eigenschaft verwerfen und zu einer neuen Eigenschaft wechseln.

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

Typ C++ Java Rost
Boolescher Wert std::optional<bool> Optional<Boolean> bool
Ganze Zahl 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
Doppelt std::optional<double> Optional<Double> f64
Zeichenfolge 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 einer 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
}

Definieren von Systemeigenschaftenbibliotheken

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 eine rust_library , eine java_library und eine 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,
}

Für API-Prüfungen müssen Sie API-Listendateien in die Quelle einschließen. Erstellen Sie dazu API-Dateien und ein api Verzeichnis. Legen Sie das api Verzeichnis im selben Verzeichnis wie Android.bp . Die API-Dateinamen sind <module_name>-current.txt , <module_name>-latest.txt . <module_name>-current.txt enthält die API-Signaturen aktueller Quellcodes und <module_name>-latest.txt enthält die neuesten eingefrorenen API-Signaturen. Das Build-System prüft, ob die APIs geändert wurden, indem es diese API-Dateien mit generierten API-Dateien zur Build-Zeit vergleicht und gibt eine Fehlermeldung und Anweisungen zum Aktualisieren der Datei current.txt aus, 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 und ermöglicht so Clients Zugriff auf generierte APIs.

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

Beachten Sie, dass der Rust-Bibliotheksname generiert wird, indem der sysprop_library Name in Kleinbuchstaben umgewandelt und ersetzt wird . und - mit _ , dann lib voranstellen und _rust anhängen.

Im obigen Beispiel könnten Sie wie folgt 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);
    }
    …
}
…