-
Notifications
You must be signed in to change notification settings - Fork 0
4week_AOS_tech_post_5
์ด๋ฏธ์ง 60์ฅ์ ์ด์ฉํ ์คํ ์ด๋ฏธ์ง ๊ธฐ๋ฅ์ ๊ตฌํ์ ํ๋ค ์บ์ฑ์ด ์๋๋ ๋ฌธ์ ๋ง์ฃผ
- Coil ImageLoader๋ฅผ ์ฌ์ฉํด์ ํ๋ฆฌ ๋ก๋ฉ์ ๊ตฌํ ํ๋๋ฐ Blinking ํ์ ๋ฐ์
- Log์๋ ์ด๋ฏธ์ง 60์ฅ url์ ๋ค ๋ถ๋ฌ์ค๋ ๊ฒ์ ํ์ธํ๊ณ ์ค์์ดํ ํ์ง๋ง ์ฌ์ ํ Blinking ํ์ ๋ฐ์
- ํ๋ก ํธ์๋ ๋ถ๊ณผ ํจ๊ป ๊ณ ๋ฏผํ ๊ฒฐ๊ณผ ์ฐํ๋
Log
๋ ์ด๋ฏธ์ง URL ์์ฒญ๋ง ํ ์ํ์ด๊ณ ์ค์ ์ด๋ฏธ์ง๋ฅผ ๋ฐ์์ค์ง๋ ์์ ์ํ๋ผ๊ณ ์ถ์ธก (๋น๋๊ธฐ์ฑ์ ๊ณ ๋ คํ์ง ์๊ณ ๋ก๊ทธ ์ฝ๋๋ฅผ ์์ฑํ ๋ฌธ์ )-
์ฝ๋ ๋ฐ ์คํ ๊ฒฐ๊ณผ
fun preloadImages(urls: List<String>) { imageLoader.memoryCache?.clear() val currentMemoryUsage = imageLoader.memoryCache?.size?.div((1024 * 1024)) val maxMemoryCacheSize = imageLoader.memoryCache?.maxSize?.div((1024 * 1024)) Log.d("test", "currentMemoryUsage: ${currentMemoryUsage}") Log.d("test", "maxMemoryCacheSize: ${maxMemoryCacheSize}") CoroutineScope(Dispatchers.IO).launch { val successfulLoads: AtomicInteger = AtomicInteger(0) urls.forEachIndexed { index, url -> val request = ImageRequest.Builder(CaArtApplication.getApplicationContext()) .data(url) .build() Log.d("test", "Call: ${url}") } } }
-
- ๋ ์ ํํ ๋ถ์์ ํ๊ธฐ ์ํด ํ๋ก ํธ์๋ ๋ถ๊ณผ ํจ๊ป
ImageLoader
์ ๋ด๋ถ ์ฝ๋๋ฅผ ๋ถ์ํ๊ธฐ ์์ - ๋ด๋ถ ์ฝ๋๋ฅผ ํ์ธํด๋ณด๋
ImageLoader
์excute
ํจ์๊ฐImageResult
๊ฐ์ฒด๋ฅผ ๋ฐํํ๊ณ ์์ฒญ ์ฑ๊ณต์SuccessResult
ํ์ ์ผ๋ก ๋ฐํ ๋๋ ๊ฒ์ ํ์ธ- ๋ถ์ ์ฝ๋
interface ImageLoader {
...
suspend fun execute(request: ImageRequest): ImageResult
...
}
class ImageRequest() {
...
@JvmDefaultWithCompatibility
interface Listener {
...
@MainThread
fun onSuccess(request: ImageRequest, result: SuccessResult) {}
...
}
...
}
- ํด๋น ๋ฐํ ํ์ ์ ํตํด 60์ฅ ๋ชจ๋ ์ฑ๊ณต์ ์ผ๋ก ์์ฒญํด์ ์ด๋ฏธ์ง ๋ฐ์์ค๋ ๊ฒ๊น์ง ํ์ธ
๋ฌธ์ 2 (์ด๋ฏธ์ง๋ฅผ ๋ชจ๋ ๋ฐ์์์ ์ค์์ดํ๋ฅผ ํด๋ ์ฌ์ ํ ๋ฐ์ํ๋ Blinking Issue)
-
์ดํ ํผ์ ๊ณ ๋ฏผํ ๊ฒฐ๊ณผ ์บ์ฑ์ด ์ ์๋์๋ค๊ณ ์ถ์ธก
-
listener๋ฅผ ํตํด onSuccess์
metadata.dataSource
๋ฅผ ์ด์ฉํด ์บ์ฑ์ด ๋์๋์ง ์๋์๋์ง ํ์ธ-
๋ด๋ถ ์ฝ๋
/** * Represents the source that an image was loaded from. * * @see SourceResult.dataSource * @see DrawableResult.dataSource */ enum class DataSource { /** * Represents an [ImageLoader]'s memory cache. * * This is a special data source as it means the request was * short circuited and skipped the full image pipeline. */ MEMORY_CACHE, /** * Represents an in-memory data source (e.g. [Bitmap], [ByteBuffer]). */ MEMORY, /** * Represents a disk-based data source (e.g. [DrawableRes], [File]). */ DISK, /** * Represents a network-based data source (e.g. [HttpUrl]). */ NETWORK }
-
-
๋๋ฒ๊น ๊ฒฐ๊ณผ ํ๋ฆฌ๋ก๋ฉ ํ ์ด๋ฏธ์ง 60์ฅ์ ์ฌ ํธ์ถํ ๋ Memory Cahing๊ณผ Disk Caching ๋๋ค ํ๋ ๊ฒ์ ํ์ธํ ์ ์์๊ณ DiskCaching์ Blinking ํ์์ด ๋ฐ์ํ๋ค๋ ๊ฒ์ ํ์ธํ ์ ์์์
-
์ฝ๋ ๋ฐ ์คํ ๊ฒฐ๊ณผ
@BindingAdapter("url", "circleCrop", "radius", "blurRadius", "blurSampling", requireAll = false) fun ImageView.setImageSrcWithUrl( url: String?, isCircleCrop: Boolean = false, radius: Float? = null, blurRadius: Int? = null, blurSampling: Float? = null ) { load(url, ImageUtils.imageLoader) { ... listener( onSuccess = { request, metadata -> Log.d("ImageLoading", "Loaded from: ${request.data}") when (metadata.dataSource) { DataSource.MEMORY -> Log.d("ImageLoading", "Loaded from Memory Cache") DataSource.DISK -> Log.d("ImageLoading", "Loaded from Disk Cache") DataSource.NETWORK -> Log.d("ImageLoading", "Loaded from Network") DataSource.MEMORY_CACHE -> Log.d("ImageLoading", "Loaded from MEMORY_CACHE") else -> Log.d("ImageLoading", "Loaded from other") } } ) ... }
-
-
memoryCache
์maxSizePercent
๋ฅผ ํตํด ์บ์ฑ ์ฉ๋์ ์ ์ฒด ๋ฉ๋ชจ๋ฆฌ์ 30%๋ก ์ค์
-
์ฌ์ ํ Blinking Issue๊ฐ ๋ฐ์ํ์ฌ ๋ฉ๋ชจ๋ฆฌ ์บ์ฑ ์ฉ๋์ ์ ์ฒด ๋ฉ๋ชจ๋ฆฌ์ 50%๋ก ์ค์
-
์ค์ ์ฝ๋
val imageLoader: ImageLoader by lazy { ImageLoader.Builder(CaArtApplication.getApplicationContext()) .memoryCache { MemoryCache.Builder(CaArtApplication.getApplicationContext()) .maxSizePercent(0.5) .build() } .build() }
-
-
์ดํ Blinking ํ์์ด ํด๊ฒฐ ๋์์ง๋ง max๊ฐ์ด ๊ณผํ ๊ฐ์ด ์๊ณ ์ค์ ๋ก 50%๋ ํ์ํ์ง ์๋ฌธ์ด ๋ค์ด ์ค์ ์บ์ฑ์ ํ์ํ ์ด ๋ฉ๋ชจ๋ฆฌ ์ฉ๋์ ์ธก์ ํด๋ณด๊ธฐ๋ก ๊ฒฐ์
-
SuceessResult
์bitmap
์ฉ๋์ ์ธก์ ํด ๋ณธ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง ํ์ฅ ๋น1.86MB
๊ฐ ํ์ํ ๊ฒ์ ํ์ธ ๊ฐ๋ฅ-
์ธก์ ์ฝ๋
fun preloadImages(urls: List<String>, onImageLoaded: (Int) -> Unit) { imageLoader.memoryCache?.clear() val currentMemoryUsage = imageLoader.memoryCache?.size?.div((1024 * 1024)) val maxMemoryCacheSize = imageLoader.memoryCache?.maxSize?.div((1024 * 1024)) Log.d("test", "currentMemoryUsage: ${currentMemoryUsage}") Log.d("test", "maxMemoryCacheSize: ${maxMemoryCacheSize}") CoroutineScope(Dispatchers.IO).launch { val successfulLoads: AtomicInteger = AtomicInteger(0) urls.forEachIndexed { index, url -> val request = ImageRequest.Builder(CaArtApplication.getApplicationContext()) .data(url) .build() val result = imageLoader.execute(request) if (result is SuccessResult) { successfulLoads.incrementAndGet() if (result.drawable is BitmapDrawable) { val bitmapSizeInBytes = (result.drawable as BitmapDrawable).bitmap.byteCount val bitmapSizeInMB = bitmapSizeInBytes.toFloat() / (1024 * 1024) Log.d("test", "Size of the cached image in bytes: $bitmapSizeInMB") } } val loadProgress = (successfulLoads.get().toFloat() / urls.size * 100).toInt() onImageLoaded(loadProgress) } } }
-
-
maxSize๋ฅผ 40%๋ก ์ค์ ํ๋ฉด ์บ์ฑํด์ ๋ด์ ์ ์๋ ์ฉ๋์
76MB
์ธ๋ฐ ์๋ฒ์์ ๋ฐ์์จ ์ด๋ฏธ์ง 60์ฅ์ ์ด ํฌ๊ธฐ๋ ์ฝ111MB
์ด๋ค.-
์ค์ ์ฝ๋ ๋ฐ ๋๋ฒ๊น ๊ฒฐ๊ณผ
val imageLoader: ImageLoader by lazy { ImageLoader.Builder(CaArtApplication.getApplicationContext()) .memoryCache { MemoryCache.Builder(CaArtApplication.getApplicationContext()) .maxSizePercent(0.4) .build() } .build() }
-
-
ํ์ฉํ๋ ์บ์ฑ ์ฉ๋์ ๋์ด์๋๋ฐ ์ด๋ป๊ฒ ์บ์ฑ์ด ๋๋์ง ์๋ฌธ์ด ๋ค์ด
ImageLoader
์ ๋ด๋ถ ์ฝ๋๋ฅผ ๋ ๋ถ์ํด๋ณด๊ธฐ๋ก ํจ -
ImageLoader
์ ๋ด๋ถ์ ์ด๋ฏธ์ง๋ฅผ ๋ก๋ฉํด์ ๋ฉ๋ชจ๋ฆฌ์ ์บ์ฑํ๋MemoryCache
ํด๋์ค๋ฅผ ํ์ธํ ์ ์์์-
ImageLoader ๋ด๋ถ ์ฝ๋
interface ImageLoader { ... /** * An in-memory cache of previously loaded images. */ val memoryCache: MemoryCache? ...
-
-
MemoryCahce
ํด๋์ค์ ๋ด๋ถ๋ฅผ ํ์ธํด๋ณด๋ฉด ์บ์ฑํ๋ ์ด๋ฏธ์ง๋ค์Value
ํด๋์ค์์ map์ ํตํด Bitmap์ผ๋ก ๊ด๋ฆฌํ๋ ๊ฒ์ ํ์ธํ ์ ์์๊ณ ์ค์ ์บ์ฑํด์ ์ ์ฅ๋์ด ์๋BitMap
์ ๊ฐ์ ธ์ค๋ ค๋ฉดMemoryChace
ํด๋์ค์์get(key)
๋ฉ์๋๋ฅผ ์ด์ฉํด์ ๊ฐ์ ธ์ฌ ์ ์๋ ๊ฒ์ ํ์ธ -
์ด ํ ์ค์ ์บ์ฑํด์ ์ ์ฅ๋์ด ์๋
BitMap
ํฌ๊ธฐ๋ฅผ ์ธก์ ํด๋ณด๋ ์ค์ ๋ก ์ด78MB
๊ฐ ํ์ํ ๊ฒ์ ํ์ธํ ์ ์์์-
์ธก์ ์ฝ๋ ๋ฐ ์ธก์ ๊ฒฐ๊ณผ
fun preloadImages(urls: List<String>, onImageLoaded: (Int) -> Unit) { imageLoader.memoryCache?.clear() val currentMemoryUsage = imageLoader.memoryCache?.size?.div((1024 * 1024)) val maxMemoryCacheSize = imageLoader.memoryCache?.maxSize?.div((1024 * 1024)) Log.d("test", "currentMemoryUsage: ${currentMemoryUsage}") Log.d("test", "maxMemoryCacheSize: ${maxMemoryCacheSize}") CoroutineScope(Dispatchers.IO).launch { val successfulLoads: AtomicInteger = AtomicInteger(0) urls.forEachIndexed { index, url -> val request = ImageRequest.Builder(CaArtApplication.getApplicationContext()) .data(url) .build() val result = imageLoader.execute(request) if (result is SuccessResult) { successfulLoads.incrementAndGet() } val loadProgress = (successfulLoads.get().toFloat() / urls.size * 100).toInt() if(loadProgress >= 100) { imageLoader.memoryCache?.keys?.forEach { key -> Log.d("test", "realSize: ${imageLoader.memoryCache?.get(key)?.bitmap?.byteCount?.div((1024 * 1024))}") } } onImageLoaded(loadProgress) } } }
-
๐ป [H6-CaArt] โDone is better than perfectโ ๐ป
- [์ต๊ทํ] Sentry ๋์ ๊ธฐ (feat. ๋ก๊ทธ ๊ด๋ฆฌ)
- [์ต๊ทํ] GPT3.5 ๋ชจ๋ธ์ ํ์ฉํด์ ์ถ์ฒ ๋ฌธ๊ตฌ ์์ฝํ๊ธฐ
- [์ต๊ทํ] ์คํ๋ง์์ OpenAI API ์ฑ๋ฅ ๊ฐ์ ๊ธฐ
- [๊ถ๋ฏผ์] CaArt CI/CD ๊ตฌ์ถ ๊ณผ์
- [๊ถ๋ฏผ์] ์ฑํ๋ฅ ๊ณ์ฐ ๋ก์ง ๊ฐ์ - Index tuning๊ณผ Batch processing
- [์ด์นํ] useReducer, useContext๋ฅผ ์ฌ์ฉํ ์ ์ญ ์ํ ๊ด๋ฆฌ with React, TS
- [์ด์นํ] ํ๋กํ ํ์ ํจํด์ด๋?
- [์ด์นํ] UX ๊ฐ์ ์ ์ํ ์ด๋ฏธ์ง ํ๋ฆฌ๋ก๋ฉ With Promise
- [์์ํ] ํ์ ์คํฌ๋ฆฝํธ ์ ๋ค๋ฆญ์ด๋?
- [๋ฐ์ธ์] Android์ Data Binding: DataBindingUtil vs Binding.inflate
- [๋ฐ์ธ์] DI, Hilt ๋ฝ๊ฐ๊ธฐ
- [๋ฐ์ธ์] Retrofit ๋์ ๊ธฐ
- [๋ฐ์ธ์] 360๋ Spin Image Touble Shooting
- [๊น์ ๋น] ์ปค์คํ ๋ค์ด์ผ๋ก๊ทธ ๊ตฌํ