عند استخدام binder للتواصل بين العمليات، يجب توخي الحذر بشكل خاص عندما تكون العملية البعيدة في حالة مخزّنة مؤقتًا أو مجمّدة. يمكن أن تؤدي عمليات استدعاء التطبيقات المخزّنة مؤقتًا أو المجمّدة إلى تعطّلها أو استهلاكها للموارد بدون داعٍ.
حالات التطبيقات المخزّنة مؤقتًا والمجمّدة
يحافظ Android على التطبيقات في حالات مختلفة لإدارة موارد النظام، مثل الذاكرة ووحدة المعالجة المركزية.
الحالة المخزّنة مؤقتًا
عندما لا يتضمّن التطبيق أي مكوّنات مرئية للمستخدم، مثل الأنشطة أو الخدمات، يمكن نقله إلى الحالة المخزّنة مؤقتًا. لمعرفة التفاصيل، يُرجى الاطّلاع على مقالة دورة حياة العمليات والتطبيقات لـ التفاصيل. يتم الاحتفاظ بالتطبيقات المخزّنة مؤقتًا في الذاكرة في حال عودة المستخدم إليها، ولكن من غير المتوقّع أن تعمل بنشاط.
عند الربط من عملية تطبيق إلى أخرى، مثل استخدام bindService،
يتم رفع حالة عملية الخادم لتكون على الأقل بنفس أهمية
عملية العميل (باستثناء الحالة التي يتم فيها تحديد Context#BIND_WAIVE_PRIORITY يتم
تحديدها). على سبيل المثال، إذا لم يكن العميل في الحالة المخزّنة مؤقتًا، لن يكون الخادم كذلك.
في المقابل، لا تحدّد حالة عملية الخادم حالة عملائه. لذلك، قد يكون للخادم اتصالات binder بالعملاء، وعادةً ما تكون على شكل دوال رد نداء، وفي حين أنّ العملية البعيدة تكون في الحالة المخزّنة مؤقتًا، لا يتم تخزين الخادم مؤقتًا.
عند تصميم واجهات برمجة التطبيقات التي تنشأ فيها دوال رد النداء في عملية مرتفعة ويتم تسليمها إلى التطبيقات، ننصحك بإيقاف إرسال دوال رد النداء مؤقتًا عندما يدخل التطبيق في الحالة المخزّنة مؤقتًا، واستئنافها عند خروجه من هذه الحالة. يمنع ذلك العمل غير الضروري في عمليات التطبيقات المخزّنة مؤقتًا.
لتتبُّع الحالات التي يدخل فيها التطبيق إلى الحالة المخزّنة مؤقتًا أو يخرج منها، استخدِم ActivityManager.addOnUidImportanceListener:
Java
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new ActivityManager.OnUidImportanceListener() { ... },
IMPORTANCE_CACHED);
Kotlin
// in ActivityManager or Context
activityManager.addOnUidImportanceListener({ uid, importance ->
// ...
}, IMPORTANCE_CACHED)
الحالة المجمّدة
يمكن للنظام تجميد تطبيق مخزّن مؤقتًا للحفاظ على الموارد. عند تجميد تطبيق، لا يتلقّى أي وقت لوحدة المعالجة المركزية ولا يمكنه تنفيذ أي عمل. لمعرفة مزيد من التفاصيل، يُرجى الاطّلاع على مقالة مجمّد التطبيقات المخزّنة مؤقتًا.
عندما ترسل عملية معاملة binder متزامنة (ليست oneway) إلى عملية بعيدة أخرى مجمّدة، يوقف النظام العملية البعيدة. يمنع ذلك تعليق سلسلة التعليمات التي تستدعي في العملية التي تستدعي إلى أجل غير مسمى أثناء انتظار إلغاء تجميد العملية البعيدة، ما قد يؤدي إلى نقص سلسلة التعليمات أو حالات التوقف التام في التطبيق الذي يستدعي.
عندما ترسل عملية معاملة binder غير متزامنة (oneway) إلى تطبيق مجمّد (عادةً من خلال إشعار دالة رد نداء، وهي عادةً طريقة oneway)، يتم تخزين المعاملة مؤقتًا إلى أن يتم إلغاء تجميد العملية البعيدة. إذا امتلأت المخزن المؤقت، يمكن أن تتعطّل عملية التطبيق المستلِم. بالإضافة إلى ذلك، قد تصبح المعاملات المخزّنة مؤقتًا قديمة بحلول الوقت الذي يتم فيه إلغاء تجميد عملية التطبيق ومعالجتها.
لتجنُّب إرهاق التطبيقات بالأحداث القديمة أو تجاوز سعة المخازن المؤقتة، يجب إيقاف إرسال دوال رد النداء مؤقتًا أثناء تجميد عملية التطبيق المستلِم.
لتتبُّع الحالات التي يتم فيها تجميد التطبيقات أو إلغاء تجميدها، استخدِم
IBinder.addFrozenStateChangeCallback:
Java
// 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
}
Kotlin
// 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 دوال رد نداء التي تسجّلها العمليات البعيدة. تتعامل هذه الفئة تلقائيًا مع إشعارات انتهاء صلاحية binder وتوفّر خيارات للتعامل مع دوال رد النداء للتطبيقات المجمّدة.
عند إنشاء RemoteCallbackList، يمكنك تحديد سياسة المتلقّي المجمّد:
FROZEN_CALLEE_POLICY_DROP: يتم تجاهل دوال رد النداء للتطبيقات المجمّدة بدون إشعار. استخدِم هذه السياسة عندما لا تكون الأحداث التي وقعت أثناء تخزين التطبيق مؤقتًا مهمة للتطبيق، على سبيل المثال، أحداث أجهزة الاستشعار في الوقت الفعلي.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: إذا تم بثّ عدة دوال رد نداء أثناء تجميد تطبيق، يتم وضع أحدثها فقط في قائمة الانتظار وتسليمها عند إلغاء تجميد التطبيق. يكون ذلك مفيدًا لدوال رد النداء المستندة إلى الحالة حيث لا يهم إلا آخر تعديل للحالة، على سبيل المثال، دالة رد نداء تُعلم التطبيق بمستوى صوت الوسائط الحالي.FROZEN_CALLEE_POLICY_ENQUEUE_ALL: يتم وضع جميع دوال رد النداء التي يتم بثّها أثناء تجميد تطبيق في قائمة الانتظار وتسليمها عند إلغاء تجميد التطبيق. يجب توخي الحذر عند استخدام هذه السياسة، لأنّها قد تؤدي إلى تجاوز سعة المخزن المؤقت إذا تم وضع عدد كبير جدًا من دوال رد النداء في قائمة الانتظار، أو إلى تراكم الأحداث القديمة.
يوضّح المثال التالي كيفية إنشاء واستخدام مثيل RemoteCallbackList يتجاهل دوال رد النداء للتطبيقات المجمّدة:
Java
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));
Kotlin
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. بما أنّ التطبيقات يمكن أن تدخل في حالات مخزّنة مؤقتًا ومجمّدة، يجب أن تتخذ خدمات النظام عناية خاصة للتعامل مع هذه التفاعلات بسلاسة للمساعدة في الحفاظ على استقرار النظام وأدائه.
تحتاج خدمات النظام إلى التعامل مع الحالات التي يتم فيها إيقاف عمليات التطبيقات لأسباب مختلفة. ويشمل ذلك إيقاف العمل نيابةً عنها وعدم محاولة مواصلة تسليم دوال رد النداء إلى العمليات التي تم إيقافها. إنّ مراعاة تجميد التطبيقات هو امتداد لمسؤولية المراقبة الحالية هذه.
تتبُّع حالات التطبيقات من خدمات النظام
يمكن لخدمات النظام، التي يتم تشغيلها في system_server أو كبرامج ديمون أصلية، استخدام واجهات برمجة التطبيقات الموضّحة سابقًا لتتبُّع أهمية عمليات التطبيقات وحالتها المجمّدة:
ActivityManager.addOnUidImportanceListener: يمكن لخدمات النظام تسجيل مستمع لتتبُّع التغييرات في أهمية رقم تعريف المستخدم. عند تلقّي استدعاء binder أو دالة رد نداء من تطبيق، يمكن للخدمة استخدامBinder.getCallingUid()للحصول على رقم تعريف المستخدم وربطه بحالة الأهمية التي يتتبّعها المستمع. يتيح ذلك لخدمات النظام معرفة ما إذا كان التطبيق الذي يستدعي في حالة مخزّنة مؤقتًا.IBinder.addFrozenStateChangeCallback: عندما تتلقّى خدمة تابعة لنظام التشغيل عنصر binder من تطبيق (على سبيل المثال، كجزء من عملية تسجيل لدوال رد النداء)، يجب أن تسجّلFrozenStateChangeCallbackعلى مثيلIBinderالمحدّد هذا. يُعلم ذلك خدمة النظام مباشرةً عندما تصبح عملية التطبيق التي تستضيف binder مجمّدة أو يتم إلغاء تجميدها.
اقتراحات لخدمات النظام
ننصح جميع خدمات النظام التي قد تتفاعل مع التطبيقات بتتبُّع الحالة المخزّنة مؤقتًا والمجمّدة لعمليات التطبيقات التي تتواصل معها. قد يؤدي عدم إجراء ذلك إلى ما يلي:
- استهلاك الموارد: يمكن أن يؤدي تنفيذ العمل للتطبيقات المخزّنة مؤقتًا وغير المرئية للمستخدم إلى إهدار موارد النظام.
- تعطُّل التطبيقات: تؤدي استدعاءات binder المتزامنة للتطبيقات المجمّدة إلى تعطّلها. تؤدي استدعاءات binder غير المتزامنة للتطبيقات المجمّدة إلى تعطّلها إذا تجاوزت سعة المخزن المؤقت للمعاملات غير المتزامنة.
- سلوك غير متوقّع للتطبيق: تتلقّى التطبيقات التي تم إلغاء تجميدها على الفور أي معاملات binder غير متزامنة مخزّنة مؤقتًا تم إرسالها إليها أثناء تجميدها. يمكن أن تقضي التطبيقات فترة غير محدّدة في المجمّد، لذا يمكن أن تكون المعاملات المخزّنة مؤقتًا قديمة جدًا.
تستخدم خدمات النظام غالبًا RemoteCallbackList لإدارة دوال رد النداء البعيدة والتعامل تلقائيًا مع العمليات التي تم إيقافها. للتعامل مع التطبيقات المجمّدة، يمكنك توسيع نطاق الاستخدام الحالي لـ RemoteCallbackList من خلال تطبيق سياسة المتلقّي المجمّد كما هو موضّح في استخدام RemoteCallbackList.