Android API istemci taraflı önbelleğe alma yönergeleri

Android API çağrıları genellikle çağrı başına önemli düzeyde gecikme ve hesaplama içerir. Bu nedenle, istemci tarafı önbelleğe alma, yararlı, doğru ve performanslı API'ler tasarlarken dikkate alınması gereken önemli bir noktadır.

Motivasyon

Android SDK'da uygulama geliştiricilere sunulan API'ler genellikle Android Framework'de istemci kodu olarak uygulanır. Bu kod, platform işleminde bir sistem hizmetine Binder IPC çağrısı yapar. Bu hizmetin görevi, bazı hesaplamalar yapmak ve istemciye bir sonuç döndürmektir. Bu işlemin gecikmesi genellikle üç faktörden etkilenir:

  • IPC yükü: Temel bir IPC çağrısı,genellikle temel bir işlem içi yöntem çağrısının 10.000 katı gecikmeye sahiptir.
  • Sunucu tarafı çekişmesi: İstemcinin isteğine yanıt olarak sistem hizmetinde yapılan çalışma hemen başlatılmayabilir (ör. bir sunucu iş parçacığı daha önce gelen diğer istekleri işlemekle meşgulse).
  • Sunucu tarafı hesaplama: Sunucudaki isteği işleme işinin kendisi önemli miktarda çalışma gerektirebilir.

Müşteri tarafında bir önbelleğe uygulayarak bu gecikme faktörlerinin üçünü de ortadan kaldırabilirsiniz. Bunun için önbelleğin aşağıdaki özelliklere sahip olması gerekir:

  • Doğru: İstemci tarafı önbelleği hiçbir zaman sunucunun döndüreceğinden farklı sonuçlar döndürmez.
  • Etkili: istemci istekleri genellikle önbellekten sunulur. Örneğin, önbelleğin isabet oranı yüksektir.
  • Verimli: İstemci tarafı önbelleği, istemci tarafı kaynaklarını verimli bir şekilde kullanır (ör. önbelleğe alınan verileri kompakt bir şekilde göstererek ve istemcinin belleğinde çok fazla önbelleğe alınmış sonuç veya eski veri depolayarak).

İstemcide sunucu sonuçlarını önbelleğe alma

Müşteriler genellikle aynı isteği birden çok kez yapıyorsa ve döndürülen değer zaman içinde değişmiyorsa istek parametrelerine göre anahtarlanmış bir önbelleği müşteri kitaplığına uygulamanız gerekir.

Uygulamanızda IpcDataCache kullanmayı deneyin:

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

Tam bir örnek için android.app.admin.DevicePolicyManager bölümüne bakın.

IpcDataCache, ana hat modülleri dahil tüm sistem kodunda kullanılabilir. Ayrıca, neredeyse aynı olan ancak yalnızca çerçeve tarafından görülebilen PropertyInvalidatedCache de vardır. Mümkün olduğunda IpcDataCache seçeneğini tercih edin.

Sunucu tarafındaki değişikliklerde önbellekleri geçersiz kılma

Sunucudan döndürülen değer zaman içinde değişebiliyorsa değişiklikleri gözlemlemek için bir geri çağırma işlevi uygulayın ve istemci tarafı önbelleği uygun şekilde geçersiz kılabilmeniz için bir geri çağırma işlevi kaydedin.

Birim test durumları arasında önbellekleri geçersiz kılma

Birim test paketinde, istemci kodunu gerçek sunucu yerine bir test çiftiyle test edebilirsiniz. Bu durumda, test senaryoları arasında istemci taraflı önbellekleri temizlediğinizden emin olun. Bu, test durumlarının birbirinden hermetik kalmasını ve bir test durumunun diğerine müdahale etmesini önlemek içindir.

@RunWith(AndroidJUnit4.class)
public class BirthdayManagerTest {

    @Before
    public void setUp() {
        BirthdayManager.clearCache();
    }

    @After
    public void tearDown() {
        BirthdayManager.clearCache();
    }

    ...
}

Dahili olarak önbelleğe alma özelliğini kullanan bir API istemcisini çalıştıran CTS testleri yazılırken önbelleğe alma, API yazarına gösterilmeyen bir uygulama ayrıntısıdır. Bu nedenle, CTS testleri için istemci kodunda kullanılan önbelleğe alma hakkında özel bilgi gerekmez.

Önbelleğe isabet ve isabetsizlikleri inceleme

IpcDataCache ve PropertyInvalidatedCache, canlı istatistikleri yazdırabilir:

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
  ...

Fields'ın oynadığı filmler

İsabetler:

  • Tanım: İstenen bir veri parçasının önbellekte başarıyla bulunup bulunmadığının sayısı.
  • Önem: Verilerin verimli ve hızlı bir şekilde alındığını gösterir. Böylece gereksiz veri alma işlemi azaltılır.
  • Genellikle daha yüksek sayılar daha iyidir.

Aşağıdakileri temizler:

  • Tanım: Geçersiz kılma nedeniyle önbelleğin temizlenme sayısı.
  • Açıklama:
    • Geçersiz kılma: Sunucudaki eski veriler.
    • Alan Yönetimi: Önbellek dolu olduğunda yeni veriler için yer açma.
  • Yüksek sayılar, sık sık değişen verileri ve olası verimsizlikleri gösterebilir.

Kaçıranlar:

  • Tanım: Önbelleğin istenen verileri sağlayamadığı durumların sayısı.
  • Nedenler:
    • Verimli olmayan önbelleğe alma: Önbelleğe alma alanı çok küçük veya doğru verileri depolamamaktadır.
    • Sık sık değişen veriler.
    • İlk kez yapılan istekler.
  • Yüksek sayılar, olası önbelleğe alma sorunlarını gösterir.

Atlamalar:

  • Tanım: Önbelleğin kullanılabileceği halde hiç kullanılmadığı durumlar.
  • Atlama nedenleri:
    • Tıkama: Android Paket Yöneticisi güncellemelerine özgüdür. Açılış sırasında çok sayıda çağrı yapıldığından, önbelleğe alma işlemi kasıtlı olarak kapatılır.
    • Ayarlanmadı: Önbellek var ancak başlatılmadı. Tek seferlik sayı ayarlanmadı. Bu, önbelleğin hiçbir zaman geçersiz kılınmadığı anlamına gelir.
    • Atlama: Önbelleği atlama konusunda bilinçli bir karar.
  • Yüksek sayılar, önbellek kullanımında olası verimsizlikleri gösterir.

Geçersiz kılar:

  • Tanım: Önbelleğe alınan verileri güncel olmayan veya eski olarak işaretleme işlemi.
  • Önem: Sistemin en güncel verilerle çalıştığına dair bir sinyal sağlayarak hataları ve tutarsızlıkları önler.
  • Genellikle verilerin sahibi olan sunucu tarafından tetiklenir.

Mevcut Boyut:

  • Tanım: Önbellekte bulunan mevcut öğe miktarı.
  • Önem: Önbelleğin kaynak kullanımını ve sistem performansı üzerindeki olası etkisini belirtir.
  • Daha yüksek değerler genellikle önbellek tarafından daha fazla bellek kullanıldığı anlamına gelir.

Maks. Boyut:

  • Tanım: Önbelleğe ayrılan maksimum alan miktarı.
  • Önem: Önbelleğin kapasitesini ve veri depolama kapasitesini belirler.
  • Uygun bir maksimum boyut ayarlamak, önbellek verimliliğini bellek kullanımıyla dengelemeye yardımcı olur. Maksimum boyuta ulaşıldığında, en uzun süredir kullanılmayan öğe kaldırılarak yeni bir öğe eklenir. Bu durum, verimsizliği gösterebilir.

Yüksek Su Seviyesi:

  • Tanım: Oluşturulduğu andan itibaren önbelleğin ulaştığı maksimum boyut.
  • Önem: En yüksek önbellek kullanımı ve olası bellek baskısı hakkında bilgi sağlar.
  • En yüksek değeri izlemek, olası darboğazları veya optimizasyona uygun alanları belirlemenize yardımcı olabilir.

Aşırı doluluklar:

  • Tanım: Önbelleğin maksimum boyutunu aştığı ve yeni girişlere yer açmak için verilerin çıkarılması gereken durum sayısı.
  • Önem: Verilerin kaldırılması nedeniyle önbelleğe baskı ve olası performans düşüşü olduğunu gösterir.
  • Yüksek taşma sayıları, önbellek boyutunun ayarlanması veya önbelleğe alma stratejisinin yeniden değerlendirilmesi gerekebileceğini gösterir.

Aynı istatistikleri hata raporlarında da bulabilirsiniz.

Önbelleğin boyutunu ayarlama

Önbellekler maksimum boyuta sahiptir. Maksimum önbellek boyutu aşıldığında girişler LRU sırasına göre çıkarılır.

  • Çok az girişin önbelleğe alınması, önbelleğe isabet oranını olumsuz yönde etkileyebilir.
  • Çok fazla girişin önbelleğe alınması, önbelleğin bellek kullanımını artırır.

Kullanım alanınız için doğru dengeyi bulun.

Gereksiz istemci çağrılarını ortadan kaldırın

Müşteriler, kısa bir süre içinde sunucuya aynı sorguyu birden fazla kez gönderebilir:

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

Önceki aramalardan elde edilen sonuçları yeniden kullanmayı deneyin:

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

Son sunucu yanıtlarının istemci tarafında hafızaya alınmasını düşünün

İstemci uygulamaları, API'yi API'nin sunucusunun anlamlı yeni yanıtlar oluşturabileceğinden daha hızlı bir hızda sorgulayabilir. Bu durumda, etkili bir yaklaşım, son görülen sunucu yanıtını istemci tarafında bir zaman damgasıyla birlikte ezberlemek ve ezberlenen sonuç yeterince güncelse sunucuyu sorgulamadan ezberlenen sonucu döndürmektir. API istemcisi yazarı, hafızaya alma süresini belirleyebilir.

Örneğin, bir uygulama, oluşturulan her karedeki istatistikleri sorgulayarak kullanıcıya ağ trafiği istatistiklerini gösterebilir:

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

Uygulama 60 Hz'de kare çizebilir. Ancak varsayımsal olarak, TrafficStats istemci kodu sunucuda istatistikleri en fazla saniyede bir sorgulamayı seçebilir ve önceki bir sorgudan sonraki bir saniye içinde sorgulanırsa en son görülen değeri döndürebilir. API dokümanlarında döndürülen sonuçların güncelliğiyle ilgili herhangi bir sözleşme bulunmadığından buna izin verilir.

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

Sunucu sorguları yerine istemci tarafı kod oluşturma işlemini kullanmayı düşünün

Sorgu sonuçları derleme zamanında sunucu tarafından bilinebiliyorsa derleme zamanında istemci tarafından da bilinip bilinmediğini ve API'nin tamamen istemci tarafında uygulanıp uygulanamayacağını düşünün.

Cihazın saat olup olmadığını (yani Wear OS'un çalışıp çalışmadığını) kontrol eden aşağıdaki uygulama kodunu inceleyin:

public boolean isWatch(Context ctx) {
    PackageManager pm = ctx.getPackageManager();
    return pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
}

Cihazın bu özelliği, derleme sırasında, özellikle de Framework bu cihazın önyükleme resmi için derlenirken bilinir. hasSystemFeature için istemci tarafı kod, uzak PackageManager sistem hizmetini sorgulamak yerine bilinen bir sonucu hemen döndürebilir.

İstemcide sunucu geri çağırmalarının tekilleştirilmesi

Son olarak API istemcisi, etkinliklerden haberdar olmak için API sunucusuna geri çağırma işlevleri kaydedebilir.

Uygulamaların aynı temel bilgiler için birden fazla geri çağırma işlevi kaydetmesi normaldir. Sunucunun, kayıtlı geri çağırma başına bir kez IPC kullanarak istemciyi bilgilendirmesi yerine, istemci kitaplığında sunucuyla IPC kullanarak bir kayıtlı geri çağırma olmalı ve ardından uygulamadaki her kayıtlı geri çağırma bilgilendirilmelidir.

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"];
  }
}