प्रोसेस के बीच कम्यूनिकेट करने के लिए बाइंडर का इस्तेमाल करते समय, इस बात का खास ध्यान रखें कि रिमोट प्रोसेस, कैश मेमोरी में सेव की गई हो या फ़्रीज़ की गई हो. कैश किए गए या फ़्रीज़ किए गए ऐप्लिकेशन में कॉल करने से, वे क्रैश हो सकते हैं या ज़रूरत से ज़्यादा संसाधनों का इस्तेमाल कर सकते हैं.
ऐप्लिकेशन की कैश मेमोरी में सेव की गई और फ़्रीज़ की गई स्थितियां
Android, ऐप्लिकेशन को अलग-अलग स्थितियों में रखता है, ताकि मेमोरी और सीपीयू जैसे सिस्टम संसाधनों को मैनेज किया जा सके.
कैश की गई स्थिति
जब किसी ऐप्लिकेशन में उपयोगकर्ता को दिखने वाले कॉम्पोनेंट नहीं होते हैं, जैसे कि गतिविधियां या सेवाएं, तो उसे कैश मेमोरी में सेव किया जा सकता है. ज़्यादा जानकारी के लिए, प्रोसेस और ऐप्लिकेशन का लाइफ़साइकल देखें. कैश किए गए ऐप्लिकेशन को मेमोरी में सेव रखा जाता है, ताकि उपयोगकर्ता उनके बीच स्विच कर सके. हालांकि, ये ऐप्लिकेशन चालू नहीं होते.
जब एक ऐप्लिकेशन प्रोसेस को दूसरी प्रोसेस से बाइंड किया जाता है, जैसे कि bindService का इस्तेमाल करना, तो सर्वर प्रोसेस की प्रोसेस स्टेट को कम से कम उतना ही ज़रूरी बना दिया जाता है जितना कि क्लाइंट प्रोसेस (सिर्फ़ तब, जब Context#BIND_WAIVE_PRIORITY को तय किया गया हो). उदाहरण के लिए, अगर क्लाइंट कैश मेमोरी में सेव नहीं है, तो सर्वर भी कैश मेमोरी में सेव नहीं होगा.
इसके उलट, सर्वर प्रोसेस की स्थिति से यह तय नहीं होता कि उसके क्लाइंट की स्थिति क्या है. इसलिए, किसी सर्वर के पास क्लाइंट के साथ बाइंडर कनेक्शन हो सकते हैं. ये कनेक्शन, आम तौर पर कॉलबैक के तौर पर होते हैं. साथ ही, रिमोट प्रोसेस के कैश मेमोरी में मौजूद होने के दौरान, सर्वर को कैश मेमोरी में नहीं रखा जाता है.
ऐसे एपीआई डिज़ाइन करते समय जहां कॉलबैक, एलिवेटेड प्रोसेस से शुरू होते हैं और ऐप्लिकेशन को डिलीवर किए जाते हैं, तब कॉलबैक को डिस्पैच करने की प्रोसेस को रोक दें, जब कोई ऐप्लिकेशन कैश मेमोरी में सेव की गई स्थिति में आ जाता है. साथ ही, जब वह इस स्थिति से बाहर निकल जाता है, तब इस प्रोसेस को फिर से शुरू कर दें. इससे, कैश की गई ऐप्लिकेशन प्रोसेस में गैर-ज़रूरी काम नहीं होता.
ऐप्लिकेशन के कैश मेमोरी में सेव होने या उससे बाहर निकलने की स्थिति को ट्रैक करने के लिए, 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)
खाता लॉक कर दिया गया है
सिस्टम, कैश मेमोरी में सेव किए गए ऐप्लिकेशन को फ़्रीज़ कर सकता है, ताकि संसाधनों को बचाया जा सके. किसी ऐप्लिकेशन को फ़्रीज़ करने पर, उसे सीपीयू का कोई समय नहीं मिलता. साथ ही, वह कोई काम नहीं कर पाता. ज़्यादा जानकारी के लिए, कैश किए गए ऐप्लिकेशन फ़्रीज़र देखें.
जब कोई प्रोसेस, फ़्रीज़ की गई किसी दूसरी रिमोट प्रोसेस को सिंक्रोनस (oneway नहीं) बाइंडर ट्रांज़ैक्शन भेजती है, तो सिस्टम उस रिमोट प्रोसेस को बंद कर देता है. इससे कॉल करने की प्रोसेस में, कॉल करने वाले थ्रेड को अनिश्चित काल के लिए हैंग होने से रोका जाता है. ऐसा तब होता है, जब रिमोट प्रोसेस के अनफ़्रीज़ होने का इंतज़ार किया जा रहा हो. इससे कॉल करने वाले ऐप्लिकेशन में थ्रेड स्टार्वेशन या डेडलॉक हो सकते हैं.
जब कोई प्रोसेस, फ़्रीज़ किए गए ऐप्लिकेशन को एसिंक्रोनस (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++
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
इंस्टेंस को बनाने और इस्तेमाल करने का तरीका बताया गया है जो फ़्रीज़ किए गए ऐप्लिकेशन को कॉलबैक भेजता है:
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() को सिर्फ़ तब शुरू करता है, जब कॉलबैक को होस्ट करने वाली प्रोसेस फ़्रीज़ न हो.
सिस्टम सेवाएं और ऐप्लिकेशन के साथ इंटरैक्शन
सिस्टम सेवाएँ, अक्सर बाइंडर का इस्तेमाल करके कई अलग-अलग ऐप्लिकेशन से इंटरैक्ट करती हैं. ऐप्लिकेशन, कैश मेमोरी में सेव किए गए और फ़्रीज़ किए गए स्टेटस में जा सकते हैं. इसलिए, सिस्टम सेवाओं को इन इंटरैक्शन को आसानी से हैंडल करने के लिए, खास ध्यान रखना चाहिए. इससे सिस्टम की स्थिरता और परफ़ॉर्मेंस को बनाए रखने में मदद मिलती है.
सिस्टम सेवाओं को पहले से ही ऐसी स्थितियों को मैनेज करना होता है जहां अलग-अलग वजहों से ऐप्लिकेशन प्रोसेस बंद हो जाती हैं. इसमें उनकी ओर से काम करना बंद करना और बंद हो चुकी प्रोसेस को कॉलबैक डिलीवर करने की कोशिश न करना शामिल है. ऐप्लिकेशन को फ़्रीज़ करने के बारे में सोचना, निगरानी करने की मौजूदा ज़िम्मेदारी का ही एक हिस्सा है.
सिस्टम सेवाओं से ऐप्लिकेशन की स्थितियों को ट्रैक करना
system_server में या नेटिव डेमॉन के तौर पर चल रही सिस्टम सेवाएं भी, पहले बताए गए एपीआई का इस्तेमाल कर सकती हैं. इससे, उन्हें ऐप्लिकेशन प्रोसेस की अहमियत और फ़्रीज़ की गई स्थिति को ट्रैक करने में मदद मिलती है:
ActivityManager.addOnUidImportanceListener: सिस्टम सेवाएं, यूआईडी की अहमियत में होने वाले बदलावों को ट्रैक करने के लिए, लिसनर को रजिस्टर कर सकती हैं. किसी ऐप्लिकेशन से बाइंडर कॉल या कॉलबैक पाने पर, सेवाBinder.getCallingUid()का इस्तेमाल करके यूआईडी पा सकती है. साथ ही, इसे लिसनर की ओर से ट्रैक की गई अहमियत की स्थिति से जोड़ सकती है. इससे सिस्टम सेवाओं को यह पता चलता है कि कॉल करने वाला ऐप्लिकेशन, कैश मेमोरी में सेव है या नहीं.IBinder.addFrozenStateChangeCallback: जब किसी सिस्टम सेवा को किसी ऐप्लिकेशन से कोई बाइंडर ऑब्जेक्ट मिलता है (उदाहरण के लिए, कॉलबैक के लिए रजिस्ट्रेशन के हिस्से के तौर पर), तो उसे उस खासIBinderइंस्टेंस परFrozenStateChangeCallbackरजिस्टर करना चाहिए. जब बाइंडर को होस्ट करने वाली ऐप्लिकेशन प्रोसेस फ़्रीज़ या अनफ़्रीज़ हो जाती है, तो यह सिस्टम सेवा को सीधे तौर पर सूचना देता है.
सिस्टम से जुड़ी सेवाओं के लिए सुझाव
हमारा सुझाव है कि ऐप्लिकेशन के साथ इंटरैक्ट करने वाली सभी सिस्टम सेवाओं को, ऐप्लिकेशन प्रोसेस के कैश मेमोरी में सेव किए गए और फ़्रीज़ किए गए स्टेटस को ट्रैक करना चाहिए. ऐसा न करने पर, ये समस्याएं हो सकती हैं:
- संसाधन का इस्तेमाल: कैश किए गए और उपयोगकर्ता को नहीं दिखने वाले ऐप्लिकेशन के लिए काम करने से, सिस्टम के संसाधन बर्बाद हो सकते हैं.
- ऐप्लिकेशन क्रैश हो रहे हैं: फ़्रीज़ किए गए ऐप्लिकेशन को सिंक्रोनस बाइंडर कॉल करने से, वे क्रैश हो जाते हैं. अगर फ़्रीज़ किए गए ऐप्लिकेशन में एसिंक्रोनस बाइंडर कॉल किए जाते हैं, तो ऐप्लिकेशन क्रैश हो जाता है. ऐसा तब होता है, जब एसिंक ट्रांज़ैक्शन बफ़र ओवरफ़्लो हो जाता है.
- ऐप्लिकेशन का अनचाहा व्यवहार: अनफ़्रीज़ किए गए ऐप्लिकेशन को, बफ़र किए गए सभी एसिंक्रोनस बाइंडर लेन-देन तुरंत मिल जाते हैं. ये लेन-देन, ऐप्लिकेशन के फ़्रीज़ होने के दौरान भेजे गए थे. ऐप्लिकेशन को फ़्रीज़ किए जाने की कोई समयसीमा नहीं होती. इसलिए, बफ़र किए गए लेन-देन बहुत पुराने हो सकते हैं.
सिस्टम सेवाएं अक्सर RemoteCallbackList का इस्तेमाल करती हैं, ताकि रिमोट कॉलबैक मैनेज किए जा सकें और डेड प्रोसेस को अपने-आप हैंडल किया जा सके. फ़्रीज़ किए गए ऐप्लिकेशन को मैनेज करने के लिए, RemoteCallbackList के मौजूदा इस्तेमाल को बढ़ाएं. इसके लिए, फ़्रीज़ किए गए कॉलर की नीति लागू करें. इसके बारे में RemoteCallbackList का इस्तेमाल करना लेख में बताया गया है.