링큐 프로젝트의 안드로이드 리포지토리입니다.
📌 진행 사항 확인
| 유지민 | 채윤지 | 홍지현 | 문현우 |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| @ugmin1030 | @KateteDeveloper | @Hongji03 | @Bidoof |
다음은 프로젝트의 구현을 위해 사용하는 기술 스택을 정리한 표입니다.
| 이름 | 설명 |
|---|---|
| Kotlin | 프로그래밍 언어 |
| Jetpack Compose | 인-코드 선언형 앱 설계 |
| Git | 체계적인 코드 관리 및 협업 |
다음과 같은 라이브러리 의존성을 가지고 있습니다.
| 이름 | 버전 | 설명 |
|---|---|---|
| Jetpack Navigation | 2.0.21 | 화면 전환 관리를 위한 라이브러리 |
| Hilt | 2.51.1 | 의존성 주입을 위한 라이브러리 |
| Retrofit2 | 2.11.0 | HTTP 통신을 위한 라이브러리 |
| SharedPreference | 1.2.1 | 로컬 데이터 저장 라이브러리 |
| Room | 2.6.1 | 로컬 데이터베이스 라이브러리 |
| Android JUnit | 1.2.1 | 단위 테스트를 위한 라이브러리 |
본 프로젝트는 멀티 모듈 아키텍쳐를 기반으로, 뷰모델을 사용한 MVVM 디자인 패턴으로 구성합니다.
다음은 본 프로젝트에 기여하는 개발자가 지켜야 할 컨벤션입니다.
본 프로젝트는 Gitflow 브랜치 전략을 따릅니다.
master: 배포 가능한 단위의 브랜치release: 배포 전 테스트가 가능한 단위의 브랜치develop: 개발 중인 브랜치feature/#issue_number: 개발 단위별 브랜치hotfix:master브랜치의 긴급 버그 수정 브랜치
모든 기능 개발은 다음 흐름을 따릅니다.
- 개발하고자 하는 기능에 대한 이슈를 등록하여 번호를 발급합니다.
develop브랜치로부터 분기하여 이슈 번호를 사용해 이름을 붙인feature브랜치를 만든 후 작업합니다.- 작업이 완료되면
develop브랜치에 풀 요청을 작성하고, 팀원의 동의를 얻으면 병합합니다.
커밋은 Gitmoji를 사용해 시각적으로 작성합니다. 다음은 본 프로젝트의 커밋 형식입니다. 각 줄 사이에는 빈 줄이 추가로 있음에 주의해주세요.
[깃모지] [제목]
[본문]
[이슈 번호 참조(선택)]
예시)
:bug: 버튼 버그 수정
키보드 콜백이 불러지지 않는 버그를 수정
관련 이슈 번호: #123, #234
각 깃모지의 의미는 이 블로그를 참고합니다. Android Studio 제공 플러그인을 사용하여 깃모지를 편리하게 이용할 수 있습니다.
이슈는 본 리포지토리에 등록된 목적에 맞는 이슈 템플릿을 사용하여 작성합니다.
Feature Template: 기능 추가를 위한 이슈에 사용Bug Template: 버그 수정을 위한 이슈에 사용
풀 요청은 본 리포지토리에 등록된 템플릿을 사용하여 작성합니다.
코드의 스타일은 Android 공식문서의 Kotlin 스타일 가이드를 최대한 따릅니다. 다음은 주요 네이밍 규칙입니다.
- 작성되는 모든 소스 파일은 UTF-8로 인코딩되어야 합니다.
- 코틀린 파일의 제목은 되도록이면
PascalCase를 사용하여야 합니다. - 컴포저블 함수의 이름은
PascalCase, 그 외 함수의 이름은 동사로 시작하는camelCase를 사용하며, 변수명은camelCase를 사용합니다. (람다식을 저장하는 변수도camelCase를 사용합니다.) - 콜백 함수를 전달하는 변수일 경우
on으로 시작합니다. ex)onButtonClicked,onDataLoaded - 안드로이드 스튜디오 상의 IDE의 노란 줄에 주의합니다.
다음은 본 프로젝트의 안드로이드 개발 환경입니다.
targetSDK: 36,minSDK: 26- Android 16(API 36) 대상 동작은 API 36 기기 또는 에뮬레이터에서 별도 런타임 회귀 검증이 필요합니다.
- 안드로이드 스튜디오 버전: Meerkat | 2024.3.2 또는 그 이상
- 테스트 환경: 안드로이드 스튜디오 제공 에뮬레이터(AVD)
-
- 기기명: Pixel 8
- API 35 (Android 15.0, x86_64)
- 1080 x 2400 px (412 x 915 dp)
다음 앱 설정은 키마다 local.properties의 non-blank 값, LinkU 전용 환경 변수 순서로 읽습니다.
따라서 Android Studio가 sdk.dir만 포함한 local.properties를 생성해도 환경 변수 fallback이 동작합니다.
local.properties 키 |
환경 변수 |
|---|---|
KAKAO_NATIVE_APP_KEY |
LINKU_KAKAO_NATIVE_APP_KEY |
DEV_KAKAO_NATIVE_APP_KEY |
LINKU_DEV_KAKAO_NATIVE_APP_KEY |
GOOGLE_WEB_CLIENT_ID |
LINKU_GOOGLE_WEB_CLIENT_ID |
DEV_GOOGLE_WEB_CLIENT_ID |
LINKU_DEV_GOOGLE_WEB_CLIENT_ID |
SERVER_DOMAIN |
LINKU_SERVER_DOMAIN |
DEV_SERVER_DOMAIN |
LINKU_DEV_SERVER_DOMAIN |
SERVER_HOST |
LINKU_SERVER_HOST |
API_VERSION |
LINKU_API_VERSION |
우선순위는 파일 전체가 아니라 각 키를 기준으로 합니다. local.properties에 특정 앱 설정이 없거나
공백이면 표에 매핑된 LINKU_ 환경 변수를 사용합니다. sdk.dir은 Android Studio가 관리하는 파일에
그대로 둘 수 있으며, 파일이 없는 환경에서는 Android SDK 경로를 표준 ANDROID_HOME 환경 변수로
제공해야 합니다.
이 환경 변수들은 로컬 파일 복사 없이 빌드 설정을 전달하기 위한 수단입니다. 값은 BuildConfig 또는
Android Manifest 등에 포함될 수 있으므로 서버 비밀이나 관리자 토큰을 저장하는 용도로 사용하지 않습니다.
Windows 사용자 환경 변수와 외부 백업 파일도 암호화된 비밀 저장소가 아니며, 동일 사용자 권한의
프로세스가 읽을 수 있습니다.
환경 변수 등록 후에는 Android Studio와 JetBrains Toolbox를 모두 완전히 종료한 뒤 다시 실행해야 새 프로세스가 Windows User 환경 변수를 상속합니다.
Android Release AAB workflow는 main 브랜치 push 시 자동 실행되며, workflow_dispatch로 다른
브랜치에서도 수동 실행할 수 있습니다(리허설 테스트 목적). version_code/version_name은 더 이상
workflow 입력값이 아니라 저장소 루트의 version.properties 파일에서 읽습니다 — 릴리즈 전에 개발자가
이 파일을 직접 수정해서 커밋해야 합니다.
Gradle의 signingConfigs.release가 CI 전용으로 생성된 key.properties를 읽어 bundleRelease
실행 중에 바로 서명까지 완료합니다(별도 jarsigner 서명 단계 없음, 검증만 jarsigner -verify로
수행). 임의의 새 keystore나 key를 생성하지 않습니다. Play App Signing을 사용하는 경우 upload key는
개발자가 Google Play에 AAB를 제출할 때 사용하는 키이며, Google이 최종 APK를 서명하는 app signing
key와는 다른 키입니다 — Google 로그인 등 실제 사용자 기기에서 서명 검증이 필요한 기능은 Play 앱
서명 키의 SHA-1도 Firebase/Google Cloud에 별도로 등록해야 합니다.
VERSION_CODE는 ASCII 10진수 양의 정수(1..2,100,000,000)여야 하며, Play Console에 등록된 최대 versionCode보다 커야 합니다. workflow는 Play Console을 조회하지 않으므로 이 값의 유효성은 실행자가 확인합니다.- 추가로 workflow는 이 저장소에서 실제로 서명까지 성공한 마지막 빌드를
release-vc-<번호>git 태그로 기록하고, 다음 빌드의VERSION_CODE가 그 태그 번호보다 커야만 진행하도록 빌드 초입에서 검사합니다(Validate version code strictly increased스텝). 이 검사는 Play Console의 실제 값과는 무관한, 저장소 자체 이력 기준의 안전장치입니다. VERSION_NAME은 사용자에게 노출되는 버전 문자열입니다(예:1.1.2).
workflow는 다음 11개 Secret이 모두 non-blank인지 빌드 전에 검사합니다. 이름은
.github/workflows/android-release.yml의 계약과 정확히 일치해야 합니다.
| 구분 | GitHub Actions Secret | 용도와 취급 원칙 |
|---|---|---|
| 앱 구성 | LINKU_KAKAO_NATIVE_APP_KEY |
Kakao SDK 초기화와 Manifest/BuildConfig에 전달하는 Android client key |
| 앱 구성 | LINKU_GOOGLE_WEB_CLIENT_ID |
Google 로그인 요청에 사용하는 OAuth web client ID |
| 앱 구성 | LINKU_SERVER_DOMAIN |
release 빌드가 사용하는, scheme과 host를 포함하는 HTTP(S) 서버 기준 URL |
| 앱 구성 | LINKU_SERVER_HOST |
scheme, port, path를 제외한 서버 host 이름 |
| 앱 구성 | LINKU_API_VERSION |
서버 기준 URL 뒤에 결합하는 API path version |
| Firebase 구성 | LINKU_GOOGLE_SERVICES_JSON_BASE64 |
app/src/release/google-services.json(운영 Firebase 프로젝트) 원본 파일의 Base64 표현. debug는 각자 로컬의 app/src/debug/google-services.json(dev 프로젝트)을 그대로 씀 |
| AAB 서명 | LINKU_UPLOAD_KEYSTORE_BASE64 |
기존 upload keystore 파일의 Base64 표현 |
| AAB 서명 | LINKU_UPLOAD_KEYSTORE_TYPE |
기존 keystore 형식(예: JKS 또는 PKCS12) |
| AAB 서명 | LINKU_UPLOAD_KEYSTORE_PASSWORD |
기존 upload keystore 비밀번호 |
| AAB 서명 | LINKU_UPLOAD_KEY_ALIAS |
기존 upload key alias |
| AAB 서명 | LINKU_UPLOAD_KEY_PASSWORD |
기존 upload key 비밀번호 |
앞의 앱 설정 5개는 로컬 빌드에서 사용하는 LINKU_* 환경 변수와 같은 이름입니다.
LINKU_GOOGLE_SERVICES_JSON_BASE64는 workflow가 임시 runner에서
app/src/release/google-services.json으로 복원하기 위한 CI 전용 전달 값입니다(운영 Firebase
프로젝트 기준). debug 빌드는 이 값과 무관하게 각자 로컬의 app/src/debug/google-services.json(dev
프로젝트)을 씁니다.
앱 구성값은 BuildConfig, Manifest 또는 Firebase resource로 최종 AAB에 포함될 수 있으므로 서버 비밀, 관리자 token 또는 backend 전용 credential을 넣지 않습니다. 반면 upload keystore와 비밀번호는 외부에 공개해서는 안 되는 배포 credential입니다. Base64는 파일 형식을 문자열로 바꾸는 encoding일 뿐 암호화가 아니므로, Base64 결과도 원본 keystore와 같은 수준으로 보호합니다.
- 저장소를 전체 히스토리(
fetch-depth: 0)로 checkout합니다 —release-vc-*태그 비교에 필요합니다. version.properties에서VERSION_CODE/VERSION_NAME을 읽습니다.- 이 저장소의
release-vc-*태그 중 최댓값보다VERSION_CODE가 큰지 검증합니다(작거나 같으면 빌드 시작 전에 실패). - 11개 Secret이 non-blank인지 검증합니다.
LINKU_GOOGLE_SERVICES_JSON_BASE64를 임시 runner의app/src/release/google-services.json으로 복원합니다.LINKU_UPLOAD_KEYSTORE_BASE64를 runner의 임시 경로에 제한된 권한으로 복원합니다.- keystore 경로와
LINKU_UPLOAD_KEYSTORE_*/LINKU_UPLOAD_KEY_*값으로 CI 전용key.properties를 저장소 루트에 생성합니다(로컬 개발자의key.properties와 동일한 형식). :app:bundleRelease "-PlinkuVersionCode=<version.properties 값>" "-PlinkuVersionName=<...>"을 실행합니다 —key.properties가 있으므로 Gradle이 빌드 중 바로 서명까지 완료한 AAB를 만듭니다.- 기존 keystore의 지정 alias를 기준으로
jarsigner -verify -strict검증을 수행합니다. - 성공·실패 여부와 관계없이 일반적인 step 종료 경로에서는 임시 keystore와
key.properties를 삭제합니다. - 앞 단계가 모두 성공한 경우에만 signed AAB와 R8 mapping을 각각 artifact로 업로드합니다.
- 마지막으로, 방금 성공한
VERSION_CODE로release-vc-<번호>git 태그를 만들어 원격에 푸시합니다 — 다음 실행의 3번 검증이 참조할 이력입니다. 이 단계 때문에 workflow 권한이contents: write로 설정되어 있습니다.
누락된 Secret, Base64 decode 오류, 버전 코드 역행, release 빌드 실패, keystore type/비밀번호/alias 불일치, 서명 실패 또는 strict 검증 실패가 발생하면 workflow는 실패하며 artifact upload와 태그 push 단계로 진행하지 않습니다. 다만 runner 강제 종료처럼 cleanup step 자체가 실행될 수 없는 상황까지 로컬 삭제를 보장하지는 않습니다. GitHub-hosted runner는 작업 후 폐기되지만, Secret 원본의 별도 보관·회수·회전 정책은 저장소 밖에서 관리해야 합니다.
성공한 run은 version.properties의 versionCode/versionName을 이름에 포함한 다음 두 artifact를
생성하고, release-vc-<version_code> git 태그를 남깁니다.
| Artifact 이름 | 업로드 소스 경로 | 용도 |
|---|---|---|
linku-signed-release-vc-<vc>-vn-<vn> |
app/build/outputs/bundle/release/app-release.aab |
Google Play에 제출할 upload key 서명 AAB (Gradle이 빌드 중 서명을 완료하므로 파일명에 -signed 접미사가 없습니다) |
linku-mapping-vc-<vc>-vn-<vn> |
app/build/outputs/mapping/release/mapping.txt |
해당 release의 R8 난독화 stack trace 복원 |
signed AAB와 mapping은 서로 다른 artifact이므로 mapping 업로드에서만 실패하면 앞서 업로드된 AAB가
실패한 run에 남을 수 있습니다. 일부 step이 성공했더라도 전체 workflow 결론이 실패 또는 취소라면 그
run의 artifact를 배포에 사용하지 않으며, 이 경우 release-vc-* 태그도 생성되지 않습니다.
- GitHub Actions Secrets 11개가 정확한 이름으로 등록되어 있고 공백이 아닌지 확인합니다.
- keystore type, alias와 두 비밀번호가 기존 앱의 upload key 계약과 일치하는지 확인합니다.
version.properties의VERSION_CODE가 Play Console 최대값보다 크고, 이 저장소의 마지막release-vc-*태그보다도 큰지 확인합니다.- workflow 전체 결론이 성공인지 확인합니다.
- signed AAB와 mapping artifact 이름의 versionCode/versionName이
version.properties와 같은지 확인합니다. - Play 업로드 후 같은 release의 mapping을 보존하고 crash/ANR 분석에 연결합니다.
Secret 값, Base64 결과, keystore 원본과 복원 파일은 저장소·issue·PR·workflow 로그에 추가하지 않습니다. Secret 이름, 주입 위치 또는 artifact 계약을 변경할 때는 workflow와 이 문서를 같은 변경에서 함께 갱신합니다.




