Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MOD/#207] 이미지 압축 로직을 다시한번 개선합니다 :( #210

Merged
merged 11 commits into from
Mar 11, 2025

Conversation

angryPodo
Copy link
Collaborator

@angryPodo angryPodo commented Mar 6, 2025

Related issue 🛠

Work Description ✏️

  • 이미지 압축 스레드 변경
  • 임시 파일 생성으로 OOM방지 였지만 메모리 기반 측정으로 다시 변경
  • ImageDecoder가 EXIF회전의 자동 보정을 지원함으로 Orientation 관련 코드 제거

Uncompleted Tasks 😅

  • 테스트 안해봤어요 서버 고쳐줘잉

To Reviewers 📢

주석으로 바뀐 부분이나 저번에 수정한 부분 설명 첨부해뒀읍니다.
노션으로 문서도 만들긴했는데 좀더 첨삭해서 스푸니 노션에 올려 놓겠습니다요~

3/9 추가

  • 테스트 결과 잘 작동 합니다~

@angryPodo angryPodo added MOD✅ 코드 수정 및 내부 파일 수정 💙민재💙 💙민재💙 labels Mar 6, 2025
@angryPodo angryPodo self-assigned this Mar 6, 2025
@angryPodo angryPodo requested a review from a team as a code owner March 6, 2025 14:47
@angryPodo angryPodo requested review from chattymin, Roel4990 and Hyobeen-Park and removed request for a team March 6, 2025 14:47
@angryPodo angryPodo linked an issue Mar 6, 2025 that may be closed by this pull request
Copy link
Collaborator

@Hyobeen-Park Hyobeen-Park left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

친절한 주석 감사합니다~~ LGTM🚀🚀

* 이 방식은 메모리 기반 측정보다 대용량 이미지 처리 시 OutOfMemory 위험을 낮춥니다.
*/
private fun measureCompressedSize(bitmap: Bitmap, quality: Int): Int {
val tempFile = File.createTempFile("measure", ".tmp")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P5: 새로운거 알아갑니다~

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하지만 삭제~

@angryPodo angryPodo added this to the 2차 스프린트 milestone Mar 6, 2025
Copy link
Member

@Roel4990 Roel4990 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다 LGTM~

Copy link
Collaborator

@chattymin chattymin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다~ 추후 서버 돌아오고 테스트 완료되면 다시 노티주세요! 잘 돌아가는지 테스트하고 머지해야하니까요~ :)

bitmap
}
val orientation = getOrientation(uri)
if (orientation != ORIENTATION_NORMAL) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사소하지만 if-else를 사용한다면 if문 내부에는 긍정을 적어주는게 가독성에 좋습니답
책 어디에 적혀있었는데 그건 기억이 안나네요~

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다! 단순 null체킹 하는 부분의 let을 제거하면서 저렇게 작성했었어요ㅎㅎ
그러나 EXIF관련 조정이 필요없어져서 삭제했습니다

fun toFormData(name: String): MultipartBody.Part =
MultipartBody.Part.createFormData(
name = name,
filename = metadata?.fileName ?: DEFAULT_FILE_NAME,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서버 내부에서 어떻게 처리를 하는지 모르겠지만, 클라이언트에 저장하는 경우에 이름이 같으면 덮어쓰기를 해버려요. 물론 서버에서 이를 처리한다면 괜찮겠지만, 애초에 예방하는 방법으로 timestemp를 사용하는 것도 있답니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 동일 이미지로 여러번 테스트 했을때 덮어쓰기가 발생하지 않는 것을 확인했습니다.
따라서 지금은 timestamp를 추가할 필요가 없지만, 혹시 추후 파일명 충돌 이슈가 생긴다면 적용하겠습니다요

@@ -60,85 +71,122 @@ class ContentUriRequestBody @Inject constructor(
}

init {
uri?.let {
metadata = extractMetadata(it)
if (uri != null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. let을 if로 변경한 이유가 있을까요?
  2. init 부분에서 uri가 null이라면 아래에 있는 모든 로직이 안돌아갈 것 같아요. 코드를 자세히 읽지는 못했지만, uri가 null이라면 Exception을 early return 하는 방식도 있을 것 같네요

Copy link
Collaborator Author

@angryPodo angryPodo Mar 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 단순 null 체크에서는 let보다 if 문이 가독성이 더 좋다고 생각하여 변경했습니다.
  2. urinull일 경우 로직이 실행되지 않는 것은 의도된 부분이긴 합니다만 얼리리턴 생각하면 let으로 유지시켜도 될것 같아요..ㅋㅋㅋㅋ

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (uri == null) {
    return
}

metadata = extractMetadata(uri)

이렇게도 돼서 if문으로 얼리 리턴도 조아요~~

그리고 let의 경우 내부적으로 변수를 하나 만들어서 null check를 하기 때문에 단순한 null check 는 if문을 사용하는게 성능상 더 좋아서 남긴 코멘트입니다! null을 체이닝으로 계속 검사를 해야하거나, let을 쓰는게 가독성이 더 좋은 경우가 있는데 그때는 let을 사용하고 단순 null 체크의 경우 if를 쓰는게 더 조아요
물론... 성능에 거의 차이가 없다싶이해서 let 쓰는것도 전 좋다고 봐요~

@@ -40,6 +42,15 @@ class ContentUriRequestBody @Inject constructor(
}
}

/**
* 이미지 압축을 위한 설정값을 담는 데이터 클래스입니다.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kdoc👍

* loadBitmap(): ImageDecoder를 사용하여 URI에서 비트맵을 로드합니다.
* 이미지 크기는 설정된 최대 크기(maxWidth, maxHeight) 이하로 리사이즈하며,
* EXIF 정보를 고려하여 회전이 필요한 경우 rotateBitmap()을 호출합니다.
*/
private suspend fun loadBitmap(uri: Uri): Result<Bitmap> =
withContext(Dispatchers.IO) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 Dispatchers.IO에서 실행되고 있는 것 같아요. withContext로 나타낸 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 Dispatchers.IO에서 실행하지만 withContext 사용해 I/O 스레드에서의 실행을 보장할수있어서 따로 명시했습니다!

- 1MB 미만 파일은 압축을 건너뛰고 100% 품질을 사용하도록 `compressImage()` 리팩토링.
- CPU 집약적 작업을 `Dispatchers.Default`에서 처리하고 메모리 효율적 압축을 위해 버퍼 크기를 동적으로 조정하도록 `compressBitmap()` 최적화.
- 동일한 URI에 대한 중복 이미지 처리를 방지하기 위해 `ConcurrentHashMap`을 사용한 캐싱 추가.
- `prepareImage()`에 대한 여러 동시 호출이 발생할 때 첫 번째 압축 작업의 결과를 공유하기 위한 지연 실행 메커니즘 구현.
- 최소 버퍼 크기를 보장하기 위해 `max()` 함수 사용.
- 메타데이터에 mimeType 추가.
`RegisterRepositoryImpl` 내에서 바디가 생성되는 방식을 수정합니다. 이제 `ContentUriRequestBody`를 직접 인스턴스화하는 대신 `ContentUriRequestBody.getOrCreate` 메서드를 활용합니다.
Copy link
Collaborator

@chattymin chattymin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Member

@Roel4990 Roel4990 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오,, 설명이 자세해서 너무 좋네요. 수고하셨습니다...!

Copy link
Collaborator

@Hyobeen-Park Hyobeen-Park left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어푸푸🚀🚀

Comment on lines +80 to +81
@Volatile
private var prepareImageDeferred: Deferred<Result<Unit>>? = null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와 진짜 잘 모르는건데 공부하겠습니다...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어려워잉..

@angryPodo angryPodo merged commit e7685fe into develop Mar 11, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
MOD✅ 코드 수정 및 내부 파일 수정 💙민재💙 💙민재💙
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[MOD] ContentUriRequestBody를 수정합니다.
4 participants