اگر سازنده تجهیزات اصلی (OEM) هستید یا از مشتری بهروزرسانی ممتاز ازطریق هوا (OTA) نگهداری میکنید، میتوانید به برنامههای حساس به امنیت در دستگاه امکان مشاهده بهروزرسانیهای امنیتی معلقه را بدهید تا بتوانند وضعیت امنیتی دستگاه را بهدقت ارزیابی کنند. برای اعمال خطمشیهای قدرتمند اعتماد صفر، برنامهها باید بتوانند نه تنها سطح وصله نصبشده روی دستگاه (سطح وصله امنیتی دستگاه، یا DSPL) را تأیید کنند، بلکه بهروزرسانیهای امنیتی دردسترس و آماده نصب (سطح وصله امنیتی دردسترس، یا ASPL) را نیز تأیید کنند.
ازآنجاییکه برنامههای مشتری بدون امتیاز نمیتوانند مستقیماً ویژگیهای
سختافزار را بخوانند، پایگاههای داده بهروزرسان خصوصی را بازرسی کنند، یا از نقاط پایانی
داخلی OEM backend پُرسمان کنند، کتابخانه AndroidX Security State Provider معماری
استاندارد و امنی برای ارتباط بینپردازشی (IPC) ارائه میدهد که مشتریان بهروزرسانی
از آن برای همرسانی اطلاعات درباره بهروزرسانیهای دردسترس استفاده میکنند. با پیادهسازی
UpdateInfoService در مشتری بهروزرسانی OTA، میتوانید
فراداده ASPL را برای سیستم بدون آشکار کردن یکپارچهسازیهای زیرینه اختصاصی منتشر کنید. درحالیکه Google پیادهسازی عنصر سیستم واحد (Mainline)
بهروزرسانها را برای دستگاههای GMS ارائه میدهد، دستگاههای غیر GMS نیز میتوانند فراداده ASPL را برای
این عناصر سیستم واحد منتشر کنند.
نمای کلی معماری
نمودار زیر نشان میدهد کتابخانه AndroidX Security State Provider چگونه چارچوب IPC استاندارد و امنی بین برنامههای مشتری بدون امتیاز و خدمات بهروزرسانی دروندستگاهی ایجاد میکند:

مدلهای ارسال داده
برنامههای کارخواه با فراخواندن
queryAllAvailableUpdates یا
fetchAvailableSecurityPatchLevel دردسترس بودن بهروزرسانی را پُرسمان میکنند. در پشت صحنه، کتابخانه
کارخواه بهطور خودکار همه سرویسهای ثبتشدهای را که
کلاس UpdateInfoService را در دستگاه از برنامههای سیستمی که
اجازه READ_PRIVILEGED_PHONE_STATE را دارند گسترش میدهند پیدا میکند و به آنها ملحق میشود.
همانطور که در نمودار قبلی نشان داده شده است، کتابخانه security-state-provider
از دو مدل ارائه داده پشتیبانی میکند:
| مدل ارائه | راهانداز همگامسازی | پاسخ مشتری | موارد استفاده توصیهشده |
|---|---|---|---|
| مدل فشاری (همگامسازی پسزمینه) | کارگران پسزمینه زمانبندیشده (WorkManager یا JobScheduler) با زیرینه شما همگامسازی میکنند و گزارشها را در UpdateInfoManager مینویسند. خدمات شما همیشه از حافظه نهان دیسک محلی (shouldFetchUpdates() = false) ارائه میشود. |
بلافاصله از حافظه نهان محلی ارائه شد. | بهروزرسانهای OTA سیستم OEM و بهروزرسانهای مؤلفه واحد همگامسازیشده در پسزمینه. |
| کشیدن مدل (همگامسازی درصورت نیاز) | وقتی گزارشهای ذخیرهشده در حافظه نهان قدیمی باشند (shouldFetchUpdates() = true)، پُرسمانهای IPC مشتری ورودی باعث واکشی شبکه میشوند. ادغام متقابل و محدود کردن نرخ (shouldThrottle()) از زیرینه شما دربرابر جهشهای ناگهانی محافظت میکند. |
وقتی حافظه نهان قدیمی است، منتظر واکشی زیرینه میماند. | بهروزرسانهای OTA سازنده اصلی محصول یکپارچه بدون کارگران همگامسازی پسزمینه زمانبندیشده. |
چندین ارائهدهنده بهروزرسانی
در دستگاههای Android تولیدی، چندین ارائهدهنده بهروزرسانی مستقل بهطور همزمان با هم همزیستی دارند. برای مثال، Mainline دردسترس بودن مؤلفههای
واحدی (COMPONENT_SYSTEM_MODULES) را منتشر میکند، درحالیکه کارخواه OTA سازنده
تجهیزات اصلی بهروزرسانیهای تصویر سیستم عامل اصلی (COMPONENT_SYSTEM) را منتشر میکند.
خدمات شما فقط باید بهروزرسانیهای مربوط به اجزای خاصی را که مدیریت میکند ثبت کند. اگر چندین ارائهدهنده در یک دستگاه بهروزرسانیهایی را برای یک
جزء منتشر کنند، برنامههای کارخواه بالاترین سطح وصله دردسترس را (بااستفاده از
fetchAvailableSecurityPatchLevel()) ارزیابی میکنند یا
گزارشهای UpdateInfo فردی را (بااستفاده از queryAllAvailableUpdates()) برای
حسابرسی سازمانی بازرسی میکنند. مطمئن شوید که سرویس شما همیشه قالب متعارف را برای عنصرتان منتشر میکند (DateBasedSecurityPatchLevel برای
COMPONENT_SYSTEM).
راهنمای گامبهگام برای آمادهسازی مشتری بهروزرسانی
برای ادغام کتابخانه «ارائهدهنده وضعیت امنیت AndroidX» در کارخواه بهروزرسانی و شروع انتشار دردسترس بودن بهروزرسانی امنیتی دستگاهتان، این مراحل را دنبال کنید.
مرحله ۱: افزودن وابستگیها
برای پیادهسازی ارائهدهنده بهروزرسانی، مطمئن شوید پروژه شما شامل مخزن Google
Maven باشد، سپس کتابخانه security-state-provider را به فایل build.gradle.kts (Kotlin DSL) یا build.gradle (Groovy DSL) واحد خود اضافه کنید:
کاتلین
// Kotlin DSL (build.gradle.kts)
dependencies {
// Core provider library for OTA and system update clients
implementation("androidx.security:security-state-provider:1.0.0")
// Required to construct UpdateInfo and DateBasedSecurityPatchLevel records
implementation("androidx.security:security-state:1.1.0")
// Optional: Guava ListenableFuture support for Java implementations
implementation("androidx.concurrent:concurrent-futures:1.2.0")
implementation("com.google.guava:guava:33.0.0-android")
}
شیک
// Groovy DSL (build.gradle)
dependencies {
// Core provider library for OTA and system update clients
implementation 'androidx.security:security-state-provider:1.0.0'
// Required to construct UpdateInfo and DateBasedSecurityPatchLevel records
implementation 'androidx.security:security-state:1.1.0'
// Optional: Guava ListenableFuture support for Java implementations
implementation 'androidx.concurrent:concurrent-futures:1.2.0'
implementation 'com.google.guava:guava:33.0.0-android'
}
مرحله ۲: سرویس بهروزرسانی را در مانیفست خود اعلام کنید
سرویس خود را در AndroidManifest.xml برنامه با
<intent-filter> مطابقت با
androidx.security.state.provider.UPDATE_INFO_SERVICE اعلام کنید. سرویس باید
صادر شود (android:exported="true") و بهعنوان سرویس تککاربر پیکربندی شود
(android:singleUser="true") تا کتابخانه کارخواه بتواند در سراسر
مرزهای کاربر و فرایند به آن متصل شود، بهویژه برای نمایههای کاری:
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.android.updater">
<application>
<service
android:name=".MyUpdateInfoService"
android:exported="true"
android:singleUser="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="androidx.security.state.provider.UPDATE_INFO_SERVICE" />
</intent-filter>
</service>
</application>
</manifest>
اگر بهروزرسان شما بهعنوان android.uid.system اجرا نمیشود، اجازههای زیر را نیز در مانیفست خود اعلام کنید و مطمئن شوید آنها را به فهرست مجاز اجازههای ممتاز خود اضافه کردهاید:
-
READ_PRIVILEGED_PHONE_STATE: برای اینکه مشتریان به ارائهدهنده شما اعتماد کنند لازم است. -
INTERACT_ACROSS_USERS: برایandroid:singleUser="true"لازم است.
مرحله ۳: پیادهسازی UpdateInfoService
برای انتشار وضعیت بهروزرسانی، باید کلاس UpdateInfoService
را پیادهسازی کنید و گزارشهای بهروزرسانی را بسازید که با مدل داده موردانتظار مطابقت داشته باشد.
مشخصات مدل داده UpdateInfo
چه مدل «فشار» را انتخاب کنید چه مدل «کشیدن»، UpdateInfo
گزارشها را بااستفاده از UpdateInfo.Builder طبق مشخصات زیر
بسازید:
| نام فیلد | روش دریافتکننده | نوع داده | الزامات قالب و اعتبارسنجی | هدف و معناشناسی سیستم |
|---|---|---|---|---|
component |
getComponent() |
String (@Component) |
ثابتهای کانونی در SecurityPatchState: COMPONENT_SYSTEM، COMPONENT_SYSTEM_MODULES، یا COMPONENT_KERNEL. |
زیرسیستم نرمافزار یا سفتافزاری را که این بهروزرسانی هدف قرار میدهد شناسایی میکند. |
securityPatchLevel |
getSecurityPatchLevel() |
SecurityPatchLevel |
باید نمونهای از DateBasedSecurityPatchLevel (YYYY-MM-DD) یا VersionedSecurityPatchLevel (major.minor.patch) باشد، یا بااستفاده از SecurityPatchState.getComponentSecurityPatchLevel() تجزیه شود. |
سطح وصله امنیتی هدف که پساز نصب این بهروزرسانی به آن دست خواهید یافت. |
publishedDateMillis |
getPublishedDateMillis() |
long |
میلیثانیه از تاریخ یونیکس (System.currentTimeMillis()). باید > 0 باشد. |
زمانی که بهروزرسانی برای کاربران دردسترس قرار گرفت، مثلاً زمان انتشار OTA. از زمان بارگیری یا نصب پایهبار استفاده نکنید. |
lastCheckTimeMillis |
getLastCheckTimeMillis() |
long |
میلیثانیه از تاریخ یونیکس. باید > 0 باشد. |
مُهر زمان زمانی که ارائهدهنده شما این گزارش بهروزرسانی را درحین همگامسازی درستیسنجی یا کشف کرده است. |
از بین گزینههای زیر، مدل ارائه را که با معماری بهروزرسان شما مطابقت دارد انتخاب کنید:
گزینه الف: مدل فشار (توصیهشده)
وقتی کارگر همگامسازی پسزمینهای سرور OTA شما را بررسی میکند، تأیید کنید که هر بهروزرسانی کشفشدهای سطح وصله فعلی دستگاه را ارتقا میدهد و آن را بااستفاده از UpdateInfoManager.registerUpdate() حفظ میکند، یا اگر بهروزرسانی امنیتی ارتقادهندهای درانتظار نیست، UpdateInfoManager.unregisterUpdate() را فراخوانی میکند. همیشه در پایان هر همگامسازی (حتی پساز فراخوانی registerUpdate() که سابقه UpdateInfo هر عنصر را حفظ میکند اما مُهر زمان آخرین بررسی سراسری را که به مشتریان برگردانده میشود بهروز نمیکند) UpdateInfoManager.setLastCheckTimeMillis() را فراخوانی کنید. میتوانید این را با
WorkManager CoroutineWorker در Kotlin یا Worker در Java پیادهسازی کنید:
کاتلین
import android.content.Context
import androidx.security.state.SecurityPatchState
import androidx.security.state.SecurityPatchState.DateBasedSecurityPatchLevel
import androidx.security.state.UpdateInfo
import androidx.security.state.provider.UpdateInfoManager
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import kotlin.math.max
class OtaSyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val updateInfoManager = UpdateInfoManager(applicationContext)
val securityPatchState = SecurityPatchState(applicationContext)
val currentSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM)
// 1. Fetch available update metadata from OEM backend
val latestUpdate = MyOtaClient.fetchLatestSystemUpdate()
val targetSplString = latestUpdate?.spl?.trim()
val targetSpl = if (!targetSplString.isNullOrEmpty()) {
DateBasedSecurityPatchLevel.fromString(targetSplString)
} else {
null
}
// 2. Defensively verify that target SPL is non-blank AND strictly newer than installed DSPL.
// If an update is a maintenance patch with no SPL increment (or if no update is available),
// unregister any stale cached record for this component.
if (latestUpdate != null && targetSpl != null && targetSpl > currentSpl) {
val updateInfo = UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.setSecurityPatchLevel(targetSpl)
.setPublishedDateMillis(latestUpdate.releaseTimeMillis)
.setLastCheckTimeMillis(System.currentTimeMillis())
.build()
updateInfoManager.registerUpdate(updateInfo)
} else {
val clearTarget = UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build()
updateInfoManager.unregisterUpdate(clearTarget)
}
// 3. Update global freshness timestamp (monotonic synchronization)
val currentCheckTime = System.currentTimeMillis()
val previousCheckTime = updateInfoManager.getLastCheckTimeMillis()
updateInfoManager.setLastCheckTimeMillis(max(previousCheckTime, currentCheckTime))
return Result.success()
}
}
جاوا
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.security.state.SecurityPatchState;
import androidx.security.state.SecurityPatchState.DateBasedSecurityPatchLevel;
import androidx.security.state.SecurityPatchState.SecurityPatchLevel;
import androidx.security.state.UpdateInfo;
import androidx.security.state.provider.UpdateInfoManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class OtaSyncWorker extends Worker {
public OtaSyncWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public Result doWork() {
// In Java, pass null for customSecurityState because UpdateInfoManager does not declare @JvmOverloads
UpdateInfoManager updateInfoManager =
new UpdateInfoManager(getApplicationContext(), /* customSecurityState= */ null);
SecurityPatchState securityPatchState = new SecurityPatchState(getApplicationContext());
SecurityPatchLevel currentSpl =
securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM);
// 1. Fetch available update metadata from OEM backend
MyOtaUpdate latestUpdate = MyOtaClient.fetchLatestSystemUpdate();
String targetSplString = (latestUpdate != null && latestUpdate.getSpl() != null)
? latestUpdate.getSpl().trim()
: null;
DateBasedSecurityPatchLevel targetSpl =
!TextUtils.isEmpty(targetSplString)
? DateBasedSecurityPatchLevel.fromString(targetSplString)
: null;
// 2. Defensively verify that target SPL is non-blank AND strictly newer than installed DSPL.
// If an update is a maintenance patch with no SPL increment (or if no update is available),
// unregister any stale cached record for this component.
if (latestUpdate != null && targetSpl != null && targetSpl.compareTo(currentSpl) > 0) {
UpdateInfo updateInfo = new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.setSecurityPatchLevel(targetSpl)
.setPublishedDateMillis(latestUpdate.getReleaseTimeMillis())
.setLastCheckTimeMillis(System.currentTimeMillis())
.build();
updateInfoManager.registerUpdate(updateInfo);
} else {
UpdateInfo clearTarget = new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build();
updateInfoManager.unregisterUpdate(clearTarget);
}
// 3. Update global freshness timestamp (monotonic synchronization)
long currentCheckTime = System.currentTimeMillis();
long previousCheckTime = updateInfoManager.getLastCheckTimeMillis();
updateInfoManager.setLastCheckTimeMillis(Math.max(previousCheckTime, currentCheckTime));
return Result.success();
}
}
در مدل مبتنی بر فشار، تکالیف پسزمینه سوابق بهروزرسانی را مستقیماً در
UpdateInfoManager ذخیره میکنند. برای اینکه چارچوب را راهنمایی کنید همیشه گزارشها را از
فضای ذخیرهسازی دیسک محلی ارائه دهد، با گسترش UpdateInfoService در Kotlin یا
ListenableFutureUpdateInfoService در Java، shouldFetchUpdates() را ملغی کنید تا false را برگرداند:
کاتلین
import androidx.security.state.UpdateInfo
import androidx.security.state.provider.UpdateInfoService
class PushUpdateInfoService : UpdateInfoService() {
// Cache is populated out-of-band by background sync tasks
override fun shouldFetchUpdates(): Boolean = false
// Never invoked under normal flow because shouldFetchUpdates() returns false
override suspend fun fetchUpdates(): List<UpdateInfo> = emptyList()
}
جاوا
import androidx.annotation.NonNull;
import androidx.security.state.UpdateInfo;
import androidx.security.state.provider.ListenableFutureUpdateInfoService;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.List;
public class PushUpdateInfoService extends ListenableFutureUpdateInfoService {
@Override
protected boolean shouldFetchUpdates() {
return false;
}
@NonNull
@Override
protected ListenableFuture<List<UpdateInfo>> fetchUpdatesAsync() {
return Futures.immediateFuture(Collections.emptyList());
}
}
گزینه ب: مدل کششی (درصورت نیاز)
در معماری مبتنی بر واکشی، سرویس شما درخواستهای بازآوری درخواستی را که برنامههای کارخواه وقتی حافظه نهان محلی قدیمی میشود راهاندازی میکنند مدیریت میکند.
برای مدیریت پُرسمانهای بهروزرسانی درخواستی، UpdateInfoService را در Kotlin گسترش دهید
(با پیادهسازی تابع تعلیقکننده fetchUpdates()) یا
ListenableFutureUpdateInfoService را در جاوا (با پیادهسازی
fetchUpdatesAsync() که ListenableFuture Guava را برمیگرداند):
کاتلین
package com.example.android.updater
import androidx.security.state.SecurityPatchState
import androidx.security.state.SecurityPatchState.DateBasedSecurityPatchLevel
import androidx.security.state.UpdateInfo
import androidx.security.state.provider.UpdateInfoManager
import androidx.security.state.provider.UpdateInfoService
import java.util.concurrent.TimeUnit
class MyUpdateInfoService : UpdateInfoService() {
// Manage local update records and check timestamps
private val updateInfoManager by lazy { UpdateInfoManager(this) }
override suspend fun fetchUpdates(): List<UpdateInfo> {
val currentSpl = SecurityPatchState(this)
.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM)
// 1. Execute network request to OTA backend
val response = MyOtaBackendClient.checkAvailableUpdates()
// 2. Defensively filter out blank or non-advancing SPLs and map to UpdateInfo objects
val validUpdates = response.updates
.mapNotNull { updateItem ->
val splString = updateItem.targetSpl?.trim()
if (splString.isNullOrEmpty()) return@mapNotNull null
val parsedSpl = DateBasedSecurityPatchLevel.fromString(splString)
if (parsedSpl > currentSpl) {
UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.setSecurityPatchLevel(parsedSpl)
.setPublishedDateMillis(updateItem.releaseTimestampMillis)
.setLastCheckTimeMillis(System.currentTimeMillis())
.build()
} else {
null
}
}
// 3. If no advancing SYSTEM update is available (or if a previously offered update was revoked),
// proactively unregister any cached record for this component.
if (validUpdates.isEmpty()) {
val clearTarget = UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build()
updateInfoManager.unregisterUpdate(clearTarget)
}
return validUpdates
}
override fun shouldFetchUpdates(): Boolean {
// Enforce custom freshness threshold (for example, 4 hours instead of default 1 hour)
val lastCheckMillis = updateInfoManager.getLastCheckTimeMillis()
val dataAge = System.currentTimeMillis() - lastCheckMillis
return dataAge > TimeUnit.HOURS.toMillis(4)
}
}
جاوا
package com.example.android.updater;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.security.state.SecurityPatchState;
import androidx.security.state.SecurityPatchState.DateBasedSecurityPatchLevel;
import androidx.security.state.SecurityPatchState.SecurityPatchLevel;
import androidx.security.state.UpdateInfo;
import androidx.security.state.provider.ListenableFutureUpdateInfoService;
import androidx.security.state.provider.UpdateInfoManager;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class MyUpdateInfoService extends ListenableFutureUpdateInfoService {
private UpdateInfoManager updateInfoManager;
@Override
public void onCreate() {
super.onCreate();
// Pass null for customSecurityState because UpdateInfoManager does not declare @JvmOverloads
updateInfoManager = new UpdateInfoManager(this, /* customSecurityState= */ null);
}
@NonNull
@Override
protected ListenableFuture<List<UpdateInfo>> fetchUpdatesAsync() {
try {
SecurityPatchLevel currentSpl = new SecurityPatchState(this)
.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM);
MyOtaBackendResponse response = MyOtaBackendClient.checkAvailableUpdates();
List<UpdateInfo> updates = new ArrayList<>();
for (MyOtaUpdateItem item : response.getUpdates()) {
String trimmedSpl = (item.getTargetSpl() != null) ? item.getTargetSpl().trim() : null;
if (!TextUtils.isEmpty(trimmedSpl)) {
DateBasedSecurityPatchLevel parsedSpl =
DateBasedSecurityPatchLevel.fromString(trimmedSpl);
if (parsedSpl.compareTo(currentSpl) > 0) {
updates.add(new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.setSecurityPatchLevel(parsedSpl)
.setPublishedDateMillis(item.getReleaseTimestampMillis())
.setLastCheckTimeMillis(System.currentTimeMillis())
.build());
}
}
}
// If no advancing SYSTEM update is available (or if a previously offered update was revoked),
// proactively unregister any cached record for this component.
if (updates.isEmpty()) {
UpdateInfo clearTarget = new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build();
updateInfoManager.unregisterUpdate(clearTarget);
}
return Futures.immediateFuture(updates);
} catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
}
@Override
protected boolean shouldFetchUpdates() {
long lastCheckMillis = updateInfoManager.getLastCheckTimeMillis();
long dataAge = System.currentTimeMillis() - lastCheckMillis;
return dataAge > TimeUnit.HOURS.toMillis(4);
}
}
مرحله ۴: پاک کردن بهروزرسانیهای اعمالشده پساز بازراهاندازی دستگاه
اگرچه UpdateInfoManager بهطور خودکار بهروزرسانیهای منسوخ را هرزمان
registerUpdate() فراخوانی شود حذف میکند، بهروزرسان شما پساز اتمام نصب بهروزرسانی ازطریق OTA تا چرخه همگامسازی سرور زمانبندیشده بعدی،
registerUpdate() را دوباره فراخوانی نخواهد کرد. برای جلوگیری از اینکه برنامههای مشتری بهروزرسانی ازقبل نصبشده را بلافاصله پساز بازراهاندازی همچنان معلقه ببینند، به ACTION_BOOT_COMPLETED گوش دهید و وقتی نصب بهروزرسانی OTA تمام شد، UpdateInfoManager.unregisterUpdate() را فراخوانی کنید تا سابقه از حافظه نهان محلی پاک شود. انجام این کار فقط زمانی که نصب بهروزرسانی تمام شده باشد، از پاک کردن بیقیدوشرط بهروزرسانیهای معلقه (حذف نصبشده) در هر بازراهاندازی عادی دستگاه جلوگیری میکند. چون کلیدهای
UpdateInfoManager گزارشها را براساس
عنصر بهروزرسانی میکنند، هنگام ساختن
شیء UpdateInfo برای لغو ثبت، فقط باید عنصر هدف را مشخص کنید:
کاتلین
// Build target identifying the component to unregister
val target = UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build()
// Unregister the update to remove it from disk cache
updateInfoManager.unregisterUpdate(target)
// Refresh last check timestamp to indicate up-to-date state
updateInfoManager.setLastCheckTimeMillis(System.currentTimeMillis())
جاوا
// Build target identifying the component to unregister
UpdateInfo target = new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build();
// Unregister the update to remove it from disk cache
updateInfoManager.unregisterUpdate(target);
// Refresh last check timestamp to indicate up-to-date state
updateInfoManager.setLastCheckTimeMillis(System.currentTimeMillis());
مرحله ۵: یکپارچهسازی را بهتأیید برسانید
برای اعتبارسنجی یکپارچهسازی سرتاسر و جلوگیری از مشکلات رایج در استقرار «تولیدکننده تجهیزات اصلی» (OEM)، بررسیهای زیر را در دستگاه Android یا شبیهساز بااستفاده از Android Debug Bridge (ADB) اجرا کنید:
تأیید کنید که مشتریان به ارائهدهنده شما اعتماد دارند: برنامههای مشتری هر ارائهدهندهای را که
READ_PRIVILEGED_PHONE_STATEنداشته باشد نادیده میگیرند، حتی اگر ازقبل نصب شده باشد. تأیید کنید که اجازه اعطا شده است:adb shell dumpsys package <your_package_name> | grep "READ_PRIVILEGED_PHONE_STATE: granted=true"سپس تأیید کنید که سرویس شما قابلکشف است و اجازه سرویس ندارد. در برونداد سرویس خود،
exported=trueوpermission=nullرا بررسی کنید:adb shell pm query-services --user 0 -a androidx.security.state.provider.UPDATE_INFO_SERVICEاگر مشتری همچنان ارائهدهنده شما را نمیبیند، logcat را برای
Ignoring untrusted update providerاز برچسبSecurityPatchStateبررسی کنید.درستیسنجی هدف در «کاربر ۰» و «نمایههای کاری»: تأیید کنید که سیستمعامل Android فیلتر هدف صادرشده
PackageManagerشما را در کاربر اصلی (User 0) و هر «نمایه کاری Android Enterprise» فعال (مثلUser 10) حلوفصل میکند:UPDATE_INFO_SERVICEadb shell pm query-services --user 0 -a androidx.security.state.provider.UPDATE_INFO_SERVICE adb shell pm query-services --user 10 -a androidx.security.state.provider.UPDATE_INFO_SERVICEوضعیت خدمات و سوابق ذخیرهشده در حافظه نهان را بااستفاده از
dumpsysدرستیسنجی کنید:UpdateInfoServicedump()را برای گزارش کردنGlobal Last Check،Should Throttle(وضعیت محدودکننده نرخ)، وCached Updatesملغی میکند. چونUpdateInfoServiceسرویس محدود است و کارخواهها بلافاصله پساز پُرسمان کردن لغو محدودیت میکنند،dumpsys activity serviceوقتی هیچ کارخواهی محدود نباشد(nothing)را برونداد میکند. پیشاز اجرایdumpsys، سرویس را بهطور صریح شروع کنید:adb shell am start-service -a androidx.security.state.provider.UPDATE_INFO_SERVICE <your_package_name>/.<service_class_name> adb shell dumpsys activity service <your_package_name>/.<service_class_name>برونداد تشخیص خرابی نمونه:
UpdateInfoService State: Active Requests: 0 Global Last Check: Thu Jan 01 12:00:00 UTC 2026 Should Throttle: false Cached Updates (1): - Component: SYSTEM SPL: 2026-01-01 Published: Thu Jan 01 00:00:00 UTC 2026 Last Checked: Thu Jan 01 12:00:00 UTC 2026راهاندازی اتصال کارخواه و تأیید نتایج تلهمتری: از یک برنامه آزمایشی بدون امتیاز (بدون داشتن اجازههای امضای سیستم)،
SecurityPatchState.queryAllAvailableUpdates()را فراخوانی کنید. اگر بازخوانهای تلهمتری را پیادهسازی کردهاید، موارد زیر را بررسی کنید:- تأیید کنید که مشتری بدون امتیاز ویژه بدون
SecurityExceptionپیوند میدهد وonClientConnected(packageName, callerUid)را راهاندازی میکند. - برای ارائهدهندگان مدل «فشاری» (
shouldFetchUpdates() == false): تأیید کنید کهonRequestCompleted(telemetry)گزارشهایUpdateFetchOutcome.CACHE_HIT(1) را باfetchDurationMillis == 0در هر پُرسمان ثبت میکند. - برای ارائهدهندگان مدل کششی (
shouldFetchUpdates() == true): تأیید کنید کهonRequestCompleted(telemetry)گزارشهایUpdateFetchOutcome.FETCHED(3) در پُرسمان اولیه حافظه نهان قدیمی، و سپسCACHE_HIT(1) در پُرسمانهای بعدی فوری وجود دارد. (برای بازنشانی محدودکننده نرخ ماندگار ۱ ساعته بین اجراهای آزمایشی مدل کششی،adb shell pm clear <your_package_name>را اجرا کنید.)
- تأیید کنید که مشتری بدون امتیاز ویژه بدون
پیکربندیهای اختیاری و پیشرفته
خطمشی ذخیره در حافظه نهان و محدود کردن نرخ
وقتی کارخواهی بهروزرسانیها را پُرسمان میکند، UpdateInfoService گردش کار قفلگذاری با بررسی مضاعف را اجرا میکند تا تازگی دادهها را دربرابر بار سرور زیرینه متعادل کند:

- مسیر سریع (
shouldFetchUpdates()): بهطور پیشفرض،shouldFetchUpdates()فقط زمانیtrue(نشاندهنده حافظه نهان قدیمی) برمیگرداند کهlastCheckTimeMillisجهانی قدیمیتر از ۱ ساعت باشد (TimeUnit.HOURS.toMillis(1)). وقتیshouldFetchUpdates()falseبرمیگرداند، سرویس بلافاصله گزارشهای ذخیرهشده در حافظه نهان را با پیامدUpdateFetchOutcome.CACHE_HITبدون دریافت قفل یا انجام ورودی/خروجی شبکه برمیگرداند. برای سفارشیسازی این خطمشی ذخیره در حافظه نهان، میتوانیدshouldFetchUpdates()را ملغی کنید. - مسیر کند و ادغام درخواست: وقتی
shouldFetchUpdates()برمیگرددtrue، سرویس یک متقابل انحصاری روتین داخلی بهدست میآورد وshouldFetchUpdates()را دوباره ارزیابی میکند (اگر درخواست همزمان درحالیکه منتظر قفل است حافظه نهان را ازقبل بازآوری کرده باشد،UpdateFetchOutcome.COALESCEDرا برمیگرداند). - محدودکننده نرخ ماندگار (
shouldThrottle()): برای محافظت از زیرساخت پشتیبان دربرابر هجوم پُرسمان یا خطاهای مکرر،shouldThrottle()حداقل فاصله ۱ ساعته ماندگاری را در بازراهاندازیهای برنامه و دستگاه اعمال میکند. UpdateInfoServiceهر تلاش را قبلاز فراخوانیfetchUpdates()ثبت میکند، بنابراین اگرfetchUpdates()استثنایی ایجاد کند (UpdateFetchOutcome.FAILEDرا پساز فراخوانیonFetchFailed(e)برمیگرداند)، پُرسمانهای بعدی درطول ۶۰ دقیقه بعدی بهطور مناسب دادههای جایگزین ذخیرهشده در حافظه نهان را با نتیجهUpdateFetchOutcome.THROTTLEDبرمیگرداند.
مشاهدهپذیری، تلهمتری، و تشخیص خرابی
UpdateInfoService قلابهای مشاهدهپذیری داخلی برای پیگیری پذیرش مشتری،
پایش تأخیر IPC، و ثبت خطاهای زیرینه بدون ابزاربندی
تهماندههای AIDL سطح پایین ارائه میدهد:
onRequestCompleted(telemetry): پساز تکمیل هر بررسی بهروزرسانی با خلاصهUpdateCheckTelemetryفراخوانده میشود.onClientConnected(packageName, callerUid): زمانی فراخوانده میشود که یک کارخواه تأییدشده جلسه را باز میکند.onClientDisconnected(packageName, callerUid): زمانی فراخوانده میشود که کارخواه لغو اتصال کند یا فرایند آن خاتمه یابد.onFetchFailed(e): اگر درطولfetchUpdates()، قبلاز اینکه سرویس دادههای جایگزین ذخیرهشده در حافظه نهان را برگرداند، استثنایی رخ دهد، فراخوانده میشود.
این برگشتتماسها را در UpdateInfoService (Kotlin) یا
ListenableFutureUpdateInfoService (Java) ملغی کنید:
کاتلین
import androidx.security.state.provider.UpdateCheckTelemetry
import androidx.security.state.provider.UpdateFetchOutcome
import androidx.security.state.provider.UpdateInfoService
abstract class MonitoredUpdateInfoService : UpdateInfoService() {
override fun onRequestCompleted(telemetry: UpdateCheckTelemetry) {
val outcomeName = when (telemetry.outcome) {
UpdateFetchOutcome.CACHE_HIT -> "CACHE_HIT"
UpdateFetchOutcome.COALESCED -> "COALESCED"
UpdateFetchOutcome.FETCHED -> "FETCHED"
UpdateFetchOutcome.THROTTLED -> "THROTTLED"
UpdateFetchOutcome.FAILED -> "FAILED"
else -> "UNKNOWN"
}
MyAnalytics.logEvent("SECURITY_UPDATE_CHECK")
.addParam("outcome", outcomeName)
.addParam("total_duration_ms", telemetry.totalDurationMillis)
.addParam("lock_wait_ms", telemetry.lockWaitDurationMillis)
.addParam("processing_ms", telemetry.processingDurationMillis)
.addParam("fetch_duration_ms", telemetry.fetchDurationMillis)
.addParam("caller_uid", telemetry.callerUid)
.send()
}
override fun onClientConnected(packageName: String, callerUid: Int) {
// Track authenticated client sessions and adoption
MyMetrics.incrementCounter("client_connected", "package", packageName)
}
override fun onClientDisconnected(packageName: String, callerUid: Int) {
// Track session termination and cleanup resources
MyMetrics.incrementCounter("client_disconnected", "package", packageName)
}
override fun onFetchFailed(e: Exception) {
// Report exceptions caught during the update check workflow
MyCrashReporter.recordException(e)
}
}
جاوا
import androidx.annotation.NonNull;
import androidx.security.state.provider.ListenableFutureUpdateInfoService;
import androidx.security.state.provider.UpdateCheckTelemetry;
import androidx.security.state.provider.UpdateFetchOutcome;
public abstract class MonitoredUpdateInfoService extends ListenableFutureUpdateInfoService {
@Override
protected void onRequestCompleted(@NonNull UpdateCheckTelemetry telemetry) {
String outcomeName;
switch (telemetry.getOutcome()) {
case UpdateFetchOutcome.CACHE_HIT: outcomeName = "CACHE_HIT"; break;
case UpdateFetchOutcome.COALESCED: outcomeName = "COALESCED"; break;
case UpdateFetchOutcome.FETCHED: outcomeName = "FETCHED"; break;
case UpdateFetchOutcome.THROTTLED: outcomeName = "THROTTLED"; break;
case UpdateFetchOutcome.FAILED: outcomeName = "FAILED"; break;
default: outcomeName = "UNKNOWN"; break;
}
MyAnalytics.logEvent("SECURITY_UPDATE_CHECK")
.addParam("outcome", outcomeName)
.addParam("total_duration_ms", telemetry.getTotalDurationMillis())
.addParam("lock_wait_ms", telemetry.getLockWaitDurationMillis())
.addParam("processing_ms", telemetry.getProcessingDurationMillis())
.addParam("fetch_duration_ms", telemetry.getFetchDurationMillis())
.addParam("caller_uid", telemetry.getCallerUid())
.send();
}
@Override
protected void onClientConnected(@NonNull String packageName, int callerUid) {
MyMetrics.incrementCounter("client_connected", "package", packageName);
}
@Override
protected void onClientDisconnected(@NonNull String packageName, int callerUid) {
MyMetrics.incrementCounter("client_disconnected", "package", packageName);
}
@Override
protected void onFetchFailed(@NonNull Exception e) {
MyCrashReporter.recordException(e);
}
}
نتایج دورسنجی و سنجههای تأخیر
UpdateCheckTelemetry مدتهای سپریشده یکنواخت را اندازهگیری میکند
(SystemClock.elapsedRealtime()) و یکی از پنج پیامد تعریفشده در UpdateFetchOutcome را گزارش میکند:
| ثابت پیامد | @IntDef کد |
گزارش ویژگیهای سنجه | شرح و وضعیت سیستم |
|---|---|---|---|
UpdateFetchOutcome.CACHE_HIT |
1 |
totalDurationMillis، processingDurationMillis، callerUid |
بلافاصله از حافظه نهان/دیسک محلی در «مسیر سریع» ارائه شد (shouldFetchUpdates() برگشت false). lockWaitDurationMillis و fetchDurationMillis 0 هستند. |
UpdateFetchOutcome.COALESCED |
2 |
totalDurationMillis، lockWaitDurationMillis، processingDurationMillis، callerUid |
پُرسمان در صف بازآوری فعال دیگری قرار گرفت؛ پساز دریافت قفل، دادهها تازه بود. از واکشی شبکه تکراری (fetchDurationMillis 0 است) اجتناب شد. |
UpdateFetchOutcome.FETCHED |
3 |
totalDurationMillis، lockWaitDurationMillis، processingDurationMillis، fetchDurationMillis، callerUid |
همگامسازی شبکه زیرینه باموفقیت اجرا شد (fetchUpdates() تکمیل شد). گزارشهای جدید در دیسک ماندگار شد. |
UpdateFetchOutcome.THROTTLED |
4 |
totalDurationMillis، lockWaitDurationMillis، processingDurationMillis، callerUid |
درخواست توسط محدودکننده نرخ مسدود شد (shouldThrottle() برگردانده شد true). دادههای ذخیرهشده در حافظه نهان بهطور ایمن به کارخواه برگردانده شد (fetchDurationMillis 0 است). |
UpdateFetchOutcome.FAILED |
5 |
totalDurationMillis، lockWaitDurationMillis، processingDurationMillis، fetchDurationMillis، callerUid |
بررسی بهروزرسانی یا درخواست شبکه استثنایی ایجاد کرد. دیوار آتش استثنا آن را گرفته است، onFetchFailed(e) را راهاندازی کرده است، و برگشتپذیر حافظه نهان را برگردانده است. |
قلاب کارگزار سرویس پیشرفته: getCallerUid()
وقتی مشتری متصل میشود، UpdateInfoService بهطور خودکار
getCallerUid() را در رشته اولیه «پوشه» (قبلاز تماس با
Binder.clearCallingIdentity() قبلاز fetchUpdates()) ارزیابی میکند،
مالکیت بسته را درستیسنجی میکند، و «شناسه کاربر» درستیسنجیشده تماسگیرنده را مستقیماً به
onClientConnected()، onClientDisconnected()، و telemetry.callerUid در
onRequestCompleted(telemetry) ارسال میکند.
برای عناصر استاندارد Android <service>، نیازی به فراخوانی یا ملغی کردن
getCallerUid() ندارید. روش protected open getCallerUid() (که بهطور پیشفرض به
Binder.getCallingUid() واگذار میکند) بهعنوان قلاب ملغی برای
برنامههای میزبان که Binder IPC را ازطریق کارگزار سرویس داخلی یا
معماری پراکسی مسیریابی میکنند ارائه میشود و به زیرکلاس امکان میدهد
بهجای UID کارگزار، UID منطقی کارخواه را برگرداند.
منابع بیشتر
برای کسب اطلاعات بیشتر درباره انتشار وضعیت امنیت، منابع زیر را ببینید:
اسناد
- درک وضعیت امنیت دستگاه
- اعلامیههای امنیتی Android
- اجزای سیستم مدولار
- وصلههای امنیتی تکمیلی
- یادداشتهای انتشار «ارائهدهنده وضعیت امنیت» نسخه ۱.۰.۰
مرجع میانای برنامهسازی کاربردی
UpdateInfoServiceListenableFutureUpdateInfoServiceUpdateInfoManagerUpdateInfoUpdateInfo.BuilderUpdateCheckTelemetryUpdateFetchOutcomeSecurityPatchState