Android 앱 크기 줄이기 완벽 가이드 — R8, Resource Shrinking, AAB, 이미지와 SQLite 최적화

Android 앱을 개발하다 보면 기능이 추가될수록 앱 크기가 커지는 문제가 발생합니다.

특히 이미지와 데이터베이스를 앱 내부에 포함하는 프로젝트에서는 소스 코드보다 리소스가 훨씬 많은 공간을 차지하기도 합니다.

예를 들어 다음과 같은 상황입니다.

  • 조류나 식물의 사진을 수백 장 포함하는 도감 앱

  • SQLite 데이터베이스를 assets 폴더에 포함하는 정보 검색 앱

  • 여러 Product Flavor에서 서로 다른 데이터를 사용하는 앱

  • Retrofit, Glide, AdMob 등 여러 라이브러리를 사용하는 앱

  • WebView와 네이티브 화면을 함께 사용하는 앱

앱 크기가 커지면 다운로드에 필요한 데이터가 증가하고, 설치에 필요한 저장 공간도 많아집니다.

또한 앱 업데이트 시 사용자에게 전달되는 데이터량이 증가할 수 있습니다.

그렇다면 Android 앱 크기를 줄이려면 무엇부터 확인해야 할까요?

무조건 이미지를 압축하거나 라이브러리를 삭제하는 것보다 앱의 어느 부분이 실제로 용량을 차지하는지 분석하는 것이 먼저입니다.

이번 글에서는 Android Studio와 Gradle을 이용해 앱 크기를 분석하고, R8, Resource Shrinking, Android App Bundle, 이미지 및 SQLite 데이터베이스를 최적화하는 방법을 알아보겠습니다.

작성 기준: 2026년 10월. Android Gradle Plugin과 Google Play의 배포 방식은 변경될 수 있으므로 실제 프로젝트에 적용할 때 공식 문서를 확인해야 합니다.


1. APK, AAB, 설치 용량은 서로 다르다

Android 앱 크기를 최적화할 때 가장 먼저 구분해야 하는 개념입니다.

구분의미
APK 크기APK 파일 자체의 크기
AAB 크기Google Play에 업로드하는 Android App Bundle 크기
다운로드 크기사용자가 기기에 다운로드하는 데이터 크기
설치 용량기기에 설치된 앱 파일이 사용하는 공간
앱 데이터 용량SQLite, 캐시, 사용자 데이터 등 앱이 실행하면서 저장한 공간

이 값들은 서로 같지 않습니다.

예를 들어 AAB 파일이 100MB라고 해서 모든 사용자가 100MB를 다운로드하는 것은 아닙니다.

Google Play는 App Bundle을 기반으로 기기 구성에 맞는 APK를 생성하고 제공할 수 있습니다.

반대로 앱이 설치된 뒤 SQLite 데이터베이스를 복사하거나 이미지를 캐시에 저장한다면 앱 데이터 용량이 추가로 증가할 수 있습니다.

따라서 앱 크기를 분석할 때는 다음 세 가지를 구분하는 것이 좋습니다.

  1. 배포 파일 크기

  2. 실제 다운로드 크기

  3. 설치 후 앱과 데이터가 차지하는 공간

2. Android 앱 용량을 차지하는 주요 요소

Android 앱은 여러 구성 요소로 이루어져 있습니다.

Android App
    │
    ├── DEX
    │    └── Kotlin / Java 코드
    │
    ├── Resources
    │    ├── Drawable
    │    ├── Layout
    │    └── Values
    │
    ├── Assets
    │    ├── SQLite DB
    │    ├── JSON
    │    └── 이미지
    │
    ├── Native Libraries
    │    └── .so 파일
    │
    └── 기타 메타데이터

실제 프로젝트에서 어떤 요소가 가장 큰지는 앱의 특성에 따라 다릅니다.

일반적인 정보 검색 앱에서는 이미지와 데이터베이스가 큰 비중을 차지할 수 있습니다.

반면 게임이나 영상 처리 앱에서는 네이티브 라이브러리와 리소스가 더 큰 비중을 차지할 수 있습니다.

3. Android Studio APK Analyzer 사용하기

앱 크기를 줄이기 전에 APK Analyzer로 내부 구성을 확인하는 것이 좋습니다.

Android Studio에서 다음 메뉴를 이용할 수 있습니다.

Build → Analyze APK

분석할 APK 또는 AAB를 선택하면 내부 파일 구조와 크기를 확인할 수 있습니다.

대표적으로 다음 항목을 살펴봅니다.

  • classes.dex

  • res/

  • assets/

  • lib/

  • resources.arsc

특히 파일의 원본 크기와 압축된 크기를 구분해서 확인하는 것이 중요합니다.

예를 들어 SQLite 파일은 원본 크기와 APK 내부에서 압축된 크기가 다를 수 있습니다.

이미 JPEG나 WebP로 압축된 이미지는 ZIP 압축으로 추가로 줄어드는 양이 적을 수 있습니다.

4. 최적화 전 기준값 기록하기

최적화 작업은 변경 전후를 비교할 수 있어야 합니다.

다음과 같은 표를 만들어 기록하면 좋습니다.

측정 항목변경 전변경 후
릴리스 APK 크기측정측정
릴리스 AAB 크기측정측정
이미지 리소스 총크기측정측정
SQLite DB 크기측정측정
DEX 크기측정측정
실제 기기 다운로드 크기측정측정
설치 후 저장 공간측정측정

여기서 중요한 것은 동일한 빌드 조건을 사용하는 것입니다.

디버그 APK와 R8이 활성화된 릴리스 AAB를 직접 비교하면 최적화 효과를 정확하게 판단하기 어렵습니다.

가능하면 동일한 릴리스 설정과 동일한 테스트 기기를 사용해야 합니다.


5. R8로 코드 크기 줄이기

R8은 Android 앱의 코드 축소와 최적화, 난독화를 담당하는 도구입니다.

사용하지 않는 클래스와 메서드를 제거하거나 코드를 최적화할 수 있습니다.

app/build.gradle.kts에서 릴리스 빌드를 다음과 같이 설정할 수 있습니다.

android {

    buildTypes {

        release {

            isMinifyEnabled = true

            proguardFiles(
                getDefaultProguardFile(
                    "proguard-android-optimize.txt"
                ),
                "proguard-rules.pro"
            )
        }
    }
}

isMinifyEnabled = true를 사용하면 R8 코드 최적화를 활성화할 수 있습니다.

하지만 R8은 앱의 모든 코드를 무조건 제거하는 것이 아닙니다.

정적 분석 결과와 keep 규칙 등을 바탕으로 제거하거나 유지할 코드를 판단합니다.

따라서 리플렉션을 사용하는 라이브러리는 추가적인 확인이 필요합니다.

6. R8 적용 시 Retrofit과 Gson 주의사항

Retrofit과 Gson을 사용하는 앱에서는 R8 적용 후 JSON 파싱이 정상적으로 동작하는지 확인해야 합니다.

예를 들어 다음과 같은 DTO가 있다고 가정하겠습니다.

data class BirdDto(
    val id: Long,
    val name: String,
    val scientificName: String
)

Gson은 필드 이름을 기준으로 JSON을 매핑할 수 있습니다.

R8이 필드 이름을 변경하면 예상하지 못한 문제가 발생할 수 있습니다.

JSON 필드 이름을 명시하려면 @SerializedName을 사용할 수 있습니다.

data class BirdDto(

    @SerializedName("id")
    val id: Long,

    @SerializedName("name")
    val name: String,

    @SerializedName("scientificName")
    val scientificName: String
)

다만 @SerializedName만으로 모든 리플렉션 문제가 해결되는 것은 아닙니다.

Retrofit과 Gson 버전에 따라 필요한 R8 규칙을 확인해야 합니다.

또한 모든 클래스를 무조건 keep하는 방식은 R8의 최적화 효과를 줄입니다.

7. 불필요하게 넓은 keep 규칙 제거하기

다음과 같은 규칙은 주의해야 합니다.

-keep class com.example.app.** { *; }

이 규칙은 앱 패키지의 광범위한 클래스와 멤버를 유지하도록 지정합니다.

실제로 필요하지 않은 코드까지 보존되어 앱 크기 감소 효과가 줄어들 수 있습니다.

따라서 다음 순서로 관리하는 것이 좋습니다.

  1. 라이브러리가 제공하는 기본 규칙 확인

  2. 릴리스 빌드 실행

  3. 기능 테스트

  4. 문제가 발생한 클래스만 필요한 범위로 keep

  5. APK Analyzer로 변경 결과 확인

특히 오래된 블로그에서 복사한 ProGuard 규칙이 프로젝트에 남아 있다면 실제로 필요한지 점검하는 것이 좋습니다.


8. Resource Shrinking 활성화하기

Android 앱에는 사용하지 않는 이미지나 문자열, 레이아웃이 남아 있을 수 있습니다.

Resource Shrinking은 사용하지 않는 리소스를 제거하는 데 도움이 됩니다.

android {

    buildTypes {

        release {

            isMinifyEnabled = true

            isShrinkResources = true
        }
    }
}

Resource Shrinking은 일반적으로 코드 축소와 함께 사용합니다.

예를 들어 더 이상 사용하지 않는 Drawable이 있다면 제거 대상이 될 수 있습니다.

하지만 모든 리소스를 완벽하게 판단할 수 있는 것은 아닙니다.

특히 다음과 같이 리소스 이름을 문자열로 조합해 사용하는 경우 주의해야 합니다.

val imageName = "bird_$birdId"

val resourceId = resources.getIdentifier(
    imageName,
    "drawable",
    requireContext().packageName
)

이러한 동적 접근 방식은 정적 분석에서 사용 여부를 파악하기 어려울 수 있습니다.

리소스가 제거되지 않거나, 반대로 필요한 리소스의 보존 규칙을 검토해야 하는 상황이 생길 수 있습니다.

가능하면 리소스를 명시적으로 참조하거나 데이터와 리소스 ID를 연결하는 구조를 사용하는 것이 좋습니다.

9. 불필요한 리소스 직접 정리하기

Resource Shrinking을 사용하더라도 프로젝트 내부의 불필요한 파일을 정리하는 것이 좋습니다.

대표적인 대상은 다음과 같습니다.

  • 더 이상 사용하지 않는 Drawable

  • 중복된 이미지 파일

  • 사용하지 않는 Layout

  • 오래된 테스트용 JSON

  • 불필요한 폰트 파일

  • 사용하지 않는 Raw 리소스

단, assets 폴더의 파일은 Resource Shrinking만으로 자동 정리된다고 기대해서는 안 됩니다.

특히 파일 이름을 직접 지정해서 읽는 SQLite나 JSON 파일은 개발자가 직접 사용 여부를 관리해야 합니다.


10. Android App Bundle(AAB) 활용하기

Android App Bundle은 Google Play에 앱을 배포할 때 사용하는 표준적인 형식입니다.

AAB에는 앱의 코드와 리소스가 포함되며, Google Play는 기기 구성에 맞는 APK를 생성해 전달할 수 있습니다.

예를 들어 기기의 화면 밀도나 언어, CPU 아키텍처에 따라 필요한 구성 요소를 전달할 수 있습니다.

Android App Bundle
        │
        ▼
Google Play
        │
        ├── 기기 A에 필요한 APK
        ├── 기기 B에 필요한 APK
        └── 기기 C에 필요한 APK

하지만 AAB를 사용한다고 모든 리소스가 자동으로 기기별 분리되는 것은 아닙니다.

특히 assets에 포함된 대용량 SQLite 파일이나 일반 이미지 파일은 기본적으로 기기별로 선택해서 전달되는 구조가 아닙니다.

따라서 AAB와 별개로 assets 자체를 최적화해야 합니다.

11. Bundletool로 실제 APK 구성 확인하기

Google Play에 업로드하는 AAB는 직접 설치하는 파일이 아닙니다.

Bundletool을 이용하면 AAB에서 APK 세트를 생성하고 테스트할 수 있습니다.

예를 들어 다음과 같은 명령어를 사용할 수 있습니다.

java -jar bundletool.jar build-apks \
  --bundle=app-release.aab \
  --output=app.apks \
  --mode=universal

이 예제는 범용 APK를 포함한 APK 세트를 생성하는 방식입니다.

범용 APK는 모든 기기에 필요한 구성을 폭넓게 포함하므로 실제 Google Play의 기기별 다운로드 크기와 같지 않을 수 있습니다.

실제 기기 구성을 반영하려면 기기 사양 기반 APK 생성 및 추출 방식을 사용하는 것이 좋습니다.

또한 AAB의 서명 및 설치 조건에 맞는 Bundletool 옵션을 확인해야 합니다.


12. 이미지 파일이 앱 용량을 크게 만드는 이유

이미지가 많은 앱에서는 사진 파일이 전체 용량에서 큰 비중을 차지할 수 있습니다.

예를 들어 이미지 500장의 평균 크기가 300KB라고 가정하면 다음과 같습니다.

500장 × 300KB
= 150,000KB
≈ 146.5MiB

이미지 파일만으로 상당한 용량이 필요합니다.

하지만 모든 이미지를 같은 방식으로 압축하는 것은 적절하지 않습니다.

이미지의 용도에 따라 포맷과 해상도를 구분해야 합니다.

이미지 유형고려할 포맷
사진JPEG, WebP
투명 배경 이미지PNG, WebP
단순 아이콘Vector Drawable
앱 아이콘Android 리소스 요구사항에 맞는 형식
고해상도 원본 사진서버 제공 또는 별도 다운로드 검토

특히 조류나 식물 사진처럼 세부 묘사가 중요한 이미지에서는 지나친 압축으로 화질이 떨어지지 않도록 주의해야 합니다.

13. JPEG와 WebP 비교하기

WebP는 Android에서 사용할 수 있는 이미지 포맷 중 하나입니다.

사진에 따라 JPEG보다 작은 크기로 비슷한 화질을 제공할 수 있습니다.

하지만 모든 이미지에서 WebP가 더 작아지는 것은 아닙니다.

이미지 내용, 품질 설정, 인코더에 따라 결과가 달라집니다.

따라서 다음 항목을 비교하는 것이 좋습니다.

  1. 원본 파일 크기

  2. 변환 후 파일 크기

  3. 이미지의 시각적 품질

  4. 앱에서의 디코딩 호환성

  5. 실제 표시 크기

Android Studio에서는 이미지 리소스를 WebP로 변환하는 기능을 사용할 수 있습니다.

다만 원본 이미지는 별도로 보관하고 변환 결과를 확인하는 것이 좋습니다.

14. 이미지 해상도를 실제 표시 크기에 맞추기

이미지 용량을 줄일 때는 압축 품질뿐 아니라 해상도도 중요합니다.

예를 들어 RecyclerView의 썸네일이 화면에서 200×200dp 정도로 표시된다고 가정하겠습니다.

이 경우 모든 썸네일에 4000×3000 원본 사진을 포함하는 것은 비효율적일 수 있습니다.

물론 실제 필요한 픽셀 크기는 기기의 화면 밀도에 따라 달라집니다.

따라서 이미지의 용도에 맞춰 다음과 같이 구분하는 방법을 고려할 수 있습니다.

원본 사진
    │
    ├── 목록용 썸네일
    │
    └── 상세 화면용 이미지

목록 화면에서는 작은 이미지를 사용하고, 상세 화면에서는 필요한 해상도의 이미지를 제공하는 구조입니다.

단, 두 버전을 모두 앱에 내장하면 오히려 전체 배포 용량이 증가할 수 있습니다.

따라서 썸네일과 상세 이미지를 모두 내장할지, 하나만 내장하고 런타임에 축소할지, 서버에서 제공할지를 비교해야 합니다.

15. Glide를 사용하면 앱 크기가 줄어들까?

Glide는 이미지 로딩과 캐싱, 디코딩을 관리하는 라이브러리입니다.

예를 들어 다음과 같이 사용할 수 있습니다.

Glide.with(imageView)
    .load(imageUrl)
    .override(400, 400)
    .centerCrop()
    .into(imageView)

override()는 요청 이미지의 디코딩 크기를 제어하는 데 도움이 됩니다.

하지만 Glide를 사용한다고 APK나 AAB에 포함된 원본 이미지 파일의 크기가 자동으로 줄어드는 것은 아닙니다.

이 점을 구분해야 합니다.

  • Glide 최적화: 주로 실행 중 이미지 로딩과 메모리 사용 관리

  • 이미지 파일 최적화: APK·AAB에 포함되는 이미지 자체의 크기 감소

따라서 배포 용량을 줄이려면 이미지 파일을 직접 최적화해야 합니다.


16. SQLite assets 데이터베이스 최적화

SQLite 데이터베이스를 앱에 내장하는 프로젝트에서는 DB 파일 크기도 중요한 요소입니다.

예를 들어 다음과 같은 구조를 사용한다고 가정하겠습니다.

app/src/main/assets/
    └── bird_database.sqlite

앱 실행 시 이 파일을 내부 데이터베이스 디렉터리로 복사하는 방식입니다.

이 경우 배포 파일에는 SQLite 데이터베이스가 포함됩니다.

그리고 앱 실행 후 내부 저장소에 DB를 복사하면 별도의 저장 공간이 추가로 필요할 수 있습니다.

따라서 SQLite 데이터베이스를 최적화하면 배포 용량뿐 아니라 설치 후 데이터 저장 공간에도 영향을 줄 수 있습니다.

17. SQLite VACUUM 사용하기

SQLite에서는 데이터 삭제나 수정 이후 사용하지 않는 페이지가 DB 파일 내부에 남을 수 있습니다.

VACUUM 명령어는 데이터베이스를 재구성해 불필요한 공간을 회수하는 데 사용할 수 있습니다.

VACUUM;

하지만 다음 사항을 주의해야 합니다.

  • VACUUM 실행에는 추가 저장 공간이 필요할 수 있음

  • 실행 중 데이터베이스 잠금이 발생할 수 있음

  • 트랜잭션 내부에서 실행할 수 없음

  • WAL 모드에서는 관련 파일 상태도 고려해야 함

앱에 포함할 정적 SQLite 데이터베이스라면 배포 전에 개발 환경에서 VACUUM을 실행하는 것이 편리할 수 있습니다.

단, VACUUM을 실행한다고 모든 데이터베이스의 크기가 반드시 크게 줄어드는 것은 아닙니다.

18. SQLite 불필요한 컬럼과 데이터 정리

SQLite 파일이 커지는 원인 중 하나는 실제 앱에서 사용하지 않는 데이터를 포함하는 것입니다.

예를 들어 다음과 같은 컬럼이 있다고 가정하겠습니다.

CREATE TABLE korean_birds (
    KTSN INTEGER PRIMARY KEY,
    Species TEXT,
    SpeciesKor TEXT,
    Description TEXT,
    OriginalImage BLOB
);

이미지 원본을 BLOB으로 저장한다면 DB 파일 크기가 크게 증가할 수 있습니다.

특히 이미지 파일을 별도로 관리할 수 있는 상황에서는 DB에 이미지 바이너리를 직접 포함하는 것보다 파일 경로나 URL을 저장하는 방법을 고려할 수 있습니다.

CREATE TABLE korean_birds (
    KTSN INTEGER PRIMARY KEY,
    Species TEXT,
    SpeciesKor TEXT,
    Description TEXT,
    ImageUrl TEXT
);

다만 모든 BLOB 저장이 잘못된 것은 아닙니다.

작은 바이너리 데이터나 트랜잭션 일관성이 중요한 데이터에서는 BLOB이 적절할 수도 있습니다.

중요한 것은 데이터 특성과 앱 구조에 맞는 저장 방식을 선택하는 것입니다.

19. SQLite 인덱스도 용량을 차지한다

SQLite 인덱스는 검색 속도를 높이는 데 도움이 됩니다.

예를 들어 다음과 같은 인덱스를 생성할 수 있습니다.

CREATE INDEX idx_birds_species
ON korean_birds(Species);

하지만 인덱스 역시 저장 공간을 사용합니다.

따라서 사용하지 않는 인덱스가 많으면 DB 파일이 불필요하게 커질 수 있습니다.

인덱스를 삭제하기 전에 실제 검색 쿼리에서 사용하는지 확인해야 합니다.

EXPLAIN QUERY PLAN
SELECT *
FROM korean_birds
WHERE Species = 'Passer montanus';

이렇게 하면 쿼리 실행 계획을 확인할 수 있습니다.

단, 인덱스 삭제로 인한 용량 감소보다 검색 성능 저하가 더 클 수 있으므로 반드시 성능 테스트를 함께 수행해야 합니다.

20. SQLite 압축 파일을 assets에 포함하는 방법

SQLite 데이터베이스를 압축한 뒤 assets에 포함하는 방법도 고려할 수 있습니다.

예를 들어 다음과 같은 구조입니다.

app/src/main/assets/
    └── bird_database.sqlite.gz

앱 실행 시 압축을 해제한 뒤 내부 저장소에 DB 파일을 생성합니다.

압축된 SQLite 파일
       │
       ▼
앱 실행
       │
       ▼
압축 해제
       │
       ▼
내부 DB 저장소
       │
       ▼
SQLiteOpenHelper

하지만 여기에는 중요한 주의사항이 있습니다.

SQLite 파일을 gzip으로 압축한다고 최종 APK나 AAB 크기가 반드시 줄어드는 것은 아닙니다.

APK 자체도 ZIP 기반 압축을 사용하기 때문입니다.

원본 SQLite 파일이 APK 내부에서 이미 효율적으로 압축된다면 별도 gzip 파일을 사용해도 배포 크기 차이가 작거나 오히려 불리할 수 있습니다.

또한 압축 해제 시간과 임시 저장 공간이 추가로 필요할 수 있습니다.

따라서 압축 방식은 APK Analyzer와 실제 기기 테스트로 비교한 뒤 선택해야 합니다.


21. Product Flavor별 assets 분리하기

여러 앱을 하나의 프로젝트에서 관리한다면 Product Flavor를 활용해 리소스를 분리할 수 있습니다.

예를 들어 조류 앱과 어류 앱을 하나의 프로젝트에서 빌드한다고 가정하겠습니다.

app/src/
    │
    ├── main/
    │    └── 공통 코드 및 리소스
    │
    ├── bird/
    │    └── assets/
    │         └── birds.sqlite
    │
    └── fish/
         └── assets/
              └── fish.sqlite

Gradle에서는 다음과 같이 설정할 수 있습니다.

android {

    flavorDimensions += "animal"

    productFlavors {

        create("bird") {
            dimension = "animal"
        }

        create("fish") {
            dimension = "animal"
        }
    }
}

이렇게 하면 각 Flavor에 필요한 assets를 구분해서 관리할 수 있습니다.

예를 들어 Bird Flavor를 빌드할 때 Fish Flavor 전용 assets가 포함되지 않도록 구성할 수 있습니다.

다만 main/assets에 두 데이터베이스를 모두 넣으면 공통 assets로 취급될 수 있으므로 디렉터리 구성을 주의해야 합니다.

22. Flavor별 이미지도 분리하기

데이터베이스뿐 아니라 이미지도 Flavor별로 분리할 수 있습니다.

app/src/
    │
    ├── bird/
    │    ├── assets/
    │    │    └── bird_images/
    │    └── res/
    │         └── drawable/
    │
    └── fish/
         ├── assets/
         │    └── fish_images/
         └── res/
              └── drawable/

이렇게 구성하면 각 앱에 필요한 이미지만 포함할 수 있습니다.

하지만 Flavor별 리소스에 동일한 이름의 파일을 중복으로 넣거나 공통 리소스를 잘못 배치하면 관리가 복잡해질 수 있습니다.

따라서 공통 리소스와 앱별 리소스를 명확하게 구분하는 것이 좋습니다.


23. Native Library 크기 확인하기

Android 앱에는 .so 확장자의 네이티브 라이브러리가 포함될 수 있습니다.

예를 들어 다음과 같은 구조입니다.

lib/
    ├── arm64-v8a/
    ├── armeabi-v7a/
    └── x86_64/

각 디렉터리는 서로 다른 CPU 아키텍처를 의미합니다.

네이티브 라이브러리가 여러 ABI에 포함되어 있다면 배포 파일 크기가 증가할 수 있습니다.

하지만 사용하지 않는 ABI를 무조건 제거해서는 안 됩니다.

특정 기기에서 앱이 실행되지 않을 수 있기 때문입니다.

Google Play App Bundle을 사용하는 경우에는 기기별 ABI에 맞는 라이브러리가 제공될 수 있습니다.

따라서 먼저 실제 다운로드 크기를 확인하는 것이 좋습니다.

24. 16KB 메모리 페이지 지원과 앱 크기

최근 Android 앱에서는 16KB 메모리 페이지 크기 지원도 중요한 호환성 항목입니다.

특히 네이티브 라이브러리를 포함하는 앱이라면 ELF 정렬과 패키징 설정을 확인해야 합니다.

하지만 앱 크기를 줄이기 위해 네이티브 라이브러리의 정렬 설정을 임의로 변경하는 것은 적절하지 않습니다.

앱 크기 최적화보다 플랫폼 호환성이 우선입니다.

따라서 다음 항목을 구분해야 합니다.

  • ABI별 라이브러리 구성

  • 사용하지 않는 네이티브 라이브러리 제거

  • 16KB 메모리 페이지 호환성

  • Android Gradle Plugin 및 NDK 버전

네이티브 라이브러리를 포함하는 앱에서는 크기와 호환성을 함께 검증해야 합니다.


25. 앱 시작 시 assets 복사가 미치는 영향

앱에 포함된 SQLite 데이터베이스를 첫 실행 시 복사하는 구조에서는 앱 시작 속도에도 영향을 줄 수 있습니다.

예를 들어 다음과 같은 작업입니다.

assets.open("birds.sqlite").use { input ->

    FileOutputStream(databaseFile).use { output ->
        input.copyTo(output)
    }
}

데이터베이스가 크면 복사 시간이 길어질 수 있습니다.

이 작업을 메인 스레드에서 실행하면 화면이 멈추거나 ANR 위험이 증가할 수 있습니다.

따라서 대용량 파일 복사는 Dispatchers.IO에서 수행하는 것이 좋습니다.

withContext(Dispatchers.IO) {

    assets.open("birds.sqlite").use { input ->

        FileOutputStream(databaseFile).use { output ->
            input.copyTo(output)
        }
    }
}

또한 이미 복사된 DB가 있다면 매번 다시 복사하지 않도록 관리해야 합니다.

단, 단순히 파일이 존재한다는 이유만으로 DB가 정상이라고 판단하는 것은 위험할 수 있습니다.

복사 도중 앱이 종료되면 불완전한 파일이 남을 수 있기 때문입니다.

가능하면 임시 파일에 복사한 뒤 검증하고 최종 파일로 교체하는 구조를 고려해야 합니다.

26. 서버에서 이미지 다운로드하기

앱에 포함되는 이미지가 많다면 일부 이미지를 서버에서 다운로드하도록 설계할 수도 있습니다.

Android App
    │
    ├── SQLite 메타데이터
    │
    ├── 기본 이미지
    │
    └── Glide
          │
          ▼
       Image Server

이 구조에서는 모든 이미지를 앱에 포함하지 않아도 됩니다.

하지만 장점과 단점이 있습니다.

장점단점
초기 다운로드 크기 감소 가능네트워크 연결 필요
이미지 교체가 쉬움서버 운영 비용
앱 업데이트 없이 이미지 변경이미지 로딩 지연
기기별 이미지 제공 가능캐시 관리 필요

특히 오프라인 사용이 중요한 도감 앱이라면 모든 이미지를 서버에서 가져오는 방식이 적절하지 않을 수 있습니다.

따라서 자주 사용하는 기본 이미지는 앱에 포함하고, 고해상도 이미지는 필요할 때 다운로드하는 혼합 방식도 고려할 수 있습니다.

27. Play Asset Delivery와 Play Feature Delivery

대용량 리소스나 선택적인 기능이 있는 앱에서는 Google Play의 전달 기능을 검토할 수 있습니다.

대표적으로 다음과 같은 기능이 있습니다.

  • Play Asset Delivery

  • Play Feature Delivery

Play Asset Delivery는 대용량 에셋을 별도로 전달하는 데 사용할 수 있습니다.

Play Feature Delivery는 기능 모듈을 필요에 따라 제공하는 구조를 지원합니다.

하지만 모든 앱에 이러한 기능이 필요한 것은 아닙니다.

특히 작은 정보 검색 앱에서는 구조가 복잡해지는 단점이 있을 수 있습니다.

또한 Google Play 배포 환경과 전달 조건에 따라 제약이 있으므로 실제 사용 전에 공식 문서를 확인해야 합니다.


28. 최적화 후 실제 기기에서 확인하기

앱 크기를 줄였다고 해서 반드시 사용자 경험이 개선되는 것은 아닙니다.

예를 들어 이미지 파일을 지나치게 압축하면 사진 품질이 떨어질 수 있습니다.

또한 SQLite 인덱스를 제거하면 검색 속도가 느려질 수 있습니다.

따라서 최적화 후에는 다음 항목을 함께 확인해야 합니다.

설치 및 다운로드

  • APK·AAB 크기

  • 실제 기기 다운로드 크기

  • 설치 후 앱 저장 공간

실행 성능

  • 앱 첫 실행 시간

  • SQLite 초기 복사 시간

  • 검색 응답 시간

  • 이미지 로딩 시간

기능 및 품질

  • 이미지 화질

  • 오프라인 사용 가능 여부

  • R8 적용 후 API 파싱

  • WebView 정상 동작

  • 광고 로딩

  • Flavor별 데이터 정상 포함 여부

최적화는 단순히 파일 크기를 줄이는 작업이 아니라 기능과 성능의 균형을 맞추는 과정입니다.

29. Android 앱 크기 최적화 체크리스트

  • APK Analyzer로 용량 구성 확인

  • 최적화 전후 기준값 기록

  • 릴리스 빌드에서 R8 활성화

  • Resource Shrinking 활성화

  • 불필요하게 넓은 keep 규칙 제거

  • Retrofit·Gson 릴리스 동작 검증

  • 사용하지 않는 Drawable 및 Layout 정리

  • 중복 이미지 제거

  • 사진의 포맷과 해상도 검토

  • Vector Drawable 사용 가능 여부 확인

  • SQLite VACUUM 적용 검토

  • 불필요한 DB 컬럼과 인덱스 확인

  • assets 압축 방식의 실제 효과 측정

  • Product Flavor별 assets 분리

  • ABI별 네이티브 라이브러리 확인

  • 16KB 메모리 페이지 호환성 유지

  • 첫 실행 시 대용량 파일 복사 최적화

  • 이미지 서버 다운로드 방식 검토

  • 실제 기기에서 설치·실행 테스트

30. 마무리

Android 앱 크기를 줄이기 위해서는 먼저 앱의 구성 요소를 정확하게 분석해야 합니다.

R8과 Resource Shrinking을 활성화하는 것만으로도 불필요한 코드와 리소스를 줄일 수 있지만, 이미지와 SQLite 데이터베이스가 큰 비중을 차지하는 앱에서는 별도의 최적화가 필요합니다.

특히 다음 순서로 접근하는 것을 추천합니다.

  1. APK Analyzer로 용량을 차지하는 요소를 확인합니다.

  2. R8과 Resource Shrinking을 적용합니다.

  3. 불필요한 이미지와 리소스를 정리합니다.

  4. SQLite 데이터베이스의 불필요한 공간과 데이터를 확인합니다.

  5. Product Flavor별 assets를 분리합니다.

  6. AAB 기반 실제 다운로드 크기를 확인합니다.

  7. 최적화 후 기능과 성능을 다시 테스트합니다.

앱 크기 최적화에서 가장 중요한 것은 무조건 용량을 줄이는 것이 아닙니다.

사용자에게 필요한 기능과 데이터 품질을 유지하면서 불필요한 용량을 제거하는 것이 핵심입니다.

특히 이미지와 SQLite 데이터베이스를 함께 사용하는 도감형 Android 앱에서는 코드 최적화보다 이미지·데이터 구성 방식의 개선이 더 큰 효과를 가져올 수 있습니다.


참고 자료

태그: Android, Kotlin, APK, AAB, Android App Bundle, R8, ProGuard, Resource Shrinking, APK Analyzer, WebP, Glide, SQLite, VACUUM, Product Flavor, Assets, 앱 용량 최적화