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

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

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

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

حدِّد سمات النظام على أنّها واجهات برمجة تطبيقات تتضمّن ملفات وصف 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) يتم فيها وضع واجهات برمجة التطبيقات التي تم إنشاؤها. على سبيل المثال، 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.

النوع 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
ULong 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 List 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 كواجهة برمجة تطبيقات للغات 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: ["libPlatformProperties"],
}

rust_binary {
    name: "rust_client",
    srcs: ["src/main.rs"],
    rustlibs: ["libplatformproperties_rust"],
}

يُرجى العِلم أنّه يتم إنشاء اسم مكتبة Rust من خلال تحويل sysprop_library الاسم إلى أحرف صغيرة، واستبدال . و- بـ _، ثم إضافة lib في البداية و_rust في النهاية.

في المثال السابق، يمكنك الوصول إلى السمات المحدّدة على النحو التالي.

مثال على 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);
    }
    
}