تغییر مقدار منابع برنامه در زمان اجرا

رونهاد منبع زمان اجرا (RRO) بسته‌ای است که مقادیر منبع بسته هدف را در زمان اجرا تغییر می‌دهد. برای مثال، برنامه‌ای که در تصویر سیستم نصب شده است ممکن است عملکردش را براساس مقدار منبع تغییر دهد. به‌جای کدنویسی سخت مقدار منبع در زمان ساخت، یک RRO نصب‌شده در پارتیشن متفاوت می‌تواند مقادیر منابع برنامه را در زمان اجرا تغییر دهد.

هم‌پوشانی‌های منبع زمان اجرا را می‌توان فعال یا غیرفعال کرد. می‌توانید به‌صورت برنامه‌نویسی‌شده وضعیت فعال/غیرفعال را تنظیم کنید تا توانایی «هم‌پوشانی منبع زمان اجرا» برای تغییر مقادیر منبع را تغییر دهید. «پوشش‌های منبع» به‌طور پیش‌فرض غیرفعال هستند (بااین‌حال، پوشش‌های منبع ایستا به‌طور پیش‌فرض فعال هستند).

منابع رونهاد

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

راه‌اندازی مانیفست

بسته زمانی بسته RRO درنظر گرفته می‌شود که حاوی برچسب <overlay> به‌عنوان فرزند برچسب <manifest> باشد.

  • مقدار مشخصه الزامی android:targetPackage نام بسته‌ای را که RRO قصد دارد رونهاد کند مشخص می‌کند.

  • مقدار مشخصه اختیاری android:targetName نام زیرمجموعه منابع هم‌پوشانی‌پذیر بسته هدف را که «هم‌پوشانی منبع زمان اجرا» قصد هم‌پوشانی آن را دارد مشخص می‌کند. اگر هدف مجموعه منابعی را که قابلیت رونهاد دارند تعریف نکند، این ویژگی نباید وجود داشته باشد.

کد زیر یک نمونه رونهاد AndroidManifest.xml را نشان می‌دهد.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"/>
</manifest>

رونهادها نمی‌توانند روی کد رونهاد شوند، بنابراین نمی‌توانند فایل‌های DEX داشته باشند. علاوه‌براین، مشخصه android:hasCode برچسب <application> در مانیفست باید روی false تنظیم شود.

تعریف نقشه منابع

در Android 11 یا بالاتر، سازوکار توصیه‌شده برای تعریف نقشه منابع رونهاد این است که فایلی در دایرکتوری res/xml بسته رونهاد ایجاد کنید، منابع هدفی را که باید رونهاد شوند و مقادیر جایگزین آن‌ها را فهرست کنید، سپس مقدار ویژگی android:resourcesMap برچسب مانیفست <overlay> را روی مرجع فایل نگاشت منبع تنظیم کنید.

کد زیر نمونه‌ای از فایل res/xml/overlays.xml را نشان می‌دهد.

<?xml version="1.0" encoding="utf-8"?>
<overlay xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- Overlays string/config1 and string/config2 with the same resource. -->
    <item target="string/config1" value="@string/overlay1" />
    <item target="string/config2" value="@string/overlay1" />

    <!-- Overlays string/config3 with the string "yes". -->
    <item target="string/config3" value="@android:string/yes" />

    <!-- Overlays string/config4 with the string "Hardcoded string". -->
    <item target="string/config4" value="Hardcoded string" />

    <!-- Overlays integer/config5 with the integer "42". -->
    <item target="integer/config5" value="42" />
</overlay>

کد زیر نمونه‌ای از مانیفست رونهاد را نشان می‌دهد.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"
                   android:resourcesMap="@xml/overlays"/>
</manifest>

ساختن بسته

‫Android 11 یا بالاتر از قانون ساخت Soong برای رونهادها پشتیبانی می‌کند که از تلاش «ابزار بسته‌بندی دارایی Android 2» (AAPT2) برای حذف پیکربندی‌های منابع با مقدار یکسان (--no-resource-deduping) و حذف منابع بدون پیکربندی‌های پیش‌فرض (--no-resource-removal) جلوگیری می‌کند. کد زیر نمونه‌ای از فایل Android.bp را نشان می‌دهد.

runtime_resource_overlay {
    name: "ExampleOverlay",
    sdk_version: "current",
}

حل‌وفصل کردن منابع

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

برای مثال، اگر رونهاد مقداری را برای پیکربندی drawable-en تعریف کند و هدف مقداری را برای drawable-en-port تعریف کند، drawable-en-port مطابقت بهتری دارد، بنابراین مقدار پیکربندی هدف drawable-en-port در زمان اجرا انتخاب می‌شود. برای رونهاد کردن همه پیکربندی‌های drawable-en، رونهاد باید برای هر پیکربندی drawable-en که هدف تعریف می‌کند مقداری تعریف کند.

رونهادها می‌توانند به منابع خودشان ارجاع دهند، با رفتارهای متفاوت بین نسخه‌های Android.

  • در Android 11 یا بالاتر، هر رونهاد فضای شناسه منبع رزروشده خودش را دارد که با فضای شناسه منبع هدف یا فضاهای شناسه منبع رونهاد دیگر هم‌پوشانی ندارد، بنابراین رونهادهایی که به منابع خودشان ارجاع می‌دهند طبق انتظار عمل می‌کنند.

  • در Android 10 یا نسخه‌های پایین‌تر، بسته‌های هدف و هم‌پوشانی‌ها فضای شناسه منبع یکسانی دارند که می‌تواند هنگام تلاش برای ارجاع به منابع خود بااستفاده از نحو @type/name باعث تداخل و رفتار غیرمنتظره شود.

فعال/غیرفعال کردن رونهادها

رونهادها را می‌توان به‌صورت دستی و برنامه‌ریزی‌شده فعال/غیرفعال کرد.

پوشش‌ها را به‌صورت دستی غیرفعال یا فعال کنید

برای فعال کردن و درستی‌سنجی دستی RRO، این فرمان را اجرا کنید:

adb shell cmd overlay enable --user current com.example.carrro
adb shell cmd overlay list --user current | grep -i com.example com.example.carrro

این کار «هم‌پوشانی منبع زمان اجرا» را برای کاربر سیستم (userId = 0) که مالک SystemUI است فعال می‌کند. این دستورالعمل بر برنامه‌هایی که کاربر پیش‌زمینه‌ای (userId = 10) شروع کرده است تأثیر نمی‌گذارد. برای فعال کردن RRO برای کاربر پیش‌زمینه‌ای، از پارامتر -–user 10 استفاده کنید:

adb shell cmd overlay enable --user 10 com.example.carrro

فعال یا غیرفعال کردن برنامه‌نویسی‌شده هم‌پوشانی‌ها

از OverlayManager API برای فعال و غیرفعال کردن رونهادهای تغییرپذیر استفاده کنید (میانای API را بااستفاده از Context#getSystemService(Context.OVERLAY_SERVICE) بازیابی کنید). رونهاد فقط می‌تواند توسط بسته‌ای که هدف آن است یا بسته‌ای با اجازه android.permission.CHANGE_OVERLAY_PACKAGES فعال شود. وقتی رونهادی فعال یا غیرفعال می‌شود، رویدادهای تغییر پیکربندی به بسته هدف منتقل می‌شود و فعالیت‌های هدف مجدداً راه‌اندازی می‌شود.

محدود کردن منابع قابل‌هم‌پوشانی

در Android 10 یا بالاتر، برچسب XML <overlayable> مجموعه‌ای از منابع را آشکار می‌کند که RROها مجاز به رونهاد کردن آن‌ها هستند. در مثال زیر، res/values/overlayable.xml فایل، string/foo و integer/bar منابعی هستند که برای زمینه‌بندی ظاهر دستگاه استفاده می‌شوند؛ برای رونهاد کردن این منابع، رونهاد باید مجموعه منابع رونهادپذیر را به‌طور صریح براساس نام هدف‌یابی کند.

<!-- The collection of resources for theming the appearance of the device -->
<overlayable name="ThemeResources">
       <policy type="public">
               <item type="string" name="foo/" />
               <item type="integer" name="bar/" />
       </policy>
       ...
</overlayable>

‫APK می‌تواند چند برچسب <overlayable> را تعریف کند، اما هر برچسب باید نام یکتایی در بسته داشته باشد. برای مثال، این موارد را دربرمی‌گیرد:

  • دو بسته مختلف می‌توانند <overlayable name="foo"> را تعریف کنند.

  • اینکه یک APK واحد دو بلوک <overlayable name="foo"> داشته باشد قابل‌قبول نیست.

کد زیر نمونه‌ای از رونهاد در فایل AndroidManifest.xml را نشان می‌دهد.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.my.theme.overlay">
       <application android:hasCode="false" />
       <!-- This overlay will override the ThemeResources resources -->
       <overlay android:targetPackage="android" android:targetName="ThemeResources">
</manifest>

وقتی برنامه‌ای برچسب <overlayable> را تعریف می‌کند، هم‌پوشانی‌هایی که آن برنامه را هدف‌یابی می‌کنند:

  • باید targetName را مشخص کنید.

  • فقط می‌تواند منابع فهرست‌شده در برچسب <overlayable> را رونهاد کند.

  • فقط می‌تواند یک نام <overlayable> را هدف‌یابی کند.

نمی‌توانید هم‌پوشانی‌ای را فعال کنید که بسته‌ای را هدف قرار می‌دهد که منابع قابل‌هم‌پوشانی را افشا می‌کند اما از android:targetName برای هدف قرار دادن برچسب <overlayable> خاصی استفاده نمی‌کند.

محدود کردن خط‌مشی‌ها

از برچسب <policy> برای اعمال محدودیت‌ها بر روی منابع رونهادپذیر استفاده کنید. مشخصه type مشخص می‌کند رونهاد برای ملغی کردن منابع گنجانده‌شده باید کدام خط‌مشی‌ها را برآورده کند. انواع پشتیبانی‌شده شامل موارد زیر است.

  • public. هر رونهادی می‌تواند منبع را لغو کند.
  • system. هرگونه هم‌پوشانی در پارتیشن سیستم می‌تواند منابع را لغو کند.
  • vendor. هر رونهادی در پارتیشن فروشنده می‌تواند منابع را ملغی کند.
  • product. هر رونهادی در بخش محصول می‌تواند منابع را ملغی کند.
  • oem. هر رونهادی در پارتیشن oem می‌تواند منابع را ملغی کند.
  • odm. هر رونهادی در بخش odm می‌تواند منابع را لغو کند.
  • signature. هر هم‌پوشانی‌ای که با همان امضای APK هدف امضا شده باشد می‌تواند منابع را لغو کند.
  • actor. هر رونهادی که با همان امضای فایل APK بازیگر امضا شده باشد می‌تواند منابع را ملغی کند. بازیگر در برچسب named-actor در پیکربندی سیستم اعلام شده است.
  • config_signature. هر رونهادی که با همان امضای فایل overlay-config apk امضا شده باشد می‌تواند منابع را ملغی کند. پیکربندی رونهاد در برچسب امضای پیکربندی رونهاد در پیکربندی سیستم اعلام می‌شود.

کد زیر نمونه‌ای از برچسب <policy> را در فایل res/values/overlayable.xml نشان می‌دهد.

<overlayable name="ThemeResources">
   <policy type="vendor" >
       <item type="string" name="foo" />
   </policy>
   <policy type="product|signature"  >
       <item type="string" name="bar" />
       <item type="string" name="baz" />
   </policy>
</overlayable>

برای مشخص کردن چند خط‌مشی، از خط عمودی (|) به‌عنوان نویسه‌های جداکننده استفاده کنید. وقتی چند خط‌مشی مشخص شده باشد، هم‌پوشانی فقط باید یک خط‌مشی را برآورده کند تا منابع فهرست‌شده در برچسب <policy> را لغو کند.

پیکربندی رونهادها

‫Android بسته به نسخه انتشار Android از سازوکارهای مختلفی برای پیکربندی تغییرپذیری، حالت پیش‌فرض، و اولویت رونهادها پشتیبانی می‌کند.

  • دستگاه‌های دارای Android 11 یا بالاتر می‌توانند به‌جای مشخصه‌های مانیفست از فایل OverlayConfig (config.xml) استفاده کنند. استفاده از فایل هم‌پوشانی روش توصیه‌شده برای هم‌پوشانی‌ها است.

  • همه دستگاه‌ها می‌توانند از مشخصه‌های مانیفست (android:isStatic و android:priority) برای پیکربندی RROهای ثابت استفاده کنند.

استفاده از OverlayConfig

در Android 11 یا بالاتر، می‌توانید از OverlayConfig برای پیکربندی تغییرپذیری، وضعیت پیش‌فرض، و اولویت رونهادها استفاده کنید. برای پیکربندی رونهاد، فایلی را که در partition/overlay/config/config.xml قرار دارد ایجاد یا اصلاح کنید، که در آن partition پارتیشن رونهاد پیکربندی‌شده است. برای پیکربندی شدن، رونهاد باید در overlay/ دایرکتوری پارتیشنی که رونهاد در آن پیکربندی شده است قرار داشته باشد. کد زیر نمونه‌ای از product/overlay/config/config.xml را نشان می‌دهد.

<config>
    <merge path="OEM-common-rros-config.xml" />
    <overlay package="com.oem.overlay.device" mutable="false" enabled="true" />
    <overlay package="com.oem.green.theme" enabled="true" />
</config>"

برچسب <overlay> به مشخصه package نیاز دارد که نشان می‌دهد کدام بسته رونهاد درحال پیکربندی است. ویژگی اختیاری enabled کنترل می‌کند که رونهاد به‌طور پیش‌فرض فعال باشد یا نه (پیش‌فرض false است). ویژگی اختیاری mutable کنترل می‌کند که رونهاد تغییرپذیر باشد یا نه و آیا وضعیت فعال آن می‌تواند به‌صورت برنامه‌ریزی‌شده در زمان اجرا تغییر کند یا نه (پیش‌فرض true است). رونهادهایی که در فایل پیکربندی فهرست نشده‌اند تغییرپذیر هستند و به‌طور پیش‌فرض غیرفعال هستند.

تقدم رونهاد

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

  • system
  • vendor
  • odm
  • oem
  • product
  • system_ext

ادغام کردن فایل‌ها

استفاده از برچسب‌های <merge> امکان ادغام سایر فایل‌های پیکربندی را در موقعیت مشخص‌شده در فایل پیکربندی فراهم می‌کند. مشخصه path برچسب نشان‌دهنده مسیر فایل برای ادغام نسبت به دایرکتوری حاوی فایل‌های پیکربندی هم‌پوشانی است.

استفاده از مشخصه‌های مانیفست/RROهای ایستا

در Android 10 یا نسخه‌های پایین‌تر، تغییرناپذیری و اولویت هم‌پوشانی بااستفاده از ویژگی‌های مانیفست زیر پیکربندی می‌شود.

  • android:isStatic. وقتی مقدار این مشخصه بولی روی true تنظیم شود، هم‌پوشانی به‌طور پیش‌فرض فعال می‌شود و تغییرناپذیر است، که از غیرفعال شدن هم‌پوشانی جلوگیری می‌کند.

  • android:priority. مقدار این مشخصه عددی (که فقط بر هم‌پوشانی‌های ایستا تأثیر می‌گذارد) اولویت هم‌پوشانی را زمانی که چندین هم‌پوشانی ایستا مقدار منبع یکسانی را هدف قرار می‌دهند پیکربندی می‌کند. عدد بالاتر نشان‌دهنده اولویت بالاتر است.

کد زیر نمونه‌ای از AndroidManifest.xml را نشان می‌دهد.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:isStatic="true"
                   android:priority="5"/>
</manifest>

تغییرات در Android 11

در Android 11 یا بالاتر، اگر فایل پیکربندی در partition/overlay/config/config.xml قرار داشته باشد، رونهادها بااستفاده از آن فایل پیکربندی می‌شوند و android:isStatic و android:priority روی رونهادهای موجود در این بخش تأثیری ندارند. تعریف فایل پیکربندی هم‌پوشانی در هر پارتیشنی، اولویت پارتیشن هم‌پوشانی را اعمال می‌کند.

علاوه‌براین، Android 11 یا بالاتر توانایی استفاده از هم‌پوشانی‌های ایستا را برای تأثیرگذاری بر مقادیر منابع خوانده‌شده درطول نصب بسته حذف می‌کند. برای مورد استفاده رایج استفاده از هم‌پوشانی‌های ثابت برای تغییر مقدار بولی‌هایی که وضعیت فعال مؤلفه را پیکربندی می‌کنند، از برچسب <component-override> SystemConfig (جدید در Android 11) استفاده کنید.

رونهادهای اشکال‌زدایی

برای فعال کردن، غیرفعال کردن، و تخلیه کردن رونهادها به‌صورت دستی، از فرمان shell مدیر رونهاد زیر استفاده کنید.

adb shell cmd overlay

استفاده از enable بدون مشخص کردن کاربر بر کاربر فعلی، یعنی کاربر سیستم (userId = 0) که مالک «واسط کاربر سیستم» است، تأثیر می‌گذارد. این کار بر کاربر پیش‌زمینه‌ای (userId = 10) که مالک برنامه‌ها است تأثیر نمی‌گذارد. برای فعال کردن RRO برای کاربر پیش‌زمینه، از پارامتر –-user 10 استفاده کنید:

adb shell cmd overlay enable --user 10 com.example.carrro

‫OverlayManagerService از idmap2 برای تخصیص دادن شناسه‌های منبع در بسته هدف به شناسه‌های منبع در بسته رونهاد استفاده می‌کند. نقشه‌نگاری‌های شناسه تولیدشده در /data/resource-cache/ ذخیره می‌شود. اگر رونهاد شما به‌درستی کار نمی‌کند، فایل idmap مربوط به رونهاد خود را در /data/resource-cache/ پیدا کنید، سپس دستور زیر را اجرا کنید.

adb shell idmap2 dump --idmap-path [file]

این فرمان نگاشت منابع را به‌صورت زیر چاپ می‌کند.

[target res id] - > [overlay res id] [resource name]
0x01040151 -> 0x01050001 string/config_dozeComponent
0x01040152 -> 0x01050002 string/config_dozeDoubleTapSensorType
0x01040153 -> 0x01050003 string/config_dozeLongPressSensorType