시스템 파티션(/system)에 설치되는 앱(루트 앱 등)을 개발하는 안드로이드 개발자라면, 앱 파일이 /system 파티션에 제대로 설치되도록 플래시 가능한 .zip 파일을 만들어야 할 때가 있습니다.
플래시 가능한 Zip의 기타 용도
- DPI 수정
- 커스텀 폰트 적용
- 커스텀 부트 애니메이션 적용
- 시스템 앱 추가 또는 제거
사용자에게 파일 시스템을 직접 조작해 파일을 옮기게 하는 것은 모두의 시간을 낭비하는 일입니다. 플래시 가능한 .zip을 만드는 것이 훨씬 편리한 방법입니다. 이 가이드에서는 안드로이드용 플래시 가능 zip을 만드는 방법을 설명합니다.
또한 addon.d 스크립트 작성법도 다룹니다. 이를 통해 커스텀 시스템 변경 사항이 더티 롬 플래시 후에도 유지되므로, 사용자가 롬 업데이트마다 zip을 다시 플래시할 필요가 없습니다.
준비물
- 루트 파일 탐색기 (MiXplorer, Solid Explorer 등)
- ZipSigner (zip 서명용) 또는 MiXplorer용 MiX Signer 플러그인
- 낸드로이드 백업 (강력 권장)
zip에 포함될 모든 파일(APK, 설정 파일, 부트 애니메이션 등)을 미리 준비하고 정리해 두세요. 이 과정은 섬세하므로 사전 준비가 중요합니다.
커스텀 Zip 템플릿
자신만의 플래시 가능 zip을 만들기 위한 기반으로 사용할 수 있는 템플릿을 제공합니다:
- 템플릿 스크립트: 기본 명령어 포함 (앱, 벨소리 경로, 부트애니메이션 등 커스텀 값 직접 추가 필요)
- 템플릿 Zip: 파일 구조 이해에 도움이 되는 예시 포함
템플릿만으로도 커스텀 스크립트 작성을 시작하기에 충분합니다.
플래시 가능 zip이 주로 대상으로 하는 /system 파티션의 주요 경로는 다음과 같습니다:
addon.d => 더티 플래시 생존을 위한 백업 스크립트 (GApps 패키지 등에서 사용) app, priv-app => 추가/제거할 시스템 앱 etc => hosts 파일 fonts => 커스텀 폰트 media => bootanimation.zip media/audio/alarms => 알람 소리 media/audio/notifications => 알림 소리 media/audio/ringtones => 벨소리 media/audio/ui => UI 소리 (배터리 부족, 잠금해제, 카메라 등) /system 루트 => build.prop 파일
중요: 더티 플래시 시 동작 원리
이 경로들에서 삭제된 파일은 더티 플래시 후 재설치되며, 수동으로 추가한 파일은 삭제됩니다. 따라서 /system 수정 사항을 백업하는 스크립트를 만드는 것이 필수적입니다.
업데이터 스크립트(Update-Script) 예시
ui_print("+-------------------------------------+");
ui_print("| CLEAN FLASH SCRIPT |");
ui_print("| |");
ui_print("| by Primokorn |");
ui_print("+-------------------------------------+");
run_program("/sbin/busybox", "umount", "/system");
run_program("/sbin/busybox", "mount", "/system");
ui_print(" ");
ui_print("*** 블로트웨어 삭제 중 ***");
delete_recursive(
"/system/app/adaway.apk",
"/system/app/AdAway",
"/system/app/BasicDreams",
"/system/app/BookmarkProvider",
"/system/app/Calendar",
"/system/app/CalendarWidget",
"/system/app/CMFileManager",
"/system/app/CMWallpapers",
"/system/app/DeskClock",
"/system/app/Eleven",
"/system/app/Email",
"/system/app/ExactCalculator",
"/system/app/Exchange2",
"/system/app/Gello",
"/system/app/HexoLibre",
"/system/app/Jelly",
"/system/app/LiveWallpapersPicker",
"/system/app/LockClock",
"/system/app/messaging",
"/system/app/MiXplorer",
"/system/app/NexusLauncher",
"/system/app/Phonograph",
"/system/app/PhotoTable",
"/system/app/PicoTts",
"/system/app/PicoTTS",
"/system/app/ResurrectionStats",
"/system/app/SoundRecorder",
"/system/app/Terminal",
"/system/app/TugaBrowser",
"/system/app/Wallpaper",
"/system/app/WallpaperPickerGoogle",
"/system/priv-app/AudioFX",
"/system/priv-app/Chrome",
"/system/priv-app/Gallery2",
"/system/priv-app/MusicFX",
"/system/priv-app/OnePlusCamera",
"/system/priv-app/OnePlusGallery",
"/system/priv-app/OnePlusMusic",
"/system/priv-app/Recorder",
"/system/priv-app/Screencast",
"/system/priv-app/Snap",
"/system/priv-app/SnapdragonCamera",
"/system/priv-app/SnapdragonGallery",
"/system/priv-app/WeatherManagerService",
"/system/priv-app/WeatherProvider",
"/system/priv-app/Tag"
);
ui_print("앱 및 모드 설치 중");
show_progress(8.800000, 5);
package_extract_dir("system", "/system/");
ui_print("*** 권한 수정 중 ***");
set_perm(0, 0, 0755, "/system/addon.d/99-dirty.sh");
set_perm(0, 0, 0644, "/system/etc/gps.conf");
set_perm(0, 0, 0644, "/system/fonts/Roboto-Regular.ttf");
set_perm(0, 0, 0644, "/system/media/audio/ringtones/PlasticRing.ogg");
set_perm(0, 0, 0644, "/system/priv-app/Phonesky.apk");
set_perm(0, 0, 0644, "/system/priv-app/microG.apk");
set_perm(0, 0, 0644, "/system/priv-app/Gsam.apk");
set_perm(0, 0, 0644, "/system/priv-app/BBS.apk");
set_perm(0, 0, 0644, "/system/priv-app/V4A-Magisk.apk");
run_program("/sbin/busybox", "mount", "/data");
package_extract_dir("data", "/data/");
set_perm(0, 0, 0755, "/data/local/afscript.sh");
show_progress(8.800000, 5);
run_program("/sbin/busybox", "umount", "/data");
run_program("/sbin/busybox", "umount", "/system");
ui_print(" ");
ui_print("완료.");
ui_print("재부팅 준비 완료.");
참고: ui_print(" ");는 화면 출력용 텍스트로, 실제 동작은 하지 않습니다.
작업 전 파티션 언마운트/리마운트
run_program("/sbin/busybox", "umount", "/system");
run_program("/sbin/busybox", "mount", "/system");
파티션 작업 전에는 항상 언마운트 후 다시 마운트해야 합니다.
시스템 구성요소/앱 삭제
마지막 줄을 제외하고 각 줄 끝에 쉼표(,)를 붙입니다.
delete_recursive( "/system/app/adaway.apk", "/system/app/AdAway", .................. "/system/priv-app/WeatherProvider", "/system/priv-app/Tag" );
설치할 시스템 파일 추출
package_extract_dir("system", "/system/");
파일 권한 설정
set_perm(0, 0, 0755, "/system/addon.d/99-dirty.sh"); .............. set_perm(0, 0, 0644, "/system/priv-app/V4A-Magisk.apk");
/data 파티션 작업 (동일 방식)
파티션 마운트 → 데이터 추출 → 권한 설정 순서로 진행합니다.
run_program("/sbin/busybox", "mount", "/data");
package_extract_dir("data", "/data/");
set_perm(0, 0, 0755, "/data/local/afscript.sh");
수정된 파티션 언마운트
run_program("/sbin/busybox", "umount", "/data");
run_program("/sbin/busybox", "umount", "/system");
Addon.d 스크립트 예시
#!/sbin/sh
#
# /system/addon.d/99-dirty.sh
# /system이 포맷되고 재설치되면 이 파일들이 복원됩니다.
#
. /tmp/backuptool.functions
list_files() {
cat <
더티 플래시 후 유지할 파일 목록 작성
list_files() {
cat <
더티 플래시 후 재설치되지 않아야 할 파일 삭제
자신의 수정사항에서 제거한 시스템 파일들(더티 플래시 시 롬에서 다시 설치될 파일들)을 지정합니다.
rm -rf /system/app/adaway.apk
rm -rf /system/app/AdAway
rm -rf /system/app/BasicDreams
rm -rf /system/app/BookmarkProvider
................................................
rm -rf /system/priv-app/WeatherProvider
rm -rf /system/priv-app/Tag
;;
플래시 가능 안드로이드 Zip 만들기 (실전)
여기서는 안드로이드 최고의 루트 탐색기이자 파일 관리자인 MiXplorer를 사용합니다.
- zip에 포함할 모든 폴더를 선택한 후 압축(Archive)을 선택합니다.

- 압축 파일 생성을 확인하고, 이름을 지정한 뒤 압축 방식은 Store(무압축)로 선택합니다.

- 생성된 플래시 가능 zip 파일을 선택하고 서명(Sign)합니다 (MiX Signer 플러그인 사용).
- TestKey를 선택해 서명 오류가 없는지 검사합니다.
- 이제 zip을 플래시할 수 있습니다. 커스텀 리커버리에서 쉽게 찾도록
/sdcard로 이동하는 것을 권장합니다.
마무리 팁
플래시 가능 zip은 클린 플래시 후 또는 /system 파티션을 와이프한 뒤 더티 플래시로 롬을 설치한 후에 설치해야 합니다. 업데이터 스크립트가 지정한 시스템 파일을 삭제/추가하지만, addon.d 스크립트는 건드리지 마세요. 사용자 개입 없이 제 역할을 수행합니다.
최초 설치 후에는 /system 파티션을 꼼꼼히 확인해 모든 것이 올바른지(파일 삭제 여부 등) 점검하세요. 파일명 오타로 삭제되지 않는 경우가 종종 있습니다.