0. 들어가며
빌드 단계에서 자동으로 이뤄지기 때문에 사람들이 모르고 지나칠 수 있는데, 사실 빌드 시 apk 파일을 정렬(뷰 정렬, 텍스트 가운데 정렬 이런거 아님)하는 작업이 이루어진다. 몰랐던 부분이라 공부한 내용을 정리해본다.
1. 정렬(Android APK Align) 이란?
zipalign
zipalign은 ZIP 압축 파일의 정렬 도구로, 압축되지 않은 모든 파일이 파일의 시작 위치를 기준으로 정렬되도록 도와줍니다.
이를 통해 파일을 *mmap(2)을 통해 직접 액세스할 수 있어, 데이터를 RAM에 복사할 필요가 없어지고 앱의 메모리 사용량이 줄어듭니다.
* mmap이란 메모리 매핑 기술로, 파일을 RAM에 복사하지 않고 파일 자체를 메모리처럼 바로 읽는 방식이다.
Android 공식문서 중 zipalign 항목에서 정렬에 대한 개념을 위와 같이 간접적으로 언급하고 있는데,
간단하게 설명하면,
정렬이란 apk 내부에 저장된 파일들을 CPU가 읽는 단위(보통 32/64 bit)에 맞게 정리하는 최적화 작업이다.


택배로 예를 들어보자.
만약 물류 센터에서 화물차로 물건을 싣는데 좌측처럼 수화물을 마구잡이로 실어 공간은 남는데 실어야 할 물건이 남아있다면 한번에 운송할 수 있는 걸 여러번에 걸쳐 왔다갔다 해야할 것이다. 하지만 우측처럼 공간을 최대한으로 활용하여 모든 물건을 다 실었다면 한큐에 물건을 다 보낼 수 있어 배송 시간이 절약될 것이다.

마찬가지로 apk 내 데이터들이 정렬되어있지 않다면 데이터를 읽기 위해 연산을 더 해야하고 그 만큼 속도가 느려질 수도 있다.
2. 안하면 어떻게 되는데?
1) 메모리 효율 저하
앞서 설명했듯 정렬된 데이터는 그 자체로 사용이 가능하지만, 정렬되지 않은 데이터는 직접 메모리에 매핑하기가 어렵다. 때문에 데이터를 임시 버퍼(RAM)으로 복사해서 정렬된 상태로 재배치한 후 사용하게 되는데, 이 과정 때문에 RAM 낭비가 심해져 메모리 효율이 떨어진다.
2) 성능 저하
1번의 연장선으로, RAM에 복사하고 정렬하는 과정이 추가되기 때문에 그 만큼 CPU가 추가적인 연산을 수행해야 한다. 특히 리소스 파일을 많이 쓰는 게임 같은 앱에서는 위 과정이 더 잦고 오래 걸리기 때문에 성능 차이가 더 크게 발생할 수 있다.
3) 앱 크래시
일부 하드웨어 아키텍처에서는 메모리 정렬이 맞지 않으면 정상적으로 파일을 읽지 못할 수도 있다고 한다.
4) Playstore 배포 제한

플레이스토어에 정렬되지 않은 apk 파일을 업로드할 경우 위와 같은 경고가 뜨며 앱이 등록되지 않는다.
3. zipalign 정렬 원리
정렬할 때 사용하는 zipalign은 APK 안의 압축되지 않은 데이터(주로 이미지, XML 등의 리소스 파일)를 4바이트 경계에 맞춰 정렬시킨다. 이 과정에서 데이터 위치를 조정하기 위해 빈 공간(패딩, padding)이 추가된다. 따라서 정렬을 수행하면 경우에 따라 파일 크기가 조금은 커질 수 있다. 단, 압축된 데이터(like dex 파일)는 정렬 대상이 아니므로 영향을 받지 않는다.
4. 언제, 어떻게 하는가?

정렬 작업은 APK 빌드 과정 막바지에 진행하는데, 공식 문서에선 서명 툴에 따라 정렬 시점을 다르게 안내하고 있다. 보통의 개발자는 Android Studio 같은 IDE로 빌드하기 때문에 정렬에 대해 신경 쓸 필요가 없으나, 수동으로 빌드하거나 리패키징을 하는 경우 꼭 주의해야 할 부분이다. 그렇다면 왜 서명 툴이 달라졌다고 정렬 시점이 달라지는걸까?
1) 왜 정렬 시점이 다른가?
이유는 apksigner와 jarsigner의 서명 방식에 대한 차이점 때문이다. jarsigner는 V1 서명 체계를 사용하며 apksigner는 V2 서명 체계를 기본으로 사용한다. 이 두 서명 방식은 APK의 무결성을 보장하는 접근법과 구조적 변화에 민감도가 다르기 때문에 정렬 시점이 달라지는 것이다. V1, V2 서명에 대해 간략히 설명하자면,
V1 서명은 APK 내 개별 파일의 해시를 계산한 후 이를 묶어 서명 값을 생성하며, 서명 데이터는 META-INF/ 폴더에 저장한다. 반면, V2 서명은 APK 전체 파일의 해시 값을 기반으로 서명을 생성하며, 서명 블록을 파일 끝에 추가한다.
따라서 jarsigner를 사용할 경우, 서명 후 zipalign으로 정렬해도 서명이 유효하다. 이는 V1 서명이 파일 내부 데이터에만 의존하기 때문이다. 하지만 apksigner를 사용할 경우, 서명은 전체 파일 구조를 기반으로 하므로, 서명 후 정렬하면 파일 내용이 바뀌어 무결성이 깨진다. 그렇기때문에 V2에서는 정렬 후에 서명하는 것이다.
2) 어떻게 하는가?
IDE로 빌드한다면 gradle 스크립트에 포함되어있어 따로 신경쓰지 않아도 된다. 하지만 수동으로 정렬을 해줘야 한다면 아래 명령어를 실행해보자.
a. zipalign 확인
zipalign은 Android Build-Tools에 포함되어있다. 먼저 Build-Tools가 없다면 (링크)로 이동하여 Build-Tools를 다운받고, 다운로드 되어있다면 터미널을 열어 해당 경로로 이동하던 환경변수를 등록하던 해서 zipalign이 잘 동작하는지 확인해본다.
~/Library/Android/sdk/build-tools/35.0.0/zipalign
# 결과
Zip alignment utility
Copyright (C) 2009 The Android Open Source Project
Usage: zipalign [-f] [-p] [-P <pagesize_kb>] [-v] [-z] <align> infile.zip outfile.zip
zipalign -c [-p] [-P <pagesize_kb>] [-v] <align> infile.zip
<align>: alignment in bytes, e.g. '4' provides 32-bit alignment
-c: check alignment only (does not modify file)
-f: overwrite existing outfile.zip
-p: 4kb page-align uncompressed .so files
-v: verbose output
-z: recompress using Zopfli
-P <pagesize_kb>: Align uncompressed .so files to the specified
page size. Valid values for <pagesize_kb> are 4, 16
and 64. '-P' cannot be used in combination with '-p'.
b. 정렬하기
# 정렬
zipalign -p -v 4 target.apk aligned.apk
# 결과
...
19544322 play-services-basement.properties (OK - compressed)
19544462 play-services-measurement-impl.properties (OK - compressed)
19544605 play-services-measurement.properties (OK - compressed)
19544748 play-services-measurement-base.properties (OK - compressed)
19544888 firebase-analytics-ktx.properties (OK - compressed)
19545017 play-services-base.properties (OK - compressed)
19545152 play-services-ads-identifier.properties (OK - compressed)
19545287 play-services-tasks.properties (OK - compressed)
19545418 play-services-auth-base.properties (OK - compressed)
19545550 play-services-stats.properties (OK - compressed)
19545675 firebase-encoders.properties (OK - compressed)
19545798 firebase-analytics.properties (OK - compressed)
19545925 firebase-annotations.properties (OK - compressed)
19546051 play-services-fido.properties (OK - compressed)
19546187 play-services-measurement-api.properties (OK - compressed)
19546326 androidsupportmultidexversion.txt (OK - compressed)
Verification succesful
c. 정렬 됐는지 확인
# 정렬 확인
zipalign -c -v 4 target.apk
# 결과
# 정렬이 안되어있다면 아래와 같이 "Verification FAILED"이 뜬다.
...
19543727 play-services-basement.properties (OK - compressed)
19543867 play-services-measurement-impl.properties (OK - compressed)
19544010 play-services-measurement.properties (OK - compressed)
19544153 play-services-measurement-base.properties (OK - compressed)
19544293 firebase-analytics-ktx.properties (OK - compressed)
19544422 play-services-base.properties (OK - compressed)
19544557 play-services-ads-identifier.properties (OK - compressed)
19544692 play-services-tasks.properties (OK - compressed)
19544823 play-services-auth-base.properties (OK - compressed)
19544955 play-services-stats.properties (OK - compressed)
19545080 firebase-encoders.properties (OK - compressed)
19545203 firebase-analytics.properties (OK - compressed)
19545330 firebase-annotations.properties (OK - compressed)
19545456 play-services-fido.properties (OK - compressed)
19545592 play-services-measurement-api.properties (OK - compressed)
19545731 androidsupportmultidexversion.txt (OK - compressed)
Verification FAILED
5. AAB는 정렬을 하지 않는다!
눈치 챘는지 모르겠지만 apk 기준으로만 정렬을 설명했다. ㅇ? aab는 정렬을 안하기 때문이다!
aab를 대충 표현하면, 완성된 APK 파일이 아니라 앱의 코드와 각종 리소스 등을 모듈화한 꾸러미이다. aab를 업Play Store에 업로드하면, 사용자가 앱을 다운로드할 때 Play Store가 사용자 기기에 맞는 최적화된 APK를 생성해서 설치시킨다. 이 때 정렬을 Play store에서 자동으로 해주기 때문에 aab는 따로 개발자가 정렬을 해주지 않아도 된다.
참고
Android Developers | APK Signature Scheme v2
'mobile > android' 카테고리의 다른 글
APK 파일 구조 (12) | 2025.01.07 |
---|---|
상용 앱 APK 추출해보기 (Feat. ADB) (8) | 2025.01.04 |
Android Build Process (1) (11) | 2025.01.04 |