پیاده سازی ویژگی های سیستم به عنوان API

ویژگی‌های سیستم (sysprops) راهی مناسب برای به اشتراک گذاشتن اطلاعات، معمولاً پیکربندی‌ها، در سطح سیستم فراهم می‌کنند. هر پارتیشن می‌تواند از ویژگی‌های سیستمی خود به صورت داخلی استفاده کند. زمانی که ویژگی‌ها در بین پارتیشن‌ها قابل دسترسی باشند، ممکن است مشکلی پیش بیاید، مانند دسترسی /vendor به ویژگی‌های تعریف‌شده /system . از اندروید ۸.۰، برخی از پارتیشن‌ها، مانند /system ، می‌توانند ارتقا یابند، در حالی که /vendor بدون تغییر باقی می‌ماند. از آنجا که ویژگی‌های سیستم فقط یک دیکشنری سراسری از جفت‌های کلید-مقدار رشته‌ای بدون هیچ طرحواره‌ای هستند، تثبیت ویژگی‌ها دشوار است. پارتیشن /system می‌تواند ویژگی‌هایی را که پارتیشن /vendor به آنها وابسته است، بدون هیچ اطلاع قبلی تغییر دهد یا حذف کند.

در اندروید ۱۰ و بالاتر، ویژگی‌های سیستم که از طریق پارتیشن‌ها قابل دسترسی هستند، در فایل‌های توصیفی sysprop شماتیک می‌شوند و APIهای دسترسی به ویژگی‌ها به صورت توابع عینی برای C++ و Rust و کلاس‌هایی برای Java تولید می‌شوند. استفاده از این APIها راحت‌تر است زیرا برای دسترسی به هیچ رشته جادویی (مانند ro.build.date ) نیاز نیست و می‌توان آنها را به صورت استاتیک تایپ کرد. پایداری ABI نیز در زمان ساخت بررسی می‌شود و در صورت بروز تغییرات ناسازگار، ساخت متوقف می‌شود. این بررسی به عنوان رابط‌های تعریف‌شده صریح بین پارتیشن‌ها عمل می‌کند. این APIها همچنین می‌توانند سازگاری بین Rust، Java و C++ را فراهم کنند.

تعریف ویژگی‌های سیستم به عنوان API

ویژگی‌های سیستم را به عنوان API با فایل‌های توضیحات Sysprop ( .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 شامل یک پیام ویژگی‌ها است که مجموعه‌ای از ویژگی‌ها را توصیف می‌کند. معنای فیلدهای آن به شرح زیر است:

میدان معنی
owner روی پارتیشنی که مالک ویژگی‌ها است تنظیم کنید: platform ، vendor یا odm .
module برای ایجاد یک فضای نام (C++) یا کلاس نهایی استاتیک (Java) که APIهای تولید شده در آن قرار می‌گیرند، استفاده می‌شود. برای مثال، com.android.sysprop.BuildProperties در C++ یک فضای نام com::android::sysprop::BuildProperties و در جاوا، کلاس BuildProperties در پکیج com.android.sysprop است.
prop فهرست املاک.

معانی فیلدهای پیام Property به شرح زیر است:

میدان معنی
api_name نام API تولید شده.
type نوع این ملک.
access Readonly : فقط API گیرنده تولید می‌کند
Writeonce ، ReadWrite : رابط‌های برنامه‌نویسی کاربردی (API) مربوط به getter و setter را تولید می‌کند.
scope Internal : فقط مالک می‌تواند دسترسی داشته باشد.
Public : همه می‌توانند به آن دسترسی داشته باشند، به جز ماژول‌های NDK.
prop_name نام ویژگی سیستم اصلی، برای مثال ro.build.date .
enum_values (فقط Enum ، EnumList ) یک رشته جدا شده با bar(|) که شامل مقادیر enum ممکن است. برای مثال، value1|value2 .
integer_as_bool (فقط Boolean و BooleanList ) تنظیم‌کننده‌ها را مجبور کنید که به جای false و true از 0 و 1 استفاده کنند.
legacy_prop_name (اختیاری، فقط برای ویژگی‌های Readonly ) نام قدیمی ویژگی سیستم اصلی. هنگام فراخوانی getter، API getter سعی می‌کند prop_name بخواند و اگر prop_name وجود نداشته باشد legacy_prop_name استفاده می‌کند. هنگام منسوخ کردن یک ویژگی موجود و انتقال به یک ویژگی جدید، legacy_prop_name استفاده کنید.

هر نوع ویژگی به انواع زیر در C++، جاوا و Rust نگاشت می‌شود:

نوع سی پلاس پلاس (تهی‌پذیر) جاوا (قابل تهی‌سازی) زنگ (اختیاری یا قابل تهی‌سازی)
بولی std::optional<bool> Optional<Boolean> Option<bool>
عدد صحیح std::optional<std::int32_t> Optional<Integer> Option<i32>
یوینت std::optional<std::uint32_t> Optional<Integer> Option<u32>
طولانی std::optional<std::int64_t> Optional<Long> Option<i64>
یو لانگ std::optional<std::uint64_t> Optional<Long> Option<u64>
دو برابر std::optional<double> Optional<Double> Option<f64>
رشته std::optional<std::string> Optional<String> Option<String>
شمارشی std::optional<{api\_name}\_values> Optional<{api\_name}\_values> Option<{ApiName}Values>
فهرست تی std::vector<std::optional<T>> List<T> Vec<T>

در اینجا مثالی از یک فایل توضیحات Sysprop که سه ویژگی را تعریف می‌کند، آورده شده است:

# 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 تعریف کنید. sysprop_library به عنوان یک API برای C++، جاوا و Rust عمل می‌کند. سیستم ساخت به صورت داخلی یک 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++ می‌توانند برای استفاده از APIهای تولید شده، به sysprop_library لینک شوند. سیستم ساخت، لینک‌هایی از کلاینت‌ها به کتابخانه‌های تولید شده C++، Java و Rust ایجاد می‌کند و به این ترتیب به کلاینت‌ها امکان دسترسی به APIهای تولید شده را می‌دهد.

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

در مثال قبلی، می‌توانستید به صورت زیر به ویژگی‌های تعریف‌شده دسترسی داشته باشید.

مثال زنگ زدگی:

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

  
}

مثال جاوا:

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

مثال سی++:

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