تنفيذ خصائص النظام كواجهات برمجة تطبيقات

توفر خصائص النظام طريقة ملائمة لمشاركة المعلومات، عادةً عمليات الضبط، على مستوى النظام. يمكن لكل قسم استخدام خصائص النظام الخاصة به داخليًا. قد تحدث مشكلة عند الوصول إلى الخصائص على مستوى الأقسام المختلفة، مثل وصول /vendor إلى الخصائص المحددة عبر /system. بدءًا من الإصدار Android 8.0، يمكن ترقية بعض الأقسام، مثل /system، بينما يتم ترك /vendor بدون تغيير. نظرًا لأن خصائص النظام هي مجرد قاموس عالمي لأزواج المفتاح والقيمة بدون مخطط، فمن الصعب استقرار الخصائص. ويمكن أن يغيّر القسم /system أو يزيل السمات التي يعتمد عليها القسم /vendor بدون أي إشعار.

بدءًا من إصدار Android 10، يتم تخطيط خصائص النظام التي يتم الوصول إليها على مستوى الأقسام في ملفات وصف Sysbro، ويتم إنشاء واجهات برمجة التطبيقات للوصول إلى الخصائص كوظائف ملموسة لكل من C++ وRust وفئات لـ Java. إنّ استخدام واجهات برمجة التطبيقات هذه أكثر سهولة في الاستخدام لأنّه لا حاجة لاستخدام سلاسل سحرية (مثل ro.build.date) للوصول إليها، ولأنّها يمكن كتابتها بشكل ثابت. يتم أيضًا التحقّق من ثبات واجهة التطبيق الثنائية (ABI) في وقت الإصدار، ويتعطّل الإصدار في حال حدوث تغييرات غير متوافقة. يعمل هذا الفحص كواجهات محددة بشكل واضح بين الأقسام. ويمكن لواجهات برمجة التطبيقات هذه أيضًا توفير الاتساق بين Rust وJava وC++.

تحديد خصائص النظام كواجهات برمجة تطبيقات

عرِّف خصائص النظام كواجهات برمجة تطبيقات باستخدام ملفات وصف Sysbro (.sysprop)، التي تستخدم TextFormat of 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;
}

يحتوي ملف وصف Syspr واحد على رسالة خصائص واحدة تصف مجموعة من الخصائص. وفي ما يلي معنى حقولها.

الحقل المعنى
owner اضبط السمة على القسم الذي يملك السمات: Platform أو Vendor أو Odm.
module يتم استخدام البيانات لإنشاء مساحة اسم (C++ ) أو فئة نهائية ثابتة (Java) يتم فيها وضع واجهات برمجة التطبيقات التي تم إنشاؤها. على سبيل المثال، ستكون com.android.sysprop.BuildProperties مساحة الاسم com::android::sysprop::BuildProperties في C++ ، والفئة BuildProperties في الحزمة في com.android.sysprop في Java.
prop قائمة المواقع

في ما يلي معاني حقول رسالة Property.

الحقل المعنى
api_name اسم واجهة برمجة التطبيقات التي تم إنشاؤها.
type تمثّل هذه السمة نوع هذا الموقع.
access Readonly: إنشاء واجهة برمجة تطبيقات getter فقط
Writeonce، ReadWrite: إنشاء واجهات برمجة تطبيقات getter وsetter
ملاحظة: المواقع التي تحمل البادئة ro. قد لا تستخدم إذن الوصول إلى ReadWrite.
scope Internal: يمكن للمالك فقط الوصول إليه.
Public: يمكن لجميع المستخدمين الوصول إلى المحتوى، باستثناء وحدات NDK.
prop_name تمثّل هذه السمة اسم السمة الأساسية للنظام، مثل ro.build.date.
enum_values (Enum، EnumList فقط) سلسلة مفصولة بشريط(|) تتكون من قيم تعداد محتملة. مثلاً: value1|value2
integer_as_bool (Boolean وBooleanList فقط) جعل المستوّقين يستخدمون 0 و1 بدلاً من false وtrue.
legacy_prop_name (اختياري، سمات Readonly فقط) الاسم القديم لموقع النظام الأساسي. عند استدعاء الدالة getter، تحاول واجهة برمجة تطبيقات getter قراءة prop_name وتستخدم legacy_prop_name في حال عدم توفّر prop_name. يمكنك استخدام السمة legacy_prop_name عند إيقاف موقع حالي نهائيًا والانتقال إلى موقع جديد.

يتم تعيين كل نوع من المواقع للأنواع التالية في لغة C++ وJava وRust.

Type C++‎ Java Rust
قيمة منطقية std::optional<bool> Optional<Boolean> bool
عدد صحيح std::optional<std::int32_t> Optional<Integer> i32
واجهة std::optional<std::uint32_t> Optional<Integer> u32
الصيغة الطويلة std::optional<std::int64_t> Optional<Long> i64
طويل std::optional<std::uint64_t> Optional<Long> u64
مزدوجة std::optional<double> Optional<Double> f64
سلسلة std::optional<std::string> Optional<String> String
التعداد std::optional<{api_name}_values> Optional<{api_name}_values> {ApiName}Values
قائمة T std::vector<std::optional<T>> List<T> Vec<T>

في ما يلي مثال على ملف وصف النظام الذي يحدد ثلاث خصائص:

# 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 باستخدام ملفات وصف SysPro. تعمل sysprop_library كواجهة برمجة تطبيقات للغة C++ وJava و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 في الدليل نفسه مثل Android.bp. اسما ملفات واجهة برمجة التطبيقات هما <module_name>-current.txt و<module_name>-latest.txt. يحتوي <module_name>-current.txt على توقيعات واجهة برمجة التطبيقات لرموز المصدر الحالية، بينما يحتفظ <module_name>-latest.txt بأحدث توقيعات البيانات المجمّدة من واجهة برمجة التطبيقات. يتحقّق نظام التصميم مما إذا كان قد تم تغيير واجهات برمجة التطبيقات من خلال مقارنة ملفات واجهات برمجة التطبيقات هذه بملفات واجهة برمجة التطبيقات التي تم إنشاؤها، ثم يعرض رسالة خطأ وتعليمات لتعديل ملف current.txt في حال عدم تطابق current.txt مع رموز المصدر. فيما يلي مثال على مؤسسة الدليل والملفات:

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

يمكن ربط وحدات برامج Rust وJava وC++ بـ sysprop_library لاستخدام واجهات برمجة التطبيقات التي تم إنشاؤها. ينشئ نظام التصميم روابط من العملاء إلى مكتبات C++ وJava وRust التي تم إنشاؤها، ما يتيح للعملاء الوصول إلى واجهات برمجة التطبيقات التي تم إنشاؤها.

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