הטמעת מאפייני מערכת כממשקי API

מאפייני מערכת מספקים דרך נוחה לשתף מידע, בדרך כלל תצורות, בכל המערכת. כל מחיצה יכולה להשתמש במאפייני מערכת משלה באופן פנימי. בעיה יכולה להתרחש כאשר נגישות למאפיינים על פני מחיצות, כגון /vendor גישה למאפיינים מוגדרים /system . מאז אנדרואיד 8.0, ניתן לשדרג חלק מהמחיצות, כגון /system , בעוד ש- /vendor נותר ללא שינוי. מכיוון שמאפייני מערכת הם רק מילון גלובלי של צמדי מפתח/ערך מחרוזת ללא סכמה, קשה לייצב מאפיינים. מחיצת /system עשויה לשנות או להסיר מאפיינים שהמחיצה /vendor תלוי בהם ללא כל הודעה מוקדמת.

החל מהגרסה של אנדרואיד 10, מאפייני מערכת שאליהם ניגש במחיצות מסוכמים לקובצי Sysprop Description, וממשקי API לגישה למאפיינים נוצרים כפונקציות קונקרטיות עבור C++ ו-Rust, ומחלקות עבור Java. ממשקי API אלו נוחים יותר לשימוש מכיוון שאין צורך במחרוזות קסם (כגון ro.build.date ) לצורך גישה, ומכיוון שניתן להקליד אותם באופן סטטי. יציבות ABI נבדקת גם בזמן הבנייה, והבנייה נשברת אם מתרחשים שינויים לא תואמים. בדיקה זו פועלת כממשקים מוגדרים במפורש בין מחיצות. ממשקי API אלה יכולים גם לספק עקביות בין Rust, Java ו-C++.

הגדרת מאפייני מערכת כממשקי API

הגדר מאפייני מערכת כממשקי API עם קבצי Sysprop Description ( .sysprop ), המשתמשים ב-TextFormat של protobuf, עם הסכימה הבאה:

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

קובץ Sysprop Description אחד מכיל הודעת מאפיינים אחת, המתארת ​​קבוצה של מאפיינים. המשמעות של תחומיה ​​היא כדלקמן.

שדה מַשְׁמָעוּת
owner הגדר למחיצה שבבעלותה המאפיינים: Platform , Vendor , או Odm .
module משמש ליצירת מרחב שמות (C++) או מחלקה סופית סטטית (Java) שבה ממוקמים ממשקי API שנוצרו. לדוגמה, com.android.sysprop.BuildProperties יהיה namespace com::android::sysprop::BuildProperties ב-C++, והמחלקה BuildProperties בחבילה ב- com.android.sysprop ב-Java.
prop רשימת נכסים.

המשמעויות של שדות הודעת Property הן כדלקמן.

שדה מַשְׁמָעוּת
api_name שם ה-API שנוצר.
type סוג הנכס הזה.
access Readonly : מייצר ממשק API של getter בלבד

Writeonce , ReadWrite : מייצר ממשקי API של getter ו-seter

הערה: מאפיינים עם הקידומת ro. לא ישתמש בגישה ReadWrite .

scope Internal : רק הבעלים יכול לגשת.

Public : כולם יכולים לגשת, למעט מודולי NDK.

prop_name שם מאפיין המערכת הבסיסי, למשל ro.build.date .
enum_values ( Enum , EnumList בלבד) מחרוזת מופרדת בסרגל (|) המורכבת מערכי enum אפשריים. לדוגמה, value1|value2 .
integer_as_bool ( Boolean , BooleanList בלבד) הפוך את המגדירים להשתמש 0 ו 1 במקום false ו- true .
legacy_prop_name (אופציונלי, מאפיינים Readonly בלבד) השם הישן של מאפיין המערכת הבסיסי. כשקוראים ל-getter, ממשק ה-API של getter מנסה לקרוא prop_name ומשתמש legacy_prop_name אם prop_name לא קיים. השתמש legacy_prop_name בעת הוצאה משימוש של נכס קיים ומעבר לנכס חדש.

כל סוג של נכס ממפה לסוגים הבאים ב-C++, Java ו-Rust.

סוּג C++ Java חֲלוּדָה
בוליאנית std::optional<bool> Optional<Boolean> bool
מספר שלם std::optional<std::int32_t> Optional<Integer> i32
UInt std::optional<std::uint32_t> Optional<Integer> u32
ארוך std::optional<std::int64_t> Optional<Long> i64
ULong std::optional<std::uint64_t> Optional<Long> u64
לְהַכפִּיל std::optional<double> Optional<Double> f64
חוּט std::optional<std::string> Optional<String> String
Enum std::optional<{api_name}_values> Optional<{api_name}_values> {ApiName}Values
רשימת T std::vector<std::optional<T>> List<T> Vec<T>

הנה דוגמה לקובץ Sysprop Description המגדיר שלושה מאפיינים:

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

הגדרת ספריות מאפייני מערכת

כעת אתה יכול להגדיר מודולי sysprop_library עם קבצי Sysprop Description. sysprop_library משמשת כ-API עבור C++, Java ו-Rust. מערכת ה-build מייצרת באופן פנימי rust_library אחת, java_library אחת ו- cc_library אחת עבור כל מופע של sysprop_library .

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

עליך לכלול קבצי רשימות API במקור עבור בדיקות API. לשם כך, צור קבצי API וספריית api . שים את ספריית api באותה ספרייה כמו Android.bp . שמות הקבצים של ה-API הם <module_name>-current.txt , <module_name>-latest.txt . <module_name>-current.txt מכיל את חתימות ה-API של קודי המקור הנוכחיים, ו- <module_name>-latest.txt מכיל את חתימות ה-API הקפואות האחרונות. מערכת הבנייה בודקת אם ממשקי ה-API משתנים על ידי השוואת קובצי ה-API הללו לקובצי ה-API שנוצרו בזמן הבנייה, ופולטת הודעת שגיאה והוראות לעדכון קובץ current.txt אם current.txt אינו תואם לקודי המקור. להלן דוגמה לארגון קובץ ומדריך:

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

מודולי לקוח Rust, Java ו-C++ יכולים לקשר מול sysprop_library כדי להשתמש בממשקי API שנוצרו. מערכת הבנייה יוצרת קישורים מלקוחות לספריות C++, Java ו-Rust שנוצרו, ובכך נותנת ללקוחות גישה לממשקי API שנוצרו.

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

שים לב ששם ספריית Rust נוצר על ידי המרת השם sysprop_library לאותיות קטנות, תוך החלפת . ו - עם _ , ולאחר מכן הקדמת lib והוספה של _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);
  }

  …
}

דוגמה של 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
        );
    }
    …
}
…

דוגמה 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);
    }
    …
}
…