التخزين المؤقت والأداء
أساسيات التخزين المؤقت في الذاكرة والقرص
التخزين المؤقت والأداء درس مجاني في Android Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Android Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Android Academy 4 دروس في المجموع.
أهمية التخزين المؤقت
يُعد تنزيل الصورة وفك ترميزها عملية مكلفة؛ فهي تستهلك النطاق الترددي والبطارية ووحدة المعالجة المركزية. وإذا أعدتم جلب الصورة نفسها في كل مرة يتم فيها تمرير قائمة، فسيبدو تطبيقكم بطيئًا وسيستهلك البيانات بكثرة.
يخزّن التخزين المؤقت الصور التي فُك ترميزها والبيانات المُنزّلة، بحيث يصبح الطلب التالي شبه فوري. يأتي Coil افتراضيًا بذاكرتي تخزين مؤقت: تخزين مؤقت في الذاكرة (سريع ويحتوي على الصور النقطية المفككة الترميز) وتخزين مؤقت على القرص (دائم ويحتوي على البايتات الخام).
يشرح هذا الدرس كيفية عملهما وكيفية ضبطهما.
طبقتان: الذاكرة والقرص
يفحص Coil ذواكر التخزين المؤقت بالترتيب التالي:
- التخزين المؤقت في الذاكرة: يحتفظ بالصور النقطية المفككة الترميز والمستخدمة حديثًا في ذاكرة RAM. ويكون الوصول إلى البيانات الموجودة فيه فوريًا، من دون الحاجة إلى فك الترميز.
- التخزين المؤقت على القرص: يخزّن البايتات الأصلية التي تم تنزيلها على الجهاز. ويؤدي العثور على البيانات فيه إلى تجاوز الشبكة، مع استمرار الحاجة إلى فك الترميز.
- الشبكة: المسار الأبطأ، ولا يُستخدم إلا عند عدم العثور على البيانات في كلا المخزنين المؤقتين.
الذاكرة سريعة لكنها صغيرة وتُفرَّغ عند إغلاق التطبيق، أما القرص فأكبر حجمًا وتبقى بياناته بعد إعادة التشغيل.
ضبط ImageLoader
توجد ذواكر التخزين المؤقت في ImageLoader، وهو المحرك الذي يقف وراء كل طلب. عادةً ما تنشئون محمّلًا مخصصًا واحدًا وتعيدون استخدامه. نضبط هنا حجم التخزين المؤقت في الذاكرة كنسبة مئوية من RAM المتاحة، وحجم التخزين المؤقت على القرص وفق حد ثابت للبايتات.
يؤدي استخدام محمّل singleton إلى تجنب إنشاء ذواكر تخزين مؤقت مكررة وإهدار الذاكرة.
import android.content.Context
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import okio.Path.Companion.toOkioPath
fun newImageLoader(context: Context): ImageLoader =
ImageLoader.Builder(context)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(context, 0.25) // 25% of app RAM
.build()
}
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("image_cache").toOkioPath())
.maxSizeBytes(50L * 1024 * 1024) // 50 MB
.build()
}
.build()توفير محمّل Singleton
يتيح لكم Coil 3 تعيين محمّل واحد على مستوى التطبيق بأكمله، وذلك من خلال تطبيق SingletonImageLoader.Factory في فئة Application الخاصة بكم. بعد ذلك، يستخدم كل AsyncImage هذا المحمّل تلقائيًا، من دون الحاجة إلى تمرير محمّل في كل مرة.
import android.app.Application
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
class MyApp : Application(), SingletonImageLoader.Factory {
override fun newImageLoader(context: PlatformContext): ImageLoader =
ImageLoader.Builder(context)
.crossfade(true)
.build()
}التحكم في سياسات التخزين المؤقت
يمكن لكل طلب تفعيل التخزين المؤقت أو تعطيله باستخدام memoryCachePolicy وdiskCachePolicy. والسياسات المتاحة هي ENABLED وREAD_ONLY وWRITE_ONLY وDISABLED.
فعلى سبيل المثال، عطّلوا التخزين المؤقت في الذاكرة للصور الضخمة التي تُستخدم مرة واحدة ولا تريدون الاحتفاظ بها في RAM، أو عطّلوا التخزين على القرص للمحتوى الحساس.
import android.content.Context
import coil3.request.CachePolicy
import coil3.request.ImageRequest
fun noMemoryCacheRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.build()تغيير الحجم: لا تفكك ترميز الصور النقطية الضخمة
أكبر خطأ في الأداء هو فك ترميز صورة بحجم 4000x3000 لتُستخدم كصورة مصغّرة بحجم 100dp. فهذا يهدر الذاكرة والوقت. يضبط Coil، افتراضيًا، حجم الصورة النقطية وفق الأبعاد المقاسة لـ composable، ولذلك يفك AsyncImage الصغير ترميز صورة نقطية صغيرة.
يمكنكم فرض حجم هدف محدد باستخدام size() في الطلب عندما تكون الأبعاد معروفة لديكم.
import android.content.Context
import coil3.request.ImageRequest
import coil3.size.Size
fun thumbnailRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
.size(Size(200, 200)) // decode at most 200x200
.build()مفاتيح تخزين مؤقت ثابتة
يستخلص Coil مفتاح التخزين المؤقت من بيانات الطلب. وإذا كانت عناوين URL تتضمن معاملات استعلام متغيرة، مثل الرمز الموقّع، فستحصل الصور المتطابقة على مفاتيح مختلفة ولن تُعثر عليها في التخزين المؤقت مطلقًا.
اضبطوا صراحةً memoryCacheKey وdiskCacheKey اعتمادًا على الجزء الثابت، مثل معرّف الصورة، حتى يعمل التخزين المؤقت كما ينبغي.
import android.content.Context
import coil3.request.ImageRequest
fun stableKeyRequest(context: Context, id: String, signedUrl: String) =
ImageRequest.Builder(context)
.data(signedUrl)
.memoryCacheKey("image_$id")
.diskCacheKey("image_$id")
.build()التحميل المسبق للصور
للحصول على تجربة أكثر سلاسة، يمكنكم تهيئة التخزين المؤقت قبل عرض الصورة، مثل جلب الصفحة التالية من موجز مسبقًا. استخدموا enqueue الخاص بالمحمّل مع طلب لا يتضمن هدفًا.
وعندما يصل المستخدم إلى الصورة، ستكون مخزنة مؤقتًا وستظهر فورًا.
import android.content.Context
import coil3.ImageLoader
import coil3.request.ImageRequest
fun preload(context: Context, loader: ImageLoader, urls: List<String>) {
urls.forEach { url ->
val request = ImageRequest.Builder(context)
.data(url)
.build()
loader.enqueue(request)
}
}مسح التخزين المؤقت
قد تحتاجون أحيانًا إلى تحرير مساحة أو فرض تحديث، مثلما يحدث بعد أن يحدّث المستخدم صورته الرمزية. يمكنكم مسح التخزين المؤقت في الذاكرة أو على القرص، أو إزالة مفتاح واحد.
يُعد مسح كل شيء عملية مكلفة، لذا يُفضّل إزالة مفاتيح محددة متى أمكن ذلك.
import coil3.ImageLoader
import coil3.memory.MemoryCache
fun refreshAvatar(loader: ImageLoader, key: String) {
// Remove one entry so the next load refetches
loader.memoryCache?.remove(MemoryCache.Key(key))
loader.diskCache?.remove(key)
}
fun clearAll(loader: ImageLoader) {
loader.memoryCache?.clear()
// Disk clearing is heavier; do it sparingly
}التلاشي التدريجي مقابل إصابات التخزين المؤقت
يبدو التلاشي التدريجي رائعًا عند التحميل من الشبكة، لكنه قد يبدو بطيئًا عندما تأتي الصورة مباشرةً من التخزين المؤقت في الذاكرة ويمكن عرضها فورًا. يتعامل Coil مع ذلك بذكاء؛ فهو يتخطى، افتراضيًا، حركة التلاشي التدريجي عند العثور على الصورة في التخزين المؤقت للذاكرة، ولا يشغّل الحركة إلا للصور التي تم تحميلها فعليًا.
وبذلك تحصلون على تلاشي سلس عند التحميل الأول، وعرض فوري عند التمرير إلى الصور المخزنة مؤقتًا، من دون الحاجة إلى أي عمل إضافي.
import android.content.Context
import coil3.request.ImageRequest
import coil3.request.crossfade
fun feedRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
// Crossfade plays on real loads; memory-cache hits show instantly
.crossfade(true)
.build()قائمة التحقق من الأداء للقوائم
عند عرض صور كثيرة في LazyColumn أو شبكة، اتبعوا القواعد التالية للحصول على تمرير سلس:
- امنحوا كل صورة حجمًا ثابتًا حتى يفك Coil ترميز صورة نقطية صغيرة.
- أعيدوا استخدام
ImageLoaderواحد من نوع singleton حتى تتشارك الطلبات ذواكر التخزين المؤقت. - استخدموا
ContentScale.Cropلتجنب تغيّر التخطيط. - حافظوا على ثبات مفاتيح التخزين المؤقت عبر عمليات إعادة التحميل.
- اتركوا Coil يلغي الطلبات خارج الشاشة تلقائيًا، ولا تحاولوا التدخل في ذلك.
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import coil3.compose.AsyncImage
@Composable
fun PhotoGrid(urls: List<String>) {
LazyVerticalGrid(columns = GridCells.Fixed(3)) {
items(urls) { url ->
AsyncImage(
model = url,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.aspectRatio(1f)
)
}
}
}اختبار سريع
يفحص Coil ذواكر التخزين المؤقت قبل الوصول إلى الشبكة. أيّ مخزن مؤقت يكون الأسرع عند العثور على البيانات فيه، لأنه يتجاوز فك الترميز بالكامل؟
مراجعة
لقد تعلمتم كيف يحافظ Coil على سرعة الصور:
- مخزنان مؤقتان: الذاكرة (مفككة الترميز وسريعة وصغيرة) والقرص (بايتات خام ودائمة وأكبر حجمًا).
- اضبطوا الأحجام في
ImageLoaderمخصص، ووفّروا واحدًا منه عبرSingletonImageLoader.Factory. - اضبطوا السلوك لكل طلب باستخدام
memoryCachePolicyوdiskCachePolicy. - فكوا الترميز دائمًا وفق حجم العرض، واضبطوا
size()صراحةً عند الحاجة. - استخدموا مفاتيح تخزين مؤقت ثابتة، وحمّلوا الصور القادمة مسبقًا، وامسحوا مفاتيح محددة لفرض التحديث.
التالي: الانتقال إلى ما يتجاوز الصور لتشغيل الصوت والفيديو.
الأسئلة الشائعة
هل درس «التخزين المؤقت والأداء» مجاني؟
نعم — نص درس «التخزين المؤقت والأداء» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Android Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Android Academy 4 دروس في المجموع.
ماذا ستتعلم في «التخزين المؤقت والأداء»؟
أساسيات التخزين المؤقت في الذاكرة والقرص تتمرن على Android Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Android Academy؟
لا تُشترط خبرة سابقة. Android Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «التخزين المؤقت والأداء»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Android Academy هذا؟
نعم. كل درس في Android Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تحميل الصور باستخدام Coil
- العناصر النائبة وحالات الخطأ
- التخزين المؤقت والأداء
- تشغيل الصوت والفيديو