1. 안드로이드의 해상도 대응
안드로이드 기기들은 다양한 화면 크기를 가지고 있다. 어떤 기기에서는 인치당 160 픽셀을 사용하지만, 다른 기기에서는 480픽셀을 사용할 수 있다. 따라서 이런 차이를 고려하지 않는다면 이미지가 깨지거나 잘못된 크기로 보여질 수 있다.
이 같은 상황에 대응하기 위해 안드로이드에서는 View 간의 거리나 크기를 정의할 때 픽셀을 사용하지 않도록 권장하고 있다. 기기마다 픽셀 밀도가 다르므로 고정된 픽셀 값을 사용하면 기기의 크기에 따라 유동적으로 적용되지 않기 때문이다.

안드로이드에서는 픽셀 대신 dp를 사용한다. 이는 Density-Independent Pixel의 약자로, 밀도 독립형 픽셀이다.
표준 단위인 dpi를 기반으로 하여 중밀도(mdpi, 160dpi)를 기준으로 논리적 화면 밀도인 dp를 정의하고, 이 값을 실제 픽셀로 변환하게 된다.
위 그림을 예시로 들면, 100 픽셀 크기의 뷰는 높은 dpi를 가진 우측 기기에서 훨씬 작게 보이지만 100dp로 정의된 경우에는 동일한 크기로 보여지게 된다.
px = dp * (dpi / 160)
2. 해상도 별 Bitmap 사용하기
다양한 dpi를 가지는 기기에서 좋은 품질의 이미지를 제공하기 위해 앱에서는 해상도별로 여러 버전의 Bitmap을 제공해야 한다. 중밀도에 대응하는 Bitmap만 제공할 경우, 높은 픽셀 밀도를 가지는 기기에서는 이미지가 깨질 수 있다.

안드로이드에서는 일반적으로 중밀도(mdpi)를 기준으로, 총 5가지 버전의 dpi에 대한 대응을 필요로 한다.
저밀도(ldpi), 중고밀도(tvdpi)도 있으나, 일반적인 모바일 기기에는 잘 사용하지 않는다.
4K Tv에도 xxxdpi를 사용하도록 권장하고 있으니, 사실상 대응할 일이 없다.
res/
drawable-xxxhdpi/
awesome_image.png
drawable-xxhdpi/
awesome_image.png
drawable-xhdpi/
awesome_image.png
drawable-hdpi/
awesome_image.png
drawable-mdpi/
awesome_image.png
위처럼 각 dpi별 drawable 디렉토리에 이미지 파일을 추가하면, 시스템이 화면의 dpi를 기반으로 적절한 비트맵을 선택한다.
또한 시스템이 자동으로 비트맵의 크기를 조정하는 것을 원치 않는 경우에는 nodpi를 사용할 수 있다. nodpi 한정자를 사용하는 디렉토리에 추가하면 시스템이 크기를 자동으로 dpi에 맞춰 조정하는 것을 방지할 수 있다.
res/drawable-nodpi/icon.png
리소스에 적용 가능한 다양한 한정자들은 Google developers의 앱 리소스 페이지에서 확인할 수 있다.
앱 리소스 개요 | App architecture | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 앱 리소스 개요 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 리소스는 코드에서 사용하는 추가 파
developer.android.com
3. dpi와 ppi
안드로이드 Developers 페이지에서는 리소스 처리를 위해 dpi 기반의 밀도 버킷을 사용하고 있지만,
안드로이드 기기들의 스펙 시트를 보면 ppi로 표현이 되어있는 것을 볼 수 있다.
- dpi : Dots-per Inch
- ppi : Pixels-per Inch
이 둘은 서로 비슷하지만 관점에 따라서는 완전히 다른 개념이다.
dpi는 프린팅 유닛(프린터)에 사용되고, ppi는 디스플레이 유닛(스마트폰)에 사용되기 때문에, 'Dot'은 이미지를 표현하기 위한 하나의 점을 말하며, Pixel은 디스플레이를 구성하는 한개의 점을 말한다.
이 둘의 정확한 개념의 차이를 여기서 알 수 있는데, 'Dot'에 비해 'Pixel'은 각각이 나타낼 수 있는 정보량이 압도적으로 많다는 것이다. 몇백 ppi를 가지는 스마트폰의 디스플레이가 몇천 dpi를 가지는 사진보다 훨씬 많은 정보를 담고 있으니 말이다.
안드로이드에서는 물리적 해상도인 ppi를 보정하여 densityDpi(mdpi, hdpi, 등)로 변환해서 사용한다.
4. 해상도 별 이미지 분류 과정 개선
나는 보통 프로젝트를 시작할 때 디자인 시스템을 먼저 구축해 두는데, 기존의 사이드 프로젝트를 진행할 때에는 Figma에서 export한 이미지들을 일일이 해상도 별 디렉토리에 분류했었다.
이 과정이 굉장히 비효율적이고 번거로워서, 좀 더 편하게 할 수 있는 방법이 없을까 찾아보다 쉘 스크립트와 run configuration 설정을 통한 원클릭 분류를 도입하게 되었다.
✨ 쉘 스크립트
다른 블로그 포스팅에서 아이디어를 얻어서 그 스크립트를 그대로 적용하려고 해봤는데, 생각보다 설명이 많이 생략되어 있어서 여러 문제에 부딪혔다.
- 모듈명
- 해상도별 폴더 경로
- 파일명 정규식
- 해상도별 폴더에 저장될 파일 명
위 항목들은 자신의 프로젝트 설정이나 네이밍 규칙에 맞게 수정해서 사용하면 된다.
난 리소스 네이밍에 스네이크 케이스를 사용하기 때문에, 언더바를 사용하기 위해 정규식을 수정했다.
#!/bin/bash
# ----------------------------------
# 1. 경로 체크 & 모듈명 설정
# ----------------------------------
CUR_DIR=$(pwd | grep -o '[^/]*$')
if [[ $CUR_DIR != "icon" ]]
then
if [ -d icon ]
then
cd icon
else
echo "[Error] 올바른 스크립트 실행 경로가 아닙니다 프로젝트 루트나 icon 디렉토리에서 bash 로 실행시켜 주세요"
exit 1
fi
fi
MODULE_NAME=$1
if [[ $MODULE_NAME = "" ]]
then
MODULE_NAME="presentation" # 필요한 경우 모듈명 변경
fi
echo "[Info] 모듈 이름은 $MODULE_NAME 입니다"
MODULE_PATH="../$MODULE_NAME"
# ----------------------------------
# 2. 해상도별 폴더 경로 준비
# >>> 필요한 경우 경로 수정 <<<
# ----------------------------------
declare -A DENSITY_PATHS=(
["mdpi"]="$MODULE_PATH/src/main/res/drawable"
["hdpi"]="$MODULE_PATH/src/main/res/drawable-hdpi"
["xhdpi"]="$MODULE_PATH/src/main/res/drawable-xhdpi"
["xxhdpi"]="$MODULE_PATH/src/main/res/drawable-xxhdpi"
["xxxhdpi"]="$MODULE_PATH/src/main/res/drawable-xxxhdpi"
)
# 폴더가 없으면 생성
for density in "${!DENSITY_PATHS[@]}"; do
mkdir -p "${DENSITY_PATHS[$density]}" >/dev/null 2>&1
done
# ----------------------------------
# 3. 파일명 검사 정규식
# >>> 소문자 알파벳, 언더스코어, 숫자 + 밀도 스케일 펙터 + png
# ----------------------------------
VALID_ASSET_REGEX="[a-z0-9_]+(@1.5x|@2x|@3x|@4x)*\.png$"
# 해상도별 접미사로 폴더 매핑
declare -A SUFFIX_TO_DENSITY=(
["@1.5x"]="hdpi"
["@2x"]="xhdpi"
["@3x"]="xxhdpi"
["@4x"]="xxxhdpi"
)
FAILED=false
# ----------------------------------
# 4. png 파일 순회하며 이동
# ----------------------------------
for f in *.png; do
# *.png 자체를 읽은 경우 건너뛰기
[[ $f == "*.png" ]] && continue
# 정규식 검사
if [[ ! $f =~ $VALID_ASSET_REGEX ]]; then
echo "[Error] $f 는 올바른 이미지 이름이 아닙니다."
FAILED=true
continue
fi
# 기본값: mdpi
targetDensity="mdpi"
targetSuffix=""
# @1.5x / @2x / @3x / @4x 중 하나가 있으면 해당 해상도로 변경
for suffix in "${!SUFFIX_TO_DENSITY[@]}"; do
if [[ $f == *"$suffix.png" ]]; then
targetDensity="${SUFFIX_TO_DENSITY[$suffix]}"
targetSuffix="$suffix"
break
fi
done
# 실제 옮겨질 파일명 (예: "icon@2x.png" → "icon.png")
destFileName="${f/%$targetSuffix.png/.png}"
# 최종 경로
destPath="${DENSITY_PATHS[$targetDensity]}/$destFileName"
echo "Moving $f → $destPath"
mv "$f" "$destPath"
done
# ----------------------------------
# 5. 스크립트 종료
# ----------------------------------
if [ "$FAILED" = true ]; then
exit 1
fi
echo "[Info] 모든 비트맵이 정상적으로 처리되었습니다."
✨ 안드로이드 스튜디오 Run configuration 추가
(1) Edit Configurations

(2) Shell script Configuration 추가

(3) Configuration 작성
Name, Script path, Working directory, Interpreter path을 입력한다.
- Script path
- icon.sh 파일의 경로를 넣어주면 된다.
- 난 프로젝트 레벨에 icon 폴더를 추가하고, 그 안에 icon.sh를 작성했다.
- Working directory
- 쉘 스크립트를 통해 해상도별 폴더에 분류될 이미지들이 존재하는 경로를 입력한다.
- 스크립트 실행 이후에는 해당 이미지들이 icon 폴더에서 해상도별 폴더로 이동하기 때문에 icon 폴더에는 스크립트 파일만 남게 된다.
- Interpreter path
- 사용할 CLI 실행 파일의 경로를 입력한다.
- 난 CLI로 bash를 사용했다.
난 bash를 사용했기 때문에 Execute in the terminal의 체크를 해제해줬다.

✨ 실행
(1) 작성한 Configuration으로 변경한다.

(2) 이미지를 icon 폴더에 추가한다.

피그마에서 총 5 종류를 export 하면 된다.
- 1x (기준 밀도, mdpi)
- 1.5x (고밀도, hdpi)
- 2x (초고밀도, xhdpi)
- 3x (초초고밀도, xxhdpi)
- 4x (초초초고밀도, xxxhdpi)
export 옵션에서 Suffix가 제대로 작성되어 있는지 확인해야 한다. 그렇지 않으면 파일명을 여러 번 변경해야 하는 불상사가 발생하게 된다.

✨ 결과

완벽하게 자동화하지는 못했지만, 번거로운 프로세스를 개선함으로써 생산성이 향상되었다.
❓ DP를 사용하면 모든 화면에서 동일한 크기로 보여질까?
'개발 > Android' 카테고리의 다른 글
[안드로이드] 난독화와 R8 컴파일러 (0) | 2025.03.17 |
---|---|
[안드로이드] Vector Drawable 변환 과정과 Bitmap(feat.dp를 사용하면 모든 화면에서 동일하게 보일까?) (0) | 2025.03.06 |
[안드로이드] ART의 GC는 어떻게 동작할까(feat.Dalvik) (0) | 2025.02.17 |
[안드로이드] 화면 회전과 ViewModel (0) | 2025.02.13 |
[안드로이드] Gson에서 Moshi로 마이그레이션하기 (0) | 2024.12.30 |
1. 안드로이드의 해상도 대응
안드로이드 기기들은 다양한 화면 크기를 가지고 있다. 어떤 기기에서는 인치당 160 픽셀을 사용하지만, 다른 기기에서는 480픽셀을 사용할 수 있다. 따라서 이런 차이를 고려하지 않는다면 이미지가 깨지거나 잘못된 크기로 보여질 수 있다.
이 같은 상황에 대응하기 위해 안드로이드에서는 View 간의 거리나 크기를 정의할 때 픽셀을 사용하지 않도록 권장하고 있다. 기기마다 픽셀 밀도가 다르므로 고정된 픽셀 값을 사용하면 기기의 크기에 따라 유동적으로 적용되지 않기 때문이다.

안드로이드에서는 픽셀 대신 dp를 사용한다. 이는 Density-Independent Pixel의 약자로, 밀도 독립형 픽셀이다.
표준 단위인 dpi를 기반으로 하여 중밀도(mdpi, 160dpi)를 기준으로 논리적 화면 밀도인 dp를 정의하고, 이 값을 실제 픽셀로 변환하게 된다.
위 그림을 예시로 들면, 100 픽셀 크기의 뷰는 높은 dpi를 가진 우측 기기에서 훨씬 작게 보이지만 100dp로 정의된 경우에는 동일한 크기로 보여지게 된다.
px = dp * (dpi / 160)
2. 해상도 별 Bitmap 사용하기
다양한 dpi를 가지는 기기에서 좋은 품질의 이미지를 제공하기 위해 앱에서는 해상도별로 여러 버전의 Bitmap을 제공해야 한다. 중밀도에 대응하는 Bitmap만 제공할 경우, 높은 픽셀 밀도를 가지는 기기에서는 이미지가 깨질 수 있다.

안드로이드에서는 일반적으로 중밀도(mdpi)를 기준으로, 총 5가지 버전의 dpi에 대한 대응을 필요로 한다.
저밀도(ldpi), 중고밀도(tvdpi)도 있으나, 일반적인 모바일 기기에는 잘 사용하지 않는다.
4K Tv에도 xxxdpi를 사용하도록 권장하고 있으니, 사실상 대응할 일이 없다.
res/
drawable-xxxhdpi/
awesome_image.png
drawable-xxhdpi/
awesome_image.png
drawable-xhdpi/
awesome_image.png
drawable-hdpi/
awesome_image.png
drawable-mdpi/
awesome_image.png
위처럼 각 dpi별 drawable 디렉토리에 이미지 파일을 추가하면, 시스템이 화면의 dpi를 기반으로 적절한 비트맵을 선택한다.
또한 시스템이 자동으로 비트맵의 크기를 조정하는 것을 원치 않는 경우에는 nodpi를 사용할 수 있다. nodpi 한정자를 사용하는 디렉토리에 추가하면 시스템이 크기를 자동으로 dpi에 맞춰 조정하는 것을 방지할 수 있다.
res/drawable-nodpi/icon.png
리소스에 적용 가능한 다양한 한정자들은 Google developers의 앱 리소스 페이지에서 확인할 수 있다.
앱 리소스 개요 | App architecture | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 앱 리소스 개요 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 리소스는 코드에서 사용하는 추가 파
developer.android.com
3. dpi와 ppi
안드로이드 Developers 페이지에서는 리소스 처리를 위해 dpi 기반의 밀도 버킷을 사용하고 있지만,
안드로이드 기기들의 스펙 시트를 보면 ppi로 표현이 되어있는 것을 볼 수 있다.
- dpi : Dots-per Inch
- ppi : Pixels-per Inch
이 둘은 서로 비슷하지만 관점에 따라서는 완전히 다른 개념이다.
dpi는 프린팅 유닛(프린터)에 사용되고, ppi는 디스플레이 유닛(스마트폰)에 사용되기 때문에, 'Dot'은 이미지를 표현하기 위한 하나의 점을 말하며, Pixel은 디스플레이를 구성하는 한개의 점을 말한다.
이 둘의 정확한 개념의 차이를 여기서 알 수 있는데, 'Dot'에 비해 'Pixel'은 각각이 나타낼 수 있는 정보량이 압도적으로 많다는 것이다. 몇백 ppi를 가지는 스마트폰의 디스플레이가 몇천 dpi를 가지는 사진보다 훨씬 많은 정보를 담고 있으니 말이다.
안드로이드에서는 물리적 해상도인 ppi를 보정하여 densityDpi(mdpi, hdpi, 등)로 변환해서 사용한다.
4. 해상도 별 이미지 분류 과정 개선
나는 보통 프로젝트를 시작할 때 디자인 시스템을 먼저 구축해 두는데, 기존의 사이드 프로젝트를 진행할 때에는 Figma에서 export한 이미지들을 일일이 해상도 별 디렉토리에 분류했었다.
이 과정이 굉장히 비효율적이고 번거로워서, 좀 더 편하게 할 수 있는 방법이 없을까 찾아보다 쉘 스크립트와 run configuration 설정을 통한 원클릭 분류를 도입하게 되었다.
✨ 쉘 스크립트
다른 블로그 포스팅에서 아이디어를 얻어서 그 스크립트를 그대로 적용하려고 해봤는데, 생각보다 설명이 많이 생략되어 있어서 여러 문제에 부딪혔다.
- 모듈명
- 해상도별 폴더 경로
- 파일명 정규식
- 해상도별 폴더에 저장될 파일 명
위 항목들은 자신의 프로젝트 설정이나 네이밍 규칙에 맞게 수정해서 사용하면 된다.
난 리소스 네이밍에 스네이크 케이스를 사용하기 때문에, 언더바를 사용하기 위해 정규식을 수정했다.
#!/bin/bash
# ----------------------------------
# 1. 경로 체크 & 모듈명 설정
# ----------------------------------
CUR_DIR=$(pwd | grep -o '[^/]*$')
if [[ $CUR_DIR != "icon" ]]
then
if [ -d icon ]
then
cd icon
else
echo "[Error] 올바른 스크립트 실행 경로가 아닙니다 프로젝트 루트나 icon 디렉토리에서 bash 로 실행시켜 주세요"
exit 1
fi
fi
MODULE_NAME=$1
if [[ $MODULE_NAME = "" ]]
then
MODULE_NAME="presentation" # 필요한 경우 모듈명 변경
fi
echo "[Info] 모듈 이름은 $MODULE_NAME 입니다"
MODULE_PATH="../$MODULE_NAME"
# ----------------------------------
# 2. 해상도별 폴더 경로 준비
# >>> 필요한 경우 경로 수정 <<<
# ----------------------------------
declare -A DENSITY_PATHS=(
["mdpi"]="$MODULE_PATH/src/main/res/drawable"
["hdpi"]="$MODULE_PATH/src/main/res/drawable-hdpi"
["xhdpi"]="$MODULE_PATH/src/main/res/drawable-xhdpi"
["xxhdpi"]="$MODULE_PATH/src/main/res/drawable-xxhdpi"
["xxxhdpi"]="$MODULE_PATH/src/main/res/drawable-xxxhdpi"
)
# 폴더가 없으면 생성
for density in "${!DENSITY_PATHS[@]}"; do
mkdir -p "${DENSITY_PATHS[$density]}" >/dev/null 2>&1
done
# ----------------------------------
# 3. 파일명 검사 정규식
# >>> 소문자 알파벳, 언더스코어, 숫자 + 밀도 스케일 펙터 + png
# ----------------------------------
VALID_ASSET_REGEX="[a-z0-9_]+(@1.5x|@2x|@3x|@4x)*\.png$"
# 해상도별 접미사로 폴더 매핑
declare -A SUFFIX_TO_DENSITY=(
["@1.5x"]="hdpi"
["@2x"]="xhdpi"
["@3x"]="xxhdpi"
["@4x"]="xxxhdpi"
)
FAILED=false
# ----------------------------------
# 4. png 파일 순회하며 이동
# ----------------------------------
for f in *.png; do
# *.png 자체를 읽은 경우 건너뛰기
[[ $f == "*.png" ]] && continue
# 정규식 검사
if [[ ! $f =~ $VALID_ASSET_REGEX ]]; then
echo "[Error] $f 는 올바른 이미지 이름이 아닙니다."
FAILED=true
continue
fi
# 기본값: mdpi
targetDensity="mdpi"
targetSuffix=""
# @1.5x / @2x / @3x / @4x 중 하나가 있으면 해당 해상도로 변경
for suffix in "${!SUFFIX_TO_DENSITY[@]}"; do
if [[ $f == *"$suffix.png" ]]; then
targetDensity="${SUFFIX_TO_DENSITY[$suffix]}"
targetSuffix="$suffix"
break
fi
done
# 실제 옮겨질 파일명 (예: "icon@2x.png" → "icon.png")
destFileName="${f/%$targetSuffix.png/.png}"
# 최종 경로
destPath="${DENSITY_PATHS[$targetDensity]}/$destFileName"
echo "Moving $f → $destPath"
mv "$f" "$destPath"
done
# ----------------------------------
# 5. 스크립트 종료
# ----------------------------------
if [ "$FAILED" = true ]; then
exit 1
fi
echo "[Info] 모든 비트맵이 정상적으로 처리되었습니다."
✨ 안드로이드 스튜디오 Run configuration 추가
(1) Edit Configurations

(2) Shell script Configuration 추가

(3) Configuration 작성
Name, Script path, Working directory, Interpreter path을 입력한다.
- Script path
- icon.sh 파일의 경로를 넣어주면 된다.
- 난 프로젝트 레벨에 icon 폴더를 추가하고, 그 안에 icon.sh를 작성했다.
- Working directory
- 쉘 스크립트를 통해 해상도별 폴더에 분류될 이미지들이 존재하는 경로를 입력한다.
- 스크립트 실행 이후에는 해당 이미지들이 icon 폴더에서 해상도별 폴더로 이동하기 때문에 icon 폴더에는 스크립트 파일만 남게 된다.
- Interpreter path
- 사용할 CLI 실행 파일의 경로를 입력한다.
- 난 CLI로 bash를 사용했다.
난 bash를 사용했기 때문에 Execute in the terminal의 체크를 해제해줬다.

✨ 실행
(1) 작성한 Configuration으로 변경한다.

(2) 이미지를 icon 폴더에 추가한다.

피그마에서 총 5 종류를 export 하면 된다.
- 1x (기준 밀도, mdpi)
- 1.5x (고밀도, hdpi)
- 2x (초고밀도, xhdpi)
- 3x (초초고밀도, xxhdpi)
- 4x (초초초고밀도, xxxhdpi)
export 옵션에서 Suffix가 제대로 작성되어 있는지 확인해야 한다. 그렇지 않으면 파일명을 여러 번 변경해야 하는 불상사가 발생하게 된다.

✨ 결과

완벽하게 자동화하지는 못했지만, 번거로운 프로세스를 개선함으로써 생산성이 향상되었다.
❓ DP를 사용하면 모든 화면에서 동일한 크기로 보여질까?
'개발 > Android' 카테고리의 다른 글
[안드로이드] 난독화와 R8 컴파일러 (0) | 2025.03.17 |
---|---|
[안드로이드] Vector Drawable 변환 과정과 Bitmap(feat.dp를 사용하면 모든 화면에서 동일하게 보일까?) (0) | 2025.03.06 |
[안드로이드] ART의 GC는 어떻게 동작할까(feat.Dalvik) (0) | 2025.02.17 |
[안드로이드] 화면 회전과 ViewModel (0) | 2025.02.13 |
[안드로이드] Gson에서 Moshi로 마이그레이션하기 (0) | 2024.12.30 |