आम तौर पर, Android API कॉल में हर बार, ज़्यादा इंतज़ार करना पड़ता है और ज़्यादा कंप्यूटेशन की ज़रूरत होती है. इसलिए, काम के, सही, और बेहतर परफ़ॉर्मेंस देने वाले एपीआई डिज़ाइन करते समय, क्लाइंट-साइड कैशिंग एक अहम पहलू है.
वजह
Android SDK में, ऐप्लिकेशन डेवलपर के लिए उपलब्ध कराए गए एपीआई अक्सर Android फ़्रेमवर्क में क्लाइंट कोड के तौर पर लागू किए जाते हैं. यह कोड, प्लैटफ़ॉर्म प्रोसेस में मौजूद किसी सिस्टम सेवा को बाइंडर आईपीसी कॉल करता है. इस सेवा का काम, कुछ कंप्यूटेशन करना और क्लाइंट को नतीजा देना होता है. आम तौर पर, इस कार्रवाई में लगने वाला समय तीन बातों पर निर्भर करता है:
- आईपीसी ओवरहेड: आम तौर पर, बुनियादी आईपीसी कॉल में, इन-प्रोसेस मेथड कॉल के मुकाबले 10,000 गुना ज़्यादा समय लगता है.
- सर्वर-साइड कॉन्टेंशन: क्लाइंट के अनुरोध के जवाब में, सिस्टम सेवा में किया गया काम तुरंत शुरू नहीं हो सकता. उदाहरण के लिए, अगर कोई सर्वर थ्रेड, पहले से मिले अन्य अनुरोधों को प्रोसेस करने में व्यस्त है.
- सर्वर-साइड कंप्यूटेशन: सर्वर में अनुरोध को प्रोसेस करने के लिए, ज़्यादा काम की ज़रूरत पड़ सकती है.
क्लाइंट साइड पर कैश मेमोरी लागू करके, इंतज़ार में लगने वाले समय से जुड़ी इन तीनों समस्याओं को खत्म किया जा सकता है. हालांकि, इसके लिए ज़रूरी है कि कैश मेमोरी:
- सही हो: क्लाइंट-साइड कैश मेमोरी कभी भी ऐसे नतीजे न दे जो सर्वर से मिलने वाले नतीजों से अलग हों.
- असरदार हो: क्लाइंट के अनुरोध अक्सर कैश मेमोरी से पूरे किए जाएं. उदाहरण के लिए, कैश मेमोरी की हिट रेट ज़्यादा हो.
- किफ़ायती हो: क्लाइंट-साइड कैश मेमोरी, क्लाइंट-साइड संसाधनों का किफ़ायती तरीके से इस्तेमाल करे. जैसे, कैश मेमोरी में सेव किए गए डेटा को कम जगह में सेव करके और क्लाइंट की मेमोरी में, कैश मेमोरी में सेव किए गए बहुत ज़्यादा नतीजों या पुरानी जानकारी को सेव न करके.
क्लाइंट में सर्वर के नतीजों को कैश मेमोरी में सेव करना
अगर क्लाइंट अक्सर एक ही अनुरोध को कई बार करते हैं और समय के साथ, अनुरोध के जवाब में मिलने वाली वैल्यू नहीं बदलती है, तो आपको क्लाइंट लाइब्रेरी में कैश मेमोरी लागू करनी चाहिए. इसे अनुरोध के पैरामीटर के हिसाब से कुंजी के तौर पर इस्तेमाल किया जा सकता है.
अपने लागू करने में IpcDataCache का इस्तेमाल करें:
public class BirthdayManager {
private final IpcDataCache.QueryHandler<User, Birthday> mBirthdayQuery =
new IpcDataCache.QueryHandler<User, Birthday>() {
@Override
public Birthday apply(User user) {
return mService.getBirthday(user);
}
};
private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache
private static final String BDAY_API = "getUserBirthday";
private final IpcDataCache<User, Birthday> mCache
new IpcDataCache<User, Birthday>(
BDAY_CACHE_MAX, MODULE_SYSTEM, BDAY_API, BDAY_API, mBirthdayQuery);
/** @hide **/
@VisibleForTesting
public static void clearCache() {
IpcDataCache.invalidateCache(MODULE_SYSTEM, BDAY_API);
}
public Birthday getBirthday(User user) {
return mCache.query(user);
}
}
पूरे उदाहरण के लिए, android.app.admin.DevicePolicyManager देखें.
IpcDataCache, सिस्टम के सभी कोड के लिए उपलब्ध है. इसमें मेनलाइन मॉड्यूल भी शामिल हैं.
PropertyInvalidatedCache भी उपलब्ध है. यह लगभग एक जैसा है, लेकिन यह सिर्फ़ फ़्रेमवर्क के लिए दिखता है. अगर मुमकिन हो, तो IpcDataCache का इस्तेमाल करें.
सर्वर-साइड में बदलाव होने पर, कैश मेमोरी को अमान्य करना
अगर सर्वर से मिलने वाली वैल्यू समय के साथ बदल सकती है, तो बदलावों को देखने के लिए कॉलबैक लागू करें. साथ ही, कॉलबैक रजिस्टर करें, ताकि क्लाइंट-साइड कैश मेमोरी को अमान्य किया जा सके.
यूनिट टेस्ट केस के बीच, कैश मेमोरी को अमान्य करना
यूनिट टेस्ट सुइट में, हो सकता है कि असली सर्वर के बजाय, टेस्ट डबल के ख़िलाफ़ क्लाइंट कोड की जांच की जाए. अगर ऐसा है, तो टेस्ट केस के बीच, क्लाइंट-साइड कैश मेमोरी को साफ़ करना न भूलें. ऐसा इसलिए किया जाता है, ताकि टेस्ट केस एक-दूसरे से अलग रहें और एक टेस्ट केस, दूसरे में दखल न दे.
@RunWith(AndroidJUnit4.class)
public class BirthdayManagerTest {
@Before
public void setUp() {
BirthdayManager.clearCache();
}
@After
public void tearDown() {
BirthdayManager.clearCache();
}
...
}
CTS टेस्ट लिखते समय, एपीआई क्लाइंट का इस्तेमाल किया जाता है. यह क्लाइंट, इंटरनल तौर पर कैशिंग का इस्तेमाल करता है. कैश मेमोरी, लागू करने से जुड़ी ऐसी जानकारी है जो एपीआई के लेखक को नहीं दिखती. इसलिए, CTS टेस्ट के लिए, क्लाइंट कोड में इस्तेमाल की गई कैशिंग के बारे में कोई खास जानकारी नहीं होनी चाहिए.
कैश मेमोरी की हिट और मिस की स्टडी करना
IpcDataCache और PropertyInvalidatedCache, लाइव आंकड़े दिखा सकते हैं:
adb shell dumpsys cacheinfo
...
Cache Name: cache_key.is_compat_change_enabled
Property: cache_key.is_compat_change_enabled
Hits: 1301458, Misses: 21387, Skips: 0, Clears: 39
Skip-corked: 0, Skip-unset: 0, Skip-bypass: 0, Skip-other: 0
Nonce: 0x856e911694198091, Invalidates: 72, CorkedInvalidates: 0
Current Size: 1254, Max Size: 2048, HW Mark: 2049, Overflows: 310
Enabled: true
...
फ़ील्ड
हिट:
- परिभाषा: अनुरोध किए गए डेटा को कैश मेमोरी में कितनी बार खोजा गया.
- अहमियत: इससे डेटा को तेज़ी से और आसानी से पाने के बारे में पता चलता है. साथ ही, इससे गैर-ज़रूरी डेटा को पाने की ज़रूरत कम हो जाती है.
- आम तौर पर, ज़्यादा संख्या बेहतर होती है.
साफ़ करना:
- परिभाषा: अमान्य होने की वजह से, कैश मेमोरी को कितनी बार साफ़ किया गया.
- साफ़ करने की वजहें:
- अमान्य होना: सर्वर से मिला पुराना डेटा.
- जगह मैनेज करना: कैश मेमोरी पूरी होने पर, नए डेटा के लिए जगह बनाना.
- ज़्यादा संख्या से, बार-बार बदलने वाले डेटा और संभावित गड़बड़ी के बारे में पता चल सकता है.
मिस:
- परिभाषा: अनुरोध किए गए डेटा को कैश मेमोरी से कितनी बार नहीं पाया जा सका.
- वजहें:
- कैशिंग की सुविधा ठीक से काम न करना: कैश मेमोरी का साइज़ बहुत छोटा होना या सही डेटा सेव न करना.
- बार-बार बदलने वाला डेटा.
- पहली बार किए गए अनुरोध.
- ज़्यादा संख्या से, कैशिंग से जुड़ी संभावित समस्याओं के बारे में पता चलता है.
स्किप:
- परिभाषा: ऐसे उदाहरण जहां कैश मेमोरी का इस्तेमाल नहीं किया गया, जबकि इसका इस्तेमाल किया जा सकता था.
- स्किप करने की वजहें:
- कॉर्किंग: यह Android Package Manager के अपडेट के लिए खास है. बूट के दौरान, ज़्यादा कॉल की वजह से, जान-बूझकर कैशिंग बंद करना.
- सेट नहीं है: कैश मेमोरी मौजूद है, लेकिन शुरू नहीं की गई है. नॉनस सेट नहीं किया गया है. इसका मतलब है कि कैश मेमोरी कभी अमान्य नहीं की गई.
- बाइपास: जान-बूझकर कैश मेमोरी को स्किप करने का फ़ैसला.
- ज़्यादा संख्या से, कैश मेमोरी के इस्तेमाल में संभावित गड़बड़ियों के बारे में पता चलता है.
अमान्य करना:
- परिभाषा: कैश मेमोरी में सेव किए गए डेटा को पुराना या अमान्य के तौर पर मार्क करने की प्रोसेस.
- अहमियत: इससे यह सिग्नल मिलता है कि सिस्टम, अप-टू-डेट डेटा के साथ काम करता है. साथ ही, इससे गड़बड़ियां और डेटा में अंतर आने से रोका जा सकता है.
- आम तौर पर, यह उस सर्वर से ट्रिगर होता है जिसके पास डेटा का मालिकाना हक होता है.
मौजूदा साइज़:
- परिभाषा: कैश मेमोरी में मौजूद एलिमेंट की मौजूदा संख्या.
- अहमियत: इससे कैश मेमोरी के संसाधन के इस्तेमाल और सिस्टम की परफ़ॉर्मेंस पर पड़ने वाले संभावित असर के बारे में पता चलता है.
- आम तौर पर, ज़्यादा वैल्यू का मतलब है कि कैश मेमोरी, ज़्यादा मेमोरी का इस्तेमाल करती है.
ज़्यादा से ज़्यादा साइज़:
- परिभाषा: कैश मेमोरी के लिए तय की गई ज़्यादा से ज़्यादा जगह.
- अहमियत: इससे कैश मेमोरी की क्षमता और डेटा सेव करने की क्षमता के बारे में पता चलता है.
- ज़्यादा से ज़्यादा साइज़ सेट करने से, कैश मेमोरी की असरदार क्षमता और मेमोरी के इस्तेमाल के बीच संतुलन बनाए रखने में मदद मिलती है. ज़्यादा से ज़्यादा साइज़ पर पहुंचने के बाद, सबसे कम इस्तेमाल किए गए एलिमेंट को हटाकर, नया एलिमेंट जोड़ा जाता है. इससे गड़बड़ी का पता चल सकता है.
हाई वॉटर मार्क:
- परिभाषा: कैश मेमोरी बनने के बाद से, उसके ज़्यादा से ज़्यादा साइज़ तक पहुंचने की संख्या.
- अहमियत: इससे कैश मेमोरी के ज़्यादा इस्तेमाल और मेमोरी पर पड़ने वाले संभावित दबाव के बारे में पता चलता है.
- हाई वॉटर मार्क की निगरानी करने से, संभावित अड़चनों या ऑप्टिमाइज़ेशन के लिए जगहों की पहचान करने में मदद मिल सकती है.
ओवरफ़्लो:
- परिभाषा: कैश मेमोरी, ज़्यादा से ज़्यादा साइज़ से ज़्यादा होने पर, नए एलिमेंट के लिए जगह बनाने के लिए, डेटा को कितनी बार हटाया गया.
- अहमियत: इससे कैश मेमोरी पर पड़ने वाले दबाव और डेटा हटाने की वजह से, परफ़ॉर्मेंस में आने वाली संभावित गिरावट के बारे में पता चलता है.
- ओवरफ़्लो की ज़्यादा संख्या से पता चलता है कि कैश मेमोरी के साइज़ को अडजस्ट करने या कैशिंग की रणनीति का फिर से आकलन करने की ज़रूरत पड़ सकती है.
बग रिपोर्ट में भी यही आंकड़े देखे जा सकते हैं.
कैश मेमोरी का साइज़ सेट करना
कैश मेमोरी का साइज़ तय होता है. कैश मेमोरी का साइज़ तय सीमा से ज़्यादा होने पर, एंट्री को एलआरयू क्रम में हटाया जाता है.
- बहुत कम एंट्री को कैश मेमोरी में सेव करने से, कैश मेमोरी की हिट रेट पर बुरा असर पड़ सकता है.
- बहुत ज़्यादा एंट्री को कैश मेमोरी में सेव करने से, कैश मेमोरी के इस्तेमाल में ज़्यादा मेमोरी लगती है.
अपने इस्तेमाल के हिसाब से सही संतुलन बनाएं.
क्लाइंट के गैर-ज़रूरी कॉल को खत्म करना
क्लाइंट, सर्वर से एक ही क्वेरी को कम समय में कई बार कर सकते हैं:
public void executeAll(List<Operation> operations) throws SecurityException {
for (Operation op : operations) {
for (Permission permission : op.requiredPermissions()) {
if (!permissionChecker.checkPermission(permission, ...)) {
throw new SecurityException("Missing permission " + permission);
}
}
op.execute();
}
}
पिछली कॉल के नतीजों को फिर से इस्तेमाल करने पर विचार करें:
public void executeAll(List<Operation> operations) throws SecurityException {
Set<Permission> permissionsChecked = new HashSet<>();
for (Operation op : operations) {
for (Permission permission : op.requiredPermissions()) {
if (!permissionsChecked.add(permission)) {
if (!permissionChecker.checkPermission(permission, ...)) {
throw new SecurityException(
"Missing permission " + permission);
}
}
}
op.execute();
}
}
हाल ही में मिले सर्वर के जवाबों को क्लाइंट-साइड कैश मेमोरी में सेव करने पर विचार करना
हो सकता है कि क्लाइंट ऐप्लिकेशन, एपीआई से इतनी तेज़ी से क्वेरी करें कि एपीआई का सर्वर, नए जवाब न दे पाए. ऐसे में, असरदार तरीका यह है कि क्लाइंट साइड पर, हाल ही में मिले सर्वर के जवाब को टाइमस्टैंप के साथ कैश मेमोरी में सेव किया जाए. साथ ही, अगर कैश मेमोरी में सेव किया गया नतीजा हाल ही का है, तो सर्वर से क्वेरी किए बिना, कैश मेमोरी में सेव किया गया नतीजा दिखाया जाए. एपीआई क्लाइंट का लेखक, कैश मेमोरी में सेव करने की अवधि तय कर सकता है.
उदाहरण के लिए, कोई ऐप्लिकेशन हर फ़्रेम में आंकड़ों के लिए क्वेरी करके, उपयोगकर्ता को नेटवर्क ट्रैफ़िक के आंकड़े दिखा सकता है:
@UiThread
private void setStats() {
mobileRxBytesTextView.setText(
Long.toString(TrafficStats.getMobileRxBytes()));
mobileRxPacketsTextView.setText(
Long.toString(TrafficStats.getMobileRxPackages()));
mobileTxBytesTextView.setText(
Long.toString(TrafficStats.getMobileTxBytes()));
mobileTxPacketsTextView.setText(
Long.toString(TrafficStats.getMobileTxPackages()));
}
ऐप्लिकेशन, 60 हर्ट्ज़ पर फ़्रेम दिखा सकता है. हालांकि, मान लें कि TrafficStats में मौजूद क्लाइंट कोड, सर्वर से हर सेकंड में ज़्यादा से ज़्यादा एक बार आंकड़ों के लिए क्वेरी कर सकता है. साथ ही, अगर पिछली क्वेरी के एक सेकंड के अंदर क्वेरी की जाती है, तो हाल ही में मिली वैल्यू दिखाई जाएगी.
इसकी अनुमति है, क्योंकि एपीआई के दस्तावेज़ में, नतीजों के अप-टू-डेट होने के बारे में कोई समझौता नहीं किया गया है.
participant App code as app
participant Client library as clib
participant Server as server
app->clib: request @ T=100ms
clib->server: request
server->clib: response 1
clib->app: response 1
app->clib: request @ T=200ms
clib->app: response 1
app->clib: request @ T=300ms
clib->app: response 1
app->clib: request @ T=2000ms
clib->server: request
server->clib: response 2
clib->app: response 2
सर्वर की क्वेरी के बजाय, क्लाइंट-साइड कोडजन का इस्तेमाल करना
अगर क्वेरी के नतीजे, बिल्ड प्रोसेस में लगने वाले समय सर्वर को पता हैं, तो देखें कि क्या वे बिल्ड प्रोसेस में लगने वाले समय क्लाइंट को भी पता हैं. साथ ही, देखें कि क्या एपीआई को पूरी तरह से क्लाइंट साइड पर लागू किया जा सकता है.
यहां ऐप्लिकेशन का वह कोड दिया गया है जो यह जांच करता है कि डिवाइस घड़ी है या नहीं. इसका मतलब है कि डिवाइस पर Wear OS चल रहा है:
public boolean isWatch(Context ctx) {
PackageManager pm = ctx.getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
}
डिवाइस की इस प्रॉपर्टी के बारे में, बिल्ड के समय पता होता है. खास तौर पर, उस समय जब इस डिवाइस की बूट इमेज के लिए फ़्रेमवर्क बनाया गया था. hasSystemFeature के लिए क्लाइंट-साइड कोड, रिमोट PackageManager सिस्टम सर्विस से क्वेरी करने के बजाय, तुरंत जाना-पहचाना नतीजा दिखा सकता है.
क्लाइंट में सर्वर के कॉलबैक को डुप्लीकेट होने से रोकना
आखिर में, एपीआई क्लाइंट, इवेंट की सूचना पाने के लिए, एपीआई सर्वर के साथ कॉलबैक रजिस्टर कर सकता है.
आम तौर पर, ऐप्लिकेशन एक ही जानकारी के लिए कई कॉलबैक रजिस्टर करते हैं. सर्वर, रजिस्टर किए गए हर कॉलबैक के लिए, आईपीसी का इस्तेमाल करके क्लाइंट को सूचना देता है. इसके बजाय, क्लाइंट लाइब्रेरी में सर्वर के साथ आईपीसी का इस्तेमाल करके, एक रजिस्टर किया गया कॉलबैक होना चाहिए. इसके बाद, ऐप्लिकेशन में रजिस्टर किए गए हर कॉलबैक को सूचना दी जानी चाहिए.
digraph d_front_back {
rankdir=RL;
node [style=filled, shape="rectangle", fontcolor="white" fontname="Roboto"]
server->clib
clib->c1;
clib->c2;
clib->c3;
subgraph cluster_client {
graph [style="dashed", label="Client app process"];
c1 [label="my.app.FirstCallback" color="#4285F4"];
c2 [label="my.app.SecondCallback" color="#4285F4"];
c3 [label="my.app.ThirdCallback" color="#4285F4"];
clib [label="android.app.FooManager" color="#F4B400"];
}
subgraph cluster_server {
graph [style="dashed", label="Server process"];
server [label="com.android.server.FooManagerService" color="#0F9D58"];
}
}