Android API के क्लाइंट-साइड कैश मेमोरी से जुड़े दिशा-निर्देश

आम तौर पर, Android एपीआई कॉल में हर कॉल के लिए, इंतज़ार का समय और कैलकुलेशन ज़्यादा होता है. इसलिए, काम के, सही, और बेहतर परफ़ॉर्म करने वाले एपीआई डिज़ाइन करने के लिए, क्लाइंट-साइड कैश मेमोरी का इस्तेमाल करना ज़रूरी है.

वजह

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();
    }

    ...
}

सीटीएस टेस्ट लिखते समय, ऐसे एपीआई क्लाइंट का इस्तेमाल किया जाता है जो कैश मेमोरी का इस्तेमाल करता है. कैश मेमोरी, लागू करने से जुड़ी ऐसी जानकारी होती है जिसे एपीआई के लेखक को नहीं दिखाया जाता. इसलिए, सीटीएस टेस्ट के लिए, क्लाइंट कोड में इस्तेमाल की गई कैश मेमोरी के बारे में खास जानकारी की ज़रूरत नहीं होती.

कैश मेमोरी में हिट और मिस की जानकारी देखना

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 Hz पर फ़्रेम दिखा सकता है. हालांकि, 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"];
  }
}