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

Các thuộc tính hệ thống giúp việc chia sẻ thông tin (thường là cấu hình) trở nên thuận tiện 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 trong nội bộ. Có thể xảy ra sự cố khi truy cập vào các thuộc tính trên nhiều 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, còn /vendor 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 của các cặp khoá-giá trị chuỗi không có giản đồ, 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 mà không có thông báo nào.

Kể từ bản phát hành Android 10, các thuộc tính hệ thống truy cập trên các phân vùng sẽ được giản đồ thành tệp Mô tả Sysprop, còn các API để truy cập vào thuộc tính được tạo dưới dạng các hàm cụ thể cho C++ và Rust và 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 thần kỳ (chẳng hạn như ro.build.date) để truy cập cũng như vì chúng có thể được nhập tĩnh. Độ ổn định của ABI cũng được kiểm tra tại thời điểm xây dựng và bản dựng sẽ bị hỏng nếu có thay đổi không tương thích. Bước kiểm tra này đóng vai trò là 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ể mang lại tính nhất quán giữa Ruust, 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 giản đồ 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;
}

Một tệp Mô tả Sysprop chứa một thông báo thuộc tính, trong đó mô tả một tập hợp các thuộc tính. Sau đây là ý nghĩa của các trường.

Trường Ý nghĩa
owner Được đặ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 một không gian tên (C++) hoặc lớp tĩnh cuối cùng (Java) nơi đặt các API được tạo. Ví dụ: com.android.sysprop.BuildProperties sẽ 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ơ sở lưu trú.

Các trường thông báo Property có ý nghĩa như sau.

Trường Ý nghĩa
api_name Tên của API đã tạo.
type Loại của cơ sở lưu trú này.
access Readonly: Chỉ tạo API getter
Writeonce, ReadWrite: Tạo API getter và setter
Lưu ý: Các thuộc tính có tiền tố ro. có thể không sử dụng quyền truy cập ReadWrite.
scope Internal: Chỉ chủ sở hữu mới có thể truy cập.
Public: Tất cả 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 (chỉ dành cho Enum, EnumList) Chuỗi được phân tách bằng thanh(|) bao gồm các giá trị enum có thể có. Ví dụ: value1|value2.
integer_as_bool (chỉ dành cho Boolean, BooleanList) Tạo phương thức setter sử dụng 01 thay vì falsetrue.
legacy_prop_name (không bắt buộc, chỉ dành cho 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 phương thức getter, API getter sẽ cố đọ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 tài sản hiện có và chuyển sang một tài sản mới.

Mỗi loại thuộc tính liên kết với các loại sau trong C++, Java và Rust.

Loại C++ Java Rust
Boolean std::optional<bool> Optional<Boolean> bool
Số nguyên std::optional<std::int32_t> Optional<Integer> i32
UInt std::optional<std::uint32_t> Optional<Integer> u32
Dài std::optional<std::int64_t> Optional<Long> i64
ULong std::optional<std::uint64_t> Optional<Long> u64
Đôi std::optional<double> Optional<Double> f64
Chuỗi std::optional<std::string> Optional<String> String
Liệt kê std::optional<{api_name}_values> Optional<{api_name}_values> {ApiName}Values
Danh sách chữ T std::vector<std::optional<T>> List<T> Vec<T>

Dưới đây là ví dụ về tệp Mô tả Sysprop xác định ba 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 thư viện thuộc tính hệ thống

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

// 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. Để thực hiện việc này, hãy tạo các tệp API và một thư mục api. Đặt thư mục api trong cùng một 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 lưu giữ các chữ ký API của mã nguồn hiện tại, còn <module_name>-latest.txt chứa các chữ ký API bị cố định mới nhất. 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 đã tạo tại thời điểm xây dựng, đồng thời đư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. Dưới đây là ví dụ về thư mục và cách sắp xếp 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 dựa vào sysprop_library để sử dụng các API đã tạo. Hệ thống xây dựng tạo đường liên kết từ ứng dụng đến các thư viện C++, Java và Rust đã tạo, từ đó cho phép khách hàng truy cập vào các API đã tạo.

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

Lưu ý rằng tên thư viện Rust được tạo bằng cách chuyển đổi tên sysprop_library thành chữ thường, thay thế .- bằng _, sau đó thêm lib vào đầu _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ề gỉ sét:

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