نمایش تصاویر با کیفیت بالا، بدون افت فریم و با مدیریت بهینه حافظه رم، یکی از ارکان اصلی توسعه اپلیکیشن های مدرن اندروید است. با گذار صنعت موبایل از View های کلاسیک به رابط کاربری اعلانی، استفاده از کتابخانه های قدیمی پردازش تصویر با چالش های جدی در مدیریت بازسازی یا Recomposition مواجه شد.
بر اساس بررسی های بنچمارک رسمی مهندسی اندروید، کتابخانه Coil به دلیل نگارش ۱۰۰ درصد بر پایه کاتلین و کورتین ها، تا ۶۰ درصد متد کمتری نسبت به Glide به پکیج نهایی APK اضافه می کند. این سبکی فوق العاده موجب کاهش زمان اجرای بیلد و صرفه جویی چشمگیر در مصرف منابع سی پی یو می گردد.
بنچمارک مهندسی: مقایسه متدهای دکس (DEX) و حجم APK
کتابخانه Coil تنها حدود ۲,۰۰۰ متد به پکیج اجرایی APK اضافه می کند؛ در حالی که Glide بیش از ۷,۰۰۰ متد تحمیل می نماید. این کاهش ۶۰ درصدی بار متدها، مانع از رسیدن پروژه به سقف ۶۴K متد (MultiDex) و کاهش زمان بیلد کاتلین می گردد.
کتابخانه مدرن Coil مخفف Coroutine Image Loader است که از ابتدا با تفکر هماهنگی کامل با کامپوننت های مدرن کاتلین و معماری Jetpack Compose مهندسی شده است. این ابزار به جای ایجاد ترد پول های سنگین و موازی اختصاصی، کارهای پس زمینه را به Coroutine Dispatchers های استاندارد محول می سازد.
در این راهنمای جامع و مرجع تخصصی، معماری مدرن کتابخانه Coil، شیوه کارکرد کامپوننت های AsyncImage، پیکربندی سفارشی ImageLoader، کش دو لایه رم و دیسک، تبدیل تصاویر، ایجاد اینترسپتورهای اختصاصی، تست نویسی واحد و تکنیک های پیشرفته برای جلوگیری از خطای کمبود حافظه OutOfMemory را بررسی خواهیم کرد.
چرا کتابخانه Coil انتخابی استاندارد برای مدرن اندروید است؟
برای سال های متمادی، کتابخانه های Glide و Picasso انتخاب های بی بدیل توسعه دهندگان اندروید بودند. با این حال، هر دو کتابخانه با معماری زبان جاوا طراحی شده بودند و ساختار درونی آنها وابستگی شدیدی به اکتیویتی ها، فرگمنت ها و چرخه حیات کدهای XML کلاسیک داشت.
ورود چارچوب آموزش جامع Jetpack Compose شیوه رندر رابط کاربری را دگرگون ساخت. در کامپوز، ویوها از بین رفته اند و جای خود را به توابع تابعی اعلانی داده اند که به دفعات مکرر بازخوانی می شوند. کتابخانه Coil بدون هیچ لایه اضافی یا Wrapper های واسط، کامپوننت بومی کامپوز را ارایه می دهد.
یکی از بزرگ ترین مزایای Coil، استفاده مستقیم از کلاینت استاندارد OkHttp برای ارتباطات شبکه است. این ویژگی موجب می شود تا نشست های امنیتی، اینترسپتورهای لاگ، کش های سرور و توکن های احراز هویت با شبکه کلی اپلیکیشن شما به صورت یکپارچه به اشتراک گذاشته شوند.
علاوه بر این، در مسیر حرفه ای آموزش برنامه نویسی اندروید تسلط بر کتابخانه هایی که حجم متدهای نهایی را پایین نگه می دارند برای عملکرد بهینه ضروری است. کتابخانه Coil تنها حدود ۲ هزار متد به دکس اضافه می کند، در حالی که کتابخانه های قدیمی تر تا ۷ هزار متد به نرم افزار تحمیل می کنند.
یکپارچگی عمیق Coil با معماری مدرن کاتلین موجب می شود تا تمامی متدهای بارگذاری به جای کال بک های تودرتو (Callback Hell)، از توابع تعلیق شونده suspend استفاده نمایند. این ساختار تمیز، هماهنگی بی نقصی با جریان های غیرهمزمان و معماری لایه ای اپلیکیشن ایجاد می کند.
معماری خط لوله اجرایی و پردازش در کورتین ها
فرآیند لود تصویر در کتابخانه Coil از یک معماری چند مرحله ای و ماژولار پیروی می کند. هسته پردازشی این زنجیره شامل بخش های تفکیک شده ImageRequest، زنجیره Interceptor ها، لایه واکشی Fetcher و دیکودر است که همگی تحت نظر Coroutine Dispatcher اجرا می گردند.
نقطه آغاز با نمونه سازی از کلاس ImageRequest شکل می گیرد. این شیء حاوی اطلاعات کامل منبع تصویر (آدرس اینترنتی، فایل محلی یا فایل ریسورس)، ابعاد درخواستی کانتینر، حالت های مقیاس گذاری Scale و تنظیمات کش است.
پس از ساخت درخواست، پایپ لاین Interceptor ها وارد عمل می شود. لایه رهگیر موتور (EngineInterceptor) ابتدا حافظه رم (MemoryCache) را بر اساس کلید یکتای آدرس و اندازه درخواست بررسی می کند. در صورت وجود تصویر در رم، بلافاصله نتیجه بدون رجوع به دیسک یا شبکه آماده نمایش می شود.
در صورت نبود تصویر در رم، وظیفه دریافت به دیسپچر کورتین منتقل می شود. این یکپارچگی عمیق باعث می شود تا هنگام خروج کاربر از صفحه یا اسکرول سریع لیست ها، جاب کورتین مربوط به واکشی شبکه به صورت خودکار کنسل شود و پهنای باند بیهوده تلف نگردد.
بهره گیری اصولی از این خط لوله پردازشی نیازمند درک عمیق مدیریت همزمانی در کاتلین با کورتین ها و Flow است؛ زیرا مدیریت ساختاریافته تردها در Coil مانع از مسدود شدن Main Thread و قفل شدن فریم های صفحه می شود.
هنگامی که بایت های تصویر توسط OkHttp دریافت می شوند، دیکودر مناسب (مانند BitmapDecoder یا SvgDecoder) وارد عمل شده و استریم خام بایت ها را به یک نمونه بهینه از Bitmap تبدیل می نماید. در طول این پروسه، نسبت ابعاد صفحه و تراکم پیکسلی نمایشگر برای جلوگیری از مصرف حافظه لحاظ می گردد.
مدیریت خودکار ابعاد: کتابخانه Coil به طور پیش فرض ابعاد واقعی اندازه المان UI را در کامپوز اندازه گیری می کند و تصویر را متناسب با اندازه صفحه در حافظه دیکود می نماید تا از تخصیص بیهوده مگابایت های اضافی رم جلوگیری شود.
نحوه نصب و راه اندازی Coil در پروژه اندروید
برای افزودن کتابخانه مدرن Coil به پروژه کامپوز، کافی است وابستگی های رسمی آن را در فایل build.gradle ماژول اپلیکیشن اضافه کنید. بسته پایه شامل اکستنشن های لازم برای کار مستقیم با Jetpack Compose است.
// build.gradle.kts (Module: app)
dependencies {
// کتابخانه پایه Coil ویژه کامپوز
implementation("io.coil-kt:coil-compose:2.7.0")
// ماژول اختیاری جهت پشتیبانی از فرمت وکتور SVG
implementation("io.coil-kt:coil-svg:2.7.0")
// ماژول اختیاری جهت پشتیبانی از فایل های متحرک GIF
implementation("io.coil-kt:coil-gif:2.7.0")
}
در صورتی که پروژه شما به ساختار چندپلتفرمی مهاجرت کرده است، نسخه Coil ۳ امکان اجرای کدهای کامپوز را علاوه بر اندروید بر روی دسکتاپ و iOS نیز با بسته اختصاصی io.coil-kt.coil۳ فراهم می کند.
همچنین اطمینان حاصل کنید که مجوز دسترسی به اینترنت در فایل AndroidManifest.xml ثبت شده باشد تا بارگذاری آنلاین تصاویر با مشکل عدم دسترسی مواجه نگردد.
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
کار با کامپوننت AsyncImage در Jetpack Compose
اصلی ترین و پرکاربردترین کامپوننت رابط کاربری در این کتابخانه، کامپوزبل AsyncImage است. این کامپوننت جایگزین مستقیم Image در کامپوز بوده و تمامی قابلیت های لود غیرهمزمان تصاویر را بدون پیچیدگی پیاده سازی می کند.
@Composable
fun UserProfileAvatar(imageUrl: String, modifier: Modifier = Modifier) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.crossfade(true)
.crossfade(300)
.build(),
contentDescription = "تصویر پروفایل کاربر",
contentScale = ContentScale.Crop,
placeholder = painterResource(R.drawable.ic_avatar_placeholder),
error = painterResource(R.drawable.ic_image_error),
modifier = modifier
.size(80.dp)
.clip(CircleShape)
)
}
در کد فوق، با استفاده از متد crossfade(true)، افکت محو شوندگی روان با زمان ۳۰۰ میلی ثانیه اعمال می شود. این انیمیشن از پرش ناگهانی تصویر پس از دانلود از شبکه جلوگیری می کند و تجربه کاربری نرمی خلق می نماید.
شما می توانید پارامتر model را به عنوان یک شیء String ساده (آدرس URL) یا یک ImageRequest پیشرفته پاس دهید. استفاده از ImageRequest زمانی الزامی است که نیاز به تنظیم لیسنرها، اندازه های دلخواه یا ترنسفورمیشن های تصویری داشته باشید.
مدیریت پیشرفته استیت ها با SubcomposeAsyncImage
در مواردی که نیاز دارید هنگام لود شدن تصویر یک ویجت کامپوز اختصاصی مانند Shimmer بارگذاری متحرک، یا دکمه تلاش مجدد در صورت بروز خطا نمایش دهید، کامپوزبل SubcomposeAsyncImage راهکار مناسبی است.
@Composable
fun ProductCoverWithShimmer(imageUrl: String) {
SubcomposeAsyncImage(
model = imageUrl,
contentDescription = "کاور محصول فروشگاه",
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth().height(220.dp)
) {
val state = painter.state
when (state) {
is AsyncImagePainter.State.Loading -> {
Box(
modifier = Modifier.fillMaxSize().background(Color.LightGray),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(strokeWidth = 2.dp)
}
}
is AsyncImagePainter.State.Error -> {
Box(
modifier = Modifier.fillMaxSize().background(Color(0xFFFEE2E2)),
contentAlignment = Alignment.Center
) {
Text("خطا در بارگذاری تصویر", color = Color.Red)
}
}
else -> {
SubcomposeAsyncImageContent()
}
}
}
}
کامپوننت SubcomposeAsyncImage با بهره گیری از مفهوم ساب کامپوزیشن، استیت درونی پینتر را در هر فاز پایش می کند. شما می توانید در هر وضعیت، کانتینر بصری سفارشی سازی شده مد نظرتان را به کاربر ارائه دهید.
این کامپوننت با متد SubcomposeAsyncImageContent() تصویر نهایی لود شده را روی صفحه ترسیم می کند. این جداسازی دقیق حالات، کنترل کاملی بر روی تجربه بصری کاربر در ارتباطات کند اینترنت فراهم می آورد.
هشدار کارایی در لیست های طویل: استفاده از SubcomposeAsyncImage به دلیل هزینه های ساب کامپوزیشن برای آیتم های سریع LazyColumn توصیه نمی شود. برای لیست های پرسرعت همیشه از AsyncImage استاندارد همراه با placeholder استفاده نمایید.
استراتژی کش دو لایه رم و دیسک در Coil
مدیریت اصولی سیستم ذخیره سازی موقت کش، تفاوت میان یک نرم افزار سریع و روان با اپلیکیشنی است که مدام دچار لک و کرش های سنگین می شود. کتابخانه Coil از معماری کش دو سطحی پیشرفته استفاده می نماید.
لایه اول کش در حافظه رم (MemoryCache) قرار دارد. این لایه خود شامل دو استخر مجزا است: مراجع قوی (Strong References) که بر اساس الگوریتم تخلیه کم کاربردترین ها (LRU) مدیریت می شوند، و مراجع ضعیف (Weak References) که به زباله روب (Garbage Collector) اجازه می دهد در شرایط فشار حافظه فضا را بدون خطای OOM آزاد کند.
لایه دوم کش در دیسک ذخیره سازی (DiskCache) جای می گیرد. این لایه پاسخ های باینری خام سرور را با استفاده از موتور استاندارد DiskLruCache در حافظه داخلی ذخیره می کند. حتی در صورت بستن کامل برنامه یا قطع اتصال اینترنت، تصاویر بلافاصله از حافظه داخلی لود می شوند.
ارزیابی کارایی این دو سطح کش ارتباط مستقیمی با مباحث پایش مصرف منابع دارد. برای بررسی وضعیت زنده تخصیص بیت مپ ها، مطالعه آموزش پروفایلینگ در اندروید و بهینه سازی مصرف رم دید عمیقی از سلامت چرخه حیات نرم افزار در اختیارتان می گذارد.
کلید کش رم در Coil شامل مقادیر ترکیبی آدرس منبع، ابعاد درخواستی کانتینر، ترنسفورمیشن های اعمال شده و پارامترهای اختیاری است. این هوشمندی مانع از بازگردانی تصاویر تار یا با ابعاد نادرست برای ویوهای متفاوت می شود.
ساخت نمونه سفارشی ImageLoader برای پروژه های سازمانی
برای کنترل دقیق سقف مصرف رم و دیسک، هدرهای امنیتی شبکه و افزودن اینترسپتورها، بهتر است یک نمونه اختصاصی از کلاس ImageLoader در کلاس Application پروژه پیکربندی نمایید.
class MyApplication : Application(), ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this)
.memoryCache {
MemoryCache.Builder(this)
// اختصاص حداکثر ۲۵ درصد از سقف رم در دسترس اپلیکیشن
.maxSizePercent(0.25)
.strongReferencesEnabled(true)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(cacheDir.resolve("image_cache"))
.maxSizeBytes(120L * 1024 * 1024) // سقف ۱۲۰ مگابایت کش دیسک
.build()
}
.okHttpClient {
OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build()
}
.respectCacheHeaders(false) // کش محلی حتی در صورت نبود هدرهای کامل سرور
.crossfade(true)
.build()
}
}
با پیاده سازی اینترفیس ImageLoaderFactory بر روی کلاس Application، این تنظیمات به صورت پیش فرض توسط تمامی کامپوننت های AsyncImage در سراسر اپلیکیشن بدون نیاز به پاس دادن دستی پارامترها به کار گرفته می شوند.
این یکپارچگی همچنین امکان هماهنگی کامل بین لایه تصاویر با کتابخانه های رتروفیت را مهیا می سازد. با تنظیمات مشترک شبکه که در آموزش رتروفیت Retrofit در اندروید آموخته اید، احراز هویت با Bearer Token ها در درخواست های تصویر نیز به راحتی پشتیبانی می شود.
شما می توانید در پروپوزال های معماری بزرگ، نمونه های چندگانه ای از ImageLoader برای بخش های با الزامات کش متفاوت (مثلا تصاویر موقت چت در برابر آواتارهای پایدار) با تزریق وابستگی ایجاد نمایید.
پایپ لاین رهگیرها (Interceptors) و منطق سفارشی در Coil
یکی از قدرتمندترین قابلیت های پیشرفته در معماری Coil، مفهوم زنجیره رهگیرها یا Interceptor ها است. این الگو دقیقا مشابه رهگیرهای معروف OkHttp عمل می کند و امکان دستکاری درخواست قبل یا بعد از پردازش را به توسعه دهنده می دهد.
با استفاده از اینترفیس Interceptor می توانید قابلیت هایی نظیر تزریق هدرهای اختصاصی، ثبت زمان دریافت تصویر در ابزار های آنالیتیکس و جایگزینی خودکار تصاویر منقضی شده با آدرس های رزرو را پیاده سازی نمایید.
class AuthHeaderInterceptor(private val tokenProvider: () -> String) : Interceptor {
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val originalRequest = chain.request
val token = tokenProvider()
val authenticatedRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer $token")
.addHeader("X-Client-Platform", "Android-Compose")
.build()
return chain.proceed(authenticatedRequest)
}
}
برای فعال سازی این رهگیر، کافی است نمونه آن را در متد components در سازنده ImageLoader اضافه کنید. تمامی درخواست های خروجی ابتدا از این فیلتر عبور کرده و هدرهای امنیتی به صورت پویا به درخواست ضمیمه می شوند.
val imageLoader = ImageLoader.Builder(context)
.components {
add(AuthHeaderInterceptor { sessionManager.getAuthToken() })
}
.build()
تبدیل و گرد کردن گوشه های تصویر با Transformations
کتابخانه Coil از قابلیت اعمال افکت ها و تبدیلات گرافیکی روی بیت مپ ها پشتیبانی می کند. تبدیلات پرکاربرد به صورت پیش فرض در کتابخانه تعبیه شده اند و نیازی به کد نویسی محاسبات ریاضی ندارند.
@Composable
fun RoundedBannerImage(bannerUrl: String) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(bannerUrl)
.transformations(
RoundedCornersTransformation(
topLeft = 16f,
topRight = 16f,
bottomLeft = 0f,
bottomRight = 0f
)
)
.build(),
contentDescription = "بنر تبلیغاتی با گوشه های گرد",
modifier = Modifier.fillMaxWidth().height(160.dp),
contentScale = ContentScale.Crop
)
}
علاوه بر RoundedCornersTransformation، کلاس CircleCropTransformation برای دایره ای کردن تصاویر بدون نیاز به کلیپ های سنگین رابط کاربری در دسترس است که پردازش را مستقیما در سطح پردازنده گرافیکی بهینه می سازد.
شما همچنین می توانید ترنسفورمیشن های سفارشی سازی شده نظیر افکت تارشدگی (Blur) یا سیاه و سفید کردن را با پیاده سازی اینترفیس Transformation و متد transform بنویسید. در این متد، یک نمونه Bitmap ورودی دریافت می کنید و بیت مپ تغییر یافته را برمی گردانید.
class GrayscaleTransformation : Transformation {
override val cacheKey: String = "grayscale_transformation"
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
val output = Bitmap.createBitmap(input.width, input.height, input.config ?: Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val paint = Paint().apply {
colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { setSaturation(0f) })
}
canvas.drawBitmap(input, 0f, 0f, paint)
return output
}
}
پشتیبانی از فرمت های برداری SVG و انیمیشن های GIF
یکی از نیازمندی های پرتکرار در اپلیکیشن ها، نمایش آیکون های برداری مقیاس پذیر SVG و گیف های متحرک است. به جای نوشتن کد های کاستوم با وب ویو، کافی است دیکودرهای مربوطه را به سازنده رجیستری Coil معرفی کنید.
val customImageLoader = ImageLoader.Builder(context)
.components {
// اضافه کردن دیکودر اختصاصی برای فایل های برداری SVG
add(SvgDecoder.Factory())
// دیکودر کارآمد برای پردازش فایل های گیف
if (Build.VERSION.SDK_INT >= 28) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
}
.build()
با این پیکربندی ساده، پاس دادن یک فایل با پسوند .svg به کامپوننت AsyncImage به صورت هوشمندانه توسط دیکودر برداری شناسایی و با بالاترین رزولوشن بدون افت کیفیت ترسیم می گردد.
پشتیبانی از انیمیشن های گیف نیز با بهره گیری از ImageDecoderDecoder در اندروید پای (API ۲۸) به بالا، پردازش انیمیشن را مستقیما به سیستم عامل محول می کند که باعث کاهش شدید مصرف باتری و دمای پردازنده گوشی می گردد.
تکنیک های پیشگیری از خطای نشت حافظه و OOM
خطای کمبود حافظه OutOfMemory زمانی رخ می دهد که بیت مپ هایی با ابعاد بزرگتر از کانتینر صفحه در حافظه رم دیکود شوند. به عنوان مثال، دیکود کردن یک عکس ۱۲ مگاپیکسلی در اندازه کامل، نیازمند بیش از ۴۸ مگابایت رم پیوسته در فرمت ARGB_۸۸۸۸ است.
برای پیشگیری از این رویداد در لیست های اسکرولی مانند LazyColumn و LazyVerticalGrid، نکات مهندسی زیر را همواره رعایت نمایید:
- تنظیم ابعاد دقیق کانتینر: همیشه ابعاد Modifier را به صورت صریح با اندازه های dp مشخص کنید تا اندازه گیر درونی Coil تصویر را با فاکتور نمونه برداری صحیح (Downsampling) دیکود کند.
- بهره گیری از کلیدهای یکتا در لیست: در LazyColumn حتما پارامتر
keyرا مقداردهی نمایید تا با بازسازی آیتم ها، درخواست های تصویر دوباره ارسال نشوند. - پیش بارگذاری تصاویر (Preloading): برای اسکرول فوق العاده روان با ۱۲۰ فریم بر ثانیه، می توانید تصاویر آیتم های بعدی را با متد
imageLoader.enqueue()در حافظه کش آماده کنید. - استفاده از فرمت سخت افزاری Hardware Bitmap: در اندروید نسخه ۸.۰ به بالا، بیت مپ های سخت افزاری مستقیما در حافظه گرافیکی Graphic Memory ذخیره می شوند و فضای هیپ اپلیکیشن را اشغال نمی کنند.
- غیرفعال سازی سخت افزاری هنگام ترسیم سفارشی: اگر قصد اعمال فیلتر پیکسلی دستی روی بیت مپ دارید، متد
allowHardware(false)را فراخوانی کنید تا خطای بازگشت کانواس سخت افزاری ایجاد نشود.
با ترکیب این راهکارها، اپلیکیشن شما حتی در هنگام اسکرول سریع میان صدها تصویر در لیست های سنگین تجارت الکترونیک، نرخ ۶۰ یا ۱۲۰ فریم بر ثانیه را بدون کوچک ترین پرش یا نشت حافظه حفظ می نماید.
استراتژی لود تصاویر در معماری های آفلاین (Offline-First)
در اپلیکیشن های مدرن اندروید که از معماری آفلاین فرست پیروی می کنند، کاربر باید بتواند حتی بدون اتصال فعال اینترنت محتوای قبلی و تصاویر محصولات را مشاهده کند. مدیریت این سناریو نیازمند هماهنگی دقیق میان دیتابیس محلی، کش دیسک Coil و تنظیمات هدرهای شبکه است.
برای پیاده سازی این سناریو، می توانید سیاست کش شبکه را با استفاده از کلاس CachePolicy کنترل نمایید. هنگامی که دستگاه آفلاین است، با تنظیم حالت CachePolicy.ENABLED برای دیسک و غیرفعال سازی شبکه، برنامه مستقیما داده را از حافظه داخلی فراخوانی می کند.
val offlineRequest = ImageRequest.Builder(context)
.data(imageUrl)
.networkCachePolicy(if (isOnline) CachePolicy.ENABLED else CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.memoryCachePolicy(CachePolicy.ENABLED)
.build()
همچنین در پروژه هایی که تصاویر ابتدا در حافظه داخلی ذخیره شده و مسیر آنها در دیتابیس محلی ثبت می شود، می توان آدرس فایل های محلی را مستقیما به Coil ارسال کرد. موتور Coil به صورت هوشمند نوع پروتکل را شناسایی می کند و تصویر را با همان پایپ لاین بهینه و بدون سربار شبکه دیکود می نماید.
علاوه بر این، برای جلوگیری از پاک شدن تصاویر کلیدی در پاکسازی های خودکار سیستم عامل، می توانید دایرکتوری کش دیسک را به جای cacheDir بر روی filesDir تنظیم کنید تا تصاویر حیاتی برنامه همواره در حافظه پایدار بمانند.
تست نویسی واحد و کامپوز بدون وابستگی به شبکه
در تیم های بزرگ مهندسی نرم افزار، نوشتن تست های خودکار رابط کاربری (UI Tests) نقشی حیاتی در پایداری پروژه دارد. اگر تست های کامپوز شما برای لود تصاویر به اینترنت وابسته باشند، قطعی موقت سرور موجب شکست بی دلیل پایپ لاین CI/CD می شود.
خوشبختانه معماری کامپوز امکان تعویض آسان نمونه ImageLoader را از طریق CompositionLocalProvider فراهم ساخته است. شما می توانید در محیط تست، یک Fake ImageLoader تزریق کنید که در کسری از ثانیه یک بیت مپ ثابت و فوری را بازگرداند.
@Test
fun userProfileAvatar_displaysCorrectly() {
val fakeEngine = FakeImageLoaderEngine.Builder()
.intercept("https://example.com/avatar.jpg", ColorDrawable(Color.BLUE))
.build()
val testImageLoader = ImageLoader.Builder(context)
.components { add(fakeEngine) }
.build()
composeTestRule.setContent {
CompositionLocalProvider(LocalImageLoader provides testImageLoader) {
UserProfileAvatar(imageUrl = "https://example.com/avatar.jpg")
}
}
composeTestRule.onNodeWithContentDescription("تصویر پروفایل کاربر")
.assertIsDisplayed()
}
با این الگوی مهندسی، تست های خودکار با سرعت فوق العاده اجرا شده و آزمون های مربوط به نمایش آواتارها و کاورها بدون هیچ وابستگی خارجی یا دسترسی به وب سرور ارزیابی می گردند.
مقایسه تخصصی کتابخانه های برتر لود تصویر در اندروید
انتخاب ابزار مناسب بر اساس معماری، زبان پایه پروژه و نسخه فریم ورک انجام می پذیرد. در جدول زیر، مقایسه جامعی از قابلیت های کلیدی Coil در برابر سایر رقبا ارائه شده است:
| کتابخانه تصویر | سازگاری کامل کامپوز | زبان اصلی پایه | سربار متد به APK | پشتیبانی KMP | سیستم همزمانی |
|---|---|---|---|---|---|
| Coil (پیشنهادی) | کاملا بومی (AsyncImage) | ۱۰۰٪ کاتلین | حدود ۲,۰۰۰ متد | بله (در Coil ۳) | کورتین های کاتلین |
| Glide | از طریق اکستنشن واسط | جاوا | بیش از ۷,۰۰۰ متد | خیر | ترد پول های جاوا |
| Picasso | محدود | جاوا | حدود ۲,۵۰۰ متد | خیر | کلاینت OkHttp |
| Fresco | پیچیده با DraweeView | سی پلاس پلاس و جاوا | بیش از ۱۲,۰۰۰ متد | خیر | حافظه Ashmem |
هنگام برنامه ریزی برای مقیاس پذیری بلندمدت در پروژه های سازمانی، ارزیابی قابلیت های نسخه Coil ۳ اهمیت بسزایی دارد. در نسخه ۳، وابستگی مستقیم به پلتفرم اندروید کاملا ماژولار شده و بسته هسته کتابخانه قابلیت استفاده در پلتفرم های دسکتاپ، وب و iOS را فراهم می آورد. این ویژگی باعث می شود تا معماری لود تصاویر در پروژه های چندپلتفرمی به صورت یکپارچه نگه داری شود.
همچنین در Coil ۳ مدیریت حافظه رم با بهره گیری از مکانیسم های بهینه سازی موتور KMP و پشتیبانی بومی از فرمت های مدرن WebP و AVIF ارتقا یافته است. به کارگیری این فرمت ها، پهنای باند مصرفی کاربران را تا ۳۵ درصد نسبت به JPEG کاهش می دهد و سرعت بارگذاری صفحات را در شبکه های موبایلی به حداکثر می رساند.
برای تکمیل مهارت های معماری و پیاده سازی سیستم های واکنشی در کاتلین، مقالات تخصصی زیر را به عنوان مراجع تکمیلی مطالعه نمایید:
یکی دیگر از ملاحظات مهندسی در توسعه رابط های کاربری پیشرفته، سازگاری تصاویر و پلیس هولدرها با تم تیره (Dark Mode) و پالت های رنگی داینامیک متریال دیزاین ۳ است. در کامپوز می توانید با بررسی تابع isSystemInDarkTheme، رنگ کانتینر لودینگ و آیکون های خطا را به صورت خودکار تغییر دهید تا کنتراست بصری چشم نوازی خلق شود.
همچنین هماهنگی با شبکه های توزیع محتوا (CDN) برای ارسال ابعاد بهینه تصاویر بر اساس تراکم پیکسلی نمایشگر گوشی، باعث صرفه جویی شدید در ترافیک اینترنت و زمان بارگذاری می شود. با پاس دادن عرض و ارتفاع مورد نظر به کوئری استرینگ های CDN در سازنده ImageRequest، سرور دقیقا تصویر تغییر اندازه یافته را ارسال می نماید.
پرسش های متداول توسعه دهندگان درباره کتابخانه Coil
تفاوت اصلی کتابخانه Coil با Glide در اندروید چیست؟
کتابخانه Coil از ابتدا با زبان کاتلین و کورتین ها توسعه یافته است و به صورت بومی با Jetpack Compose کار می کند، در حالی که Glide کدهای پایه جاوا دارد و حجم متدهای اضافه شده به فایل APK در Coil تا یک سوم گلاید است.
آیا کتابخانه Coil از فرمت های برداری SVG پشتیبانی می کند؟
بله، با اضافه کردن وابستگی coil-svg و ثبت دیکودر SvgDecoder.Factory در سازنده ImageLoader، تمامی فایل های SVG به صورت خودکار با کیفیت رتینا رندر می شوند.
چرا هنگام اسکرول در LazyColumn تصاویر پرش دارند یا دوباره لود می شوند؟
عدم تعیین پارامتر کلید یکتا (key) برای آیتم های LazyColumn یا نمونه سازی مکرر از ImageRequest در هر بار Recomposition موجب این مسئله است. همواره برای آیتم ها شناسه یکتا تعریف کنید و استیت های مدل را با ثبات نگه دارید.
چه تفاوتی میان کامپوزبل AsyncImage و SubcomposeAsyncImage وجود دارد؟
کامپوزبل AsyncImage سبک تر، سریع تر و برای لیست های بلند مناسب تر است. کامپوزبل SubcomposeAsyncImage امکان تعریف ویجت های کامپوز سفارشی برای حالت های در حال بارگذاری و خطا را می دهد اما سربار ری کامپوزیشن بالاتری دارد.
چگونه می توان زمان انقضای کش دیسک را در Coil مدیریت کرد؟
کش دیسک Coil از منطق استاندارد HTTP Cache هدرهای OkHttp پیروی می کند. با متد respectCacheHeaders(false) می توانید تصاویر را صرف نظر از تنظیمات سرور در حافظه محلی پایدار نگه دارید.
آیا کتابخانه Coil در پروژه های کاتلین مولتی پلتفرم (KMP) کار می کند؟
بله، در نسخه Coil ۳ معماری این کتابخانه برای پشتیبانی از چندپلتفرمی بازنویسی شده و بدون نیاز به پلتفرم اندروید روی Compose Multiplatform در iOS، Desktop و Web کار می کند.
چگونه می توان هدرهای احراز هویت توکن دار را به درخواست های Coil اضافه کرد؟
با تزریق یک نمونه سفارشی OkHttpClient حاوی اینترسپتور احراز هویت (Auth Interceptor) به سازنده ImageLoader، تمامی درخواست های بارگذاری تصویر هدرهای لازم را دریافت می کنند.
چرا بیت مپ های سخت افزاری (Hardware Bitmaps) در برخی ترسیم های کانواس خطا می دهند؟
بیت مپ های سخت افزاری مستقیما در بافر حافظه گرافیکی ذخیره می شوند و قابلیت تغییر پیکسل توسط نرم افزار یا ترسیم روی Canvas نرم افزاری را ندارند. در صورت نیاز به ویرایش پیکسلی، باید متد allowHardware(false) را در درخواست فعال کنید.
بهترین استراتژی برای پیش بارگذاری (Preloading) تصاویر در لیست ها چیست؟
با استفاده از متد enqueue در شیء ImageLoader و ساخت ImageRequest برای آیتم های بعدی که هنوز وارد دید کاربر نشده اند، بایت های تصویر از قبل واکشی و در کش دیسک یا رم ذخیره می گردند.
چگونه می توان از Coil برای نمایش تصاویر فایل های محلی یا گالری استفاده کرد؟
کتابخانه Coil به صورت خودکار انواع ورودی ها شامل File، Uri، ریسورس های R.drawable و ByteArray را پشتیبانی می کند و نیاز به تنظیمات اختصاصی اضافی ندارد.


امتیاز و نظرات
هنوز نظری ثبت نشده است. اولین نفر باشید.