AIDL پایدار

اندروید 10 پشتیبانی از زبان تعریف رابط کاربری پایدار اندروید (AIDL) را اضافه می کند، راهی جدید برای پیگیری رابط برنامه کاربردی (API) و رابط باینری برنامه (ABI) ارائه شده توسط رابط های AIDL. AIDL پایدار دقیقاً مانند AIDL کار می‌کند، اما سیستم ساخت، سازگاری رابط را دنبال می‌کند و محدودیت‌هایی برای کارهایی که می‌توانید انجام دهید وجود دارد:

  • اینترفیس ها در سیستم ساخت با aidl_interfaces تعریف می شوند.
  • رابط ها فقط می توانند حاوی داده های ساختاری باشند. بسته‌بندی‌هایی که نشان‌دهنده انواع ترجیحی هستند به‌طور خودکار براساس تعریف AIDL آنها ایجاد می‌شوند و به‌طور خودکار مارشال‌شده و بدون مارشال می‌شوند.
  • رابط ها را می توان به عنوان پایدار (سازگار با عقب) اعلام کرد. هنگامی که این اتفاق می افتد، API آنها در فایلی در کنار رابط AIDL ردیابی و نسخه بندی می شود.

AIDL ساختار یافته در مقابل پایدار

AIDL ساختاریافته به انواعی اطلاق می شود که صرفاً در AIDL تعریف شده اند. به عنوان مثال، یک اعلامیه قابل بسته بندی (یک بسته سفارشی) AIDL ساختاری ندارد. بسته‌بندی‌ها با فیلدهایشان که در AIDL تعریف شده‌اند، بسته‌پذیرهای ساختاریافته نامیده می‌شوند.

AIDL پایدار به AIDL ساختاریافته نیاز دارد تا سیستم ساخت و کامپایلر بتوانند بفهمند آیا تغییرات ایجاد شده در بسته‌بندی‌ها سازگار با عقب هستند یا خیر. با این حال، همه رابط های ساختاری پایدار نیستند. برای پایدار بودن، یک رابط باید فقط از انواع ساختار یافته استفاده کند و همچنین باید از ویژگی های نسخه سازی زیر استفاده کند. برعکس، اگر از سیستم ساخت هسته برای ساخت آن استفاده شود یا اگر unstable:true تنظیم شده باشد، یک رابط پایدار نیست.

رابط AIDL را تعریف کنید

تعریف aidl_interface به شکل زیر است:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            enabled: true,
        },
    },

}
  • name : نام ماژول رابط AIDL که به طور منحصر به فرد یک رابط AIDL را شناسایی می کند.
  • srcs : لیستی از فایل های منبع AIDL که رابط را تشکیل می دهند. مسیر یک Foo نوع AIDL تعریف شده در بسته com.acme باید در <base_path>/com/acme/Foo.aidl باشد، جایی که <base_path> می‌تواند هر دایرکتوری مربوط به دایرکتوری باشد که Android.bp در آن قرار دارد. در مثال قبل، <base_path> srcs/aidl است.
  • local_include_dir : مسیری که نام بسته از آنجا شروع می شود. مطابق با <base_path> توضیح داده شده در بالا است.
  • imports : لیستی از ماژول های aidl_interface که این مورد استفاده می کند. اگر یکی از اینترفیس های AIDL شما از یک رابط یا یک بسته بندی از aidl_interface دیگر استفاده می کند، نام آن را در اینجا قرار دهید. این نام می تواند به خودی خود برای اشاره به آخرین نسخه یا نامی با پسوند نسخه (مانند -V1 ) برای اشاره به یک نسخه خاص باشد. تعیین نسخه از اندروید 12 پشتیبانی می شود
  • versions : نسخه‌های قبلی رابط کاربری که تحت api_dir فریز شده‌اند، از اندروید ۱۱ شروع می‌شود، versions تحت aidl_api/ name فریز می‌شوند. اگر نسخه ثابتی از یک رابط وجود نداشته باشد، این نباید مشخص شود، و بررسی سازگاری وجود نخواهد داشت. این قسمت با versions_with_info برای Android 13 و بالاتر جایگزین شده است.
  • versions_with_info : فهرستی از تاپل ها، که هر کدام شامل نام یک نسخه ثابت و فهرستی با واردات نسخه های دیگر ماژول های aidl_interface است که این نسخه از aidl_interface وارد شده است. تعریف نسخه V یک رابط AIDL IFACE در aidl_api/ IFACE / V قرار دارد. این فیلد در اندروید 13 معرفی شد و قرار نیست مستقیماً در Android.bp اصلاح شود. این فیلد با فراخوانی *-update-api یا *-freeze-api اضافه یا به روز می شود. همچنین، وقتی کاربر *-update-api یا *-freeze-api را فراخوانی می کند، فیلدهای versions به طور خودکار به versions_with_info منتقل می شوند.
  • stability : پرچم اختیاری برای وعده پایداری این رابط. این فقط از "vintf" پشتیبانی می کند. اگر stability تنظیم نشده باشد، سیستم ساخت بررسی می‌کند که این رابط سازگار با عقب باشد، مگر اینکه unstable مشخص شده باشد. تنظیم نشدن مربوط به یک رابط با ثبات در این زمینه کامپایل است (بنابراین یا همه چیزهای سیستم، به عنوان مثال، چیزهای موجود در system.img و پارتیشن‌های مرتبط، یا همه چیزهای فروشنده، برای مثال، چیزهایی در vendor.img و پارتیشن‌های مرتبط). اگر stability روی "vintf" تنظیم شود، این با یک وعده پایداری مطابقت دارد: رابط باید تا زمانی که استفاده می‌شود پایدار بماند.
  • gen_trace : پرچم اختیاری برای روشن یا خاموش کردن ردیابی. با شروع اندروید 14، پیش‌فرض برای پشتیبان‌های cpp و java true است.
  • host_supported : پرچم اختیاری که وقتی روی true تنظیم می‌شود، کتابخانه‌های تولید شده را در دسترس محیط میزبان قرار می‌دهد.
  • unstable : پرچم اختیاری مورد استفاده برای نشان دادن این که این رابط نیازی به پایدار بودن ندارد. هنگامی که این مقدار روی true تنظیم شود، سیستم ساخت نه خالی API را برای رابط ایجاد می کند و نه نیاز به به روز رسانی آن را دارد.
  • frozen : پرچم اختیاری که وقتی روی true تنظیم شود به این معنی است که رابط نسبت به نسخه قبلی رابط تغییری نکرده است. این امکان بررسی بیشتر در زمان ساخت را فراهم می کند. وقتی روی false تنظیم شود، این بدان معنی است که رابط در حال توسعه است و تغییرات جدیدی دارد، بنابراین اجرای foo-freeze-api یک نسخه جدید تولید می کند و به طور خودکار مقدار را به true تغییر می دهد. در اندروید 14 معرفی شد.
  • backend.<type>.enabled : این پرچم ها هر یک از backend هایی را که کامپایلر AIDL برای آنها کد تولید می کند تغییر می دهد. چهار بک‌اند پشتیبانی می‌شوند: Java، C++، NDK و Rust. Java، C++، و Backendهای NDK به طور پیش فرض فعال هستند. اگر هر یک از این سه backend مورد نیاز نیست، باید به طور صریح غیرفعال شود. Rust به طور پیش فرض تا اندروید 15 غیرفعال است.
  • backend.<type>.apex_available : لیستی از نام های APEX که کتابخانه خرد تولید شده برای آنها در دسترس است.
  • backend.[cpp|java].gen_log : پرچم اختیاری که کنترل می‌کند کد اضافی برای جمع‌آوری اطلاعات مربوط به تراکنش تولید شود یا خیر.
  • backend.[cpp|java].vndk.enabled : پرچم اختیاری برای تبدیل این رابط به بخشی از VNDK. پیش فرض false است.
  • backend.[cpp|ndk].additional_shared_libraries : این پرچم که در Android 14 معرفی شد، وابستگی هایی را به کتابخانه های بومی اضافه می کند. این پرچم با ndk_header و cpp_header مفید است.
  • backend.java.sdk_version : پرچم اختیاری برای تعیین نسخه SDK که کتابخانه خرد جاوا بر اساس آن ساخته شده است. پیش فرض "system_current" است. وقتی backend.java.platform_apis true است، این نباید تنظیم شود.
  • backend.java.platform_apis : پرچم اختیاری که باید در زمانی که کتابخانه های تولید شده نیاز به ساخت بر اساس API پلتفرم به جای SDK دارند، روی true تنظیم شود.

برای هر ترکیبی از نسخه ها و باطن های فعال، یک کتابخانه خرد ایجاد می شود. برای چگونگی مراجعه به نسخه خاصی از کتابخانه خرد برای یک باطن خاص، قوانین نامگذاری ماژول را ببینید.

فایل های AIDL را بنویسید

رابط‌های موجود در AIDL پایدار شبیه رابط‌های سنتی هستند، با این تفاوت که آنها مجاز به استفاده از بسته‌بندی‌های بدون ساختار نیستند (زیرا اینها پایدار نیستند! به AIDL ساختاریافته در مقابل پایدار مراجعه کنید). تفاوت اصلی در AIDL پایدار در نحوه تعریف بسته‌بندی‌ها است. پیش از این، بسته‌ها اعلام شده بودند. در AIDL پایدار (و بنابراین ساختار یافته)، فیلدها و متغیرهای parcelable به صراحت تعریف می شوند.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

یک پیش‌فرض برای boolean ، char ، float ، double ، byte ، int ، long و String پشتیبانی می‌شود (اما لازم نیست). در اندروید 12، پیش‌فرض‌های شمارش‌های تعریف‌شده توسط کاربر نیز پشتیبانی می‌شوند. هنگامی که یک پیش فرض مشخص نشده است، از یک مقدار 0 مانند یا خالی استفاده می شود. شمارش‌های بدون مقدار پیش‌فرض به 0 مقداردهی می‌شوند، حتی اگر شمارشگر صفر وجود نداشته باشد.

از کتابخانه های خرد استفاده کنید

پس از افزودن کتابخانه های خرد به عنوان یک وابستگی به ماژول خود، می توانید آنها را در فایل های خود قرار دهید. در اینجا نمونه‌هایی از کتابخانه‌های خرد در سیستم ساخت وجود دارد ( Android.mk همچنین می‌تواند برای تعاریف ماژول قدیمی استفاده شود):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if your preference is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

مثال در C++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

مثال در جاوا:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

مثال در Rust:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

رابط های نسخه سازی

اعلان یک ماژول با نام foo همچنین یک هدف در سیستم ساخت ایجاد می کند که می توانید از آن برای مدیریت API ماژول استفاده کنید. هنگامی که ساخته شد، foo-freeze-api یک تعریف API جدید تحت api_dir یا aidl_api/ name ، بسته به نسخه Android اضافه می‌کند، و یک فایل .hash اضافه می‌کند که هر دو نسخه تازه منجمد شده رابط را نشان می‌دهد. foo-freeze-api همچنین ویژگی versions_with_info را به‌روزرسانی می‌کند تا نسخه اضافی و imports نسخه را منعکس کند. اساساً imports در versions_with_info از قسمت imports کپی می شود. اما آخرین نسخه پایدار در imports در versions_with_info برای import مشخص شده است که نسخه واضحی ندارد. پس از مشخص شدن ویژگی versions_with_info ، سیستم ساخت، بررسی های سازگاری را بین نسخه های فریز شده و همچنین بین Top of Tree (ToT) و آخرین نسخه فریز شده اجرا می کند.

علاوه بر این، باید تعریف API نسخه ToT را مدیریت کنید. هر زمان که یک API به روز می شود، foo-update-api را برای به روز رسانی aidl_api/ name /current که حاوی تعریف API نسخه ToT است، اجرا کنید.

برای حفظ ثبات یک رابط، صاحبان می توانند موارد زیر را اضافه کنند:

  • روش‌های پایان یک رابط (یا روش‌هایی با سریال‌های جدید به‌صراحت تعریف شده)
  • عناصر در انتهای یک بسته بندی (نیاز به اضافه شدن یک پیش فرض برای هر عنصر دارد)
  • ارزش های ثابت
  • در اندروید 11، شمارشگرها
  • در اندروید 12، فیلدها را تا انتهای یک اتحادیه قرار دهید

هیچ اقدام دیگری مجاز نیست، و هیچ کس دیگری نمی تواند یک رابط را تغییر دهد (در غیر این صورت خطر برخورد با تغییراتی که مالک ایجاد می کند وجود دارد).

برای آزمایش اینکه همه رابط‌ها برای انتشار ثابت هستند، می‌توانید با مجموعه متغیرهای محیطی زیر بسازید:

  • AIDL_FROZEN_REL=true m ... - ساخت نیاز به فریز کردن تمام رابط های پایدار AIDL دارد که هیچ owner: فیلد مشخص شده است.
  • AIDL_FROZEN_OWNERS="aosp test" - ساخت نیاز دارد که تمام رابط های پایدار AIDL با owner: فیلد مشخص شده به عنوان "aosp" یا "test".

ثبات واردات

به‌روزرسانی نسخه‌های وارداتی برای نسخه‌های فریزشده یک رابط، در لایه Stable AIDL سازگار با عقب است. با این حال، به‌روزرسانی این موارد مستلزم به‌روزرسانی همه سرورها و کلاینت‌هایی است که از نسخه قبلی رابط استفاده می‌کنند و ممکن است برخی از برنامه‌ها هنگام ترکیب نسخه‌های مختلف دچار اشتباه شوند. به طور کلی، برای بسته‌های فقط نوع یا معمولی، این کار ایمن است زیرا برای مدیریت انواع ناشناخته از تراکنش‌های IPC باید کد قبلاً نوشته شده باشد.

کد پلتفرم اندروید android.hardware.graphics.common بزرگترین نمونه از این نوع ارتقاء نسخه است.

از رابط های نسخه شده استفاده کنید

روش های رابط

در زمان اجرا، هنگام تلاش برای فراخوانی روش‌های جدید در سرور قدیمی، مشتریان جدید بسته به باطن، خطا یا استثنا دریافت می‌کنند.

  • cpp backend ::android::UNKNOWN_TRANSACTION دریافت می کند.
  • ndk باطن STATUS_UNKNOWN_TRANSACTION دریافت می کند.
  • باطن java android.os.RemoteException را با پیامی دریافت می کند که می گوید API اجرا نشده است.

برای استراتژی‌هایی که می‌توانند این کار را انجام دهند، نسخه‌های جستجو و استفاده از پیش‌فرض‌ها را ببینید.

بسته بندی ها

هنگامی که فیلدهای جدید به بسته‌بندی‌ها اضافه می‌شوند، مشتریان و سرورهای قدیمی آن‌ها را حذف می‌کنند. هنگامی که مشتریان و سرورهای جدید بسته‌بندی‌های قدیمی را دریافت می‌کنند، مقادیر پیش‌فرض فیلدهای جدید به‌طور خودکار پر می‌شوند.

کلاینت ها نباید انتظار داشته باشند که سرورها از فیلدهای جدید استفاده کنند، مگر اینکه بدانند سرور در حال پیاده سازی نسخه ای است که فیلد آن تعریف شده است ( نسخه های جستجو را ببینید).

اعداد و ثابت ها

به طور مشابه، مشتریان و سرورها باید مقادیر ثابت و شمارشگرهای ناشناخته را رد یا نادیده بگیرند، زیرا ممکن است در آینده موارد بیشتری اضافه شود. به عنوان مثال، یک سرور نباید زمانی که شمارشی را دریافت می کند که از آن اطلاعی ندارد، قطع شود. سرور یا باید شمارشگر را نادیده بگیرد، یا چیزی را برگرداند تا مشتری بداند در این پیاده سازی پشتیبانی نمی شود.

اتحادیه ها

اگر گیرنده قدیمی باشد و از فیلد اطلاعی نداشته باشد، تلاش برای ارسال اتحادیه با یک فیلد جدید با شکست مواجه می شود. اجرا هرگز شاهد اتحاد با حوزه جدید نخواهد بود. اگر تراکنش یک طرفه باشد، شکست نادیده گرفته می شود. در غیر این صورت خطا BAD_VALUE (برای C++ یا NDK backend) یا IllegalArgumentException (برای باطن جاوا) است. این خطا در صورتی دریافت می‌شود که کلاینت یک مجموعه اتحادیه را به فیلد جدید به یک سرور قدیمی ارسال کند، یا زمانی که یک کلاینت قدیمی اتحادیه را از یک سرور جدید دریافت می‌کند.

چندین نسخه را مدیریت کنید

یک فضای نام پیوند دهنده در اندروید می تواند تنها 1 نسخه از یک رابط aidl خاص داشته باشد تا از موقعیت هایی که انواع aidl تولید شده دارای تعاریف متعدد هستند جلوگیری شود. C++ دارای قانون یک تعریف است که فقط به یک تعریف از هر نماد نیاز دارد.

زمانی که یک ماژول به نسخه‌های مختلف کتابخانه aidl_interface یکسان وابسته است، ساخت آندروید یک خطا ارائه می‌دهد. ماژول ممکن است به طور مستقیم یا غیرمستقیم از طریق وابستگی های وابستگی های آنها به این کتابخانه ها وابسته باشد. این خطاها نمودار وابستگی را از ماژول خراب به نسخه های متضاد کتابخانه aidl_interface نشان می دهد. همه وابستگی ها باید به روز شوند تا نسخه مشابه (معمولاً آخرین) این کتابخانه ها را شامل شود.

اگر کتابخانه رابط توسط ماژول‌های مختلف استفاده می‌شود، ایجاد cc_defaults ، java_defaults و rust_defaults برای هر گروهی از کتابخانه‌ها و فرآیندهایی که نیاز به استفاده از نسخه مشابه دارند، می‌تواند مفید باشد. هنگام معرفی یک نسخه جدید از رابط، می توان آن پیش فرض ها را به روز کرد و همه ماژول هایی که از آنها استفاده می کنند با هم به روز می شوند و اطمینان حاصل شود که از نسخه های مختلف رابط استفاده نمی کنند.

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

وقتی ماژول‌های aidl_interface ماژول‌های دیگر aidl_interface را وارد می‌کنند، این وابستگی‌های اضافی ایجاد می‌کند که نیاز به نسخه‌های خاصی دارد که با هم استفاده شوند. هنگامی که ماژول های مشترک aidl_interface که در چندین ماژول aidl_interface که با هم در یک فرآیند استفاده می شوند، وارد می شوند، ممکن است مدیریت این وضعیت دشوار شود.

aidl_interfaces_defaults می‌تواند برای حفظ یک تعریف از آخرین نسخه‌های وابستگی‌ها برای یک aidl_interface که می‌تواند در یک مکان به‌روزرسانی شود و توسط همه ماژول‌های aidl_interface که می‌خواهند آن رابط مشترک را وارد کنند، استفاده شود.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

توسعه مبتنی بر پرچم

رابط‌های در حال توسعه (یخ‌نشده) نمی‌توانند در دستگاه‌های عرضه‌کننده استفاده شوند، زیرا تضمینی برای سازگاری با نسخه‌های قبلی وجود ندارد.

AIDL از زمان اجرای مجدد برای این کتابخانه‌های رابط غیر منجمد پشتیبانی می‌کند تا کد در برابر آخرین نسخه فریز نشده نوشته شود و همچنان در دستگاه‌های انتشار استفاده شود. رفتار سازگار با عقب‌نشینی مشتریان مشابه رفتار موجود است و با بازگشت به عقب، پیاده‌سازی‌ها نیز باید از آن رفتارها پیروی کنند. به استفاده از رابط های نسخه شده مراجعه کنید.

پرچم ساخت AIDL

پرچمی که این رفتار را کنترل می کند RELEASE_AIDL_USE_UNFROZEN است که در build/release/build_flags.bzl تعریف شده است. true به این معنی است که نسخه unfrozen رابط در زمان اجرا استفاده می شود و false به این معنی است که کتابخانه های نسخه های unfrozen همگی مانند آخرین نسخه فریز شده خود عمل می کنند. برای توسعه محلی می‌توانید پرچم را روی true تغییر دهید، اما قبل از انتشار باید آن را به false برگردانید. به طور معمول توسعه با پیکربندی انجام می شود که پرچم آن روی true تنظیم شده است.

ماتریس سازگاری و آشکار

اشیاء رابط فروشنده (اشیاء VINTF) تعریف می کنند که چه نسخه هایی مورد انتظار است و چه نسخه هایی در دو طرف رابط فروشنده ارائه می شوند.

بیشتر دستگاه‌های غیر کوتلفیش آخرین ماتریس سازگاری را تنها پس از ثابت شدن رابط‌ها هدف قرار می‌دهند، بنابراین هیچ تفاوتی در کتابخانه‌های AIDL بر اساس RELEASE_AIDL_USE_UNFROZEN وجود ندارد.

ماتریس ها

رابط های متعلق به شریک به ماتریس های سازگاری خاص دستگاه یا محصول که دستگاه در طول توسعه هدف قرار می دهد، اضافه می شود. بنابراین هنگامی که یک نسخه جدید و بدون فریز از یک رابط به ماتریس سازگاری اضافه می شود، نسخه های ثابت قبلی باید برای RELEASE_AIDL_USE_UNFROZEN=false باقی بمانند. می‌توانید با استفاده از فایل‌های ماتریس سازگاری مختلف برای پیکربندی‌های مختلف RELEASE_AIDL_USE_UNFROZEN یا اجازه دادن به هر دو نسخه در یک فایل ماتریس سازگاری که در همه پیکربندی‌ها استفاده می‌شود، این کار را انجام دهید.

به عنوان مثال، هنگام افزودن یک نسخه یخ نشده 4، از <version>3-4</version> استفاده کنید.

وقتی نسخه 4 ثابت است، می توانید نسخه 3 را از ماتریس سازگاری حذف کنید زیرا نسخه ثابت 4 زمانی استفاده می شود که RELEASE_AIDL_USE_UNFROZEN false باشد.

تجلی می کند

در اندروید 15، تغییری در libvintf برای اصلاح فایل‌های مانیفست در زمان ساخت بر اساس مقدار RELEASE_AIDL_USE_UNFROZEN معرفی شده است.

مانیفست‌ها و قطعات مانیفست نشان می‌دهند که یک سرویس کدام نسخه از رابط را پیاده‌سازی می‌کند. هنگام استفاده از آخرین نسخه unfrozen یک رابط، مانیفست باید به روز شود تا این نسخه جدید را منعکس کند. وقتی RELEASE_AIDL_USE_UNFROZEN=false ، ورودی های مانیفست توسط libvintf تنظیم می شوند تا تغییر در کتابخانه AIDL ایجاد شده را منعکس کنند. این نسخه از نسخه unfrozen، N ، به آخرین نسخه منجمد N - 1 تغییر یافته است. بنابراین، کاربران نیازی به مدیریت چند مانیفست یا قطعات مانیفست برای هر یک از خدمات خود ندارند.

کلاینت HAL تغییر می کند

کد سرویس گیرنده HAL باید با هر نسخه ثابت پشتیبانی شده قبلی سازگار باشد. وقتی RELEASE_AIDL_USE_UNFROZEN false است، سرویس‌ها همیشه شبیه آخرین نسخه ثابت یا قبل از آن هستند (برای مثال، فراخوانی روش‌های ثابت نشده جدید UNKNOWN_TRANSACTION را برمی‌گرداند، یا فیلدهای parcelable جدید مقادیر پیش‌فرض خود را دارند). کلاینت‌های چارچوب Android باید با نسخه‌های قبلی دیگر سازگار باشند، اما این یک جزئیات جدید برای مشتریان فروشنده و مشتریان رابط‌های متعلق به شریک است.

تغییرات اجرای HAL

بزرگترین تفاوت در توسعه HAL با توسعه مبتنی بر پرچم، الزام اجرای HAL برای سازگاری با آخرین نسخه ثابت برای کار در زمانی که RELEASE_AIDL_USE_UNFROZEN false است، است. در نظر گرفتن سازگاری به عقب در پیاده سازی ها و کد دستگاه یک تمرین جدید است. به استفاده از رابط های نسخه شده مراجعه کنید.

ملاحظات مربوط به سازگاری به عقب عموماً برای کلاینت ها و سرورها و برای کد چارچوب و کد فروشنده یکسان است، اما تفاوت های ظریفی وجود دارد که باید از آنها آگاه باشید، زیرا اکنون به طور مؤثر دو نسخه را پیاده سازی می کنید که از کد منبع یکسان استفاده می کنند. (نسخه فعلی، unfrozen).

مثال: یک رابط دارای سه نسخه ثابت است. رابط کاربری با روش جدیدی به روز می شود. سرویس گیرنده و سرویس هر دو برای استفاده از کتابخانه نسخه جدید 4 به روز شده اند. از آنجایی که کتابخانه V4 مبتنی بر نسخه unfrozen رابط است، مانند آخرین نسخه ثابت، نسخه 3، زمانی که RELEASE_AIDL_USE_UNFROZEN false است، رفتار می کند و از استفاده از روش جدید جلوگیری می کند.

وقتی رابط ثابت است، تمام مقادیر RELEASE_AIDL_USE_UNFROZEN از آن نسخه ثابت شده استفاده می‌کنند و کدی که سازگاری به عقب را مدیریت می‌کند حذف می‌شود.

هنگام فراخوانی روش‌ها در تماس‌های برگشتی، باید وقتی UNKNOWN_TRANSACTION برگردانده می‌شود، با ظرافت رسیدگی کنید. ممکن است کلاینت ها بر اساس پیکربندی انتشار دو نسخه متفاوت از یک فراخوان را اجرا کنند، بنابراین نمی توانید فرض کنید که مشتری جدیدترین نسخه را ارسال می کند و روش های جدید ممکن است این را برگردانند. این شبیه به چگونگی سازگاری پایدار کلاینت‌های AIDL با سرورها است که در استفاده از رابط‌های نسخه‌بندی شده توضیح داده شده است.

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

وقتی RELEASE_AIDL_USE_UNFROZEN false است و مقادیر فیلدهای جدیدی که سرویس سعی می کند ارسال کند در هنگام خروج از فرآیند حذف می شوند، ممکن است فیلدهای جدید در انواع موجود ( parcelable ، enum ، union ) وجود نداشته باشند یا حاوی مقادیر پیش فرض خود باشند.

انواع جدیدی که در این نسخه فریز نشده اضافه شده اند را نمی توان از طریق رابط ارسال یا دریافت کرد.

زمانی که RELEASE_AIDL_USE_UNFROZEN false است، پیاده سازی هرگز از هیچ کلاینتی برای روش های جدید فراخوانی دریافت نمی کند.

مراقب باشید که از شمارشگرهای جدید فقط با نسخه ای که در آن معرفی شده اند استفاده کنید و نه نسخه قبلی.

به طور معمول، شما از foo->getInterfaceVersion() استفاده می کنید تا ببینید رابط راه دور از کدام نسخه استفاده می کند. با این حال، با پشتیبانی از نسخه‌سازی مبتنی بر پرچم، دو نسخه متفاوت را پیاده‌سازی می‌کنید، بنابراین ممکن است بخواهید نسخه رابط فعلی را دریافت کنید. می توانید این کار را با دریافت نسخه رابط شی فعلی انجام دهید، به عنوان مثال، this->getInterfaceVersion() یا روش های دیگر برای my_ver . برای اطلاعات بیشتر به درخواست نسخه رابط شیء راه دور مراجعه کنید.

رابط های پایدار VINTF جدید

هنگامی که یک بسته رابط AIDL جدید اضافه می شود، آخرین نسخه ثابت وجود ندارد، بنابراین زمانی که RELEASE_AIDL_USE_UNFROZEN false است، هیچ رفتاری برای بازگشت به آن وجود ندارد. از این رابط ها استفاده نکنید. وقتی RELEASE_AIDL_USE_UNFROZEN false است، Service Manager به سرویس اجازه نمی دهد رابط را ثبت کند و کلاینت ها آن را پیدا نمی کنند.

می توانید خدمات را به صورت مشروط بر اساس مقدار پرچم RELEASE_AIDL_USE_UNFROZEN در فایل ساخت دستگاه اضافه کنید:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

اگر سرویس بخشی از یک فرآیند بزرگتر است، بنابراین نمی توانید آن را به صورت مشروط به دستگاه اضافه کنید، می توانید بررسی کنید که آیا سرویس با IServiceManager::isDeclared() اعلان شده است یا خیر. اگر اعلام شد و ثبت نشد، فرآیند را لغو کنید. اگر اعلام نشود، انتظار می رود که ثبت نام نکند.

کاسه ماهی به عنوان ابزار توسعه

هر سال پس از منجمد شدن VINTF، ماتریس سازگاری چارچوب (FCM) target-level و PRODUCT_SHIPPING_API_LEVEL Cuttlefish تنظیم می‌کنیم تا دستگاه‌هایی را که با عرضه سال آینده راه‌اندازی می‌شوند منعکس کنند. ما target-level و PRODUCT_SHIPPING_API_LEVEL را تنظیم می‌کنیم تا مطمئن شویم دستگاه راه‌اندازی وجود دارد که آزمایش شده است و شرایط جدید را برای انتشار سال آینده برآورده می‌کند.

وقتی RELEASE_AIDL_USE_UNFROZEN true باشد، Cuttlefish برای توسعه نسخه‌های اندرویدی آینده استفاده می‌شود. سطح FCM و PRODUCT_SHIPPING_API_LEVEL نسخه اندروید سال آینده را هدف قرار می‌دهد، که باید الزامات نرم‌افزار فروشنده نسخه بعدی (VSR) را برآورده کند.

وقتی RELEASE_AIDL_USE_UNFROZEN false است، Cuttlefish target-level قبلی و PRODUCT_SHIPPING_API_LEVEL را برای انعکاس دستگاه رهاسازی دارد. در اندروید 14 و پایین‌تر، این تمایز با شاخه‌های مختلف Git انجام می‌شود که تغییر به target-level FCM، سطح API حمل و نقل یا هر کد دیگری را که نسخه بعدی را هدف قرار می‌دهد، انجام نمی‌دهند.

قوانین نامگذاری ماژول ها

در اندروید 11، برای هر ترکیبی از نسخه‌ها و باطن‌های فعال، یک ماژول کتابخانه خرد به‌طور خودکار ایجاد می‌شود. برای ارجاع به یک ماژول کتابخانه خرد خاص برای پیوند، از نام ماژول aidl_interface استفاده نکنید، بلکه از نام ماژول کتابخانه خرد استفاده کنید که ifacename - version - backend است.

  • ifacename : نام ماژول aidl_interface
  • version یکی از
    • V version-number برای نسخه های فریز شده
    • V latest-frozen-version-number + 1 برای نسخه نوک درخت (هنوز منجمد نشده)
  • backend یکی از این دو است
    • java برای باطن جاوا،
    • cpp برای باطن ++C،
    • ndk یا ndk_platform برای باطن NDK. اولی برای برنامه‌ها و دومی برای استفاده از پلتفرم تا Android 13 است. در Android 13 و بالاتر، فقط از ndk استفاده کنید.
    • rust for Rust backend.

فرض کنید یک ماژول با نام foo وجود دارد و آخرین نسخه آن 2 است و از NDK و C++ پشتیبانی می کند. در این مورد، AIDL این ماژول ها را تولید می کند:

  • بر اساس نسخه 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • بر اساس نسخه 2 (آخرین نسخه پایدار)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • بر اساس نسخه ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

در مقایسه با اندروید 11:

  • foo- backend که به آخرین نسخه پایدار اشاره می کند تبدیل به foo- V2 - backend می شود
  • foo-unstable- backend ، که به نسخه ToT اشاره دارد، تبدیل به foo- V3 - backend می شود.

نام فایل های خروجی همیشه با نام ماژول ها یکسان است.

  • بر اساس نسخه 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • بر اساس نسخه 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • بر اساس نسخه ToT: foo-V3-(cpp|ndk|ndk_platform|rust).so

توجه داشته باشید که کامپایلر AIDL یک ماژول نسخه unstable یا یک ماژول غیرنسخه ای برای یک رابط AIDL پایدار ایجاد نمی کند. از اندروید 12، نام ماژول تولید شده از یک رابط پایدار AIDL همیشه شامل نسخه آن می شود.

روش های جدید رابط متا

اندروید 10 چندین روش رابط متا را برای AIDL پایدار اضافه می کند.

نسخه رابط شی راه دور را پرس و جو کنید

کلاینت‌ها می‌توانند نسخه و هش رابطی را که شی راه دور پیاده‌سازی می‌کند پرس و جو کرده و مقادیر بازگشتی را با مقادیر رابطی که مشتری استفاده می‌کند مقایسه کنند.

مثال با باطن cpp :

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

مثال با ndkndk_platform ) باطن:

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

مثال با باطن java :

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

برای زبان جاوا، سمت راه دور باید getInterfaceVersion() و getInterfaceHash() به صورت زیر اجرا کند (به جای IFoo برای جلوگیری از اشتباهات کپی و چسباندن از super استفاده می شود. بسته به اینکه ممکن است برای غیرفعال کردن هشدارها به حاشیه نویسی @SuppressWarnings("static") نیاز باشد. پیکربندی javac ):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

دلیل آن این است که کلاس‌های تولید شده ( IFoo ، IFoo.Stub ، و غیره) بین مشتری و سرور به اشتراک گذاشته می‌شوند (برای مثال، کلاس‌ها می‌توانند در مسیر کلاس راه‌اندازی باشند). وقتی کلاس‌ها به اشتراک گذاشته می‌شوند، سرور نیز با جدیدترین نسخه کلاس‌ها مرتبط می‌شود، حتی اگر ممکن است با نسخه قدیمی‌تری از رابط ساخته شده باشد. اگر این رابط متا در کلاس اشتراکی پیاده سازی شود، همیشه جدیدترین نسخه را برمی گرداند. با این حال، با پیاده‌سازی روش فوق، شماره نسخه رابط در کد سرور تعبیه می‌شود (زیرا IFoo.VERSION یک static final int است که در صورت ارجاع درون خطی می‌شود) و بنابراین روش می‌تواند نسخه دقیقی را که سرور ساخته شده است برگرداند. با

با رابط های قدیمی تر مقابله کنید

ممکن است یک کلاینت با نسخه جدیدتر یک رابط AIDL به روز شود اما سرور از رابط قدیمی AIDL استفاده می کند. در چنین مواردی، فراخوانی یک متد در یک رابط قدیمی UNKNOWN_TRANSACTION را برمی‌گرداند.

با AIDL پایدار، مشتریان کنترل بیشتری دارند. در سمت کلاینت، می توانید یک پیاده سازی پیش فرض را روی یک رابط AIDL تنظیم کنید. یک روش در اجرای پیش‌فرض تنها زمانی فراخوانی می‌شود که متد در سمت راه دور پیاده‌سازی نشده باشد (زیرا با نسخه قدیمی‌تر رابط ساخته شده است). از آنجایی که پیش‌فرض‌ها به صورت سراسری تنظیم می‌شوند، نباید از زمینه‌های بالقوه مشترک استفاده شوند.

مثال در C++ در اندروید ۱۳ و بالاتر:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

مثال در جاوا:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process

foo.anAddedMethod(...);

شما نیازی به ارائه اجرای پیش فرض همه روش ها در یک رابط AIDL ندارید. روش‌هایی که تضمین می‌شوند در سمت راه دور پیاده‌سازی می‌شوند (زیرا مطمئن هستید که ریموت زمانی ساخته شده است که متدها در توضیحات رابط AIDL بودند) نیازی به لغو در کلاس impl پیش‌فرض ندارند.

AIDL موجود را به AIDL ساختاریافته یا پایدار تبدیل کنید

اگر یک رابط AIDL و کدی دارید که از آن استفاده می کند، از مراحل زیر برای تبدیل رابط به یک رابط AIDL پایدار استفاده کنید.

  1. تمام وابستگی های رابط کاربری خود را شناسایی کنید. برای هر بسته، رابط به آن بستگی دارد، مشخص کنید که بسته در AIDL پایدار تعریف شده است یا خیر. اگر تعریف نشده باشد، بسته باید تبدیل شود.

  2. تمام بسته‌های موجود در رابط خود را به بسته‌بندی‌های پایدار تبدیل کنید (فایل‌های رابط می‌توانند بدون تغییر باقی بمانند). این کار را با بیان ساختار آنها به طور مستقیم در فایل های AIDL انجام دهید. برای استفاده از این انواع جدید، کلاس های مدیریت باید بازنویسی شوند. این را می توان قبل از ایجاد یک بسته aidl_interface (در زیر) انجام داد.

  3. یک بسته aidl_interface (همانطور که در بالا توضیح داده شد) ایجاد کنید که حاوی نام ماژول شما، وابستگی های آن و هر اطلاعات دیگری که نیاز دارید باشد. برای تثبیت آن (نه فقط ساختاری)، همچنین باید نسخه شود. برای اطلاعات بیشتر، نسخه‌سازی رابط‌ها را ببینید.