1. Empty Views Activity 템플릿으로 생성한 프로젝트 폴더 구조
- UI는 XML로 구현
프로젝트 구조 (Project View):
MyApp/ // 프로젝트 루트 디렉토리
├── app/ // 메인 앱 모듈
│ ├── src/ // 소스코드 루트
│ │ ├── main/ // 메인 소스셋
│ │ │ ├── java/com.example.myapp/ // Kotlin/Java 소스 파일들
│ │ │ │ └── MainActivity.kt // 메인 액티비티 클래스
│ │ │ ├── res/ // 리소스 폴더
│ │ │ │ ├── layout/ // UI 레이아웃 XML 파일들
│ │ │ │ │ └── activity_main.xml // 메인 액티비티 레이아웃
│ │ │ │ ├── values/ // 기본 값 리소스들
│ │ │ │ │ ├── strings.xml // 문자열 리소스
│ │ │ │ │ ├── colors.xml // 색상 리소스
│ │ │ │ │ └── themes.xml // 테마/스타일 리소스
│ │ │ │ ├── values-night/ // 다크 테마용 리소스들
│ │ │ │ │ └── themes.xml // 다크 테마 스타일
│ │ │ │ ├── xml/ // 기타 XML 설정 파일들
│ │ │ │ │ └── backup_rules.xml // 앱 백업 규칙 설정
│ │ │ │ ├── drawable/ // 이미지, 벡터, 셰이프 드로어블
│ │ │ │ └── mipmap/ // 앱 아이콘 (다양한 해상도)
│ │ │ └── AndroidManifest.xml // 앱 설정 및 컴포넌트 선언
│ │ ├── androidTest/ // 계측 테스트 (실제 기기용)
│ │ │ └── java/com.example.myapp/ // 안드로이드 테스트 소스
│ │ └── test/ // 단위 테스트 (로컬 JVM용)
│ │ └── java/com.example.myapp/ // 유닛 테스트 소스
│ └── build.gradle // 모듈 레벨 빌드 설정
├── gradle/ // Gradle 래퍼 파일들
└── build.gradle // 프로젝트 레벨 빌드 설정
각 폴더 구조 설명:
📁 app/src/main/
모든 메인 안드로이드 컴포넌트를 포함하는 폴더 - Java/Kotlin 파일과 리소스 파일들을 분리해서 관리
📁 res/ 폴더 내부
기본 폴더들:
- layout/ - UI 레이아웃 XML 파일들
- values/ - 문자열, 색상, 스타일 등의 값들
- drawable/ - 이미지, 벡터 드로잉 리소스
- mipmap/ - 앱 아이콘 (다양한 해상도)
추가 폴더들:
- values-night/ - 다크 테마용 별도 값들 (Android 10+ 지원)
- xml/ - 앱 백업 규칙, 네트워크 보안 설정 등의 구성 파일들
📁 테스트 폴더 test/ vs androidTest/ 차이
| 구분 | test/ | androidTest/ |
|---|---|---|
| 실행 환경 | 로컬 JVM (Java Virtual Machine) | 실제 Android 기기나 에뮬레이터 |
| 테스트 종류 | 순수 단위 테스트 (Unit Test) | 계측 테스트 (Instrumented Test) |
| Android API 접근 | 불가능 (Mock 객체 사용) | 가능 (실제 Context, View 등) |
| 실행 속도 | 빠름 | 느림 (APK 설치 필요) |
| 사용 예시 | 비즈니스 로직, 계산 함수 | UI 테스트, 데이터베이스, 권한 |
언제 어떤 테스트를 사용할까?
- test/ - Android 프레임워크를 사용하지 않는 순수 Java/Kotlin 코드
- androidTest/ - Android 종속성이 있거나 Mock 객체로 만족할 수 없는 코드
2. MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // 부모 클래스인 AppCompatActivity의 onCreate()를 호출
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
// 시스템 바가 차지하는 영역의 크기(패딩 값)를 추출
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
// 가져온 시스템 바 크기만큼 뷰에 패딩을 설정
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
// insets 반환
insets
}
}
}
super.onCreate(savedInstanceState)
- 목적: 액티비티를 생성하고 초기화
- 동작: AppCompatActivity와 같은 부모 클래스의 onCreate() 메서드를 호출하여 액티비티의 생명주기를 올바르게 시작하고 필요한 초기 설정을 진행
savedInstanceState: Bundle?
- 목적: 액티비티가 비정상적으로 종료되기 전의 상태를 저장하고, 다시 시작될 때 해당 상태를 복원하는 데 사용
예를 들어, 기기 회전 시 화면이 재구성될 때 이전 데이터를 잃지 않도록 도와줍니다. - 동작:
Bundle객체는 키-값(key-value) 쌍 형태로 데이터를 저장
안드로이드 시스템은onSaveInstanceState()콜백 메서드에서 이 Bundle에 데이터를 저장하고,onCreate()로 다시 전달하여 이전 상태를 그대로 복원할 수 있게 해줍니다.
setContentView()
- 목적: 액티비티(화면)에 표시할 UI 레이아웃을 연결
- 동작: res/layout/ 폴더에 있는 activity_main.xml 파일을 읽어 화면에 뷰들을 생성하고 배치
Edge-to-Edge 코드 : enableEdgeToEdge(), ViewCompat.setOnApplyWindowInsetsListener()
enableEdgeToEdge()- 목적: 시스템 바(상태바, 네비게이션바)를 투명하게 만들어 앱 화면이 전체 디스플레이를 사용
- 효과: 앱이 상태바와 네비게이션바 뒤까지 그려짐
- 도입 시기: Android 15부터 기본 적용
ViewCompat.setOnApplyWindowInsetsListener()- 목적: 시스템 바와 겹치는 부분에 패딩을 추가하여 콘텐츠가 시스템 바에 가려지지 않게 함
- 동작: 시스템 바 크기만큼 뷰에 패딩을 자동으로 적용
- 필요성: enableEdgeToEdge() 사용 시 필수
Edge-to-Edge 없이는 앱 콘텐츠가 시스템 바 아래 영역에서만 표시되지만,
Edge-to-Edge 적용 시 전체 화면을 사용하므로 시스템 바와 겹치는 문제를 해결해야 합니다.
Activity 상속 계층 구조
java.lang.Object // Java 기반 | kotlin.Any (= Java의 Object) // 코틀린 기반
↓
android.content.Context (추상클래스)
↓
android.content.ContextWrapper
↓
android.content.ContextThemeWrapper
↓
android.app.Activity
↓
androidx.fragment.app.FragmentActivity
↓
androidx.appcompat.app.AppCompatActivity
차이점: Kotlin에서는 kotlin.Any가 최상위 클래스이고, Java의 Object와 동일한 역할을 합니다.
각 클래스 역할
| 클래스 | 역할 |
|---|---|
| Context | 앱 환경 정보에 접근하는 추상 클래스 - 리소스, 파일, 데이터베이스 접근 |
| ContextWrapper | Context를 래핑하여 기능을 확장할 수 있게 하는 클래스 |
| ContextThemeWrapper | 테마 지원을 추가한 Context |
| Activity | 사용자와 상호작용하는 단일 화면을 제공하는 기본 클래스 |
| FragmentActivity | Fragment 지원을 추가한 Activity |
| AppCompatActivity | 하위 버전 호환성 지원 (Material Design, ActionBar 등) |
3. AndroidManifest.xml과 앱의 첫 시작점
MainActivity가 main 함수 같은 역할인가? : 비슷하지만 다릅니다.
유사점:
- 앱 진입점 역할
- 가장 먼저 실행되는 코드
차이점:
- Java/Kotlin의 main()은 프로그램 시작점
- MainActivity는 여러 Activity 중 첫 번째로 표시되는 화면
앱 실행 시작점 결정 방법
AndroidManifest.xml에서 결정:
안드로이드 앱은 다음 4가지 주요 컴포넌트(4대 컴포넌트)로 구성됩니다:
- Activity - 사용자 인터페이스가 있는 하나의 화면
- Service - 백그라운드에서 실행되는 컴포넌트
- Content Provider - 데이터 공유를 위한 컴포넌트
- Broadcast Receiver - 시스템 이벤트를 수신하는 컴포넌트
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
핵심 요소:
- android.intent.action.MAIN - 앱의 주요 진입점임을 알림
- android.intent.category.LAUNCHER - 런처(홈화면)에서 실행 가능함을 알림
실제 앱 아이콘 위치:
LAUNCHER 카테고리는 앱 서랍(App Drawer) 또는 모든 앱 메뉴에 아이콘을 생성합니다.
홈화면에 바로 나타나지는 않고, 사용자가 직접 드래그하거나 설치 후 홈화면 추가를 선택해야 홈화면에 표시됩니다.
여러 Activity와 XML 연결
Activity-XML 연결 과정:
- 각 Activity마다 별도 XML 레이아웃 파일
- setContentView()로 연결
- findViewById() / ViewBinding 등로 개별 뷰 참조
// MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) // activity_main.xml 연결
}
}
// SecondActivity.kt
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second) // activity_second.xml 연결
}
}
파일 구조 예시:
res/layout/
├── activity_main.xml (MainActivity용)
├── activity_second.xml (SecondActivity용)
└── activity_settings.xml (SettingsActivity용)
4. R 클래스 개념
R은 AAPT(Android Asset Packaging Tool)가 자동 생성하는 클래스로, res/ 디렉토리의 모든 리소스에 대한 ID를 포함합니다.
이러한 R 클래스는 컴파일 시점에 생성됩니다.
R 클래스의 역할:
- 모든 리소스에 고유한 정수 ID 할당
- XML에서 문자열로 참조하던 것을 컴파일 타임에 정수로 변환
- 타입 안전성 제공 (잘못된 리소스 참조 시 컴파일 에러)
R 클래스 구조 예시:
public final class R {
public static final class layout {
public static final int activity_main = 0x7f0c001a;
public static final int activity_second = 0x7f0c001b;
}
public static final class id {
public static final int textView = 0x7f080045;
public static final int button = 0x7f080046;
}
public static final class string {
public static final int app_name = 0x7f0f0001;
}
}
사용 예시:
setContentView(R.layout.activity_main) // 레이아웃 리소스
findViewById<TextView>(R.id.textView) // ID 리소스
getString(R.string.app_name) // 문자열 리소스
5. 유용한 Activity 함수들
View 참조 방법들
1. findViewById() (전통적 방법)
val textView: TextView = findViewById(R.id.textView)
val button: Button = findViewById(R.id.button)
2. View Binding (권장) - 추가 설정 필요
build.gradle(Module: app) 설정:
android {
buildFeatures { // 안드로이드 스튜디오 4.0 이상 시
viewBinding = true
}
// 이하라면 아래처럼 작성 3.6 보다 낮으면 안 됨
// viewBinding {
// enabled true
//}
}
적용:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 직접 접근 가능
binding.textView.text = "Hello"
binding.button.setOnClickListener { }
}
}
View Binding이 권장되는 이유:findViewById() 을 대체할 수 있고, 다음과 같은 장점이 있습니다:
- Null 안전성: 존재하지 않는 뷰 참조 방지
- 타입 안전성: 잘못된 타입 캐스팅 방지
- 빠른 컴파일: 어노테이션 처리 불필요
3. Data Binding
// XML에서 직접 데이터 바인딩
<TextView
android:text="@{user.name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
ID를 기반으로 특정 View 찾기
<TextView
android:id="@+id/tv_title"
android:text="제목"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_description"
android:text="설명"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_time"
android:text="시간"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
findViewById 사용 시:
val titleView = findViewById<TextView>(R.id.tv_title)
val descView = findViewById<TextView>(R.id.tv_description)
val timeView = findViewById<TextView>(R.id.tv_time)
View Binding 사용 시:
binding.tvTitle.text = "제목" // tv_title → tvTitle (camelCase 변환)
binding.tvDescription.text = "설명" // tv_description → tvDescription
binding.tvTime.text = "시간" // tv_time → tvTime
정리:
- XML ID: @+id/tv_title → R 클래스: R.id.tv_title
- View Binding: snake_case → camelCase 자동 변환
- 같은 타입의 뷰라도 각각 다른 고유 ID를 부여하여 구분
리소스 접근 함수들
| 함수 | 역할 | 예시 |
|---|---|---|
| getString() | 문자열 리소스 가져오기 | getString(R.string.app_name) |
| getColor() | 색상 리소스 가져오기 | getColor(R.color.primary) |
| getDrawable() | 드로어블 리소스 가져오기 | getDrawable(R.drawable.icon) |
액티비티의 생명주기 관련 및 스레드 관련 기타 함수들
| 함수 | 역할 | 예시 |
|---|---|---|
| startActivity() | 다른 Activity 시작 | startActivity(Intent(this, SecondActivity::class.java)) |
| finish() | 현재 Activity 종료 | finish() |
| runOnUiThread() | UI 스레드에서 코드 실행 | runOnUiThread { textView.text = "Updated" } |
| getSystemService() | 시스템 서비스 접근 | getSystemService(Context.WIFI_SERVICE) |
참고자료
- Android 공식 문서 - Application Fundamentals
- Android 공식 문서 - 계측 테스트
- Android 공식 문서 - 로컬 단위 테스트
- Android 공식 문서 - Activity 클래스
- Stack Overflow - Activity 상속 구조
- Android 공식 문서 - Edge-to-Edge
- Medium - Android 15 Edge-to-Edge 가이드
- Android 공식 문서 - AndroidManifest.xml
- GeeksforGeeks - Android Manifest
- Android 공식 문서 - View Binding
- Android 공식 문서 - R 클래스와 리소스 접근
- todaycode.tistory - 안드로이드 뷰 바인딩(view binding)
'CS 기초부터 한 걸음씩' 카테고리의 다른 글
| [Android] Fragment 개념 정리 (4) | 2025.08.21 |
|---|---|
| [Android] XML 레이아웃과 View 기본 개념 (0) | 2025.08.20 |
| [Android] Android Studio Logcat과 로그 레벨 (3) | 2025.08.18 |
| [Android] Activity와 Activity Lifecycle 기초 개념 (5) | 2025.08.18 |
| word2vec 이란? - 분포 가설부터 학습 알고리즘까지 (4) | 2025.08.04 |