Triển khai các thuộc tính hệ thống dưới dạng API

Thuộc tính hệ thống (sysprop) cung cấp một cách thuận tiện để chia sẻ thông tin, thường là các cấu hình trên toàn hệ thống. Mỗi phân vùng có thể sử dụng các thuộc tính hệ thống riêng ở bên trong. Vấn đề có thể xảy ra khi các thuộc tính được truy cập trên các phân vùng, chẳng hạn như /vendor truy cập vào các thuộc tính do /system xác định. Kể từ Android 8.0, một số phân vùng, chẳng hạn như /system, có thể được nâng cấp, trong khi /vendor vẫn không thay đổi. Vì các thuộc tính hệ thống chỉ là một từ điển toàn cục gồm các cặp khoá-giá trị chuỗi không có lược đồ, nên rất khó để ổn định các thuộc tính. Phân vùng /system có thể thay đổi hoặc xoá các thuộc tính mà phân vùng /vendor phụ thuộc vào mà không cần thông báo.

Trong Android 10 trở lên, các thuộc tính hệ thống được truy cập trên các phân vùng được lập sơ đồ thành các tệp mô tả sysprop và các API để truy cập vào các thuộc tính được tạo dưới dạng các hàm cụ thể cho C++ và Rust, cũng như các lớp cho Java. Các API này thuận tiện hơn khi sử dụng vì không cần chuỗi ma thuật (chẳng hạn như ro.build.date) để truy cập và vì chúng có thể được nhập tĩnh. Tính ổn định của ABI cũng được kiểm tra tại thời điểm xây dựng và quá trình xây dựng sẽ bị gián đoạn nếu xảy ra các thay đổi không tương thích. Quá trình kiểm tra này hoạt động như các giao diện được xác định rõ ràng giữa các phân vùng. Các API này cũng có thể cung cấp tính nhất quán giữa Rust, Java và C++.

Xác định các thuộc tính hệ thống dưới dạng API

Xác định các thuộc tính hệ thống dưới dạng API bằng các tệp Mô tả sysprop (.sysprop), sử dụng TextFormat của protobuf, với lược đồ sau:

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

Tệp mô tả sysprop chứa một thông báo thuộc tính, mô tả một tập hợp các thuộc tính. Ý nghĩa của các trường trong tệp này như sau:

Trường Ý nghĩa
owner Đặt thành phân vùng sở hữu các thuộc tính: platform, vendor, hoặc odm.
module Dùng để tạo không gian tên (C++) hoặc lớp cuối cùng tĩnh (Java) nơi đặt các API được tạo. Ví dụ: com.android.sysprop.BuildProperties là không gian tên com::android::sysprop::BuildProperties trong C++ và lớp BuildProperties trong gói trong com.android.sysprop trong Java.
prop Danh sách các thuộc tính.

Ý nghĩa của các trường thông báo Property như sau:

Trường Ý nghĩa
api_name Tên của API được tạo.
type Loại thuộc tính này.
access Readonly: Chỉ tạo API getter
Writeonce, ReadWrite: Tạo API getter và setter
scope Internal: Chỉ chủ sở hữu mới có thể truy cập.
Public: Mọi người đều có thể truy cập, ngoại trừ các mô-đun NDK.
prop_name Tên của thuộc tính hệ thống cơ bản, ví dụ: ro.build.date.
enum_values (Enum, EnumList chỉ) Một chuỗi được phân tách bằng dấu gạch đứng(|) bao gồm các giá trị enum có thể có. Ví dụ: value1|value2.
integer_as_bool (Boolean, BooleanList chỉ) Tạo các setter sử dụng 01 thay vì falsetrue.
legacy_prop_name (không bắt buộc, chỉ các thuộc tính Readonly ) Tên cũ của thuộc tính hệ thống cơ bản. Khi gọi getter, API getter sẽ cố gắng đọc prop_name và sử dụng legacy_prop_name nếu prop_name không tồn tại. Sử dụng legacy_prop_name khi ngừng sử dụng một thuộc tính hiện có và chuyển sang một thuộc tính mới.

Mỗi loại thuộc tính sẽ ánh xạ đến các loại sau trong C++, Java và Rust:

Loại C++ (có thể rỗng) Java (có thể rỗng) Rust (không bắt buộc hoặc có thể rỗng)
Boolean std::optional<bool> Optional<Boolean> Option<bool>
Số nguyên std::optional<std::int32_t> Optional<Integer> Option<i32>
UInt std::optional<std::uint32_t> Optional<Integer> Option<u32>
Long std::optional<std::int64_t> Optional<Long> Option<i64>
ULong std::optional<std::uint64_t> Optional<Long> Option<u64>
Double std::optional<double> Optional<Double> Option<f64>
String std::optional<std::string> Optional<String> Option<String>
Enum std::optional<{api\_name}\_values> Optional<{api\_name}\_values> Option<{ApiName}Values>
Danh sách T std::vector<std::optional<T>> List<T> Vec<T>

Sau đây là ví dụ về tệp Mô tả sysprop xác định 3 thuộc tính:

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

Xác định các thư viện thuộc tính hệ thống

Bạn có thể xác định các mô-đun sysprop_library bằng các tệp Mô tả sysprop. sysprop_library đóng vai trò là API cho C++, Java và Rust. Hệ thống xây dựng sẽ tạo một rust_library, một java_library và một cc_library cho mỗi thực thể của sysprop_library ở bên trong.

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

Bạn phải đưa các tệp danh sách API vào nguồn để kiểm tra API. Để làm việc này, hãy tạo các tệp API và thư mục api. Đặt thư mục api trong cùng thư mục với Android.bp. Tên tệp API là <module_name>-current.txt<module_name>-latest.txt. <module_name>-current.txt chứa chữ ký API của mã nguồn hiện tại và <module_name>-latest.txt chứa chữ ký API mới nhất đã được cố định. Hệ thống xây dựng sẽ kiểm tra xem các API có thay đổi hay không bằng cách so sánh các tệp API này với các tệp API được tạo tại thời điểm xây dựng và đưa ra thông báo lỗi cũng như hướng dẫn cập nhật tệp current.txt nếu current.txt không khớp với mã nguồn. Sau đây là ví dụ về tổ chức thư mục và tệp:

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

Các mô-đun ứng dụng Rust, Java và C++ có thể liên kết với sysprop_library để sử dụng các API được tạo. Hệ thống xây dựng sẽ tạo các đường liên kết từ ứng dụng đến các thư viện C++, Java và Rust được tạo, nhờ đó cho phép ứng dụng truy cập vào các API được tạo.

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

Trong ví dụ trước, bạn có thể truy cập vào các thuộc tính đã xác định như sau.

Ví dụ về 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);
  }

  
}

Ví dụ về 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
        );
    }
    
}

Ví dụ về 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);
    }
    
}