Skip to content

4week_AOS_tech_post_5

sey2 edited this page Jan 9, 2024 · 3 revisions

๋ฌธ์ œ 1 (Blinking Issue ๋ฐœ์ƒ)

์ด๋ฏธ์ง€ 60์žฅ์„ ์ด์šฉํ•œ ์Šคํ•€ ์ด๋ฏธ์ง€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„์„ ํ•˜๋‹ค ์บ์‹ฑ์ด ์•ˆ๋˜๋Š” ๋ฌธ์ œ ๋งˆ์ฃผ

Untitled

  • 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}")
                  }
              }
          }

image

  • ๋” ์ •ํ™•ํ•œ ๋ถ„์„์„ ํ•˜๊ธฐ ์œ„ํ•ด ํ”„๋ก ํŠธ์—”๋“œ ๋ถ„๊ณผ ํ•จ๊ป˜ 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")
                      }
                  }
              )
      			...
      }

image

  • memoryCache์˜ maxSizePercent๋ฅผ ํ†ตํ•ด ์บ์‹ฑ ์šฉ๋Ÿ‰์„ ์ „์ฒด ๋ฉ”๋ชจ๋ฆฌ์˜ 30%๋กœ ์„ค์ •

๋ฌธ์ œ 3 (๋ฉ”๋ชจ๋ฆฌ ์šฉ๋Ÿ‰์„ ์˜ฌ๋ ค๋„ ์กฐ๊ธˆ์”ฉ ๋ฐœ์ƒํ•˜๋Š” Blinking Issue)

  • ์—ฌ์ „ํžˆ 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)
                  }
              }
          }

image

๋ฌธ์ œ 4 (์‹ค์ œ ์บ์‹ฑํ•  ์ˆ˜ ์žˆ๋Š” ์šฉ๋Ÿ‰์„ ์ดˆ๊ณผํ•ด๋„ ์ •์ƒ ์ž‘๋™ํ•˜๋Š” ๋ฌธ์ œ)

  • maxSize๋ฅผ 40%๋กœ ์„ค์ •ํ•˜๋ฉด ์บ์‹ฑํ•ด์„œ ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ์šฉ๋Ÿ‰์€ 76MB ์ธ๋ฐ ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ์ด๋ฏธ์ง€ 60์žฅ์˜ ์ด ํฌ๊ธฐ๋Š” ์•ฝ 111MB ์ด๋‹ค.

    • ์„ค์ • ์ฝ”๋“œ ๋ฐ ๋””๋ฒ„๊น… ๊ฒฐ๊ณผ

      val imageLoader: ImageLoader by lazy {
              ImageLoader.Builder(CaArtApplication.getApplicationContext())
                  .memoryCache {
                      MemoryCache.Builder(CaArtApplication.getApplicationContext())
                          .maxSizePercent(0.4)
                          .build()
                  }
                  .build()
          }

      Untitled

  • ํ—ˆ์šฉํ•˜๋Š” ์บ์‹ฑ ์šฉ๋Ÿ‰์„ ๋„˜์–ด์„œ๋Š”๋ฐ ์–ด๋–ป๊ฒŒ ์บ์‹ฑ์ด ๋˜๋Š”์ง€ ์˜๋ฌธ์ด ๋“ค์–ด 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)
                  }
              }
          }

image


๊ฒฐ๊ณผ

Untitled (1)

๐Ÿ’ป Projects

๐Ÿค Rules

โ˜€๏ธ Meetings

๐ŸŒต Reviews

1์ฃผ์ฐจ
2์ฃผ์ฐจ
3์ฃผ์ฐจ
4์ฃผ์ฐจ

๐ŸŒˆ Scrums

1์ฃผ์ฐจ
2์ฃผ์ฐจ
3์ฃผ์ฐจ
4์ฃผ์ฐจ

๐Ÿ›  Tech Posts & Mini seminar

๐Ÿ’ช๐Ÿผ [BE]

๐Ÿ›ค [FE]

๐Ÿ›ธ [AOS]


Clone this wiki locally