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

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

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

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

حدِّد خصائص النظام كواجهات برمجة تطبيقات باستخدام ملفات 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 واحد على رسالة خصائص واحدة تصف مجموعة من الخصائص. في ما يلي معنى حقول هذا الجدول:

الحقل المعنى
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: إنشاء واجهة برمجة تطبيقات للحصول على البيانات فقط
Writeonce وReadWrite: إنشاء واجهات برمجة تطبيقات للحصول على البيانات وضبطها
ملاحظة: قد لا تستخدم السمات التي تحتوي على البادئة 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.

النوع C++‎ Java الصدأ (Rust)
قيمة منطقية 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
طويل 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 يحدِّد ثلاث سمات:

# 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 لواجهة برمجة تطبيقات 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 إذا كان 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);
    }
    …
}
…