هنگام استفاده از binder برای برقراری ارتباط بین فرایندها، وقتی فرایند راه دور در حالت حافظه نهان یا ثابت است، دقت ویژهای داشته باشید. فراخوانی برنامههای ذخیرهشده در حافظه پنهان یا منجمدشده میتواند باعث ازکارافتادن آنها یا مصرف غیرضروری منابع شود.
وضعیتهای برنامه ذخیرهشده در حافظه نهان و ثابت
Android برنامهها را در حالتهای مختلف نگه میدارد تا منابع سیستم مانند حافظه و CPU را مدیریت کند.
وضعیت ذخیرهشده در حافظه پنهان
وقتی برنامهای مؤلفههای قابلمشاهده برای کاربر (مثل فعالیتها یا سرویسها) نداشته باشد، میتواند به وضعیت ذخیرهشده در حافظه نهان منتقل شود. برای جزئیات، به فرایندها و چرخه حیات برنامه مراجعه کنید. برنامههای ذخیرهشده در حافظه نهان در حافظه نگهداری میشوند تا درصورت بازگشت کاربر به آنها، اما انتظار نمیرود که بهطور فعال کار کنند.
هنگام اتصال از یک فرایند برنامه به فرایند دیگر، مثلاً بااستفاده از bindService،
وضعیت فرایند سرور به حداقل اهمیت فرایند مشتری ارتقا مییابد (بهجز زمانی که Context#BIND_WAIVE_PRIORITY مشخص شده باشد). برای مثال، اگر کارخواه در وضعیت حافظه نهان نباشد، سرور هم در این وضعیت نیست.
برعکس، وضعیت فرایند سرور وضعیت کارخواههای آن را تعیین نمیکند. بنابراین ممکن است سرور اتصالهای binder به کارخواهها داشته باشد، که معمولاً بهصورت فراخوانیهای برگشتی است، و درحالیکه فرایند ازراهدور در وضعیت ذخیرهشده در حافظه نهان است، سرور در حافظه نهان ذخیره نمیشود.
هنگام طراحی میاناهای برنامهسازی کاربردی که در آنها فراخوانیهای برگشتی از فرایندی با سطح دسترسی بالا منشأ میگیرند و به برنامهها تحویل داده میشوند، توقف موقت ارسال فراخوانیهای برگشتی را هنگام ورود برنامه به حالت ذخیرهشده در حافظه پنهان و ازسرگیری آن را هنگام خروج از این حالت درنظر بگیرید. این کار از کار غیرضروری در فرایندهای برنامه ذخیرهشده در حافظه نهان جلوگیری میکند.
برای ردیابی زمان ورود یا خروج برنامهها از حالت ذخیرهشده در حافظه پنهان، از
ActivityManager.addOnUidImportanceListener استفاده کنید:
جاوا
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new ActivityManager.OnUidImportanceListener() { ... },
IMPORTANCE_CACHED);
کاتلین
// in ActivityManager or Context
activityManager.addOnUidImportanceListener({ uid, importance ->
// ...
}, IMPORTANCE_CACHED)
وضعیت معلق
سیستم میتواند برنامه ذخیرهشده در حافظه نهان را برای صرفهجویی در منابع منجمد کند. وقتی برنامهای منجمد میشود، زمان CPU صفر دریافت میکند و نمیتواند کاری انجام دهد. برای جزئیات بیشتر، فریز کردن برنامههای حافظه نهان را ببینید.
وقتی فرایندی تراکنش همگام (نه oneway) binder را به فرایند راهدور دیگری که منجمد شده است ارسال میکند، سیستم فرایند راهدور را متوقف میکند. این کار باعث میشود رشته تماس در فرایند تماس هنگام انتظار برای رفع انسداد فرایند از دور برای همیشه معلق نماند، که میتواند باعث گرسنگی رشته یا بنبست در برنامه تماس شود.
وقتی فرایندی تراکنش چسباننده ناهمزمان (oneway) را به برنامه منجمدشده
ارسال میکند (معمولاً با مطلع کردن یک تابع برگشتی که معمولاً روش oneway است)،
تراکنش تا زمانی که فرایند راه دور از انجماد خارج شود در بافر قرار میگیرد. اگر میانگیر
سرریز شود، فرایند برنامه گیرنده ممکن است ازکار بیفتد. علاوهبراین، تراکنشهای
بافرشده ممکن است تا زمانی که فرایند برنامه از حالت انجماد خارج شود و
آنها را پردازش کند، قدیمی شوند.
برای جلوگیری از غرق شدن برنامهها در رویدادهای قدیمی یا سرریز شدن بافرهای آنها، باید ارسال پاسخهای تماس را درحالیکه فرایند برنامه گیرنده منجمد است موقتاً متوقف کنید.
برای پیگیری زمان ثابت شدن یا رفع انجماد برنامهها، از
IBinder.addFrozenStateChangeCallback استفاده کنید:
جاوا
// The binder token of the remote process
IBinder binder = service.getBinder();
// Keep track of frozen state
AtomicBoolean remoteFrozen = new AtomicBoolean(false);
// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(
myExecutor,
new IBinder.FrozenStateChangeCallback() {
@Override
public void onFrozenStateChanged(boolean isFrozen) {
remoteFrozen.set(isFrozen);
}
});
// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
// dispatch callback to remote process
}
کاتلین
// The binder token of the remote process
val binder: IBinder = service.getBinder()
// Keep track of frozen state
val remoteFrozen = AtomicBoolean(false)
// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(myExecutor) { isFrozen ->
remoteFrozen.set(isFrozen)
}
// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
// dispatch callback to remote process
}
C++
برای کد پلاتفرم بااستفاده از میاناهای برنامهسازی کاربردی binder در C++:
#include <binder/Binder.h>
#include <binder/IBinder.h>
// The binder token of the remote process
android::sp<android::IBinder> binder = service->getBinder();
// Keep track of frozen state
std::atomic<bool> remoteFrozen = false;
// Define a callback class
class MyFrozenStateCallback : public android::IBinder::FrozenStateChangeCallback {
public:
explicit MyFrozenStateCallback(std::atomic<bool>* frozenState) : mFrozenState(frozenState) {}
void onFrozenStateChanged(bool isFrozen) override {
mFrozenState->store(isFrozen);
}
private:
std::atomic<bool>* mFrozenState;
};
// Update remoteFrozen when the remote process freezes or unfreezes
if (binder != nullptr) {
binder->addFrozenStateChangeCallback(android::sp<android::IBinder::FrozenStateChangeCallback>::make(
new MyFrozenStateCallback(&remoteFrozen)));
}
// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.load()) {
// dispatch callback to remote process
}
استفاده از RemoteCallbackList
کلاس RemoteCallbackList دستیاری برای مدیریت فهرستهای
IInterface کاربردهای برگشتی است که فرایندهای دور ثبت میکنند. این کلاس بهطور خودکار
اعلانهای مرگ بایندر را مدیریت میکند و گزینههایی برای مدیریت بازخوانیها
به برنامههای منجمدشده ارائه میدهد.
هنگام ساختن RemoteCallbackList، میتوانید خطمشی فراخواندهشده منجمد را مشخص کنید:
FROZEN_CALLEE_POLICY_DROP: فراخوانیهای برگشتی به برنامههای ثابتشده بدون اطلاع حذف میشوند. وقتی رویدادهایی که در زمان ذخیره شدن برنامه در حافظه نهان رخ دادهاند برای برنامه مهم نیستند، برای مثال، رویدادهای حسگر همزمان، از این خطمشی استفاده کنید.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: اگر چندین فراخوانی برگشتی درحالیکه برنامه ثابت شده است همهفرستی شود، فقط جدیدترین فراخوانی برگشتی در صف قرار میگیرد و وقتی برنامه از حالت ثابت خارج شد تحویل داده میشود. این برای کاربردهای برگشتی مبتنی بر وضعیت مفید است که در آنها فقط جدیدترین بهروزرسانی وضعیت اهمیت دارد، برای مثال، کاربرد برگشتی که برنامه را از میزان صدای رسانه فعلی مطلع میکند.-
FROZEN_CALLEE_POLICY_ENQUEUE_ALL: همه تماسهای برگشتی که درحالیکه برنامه منجمد است همهفرستی میشوند در صف قرار میگیرند و وقتی برنامه از انجماد خارج شد تحویل داده میشوند. مراقب این خطمشی باشید، زیرا اگر تعداد زیادی فراخوانی برگشتی در صف قرار گیرد، میتواند منجر به سرریز میانگیر یا انباشته شدن رویدادهای قدیمی شود.
مثال زیر نحوه ایجاد و استفاده از RemoteCallbackList
نمونهای را نشان میدهد که کاربران برگشتی را به برنامههای منجمدشده حذف میکند:
جاوا
RemoteCallbackList<IMyCallbackInterface> callbacks =
new RemoteCallbackList.Builder<IMyCallbackInterface>(
RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
.setExecutor(myExecutor)
.build();
// Registering a callback:
callbacks.register(callback);
// Broadcasting to all registered callbacks:
callbacks.broadcast((callback) -> callback.onSomeEvent(eventData));
کاتلین
val callbacks =
RemoteCallbackList.Builder<IMyCallbackInterface>(
RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP
)
.setExecutor(myExecutor)
.build()
// Registering a callback:
callbacks.register(callback)
// Broadcasting to all registered callbacks:
callbacks.broadcast { callback -> callback.onSomeEvent(eventData) }
اگر از FROZEN_CALLEE_POLICY_DROP استفاده میکنید، سیستم تنها درصورتی callback.onSomeEvent() را فرا میخواند که فرایند میزبانی تماس برگشتی منجمد نشده باشد.
خدمات سیستم و تعاملات برنامه
سرویسهای سیستم اغلب با برنامههای مختلفی بااستفاده از binder تعامل دارند. Because apps can enter cached and frozen states, system services must take special care to handle these interactions gracefully to help maintain system stability and performance.
خدمات سیستم ازقبل باید موقعیتهایی را که در آن فرایندهای برنامه بهدلایل مختلف متوقف میشوند مدیریت کنند. این کار شامل توقف کار ازطرف آنها و تلاش نکردن برای ادامه ارائه تماسهای برگشتی به فرایندهای مرده است. درنظر گرفتن برنامههایی که مسدود میشوند، گسترش این مسئولیت نظارتی موجود است.
ردیابی وضعیتهای برنامه از خدمات سیستم
خدمات سیستم که در system_server یا بهعنوان دیمونهای بومی اجرا میشوند نیز میتوانند از
میاناهای برنامهسازی کاربردی که قبلاً توضیح داده شد برای پیگیری اهمیت و وضعیت منجمد برنامههای
دردست پردازش استفاده کنند:
ActivityManager.addOnUidImportanceListener: خدمات سیستم میتوانند شنوندهای را برای پیگیری تغییرات اهمیت UID ثبت کنند. هنگام دریافت تماس پیونددهنده یا تماس برگشتی از برنامه، سرویس میتواند ازBinder.getCallingUid()برای دریافت UID و مرتبط کردن آن با وضعیت اهمیت ردیابیشده توسط شنونده استفاده کند. این کار به خدمات سیستم اطلاع میدهد که آیا برنامه برقراری تماس در حالت ذخیرهشده در حافظه نهان است یا نه.IBinder.addFrozenStateChangeCallback: وقتی سرویس سیستمی شیء پیونددهنده را از برنامهای دریافت میکند (برای مثال، بهعنوان بخشی از ثبتنام برای تماسهای برگشتی)، بایدFrozenStateChangeCallbackرا در آن نمونهIBinderخاص ثبت کند. وقتی پردازش برنامه میزبان آن چسبنده منجمد یا رفع انجماد میشود، این کار مستقیماً به سرویس سیستم اطلاع میدهد.
توصیههایی برای خدمات سیستم
توصیه میکنیم همه سرویسهای سیستم که ممکن است با برنامهها تعامل داشته باشند، وضعیت ذخیرهشده در حافظه نهان و منجمد فرایندهای برنامهای را که با آن ارتباط برقرار میکنند ردیابی کنند. عدم انجام این کار میتواند منجر به موارد زیر شود:
- مصرف منابع: انجام کار برای برنامههایی که در حافظه نهان هستند و برای کاربر قابلمشاهده نیستند میتواند منابع سیستم را هدر دهد.
- ازکارافتادن برنامه: فراخوانیهای همگامساز به برنامههای متوقفشده باعث ازکارافتادن آنها میشود. اگر بافر تراکنش ناهمگام برنامههای مسدودشده سرریز شود، فراخوانیهای ناهمگام binder به خرابی منجر میشود.
- رفتار غیرمنتظره برنامه: برنامههای رفع انجمادشده بلافاصله هرگونه تراکنش پیونددهنده ناهمزمان بافرشدهای را که در زمان انجمادشان برای آنها ارسال شده است دریافت میکنند. برنامهها میتوانند مدت نامحدودی را در حالت فریز بگذرانند، بنابراین تراکنشهای میانگیرشده میتوانند بسیار قدیمی باشند.
سرویسهای سیستم اغلب از RemoteCallbackList برای مدیریت کردن تماسهای برگشتی از دور
و مدیریت خودکار فرایندهای مرده استفاده میکنند. برای مدیریت برنامههای مسدودشده، با اعمال خطمشی تماسگیرنده مسدودشده،
استفاده موجود از RemoteCallbackList را گسترش دهید
همانطور که در استفاده از RemoteCallbackList توضیح داده شده است.