Android API calls typically involve significant latency and computation per invocation. Client-side caching is therefore an important consideration in designing APIs that are helpful, correct, and performant.
অনুপ্রেরণা
APIs exposed to app developers in the Android SDK are often implemented as client code in the Android Framework that makes a Binder IPC call to a system service in a platform process, whose job it is to perform some computation and return a result to the client. The latency of this operation is typically dominated by three factors:
- IPC overhead: a basic IPC call is typically 10,000x the latency of a basic in-process method call.
- Server-side contention: the work done in the system service in response to the client's request may not start immediately, for example if a server thread is busy handling other requests that arrived earlier.
- সার্ভার-সাইড গণনা: সার্ভারে অনুরোধটি পরিচালনা করার কাজটিই বেশ শ্রমসাধ্য হতে পারে।
ক্লায়েন্ট সাইডে একটি ক্যাশে প্রয়োগ করার মাধ্যমে আপনি এই তিনটি লেটেন্সি ফ্যাক্টরই দূর করতে পারেন, তবে শর্ত হলো ক্যাশেটিকে অবশ্যই নিম্নলিখিত শর্তগুলো পূরণ করতে হবে:
- সঠিক: ক্লায়েন্ট-সাইড ক্যাশে কখনোই এমন ফলাফল ফেরত দেয় না যা সার্ভারের ফেরত দেওয়া ফলাফলের থেকে ভিন্ন হবে।
- কার্যকরী: ক্লায়েন্টের অনুরোধগুলো প্রায়শই ক্যাশ থেকে পূরণ করা হয়, উদাহরণস্বরূপ ক্যাশের হিট রেট বেশি।
- Efficient: the client-side cache makes efficient use of client-side resources, such as by representing cached data in a compact way and by not storing too many cached results or stale data in the client's memory.
Consider caching server results in the client
If clients often make the exact same request multiple times, and the value returned doesn't change over time, then you should implement a cache in the client library keyed by the request parameters.
Consider using IpcDataCache in your implementation:
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 is available to all system code, including mainline modules. There is also PropertyInvalidatedCache which is nearly identical, but is only visible to the framework. Prefer IpcDataCache when possible.
Invalidate caches on server-side changes
যদি সার্ভার থেকে প্রাপ্ত মান সময়ের সাথে সাথে পরিবর্তিত হতে পারে, তবে পরিবর্তনগুলো পর্যবেক্ষণ করার জন্য একটি কলব্যাক প্রয়োগ করুন এবং একটি কলব্যাক নিবন্ধন করুন যাতে আপনি সেই অনুযায়ী ক্লায়েন্ট-সাইড ক্যাশে বাতিল করতে পারেন।
Invalidate caches between unit test cases
In a unit test suite, you might test the client code against a test double rather than the real server. If so, then be sure to clear any client-side caches between test cases. This is to keep test cases mutually hermetic, and prevent one test case from interfering with another.
@RunWith(AndroidJUnit4.class)
public class BirthdayManagerTest {
@Before
public void setUp() {
BirthdayManager.clearCache();
}
@After
public void tearDown() {
BirthdayManager.clearCache();
}
...
}
When writing CTS tests that exercise an API client that uses caching internally, the cache is an implementation detail that is not exposed to the API author, therefore CTS tests shouldn't require any special knowledge of caching used in client code.
Study cache hits and misses
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
...
Fields
Hits:
- সংজ্ঞা: ক্যাশে অনুরোধ করা কোনো ডেটা কতবার সফলভাবে খুঁজে পাওয়া গেছে তার সংখ্যা।
- তাৎপর্য: এটি তথ্যের কার্যকর ও দ্রুত পুনরুদ্ধার নির্দেশ করে, যার ফলে অপ্রয়োজনীয় তথ্য পুনরুদ্ধার হ্রাস পায়।
- Higher counts are generally better.
Clears:
- Definition: The number of times the cache was cleared because of invalidation.
- Reasons for Clearing:
- Invalidation: Outdated data from the server.
- স্থান ব্যবস্থাপনা: ক্যাশে পূর্ণ হয়ে গেলে নতুন ডেটার জন্য জায়গা তৈরি করা।
- উচ্চ সংখ্যা ঘন ঘন পরিবর্তনশীল ডেটা এবং সম্ভাব্য অদক্ষতার ইঙ্গিত দিতে পারে।
Misses:
- সংজ্ঞা: যতবার ক্যাশে অনুরোধ করা ডেটা সরবরাহ করতে ব্যর্থ হয়েছে তার সংখ্যা।
- Causes:
- অদক্ষ ক্যাশিং: ক্যাশে খুব ছোট অথবা সঠিক ডেটা সংরক্ষণ করছে না।
- Frequently changing data.
- First-time requests.
- High counts suggest potential caching issues.
Skips:
- সংজ্ঞা: এমন পরিস্থিতি যেখানে ক্যাশে একেবারেই ব্যবহৃত হয়নি, যদিও তা হতে পারত।
- Reasons for skipping:
- কর্কিং: এটি অ্যান্ড্রয়েড প্যাকেজ ম্যানেজার আপডেটের একটি নির্দিষ্ট প্রক্রিয়া, যা বুট করার সময় বিপুল সংখ্যক কলের কারণে ইচ্ছাকৃতভাবে ক্যাশিং বন্ধ করে দেয়।
- অনির্ধারিত: ক্যাশে বিদ্যমান কিন্তু প্রারম্ভিকীকরণ করা হয়নি। ননসটি অনির্ধারিত ছিল, যার অর্থ ক্যাশেটি কখনও বাতিল করা হয়নি।
- Bypass: Intentional decision to skip the cache.
- উচ্চ সংখ্যা ক্যাশে ব্যবহারে সম্ভাব্য অদক্ষতার ইঙ্গিত দেয়।
Invalidates:
- সংজ্ঞা: ক্যাশ করা ডেটাকে অপ্রচলিত বা বাসি হিসেবে চিহ্নিত করার প্রক্রিয়া।
- তাৎপর্য: এটি এই সংকেত দেয় যে সিস্টেমটি সবচেয়ে হালনাগাদ ডেটা নিয়ে কাজ করছে, যা ত্রুটি এবং অসঙ্গতি প্রতিরোধ করে।
- সাধারণত ডেটার মালিক সার্ভার দ্বারা এটি সক্রিয় হয়।
Current Size:
- সংজ্ঞা: ক্যাশে থাকা উপাদানের বর্তমান পরিমাণ।
- তাৎপর্য: এটি ক্যাশের রিসোর্স ব্যবহার এবং সিস্টেম পারফরম্যান্সের উপর এর সম্ভাব্য প্রভাব নির্দেশ করে।
- সাধারণত উচ্চতর মানের অর্থ হলো ক্যাশে বেশি মেমরি ব্যবহার করে।
Max Size:
- Definition: The maximum amount of space allocated for the cache.
- গুরুত্ব: এটি ক্যাশের ধারণক্ষমতা এবং ডেটা সংরক্ষণের ক্ষমতা নির্ধারণ করে।
- Setting an appropriate max size helps balance cache efficacy with memory usage. Once the maximum size is reached, a new element is added by evicting the least-recently used element, which can indicate inefficiency.
High Water Mark:
- সংজ্ঞা: ক্যাশে তৈরির পর থেকে এর সর্বোচ্চ আকার।
- গুরুত্ব: সর্বোচ্চ ক্যাশ ব্যবহার এবং সম্ভাব্য মেমোরি চাপ সম্পর্কে ধারণা দেয়।
- সর্বোচ্চ জলসীমা পর্যবেক্ষণ সম্ভাব্য প্রতিবন্ধকতা বা উন্নতির ক্ষেত্রগুলো চিহ্নিত করতে সাহায্য করতে পারে।
Overflows:
- সংজ্ঞা: যতবার ক্যাশে তার সর্বোচ্চ আকার অতিক্রম করেছে এবং নতুন এন্ট্রির জন্য জায়গা করে দিতে ডেটা সরিয়ে ফেলতে হয়েছে, তার সংখ্যা।
- গুরুত্ব: ডেটা অপসারণের কারণে ক্যাশের উপর চাপ এবং সম্ভাব্য কর্মক্ষমতা হ্রাসের ইঙ্গিত দেয়।
- অত্যধিক ওভারফ্লো সংখ্যা ইঙ্গিত দেয় যে ক্যাশের আকার সামঞ্জস্য করার বা ক্যাশিং কৌশলটি পুনর্মূল্যায়ন করার প্রয়োজন হতে পারে।
একই পরিসংখ্যান একটি বাগ রিপোর্টেও পাওয়া যেতে পারে।
Tune the size of the cache
ক্যাশের একটি সর্বোচ্চ আকার থাকে। সর্বোচ্চ ক্যাশ আকার অতিক্রম করলে, এন্ট্রিগুলো LRU ক্রমে সরিয়ে দেওয়া হয়।
- খুব কম এন্ট্রি ক্যাশ করলে তা ক্যাশ হিট রেটকে নেতিবাচকভাবে প্রভাবিত করতে পারে।
- Caching too many entries increases the cache's memory usage.
Find the right balance for your use case.
Eliminate redundant client calls
ক্লায়েন্টরা অল্প সময়ের মধ্যে সার্ভারে একই কোয়েরি একাধিকবার করতে পারে:
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()));
}
The app may draw frames at 60 Hz. But hypothetically, the client code in TrafficStats may choose to query the server for stats at most once per second, and if queried within a second of a previous query, return the last seen value. This is allowed since the API documentation doesn't provide any contract regarding the freshness of the results returned.
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
সার্ভার কোয়েরির পরিবর্তে ক্লায়েন্ট-সাইড কোডজেন বিবেচনা করুন।
If the query results are knowable to the server at build time, then consider if they are knowable to the client at build time as well, and consider whether the API could be implemented entirely in the client side.
নিম্নলিখিত অ্যাপ কোডটি বিবেচনা করুন যা পরীক্ষা করে দেখে যে ডিভাইসটি একটি ঘড়ি কিনা (অর্থাৎ, ডিভাইসটিতে Wear OS চলছে কিনা):
public boolean isWatch(Context ctx) {
PackageManager pm = ctx.getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
}
This property of the device is known at build time, specifically at the time that the Framework was built for this device's boot image. The client-side code for hasSystemFeature could return a known result immediately, rather than querying the remote PackageManager system service.
Deduplicate server callbacks in the client
সর্বশেষে, বিভিন্ন ঘটনা সম্পর্কে অবহিত হওয়ার জন্য এপিআই ক্লায়েন্ট এপিআই সার্ভারের কাছে কলব্যাক নিবন্ধন করতে পারে।
It's typical for apps to register multiple callbacks for the same underlying information. Rather than have the server notify the client once per registered callback using IPC, the client library should have one registered callback using IPC with the server, and then notify each registered callback in the app.
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"];
}
}