[안드로이드] 사진 액자 앱을 바이브코딩으로 직접 만들기 — 요구사항에서 홈 앱까지
서두
지난 글(오딘 플래싱 편)에서 웅진북클럽 태블릿을 순정 펌웨어로 되살렸다. 이제 이 기기를 거실 사진 액자로 만들 차례다. 구글포토 연동 액자 앱(Fotoo류)을 쓰려고 했지만, 2025년 4월부터 구글이 개인 앱의 사진 라이브러리 읽기 API를 차단해서 이 계열 앱들이 사실상 망가졌다. 그래서 결정했다: 구글 계정도, 서버도, 네트워크도 필요 없는 로컬 사진 액자 앱을 직접 만들자.
코딩은 전부 AI에게 부탁하는 바이브코딩으로 했다. 이 글의 프롬프트를 그대로 복사해서 쓸 수 있게 남긴다. 요구사항을 말하고 → 받은 코드를 넣고 → 실기기에서 돌려보고 → 부서지는 대로 고치는 반복이 전부다.
앱 설계 한눈에 보기
| 구성 요소 | 역할 |
|---|---|
| MainActivity | 사진 스캔 + 슬라이드쇼 + 제스처(스와이프·더블탭·탭) |
| BootReceiver | 부팅하면 액자 앱 자동 실행 |
| NightReceiver | 밤 11시~아침 7시 화면 자동 어둡게 |
| RefreshReceiver | PC에서 사진 추가하면 즉시 재스캔 |
| SettingsManager | 설정값(간격·밝기·밤 시간대) 저장/읽기 |
제약 조건을 먼저 정했다: Java만, 외부 라이브러리 없음, 안드로이드 8.0(minSdk 26) — 2016년 기기라 최신 기술은 전부 배제.
STEP 1. 최소 슬라이드쇼
프롬프트 1
안드로이드 사진 액자 앱을 만들어줘. 조건: - Java만 사용, 외부 라이브러리 금지, minSdk 26 - 패키지명 com.handy.photoframe - /sdcard/PhotoFrame 폴더의 이미지(jpg/png/webp)를 전부 읽어서 풀스크린 ImageView로 30초마다 다음 사진을 보여준다 - 파일명 오름차순 정렬, 끝에 도달하면 처음으로 되돌아간다 - 화면 맨 위에 작은 디버그 텍스트(권한 여부, 사진 개수) 표시 - 사진 로딩 실패 시 화면이 검게 변하지 않게 마지막 사진 유지 - 액티비티는 세로·가로 회전에 대응(configChanges) 코드 전체를 파일별로 나눠서 줘.
여기서 나온 코드가 앱의 뼈대다. 스캔 → 정렬 → Handler로 30초마다 showNext() → ImageView에 비트맘. 비트맵은 크기 조절(샘플링)해서 메모리 폭발을 막도록 부탁하는 걸 잊지 말자.
STEP 2. 전원이 나가도 다시 살아나야 한다
액자는 24시간 돌아가니까 정전·재부팅 후에 사람 손이 안 대도 다시 켜져야 한다.
프롬프트 2
방금 만든 앱에 두 가지를 추가해줘: 1. BootReceiver: 부팅 완료 브로드캐스트를 받으면 MainActivity를 자동 실행 (RECEIVE_BOOT_COMPLETED 권한, 매니페스트에 receiver 등록 포함) 2. MainActivity의 FLAG_KEEP_SCREEN_ON 추가 — 앱이 보이는 동안 화면이 절대 꺼지지 않게 주의사항: Android 8에서 부팅 브로드캐스트 제약 회피 방법도 설명해줘.
STEP 3. 밤에는 어둡게 — 여기서 첫 번째 전투
거실 액자는 밤에도 켜져 있으면 빛 공해다. 밤 11시~아침 7시에 화면을 완전히 끄자.
프롬프트 3
야간 모드를 추가해줘: - SettingsManager: nightStart(23), nightEnd(7), intervalMs, dayBrightness를 SharedPreferences로 저장 - 시간이 23~7시(자정 걸침 포함)면 화면 밝기를 0으로, 낮이면 120으로 복원 - WRITE_SETTINGS 권한 필요 — 권한 없으면 상단 텍스트를 탭했을 때 권한 설정 화면으로 이동 - NightReceiver: 부팅 시와 시간대 변경 시 밝기를 다시 적용 단, 밤에는 사진을 아예 렌더링하지 말 것(완전 검은 화면).
실전에서 여기서 문제가 생겼다. 기존에 깔려 있던 다른 앱(웅진 잔존 앱)도 밝기를 계속 조작해서, 내 앱과 밝기 조작이 무한 루프로 싸우는 것. 해법: 싸우지 말고, 밝기를 바꿀 때 "내가 바꿨는지" 추적(nightApplied 플래그)해서 중복 조작을 없애는 것. 액자 앱끼리 밝기를 두고 싸우는 꼴은 흔한 함정이다.
STEP 4. 폴더 구조 — 재귀 스캔
사진을 주제별 폴더(가족/아이돌/학습)로 나누니 하위 폴더 스캔이 필요해졌다.
프롬프트 4
collect()가 /sdcard/PhotoFrame 아래를 재귀적으로 훑도록 바꿔줘. 이미지 확장자(jpg/jpeg/png/webp)만, 숨김폴더(.thumbnails) 제외. 단 목록이 자주 바뀌니, 슬라이드 20번마다 조용히 재스캔해서 개수가 바뀐 경우에만 목록을 갈아끼워줘 (표시 중 사진이 튀지 않게).
STEP 5. 홈 런처화 — 여기서 함정 두 번째
액자는 전원 켜면 바로 액자 화면이어야 한다. 앱을 기본 홈(런처)으로 만들자.
프롬프트 5
MainActivity를 홈 런처로 만들어줘. 매니페스트의 인텐트 필터에 ACTION_MAIN + CATEGORY_HOME + CATEGORY_DEFAULT를 추가해줘.
여기서 AI가 실수하기 딱 좋은 부분이 category를 action으로 잘못 쓰는 것. 실제로 내 앱은 <action android:name="android.intent.category.HOME"/>이라는, 문법上是 말이 안 되는 필터가 들어가 있었고 adb shell cmd package set-home-activity가 "cannot be home" 오류를 뱉었다. 홈 앱이 안 잡히면 인텐트 필터부터 의심하자.
STEP 6. 제스처 — 넘기고, 유튜브 열고
액자가 사람과 상호작용하기 시작하는 단계.
프롬프트 6
GestureDetector로 세 가지 제스처를 추가해줘: 1. 좌우 스와이프: 왼쪽으로 쓸면 다음 사진, 오른쪽이면 이전 사진 (가로 이동이 세로보다 1.5배 크고 80px 이상일 때만, 자동 타이머는 리셋) 2. 사진 더블탭: org.schabi.newpipe(뉴파이프) 실행 — 설치돼 있을 때만 3. 한 번 탭: 설정 화면(SettingsActivity) 열기 제스처가 서로 충돌하지 않게 onDown은 true 반환, 터치는 ImageView에만 바인딩.
설계 포인트: 자동 슬라이드쇼는 "무작위"가 아니라 "날짜순(파일명순)"으로 정렬해뒀다. 스와이프로 넘기는 게 앨범처럼 느껴지려면 순서가 의미가 있어야 하기 때문이다.
STEP 7. 설정 화면
간격·밝기를 바꾸려고 PC를 꽂는 일이 있으면 안 된다.
프롬프트 7
SettingsActivity를 XML 없이 코드로 만들어줘: - 사진 간격: 5초~300초 슬라이더 + 프리셋 버튼(5초/10초/30초/60초/5분) - 낮 밝기 슬라이더(10~255), 밤 밝기 슬라이더(0~50) - 밤 시작/끝 시간 ±버튼(0~23 순환) - '사진 다시 읽기' 버튼(RefreshReceiver에 명시적 브로드캐스트) - 닫기 버튼. 검은 배경 + 큰 글씨, FLAG_KEEP_SCREEN_ON. 변경값은 즉시 저장하고, MainActivity 슬라이드 루프는 매 틱 최신 설정값을 읽도록 (재시작 없이 적용).
함정 하나: PC에서 앱을 갱신 설치하면 기본 홈 역할이 삼성 런처로 튕겨 나간다. 재설치 후엔 adb shell cmd package set-home-activity <pkg>/.MainActivity를 다시 실행해야 한다.
STEP 8. 폴더 카테고리 + 꽉 찬 표시
프롬프트 8
1) 설정 화면에 표시 항목 선택 행을 추가: [전체][가족][아이돌] 버튼. PhotoFrame 루트의 하위 폴더(1_가족, 2_아이돌 등)를 동적으로 감지해서 새 폴더가 생기면 버튼도 자동 추가. 선택은 저장 후 재부팅에도 유지. 2) 표시 모드 2종: 꽉 찬 채움(CENTER_CROP, 가장자리 절단)과 비율 맞춤(FIT_CENTER). 기본은 꽉 찬 채움. 3) 몰입형 전체화면: 상태바·내비게이션바 숨김(IMMERSIVE_STICKY), 사진이 화면 끝까지 차도록.
꽉 찬 채움만으로는 부족했다 — 시스템 상태바(시계)와 소프트 내비게이션바가 사진 위를 덮고 있어서, 몰입형 모드까지 켜야 진짜 테두리 없는 액자가 됐다.
실전에서 배운 것들
- 야간 렌더링 금지 — 밝기 0만으론 부족하다. 사진 자체를 안 그려야 진짜 검다.
- 밝기 조작 경합 — 다른 앱과 밝기를 두고 싸우지 말 것. 추적 플래그로 중복 조작 제거.
- 인텐트 필터 오타 — 홈이 안 잡히면 action/category 혼동부터 의심.
- Android 8 암시적 브로드캐스트 차단 — 매니페스트 수신자에게
am broadcast -a ...만 날리면 못 받는다.-n <pkg>/.Receiver로 명시적 컴포넌트를 지정하자 (PC 동기화 도구가 갱신 신호를 안 먹혀서 하루를 태웠다). - 재설치마다 홈 역할 리셋 — 업데이트 후 set-home-activity 재실행 루틴화.
- 버전코드 습관 — versionCode를 올리지 않고 재설치하면 나중에 나 자신이 헷갈린다.
마무리 — 총 8번의 프롬프트로
최종 스펙: 사진 309장+ 웹 고화질, 10초 간격 슬라이드쇼, 좌우 스와이프 앨범 넘김, 더블탭 유튜브(뉴파이프), 폴더 카테고리 필터, 꽉 찬 전체화면, 밤 11시~7시 자동 어둡게, 부팅 자동 실행, PC 동기화 즉시 반영.
바이브코딩의 진짜 힘은 코드를 안 짜는 게 아니라, 요구사항을 정확히 말하는 능력이 곧 개발력이 된다는 데 있다. 실기 테스트에서 부서지는 대로 프롬프트를 고쳐 쓰는 반복 — 그게 이 앱의 개발 과정 전부였다.
다음 편 예고: PC에서 사진을 넣으면 액자에 즉시 반영되는 동기화 도구 만들기 (adb로 백엔드 없이).