Skip to content

hundong2/AndroidStudy

Repository files navigation

TsetApplication - Android 초보자 가이드

Android 개발을 처음 시작하는 분들을 위한 상세한 가이드입니다.

목차

  1. 프로젝트 소개
  2. 프로젝트 폴더 구조
  3. 코틀린 기본 지식
  4. Android 앱 실행 원리
  5. UI와 상호작용하는 기술
  6. 코드 상세 분석
  7. 앱 빌드 및 실행 방법

프로젝트 소개

이 프로젝트는 Android 멀티 모듈 애플리케이션입니다. 스마트폰과 자동차(Android Automotive) 모두에서 작동하는 음악 플레이어 앱의 기본 구조를 담고 있습니다.

프로젝트 정보

  • 언어: Kotlin (코틀린) 2.0.21
  • 최소 SDK: Android 9.0 (API 28)
  • 대상 SDK: Android 36
  • 빌드 도구: Gradle 8.13
  • UI 프레임워크: Material Design 3

모듈 구성

  1. mobile: 스마트폰용 앱
  2. automotive: 자동차용 앱
  3. shared: 두 앱이 공유하는 라이브러리

프로젝트 폴더 구조

전체 구조 개요

TsetApplication/
├── mobile/                    # 📱 스마트폰 앱 모듈
├── automotive/                # 🚗 자동차 앱 모듈
├── shared/                    # 📦 공유 라이브러리
├── gradle/                    # ⚙️ Gradle 빌드 설정
├── build.gradle.kts           # 🔧 루트 빌드 설정
├── settings.gradle.kts        # 🔧 프로젝트 설정
└── README.md                  # 📖 이 문서

1. mobile 모듈 상세 구조

mobile/
├── build.gradle.kts                          # 모듈 빌드 설정
├── src/
│   ├── main/                                 # 메인 소스 코드
│   │   ├── java/com/example/tsetapplication/ # Kotlin 코드
│   │   │   └── MainActivity.kt               # 메인 화면 액티비티
│   │   ├── res/                              # 리소스 파일들
│   │   │   ├── layout/                       # 화면 레이아웃
│   │   │   │   └── activity_main.xml         # 메인 화면 레이아웃
│   │   │   ├── drawable/                     # 이미지, 도형 등
│   │   │   ├── mipmap-*/                     # 앱 아이콘 (화질별)
│   │   │   ├── values/                       # 값 리소스
│   │   │   │   ├── colors.xml                # 색상 정의
│   │   │   │   ├── strings.xml               # 문자열 정의
│   │   │   │   └── themes.xml                # 테마 정의
│   │   │   └── xml/                          # 기타 XML 설정
│   │   └── AndroidManifest.xml               # 앱 설정 파일
│   ├── test/                                 # 단위 테스트
│   └── androidTest/                          # UI 테스트
└── proguard-rules.pro                        # 코드 난독화 규칙

주요 폴더 설명

src/main/java/ - 코틀린 소스 코드
  • 앱의 실제 로직이 작성되는 곳
  • 패키지명 형식: com.example.tsetapplication
  • 모든 .kt 파일(코틀린 파일)이 여기에 위치
src/main/res/ - 리소스 폴더
  • layout/: XML로 작성된 화면 레이아웃
  • drawable/: 이미지, 벡터 그래픽, 도형 등
  • mipmap-*/: 앱 아이콘 (hdpi, xhdpi 등 화질별 분류)
  • values/: 색상, 문자열, 스타일 등의 값 정의
  • xml/: 백업 규칙, 기타 설정 파일
AndroidManifest.xml - 앱 매니페스트
  • 앱의 기본 정보를 선언하는 필수 파일
  • 앱 이름, 아이콘, 권한, 액티비티 등을 정의

2. shared 모듈 상세 구조

shared/
├── build.gradle                              # 라이브러리 빌드 설정
├── src/
│   └── main/
│       ├── java/com/example/tsetapplication/shared/
│       │   └── MyMusicService.kt             # 음악 서비스
│       ├── res/
│       │   └── xml/
│       │       └── automotive_app_desc.xml   # Automotive 설정
│       └── AndroidManifest.xml               # 서비스 선언

3. Gradle 설정 파일

build.gradle.kts (루트)

  • 전체 프로젝트의 공통 플러그인 정의
  • 모든 모듈에 적용되는 기본 설정

settings.gradle.kts

  • 프로젝트에 포함될 모듈 선언
  • 저장소(Repository) 설정

gradle.properties

  • Gradle 빌드 성능 설정
  • JVM 메모리 옵션 등

gradle/libs.versions.toml

  • 의존성 라이브러리 버전 관리
  • 중앙 집중식 버전 관리 파일

코틀린 기본 지식

1. 코틀린이란?

코틀린(Kotlin)은 JetBrains가 개발한 현대적인 프로그래밍 언어로, Google이 Android 공식 언어로 채택했습니다.

코틀린의 특징

  • 간결함: Java보다 적은 코드로 같은 기능 구현
  • 안전성: Null 안전성으로 런타임 오류 감소
  • 상호운용성: Java 코드와 100% 호환
  • 현대적: 함수형 프로그래밍, 코루틴 등 지원

2. 코틀린 기본 문법

변수 선언

// val: 불변 변수 (값 변경 불가, Java의 final)
val name = "Android"
val count: Int = 10

// var: 가변 변수 (값 변경 가능)
var score = 0
score = 100  // OK

함수 선언

// 기본 형태
fun greet(name: String): String {
    return "Hello, $name!"
}

// 단일 표현식 함수
fun add(a: Int, b: Int) = a + b

// 반환값 없는 함수
fun printMessage(msg: String): Unit {  // Unit은 생략 가능
    println(msg)
}

클래스 선언

// 기본 클래스
class Person {
    var name: String = ""
    var age: Int = 0
}

// 생성자가 있는 클래스
class Student(val name: String, var grade: Int) {
    fun study() {
        println("$name is studying")
    }
}

// 클래스 사용
val student = Student("John", 10)
student.study()  // "John is studying"

상속 (Inheritance)

// 부모 클래스 (open 키워드 필요)
open class Animal {
    open fun sound() {
        println("Some sound")
    }
}

// 자식 클래스
class Dog : Animal() {
    override fun sound() {
        println("Bark!")
    }
}

Null 안전성

// Nullable 타입 (? 붙임)
var name: String? = null
name = "Kotlin"

// Non-null 타입 (? 없음)
var age: Int = 10
// age = null  // 컴파일 오류!

// 안전한 호출
val length = name?.length  // name이 null이면 length도 null

// Elvis 연산자
val len = name?.length ?: 0  // name이 null이면 0 반환

3. Android에서 자주 사용하는 코틀린 기능

람다 표현식

// 기본 형태
val sum = { a: Int, b: Int -> a + b }
println(sum(3, 5))  // 8

// 리스너에서 자주 사용
button.setOnClickListener { view ->
    // 버튼 클릭 시 실행
    println("Button clicked")
}

// 파라미터가 하나일 때 it 사용
items.forEach { item ->
    println(item)
}

// 더 간단하게
items.forEach {
    println(it)  // it은 현재 item
}

apply, let, run, with 등의 스코프 함수

// apply: 객체 설정에 유용
val person = Person().apply {
    name = "John"
    age = 25
}

// let: null 체크와 함께 사용
name?.let {
    println("Name is $it")
}

// with: 객체의 여러 메서드 호출
with(person) {
    println(name)
    println(age)
}

Android 앱 실행 원리

1. Android 앱의 4대 컴포넌트

Android 앱은 4가지 주요 구성 요소로 이루어집니다:

1️⃣ Activity (액티비티)

  • 사용자 인터페이스 화면을 담당
  • 하나의 Activity = 하나의 화면
  • 예: 로그인 화면, 메인 화면, 설정 화면 등

2️⃣ Service (서비스)

  • 백그라운드 작업을 담당
  • 화면 없이 실행
  • 예: 음악 재생, 파일 다운로드 등

3️⃣ Broadcast Receiver (브로드캐스트 리시버)

  • 시스템 이벤트를 수신
  • 예: 배터리 부족 알림, 네트워크 변경 등

4️⃣ Content Provider (컨텐츠 프로바이더)

  • 앱 간 데이터 공유
  • 예: 연락처, 갤러리 데이터 접근

2. 앱 실행 흐름

사용자가 앱 아이콘 터치
    ↓
Android 시스템이 앱 프로세스 시작
    ↓
Application 클래스 생성 (앱 전체 초기화)
    ↓
AndroidManifest.xml 읽기
    ↓
MAIN/LAUNCHER 액티비티 찾기
    ↓
MainActivity 생성 및 onCreate() 호출
    ↓
레이아웃 파일(activity_main.xml) 로드
    ↓
화면에 UI 표시
    ↓
사용자 입력 대기

3. Activity 생명주기 (Lifecycle)

Activity는 생명주기를 가지며, 각 단계마다 호출되는 콜백 메서드가 있습니다:

class MainActivity : AppCompatActivity() {

    // 1. 액티비티가 생성될 때 (최초 1회)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 초기화 작업: UI 설정, 변수 초기화 등
    }

    // 2. 화면이 보이기 시작할 때
    override fun onStart() {
        super.onStart()
        // 리소스 준비
    }

    // 3. 사용자와 상호작용 가능한 상태
    override fun onResume() {
        super.onResume()
        // 애니메이션 시작, 센서 등록 등
    }

    // 4. 일시 정지 (다른 앱이 foreground로 옴)
    override fun onPause() {
        super.onPause()
        // 애니메이션 중지, 데이터 저장 등
    }

    // 5. 화면이 보이지 않을 때
    override fun onStop() {
        super.onStop()
        // 리소스 해제
    }

    // 6. 액티비티가 완전히 종료될 때
    override fun onDestroy() {
        super.onDestroy()
        // 최종 정리 작업
    }
}

생명주기 흐름도

onCreate() → onStart() → onResume() [실행 중] → onPause() → onStop() → onDestroy()
             ↑                                                    ↓
             └─────────────── onRestart() ←──────────────────────┘

4. AndroidManifest.xml의 역할

모든 Android 앱은 AndroidManifest.xml 파일을 가져야 하며, 다음 정보를 포함합니다:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.tsetapplication">

    <!-- 권한 선언 -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.TsetApplication">

        <!-- 액티비티 선언 -->
        <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>

        <!-- 서비스 선언 -->
        <service android:name=".MyMusicService" />

    </application>
</manifest>

UI와 상호작용하는 기술

1. Android UI 구조

Android UI는 XML 레이아웃Kotlin 코드로 구성됩니다:

화면 = Layout (XML) + Activity (Kotlin)

View와 ViewGroup

  • View: 화면에 표시되는 모든 UI 요소의 기본 클래스

    • 예: TextView, Button, ImageView 등
  • ViewGroup: View들을 담는 컨테이너

    • 예: LinearLayout, ConstraintLayout, FrameLayout 등
ViewGroup (컨테이너)
  ├── View (텍스트)
  ├── View (버튼)
  └── ViewGroup (하위 컨테이너)
        ├── View (이미지)
        └── View (입력창)

2. 레이아웃 XML 이해하기

activity_main.xml 예제 분석

<?xml version="1.0" encoding="utf-8"?>
<!-- ConstraintLayout: 제약 조건으로 View 위치를 지정하는 레이아웃 -->
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main"
    android:layout_width="match_parent"    <!-- 부모 너비만큼 -->
    android:layout_height="match_parent">  <!-- 부모 높이만큼 -->

    <!-- TextView: 텍스트를 표시하는 View -->
    <TextView
        android:layout_width="wrap_content"   <!-- 내용만큼 -->
        android:layout_height="wrap_content"  <!-- 내용만큼 -->
        android:text="Hello World!"

        <!-- 제약 조건: 화면 중앙에 배치 -->
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

주요 속성 설명

속성 설명 가능한 값
android:id View의 고유 식별자 @+id/view_name
layout_width 너비 match_parent, wrap_content, 100dp
layout_height 높이 match_parent, wrap_content, 100dp
android:text 텍스트 내용 "문자열" 또는 @string/name
android:textSize 글자 크기 16sp
android:textColor 글자 색상 #FF0000 또는 @color/red

3. Layout 종류

ConstraintLayout (추천)

  • 유연한 레이아웃
  • 성능 최적화
  • 복잡한 UI도 깊이 없이 구현 가능
<androidx.constraintlayout.widget.ConstraintLayout>
    <Button
        android:id="@+id/button1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button2"
        app:layout_constraintTop_toBottomOf="@id/button1"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

LinearLayout

  • 세로 또는 가로로 순서대로 배치
<LinearLayout
    android:orientation="vertical">  <!-- 세로 배치 -->
    <TextView android:text="첫 번째" />
    <TextView android:text="두 번째" />
    <TextView android:text="세 번째" />
</LinearLayout>

FrameLayout

  • View를 겹쳐서 배치
<FrameLayout>
    <ImageView />  <!-- 배경 이미지 -->
    <TextView />   <!-- 이미지 위에 텍스트 -->
</FrameLayout>

4. Activity에서 View 접근하기

방법 1: findViewById (전통적 방법)

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // XML의 View를 Kotlin 코드에서 가져오기
        val textView = findViewById<TextView>(R.id.textView)
        textView.text = "텍스트 변경"

        val button = findViewById<Button>(R.id.button)
        button.setOnClickListener {
            // 버튼 클릭 시 실행
            textView.text = "버튼이 클릭되었습니다"
        }
    }
}

방법 2: View Binding (권장)

// build.gradle.kts에 설정 필요
android {
    buildFeatures {
        viewBinding = true
    }
}

// Activity에서 사용
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // View에 쉽게 접근
        binding.textView.text = "텍스트 변경"
        binding.button.setOnClickListener {
            binding.textView.text = "버튼 클릭됨"
        }
    }
}

5. 사용자 입력 처리

버튼 클릭 이벤트

// 방법 1: setOnClickListener
button.setOnClickListener {
    Toast.makeText(this, "버튼 클릭!", Toast.LENGTH_SHORT).show()
}

// 방법 2: 람다 표현식으로 View 접근
button.setOnClickListener { view ->
    view.isEnabled = false  // 버튼 비활성화
}

EditText 입력 감지

val editText = findViewById<EditText>(R.id.editText)

// 텍스트 변경 감지
editText.addTextChangedListener(object : TextWatcher {
    override fun afterTextChanged(s: Editable?) {
        println("입력된 텍스트: ${s.toString()}")
    }

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})

6. Toast와 Snackbar

Toast: 간단한 메시지 표시

// 짧은 메시지 (2초)
Toast.makeText(this, "저장되었습니다", Toast.LENGTH_SHORT).show()

// 긴 메시지 (3.5초)
Toast.makeText(this, "오류가 발생했습니다", Toast.LENGTH_LONG).show()

Snackbar: 액션이 있는 메시지

Snackbar.make(view, "삭제되었습니다", Snackbar.LENGTH_LONG)
    .setAction("취소") {
        // 취소 버튼 클릭 시 실행
        println("삭제 취소")
    }
    .show()

7. Intent로 화면 전환

다른 Activity로 이동

// 간단한 이동
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)

// 데이터 전달
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("name", "John")
intent.putExtra("age", 25)
startActivity(intent)

// SecondActivity에서 데이터 받기
class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val name = intent.getStringExtra("name")
        val age = intent.getIntExtra("age", 0)

        println("Name: $name, Age: $age")
    }
}

8. RecyclerView로 리스트 표시

RecyclerView는 긴 목록을 효율적으로 표시하는 뷰입니다.

// 1. 데이터 클래스
data class Item(val title: String, val description: String)

// 2. ViewHolder
class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val titleText: TextView = view.findViewById(R.id.titleText)
    val descText: TextView = view.findViewById(R.id.descText)
}

// 3. Adapter
class ItemAdapter(private val items: List<Item>) :
    RecyclerView.Adapter<ItemViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_layout, parent, false)
        return ItemViewHolder(view)
    }

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = items[position]
        holder.titleText.text = item.title
        holder.descText.text = item.description
    }

    override fun getItemCount() = items.size
}

// 4. Activity에서 사용
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = ItemAdapter(itemList)

코드 상세 분석

1. MainActivity.kt 분석

파일 위치: mobile/src/main/java/com/example/tsetapplication/MainActivity.kt

package com.example.tsetapplication

// 필요한 클래스들을 import (가져오기)
import android.os.Bundle                          // 데이터 전달용
import androidx.activity.enableEdgeToEdge         // 전체 화면 모드
import androidx.appcompat.app.AppCompatActivity   // 기본 Activity 클래스
import androidx.core.view.ViewCompat              // View 호환성 도구
import androidx.core.view.WindowInsetsCompat      // 시스템 바 정보

// MainActivity 클래스 선언
// : AppCompatActivity() = AppCompatActivity를 상속받음
class MainActivity : AppCompatActivity() {

    // override = 부모 클래스의 메서드를 재정의
    // onCreate = Activity가 생성될 때 호출되는 메서드
    override fun onCreate(savedInstanceState: Bundle?) {
        // 부모 클래스의 onCreate 먼저 호출 (필수!)
        super.onCreate(savedInstanceState)

        // Edge-to-Edge 모드 활성화
        // 상태바, 내비게이션바 뒤까지 컨텐츠 표시
        enableEdgeToEdge()

        // XML 레이아웃 파일을 화면에 표시
        // R.layout.activity_main = res/layout/activity_main.xml
        setContentView(R.layout.activity_main)

        // 시스템 바(상태바, 내비게이션바) 영역 처리
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            // 시스템 바의 크기 정보 가져오기
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())

            // View에 패딩 설정 (시스템 바에 가려지지 않도록)
            v.setPadding(
                systemBars.left,    // 왼쪽 패딩
                systemBars.top,     // 위쪽 패딩
                systemBars.right,   // 오른쪽 패딩
                systemBars.bottom   // 아래쪽 패딩
            )

            // insets를 반환 (다른 리스너에서도 사용 가능하도록)
            insets
        }
    }
}

코드 실행 순서

  1. 사용자가 앱 아이콘 터치
  2. Android 시스템이 MainActivity 인스턴스 생성
  3. onCreate() 메서드 호출
  4. enableEdgeToEdge() - 전체 화면 모드 활성화
  5. setContentView() - activity_main.xml 레이아웃 로드
  6. setOnApplyWindowInsetsListener() - 시스템 바 영역 패딩 설정
  7. 화면 표시 완료

주요 개념 설명

AppCompatActivity

  • 모든 Activity의 기본 클래스
  • Android 이전 버전과의 호환성 제공
  • ActionBar, 테마 등의 기능 포함

Bundle

  • 데이터를 저장하고 전달하는 컨테이너
  • Activity 재생성 시 상태 복원에 사용
  • savedInstanceState로 이전 상태 받음

findViewById()

  • XML에 정의된 View를 Kotlin 코드에서 찾는 메서드
  • ID로 View를 검색
  • 반환 타입: View (형변환 필요)

2. activity_main.xml 분석

파일 위치: mobile/src/main/res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- XML 선언: 파일 버전과 인코딩 지정 -->

<!-- ConstraintLayout: 제약 조건 기반 레이아웃 -->
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"

    <!-- View의 고유 ID (MainActivity에서 findViewById로 찾을 때 사용) -->
    android:id="@+id/main"

    <!-- 레이아웃 크기 -->
    android:layout_width="match_parent"    <!-- 부모 너비만큼 (화면 전체) -->
    android:layout_height="match_parent"   <!-- 부모 높이만큼 (화면 전체) -->

    <!-- tools 네임스페이스: 개발 시에만 사용, 실제 앱에는 영향 없음 -->
    tools:context=".MainActivity">

    <!-- TextView: 텍스트를 표시하는 View -->
    <TextView
        <!-- wrap_content: 내용물 크기만큼 -->
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        <!-- 표시할 텍스트 -->
        android:text="Hello World!"

        <!-- ConstraintLayout 제약 조건들 -->
        <!-- Bottom을 parent의 Bottom에 연결 -->
        app:layout_constraintBottom_toBottomOf="parent"

        <!-- End(오른쪽)를 parent의 End에 연결 -->
        app:layout_constraintEnd_toEndOf="parent"

        <!-- Start(왼쪽)를 parent의 Start에 연결 -->
        app:layout_constraintStart_toStartOf="parent"

        <!-- Top을 parent의 Top에 연결 -->
        app:layout_constraintTop_toTopOf="parent" />

        <!-- 네 방향 모두 parent 중앙에 연결 → 화면 정중앙에 배치 -->

</androidx.constraintlayout.widget.ConstraintLayout>

ConstraintLayout 제약 조건 이해

화면 (parent)
┌─────────────────────────────┐
│                             │
│                             │ ← Top
│           TextView          │ ← 모든 방향이 parent와 연결되어
│         "Hello World!"      │   화면 정중앙에 위치
│                             │ ← Bottom
│                             │
└─────────────────────────────┘
  Start →              ← End

3. MyMusicService.kt 분석

파일 위치: shared/src/main/java/com/example/tsetapplication/shared/MyMusicService.kt

package com.example.tsetapplication.shared

// 필요한 클래스 import
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat.MediaItem
import androidx.media.MediaBrowserServiceCompat
import android.support.v4.media.session.MediaSessionCompat
import java.util.ArrayList

/**
 * MyMusicService: 음악 재생 서비스
 *
 * MediaBrowserServiceCompat를 상속받아 구현
 * - Android Auto, Automotive에서 음악 탐색 가능
 * - 백그라운드에서 음악 재생
 * - 다른 앱(예: Android Auto)에서 제어 가능
 */
class MyMusicService : MediaBrowserServiceCompat() {

    // MediaSession: 미디어 재생 상태 관리
    // lateinit: 나중에 초기화할 변수 (onCreate에서 초기화)
    private lateinit var session: MediaSessionCompat

    // callback: 사용자 동작(재생, 일시정지 등)을 처리하는 객체
    // object: 익명 클래스 인스턴스 생성
    private val callback = object : MediaSessionCompat.Callback() {

        // 재생 버튼을 눌렀을 때
        override fun onPlay() {
            // TODO: 실제 음악 재생 로직 구현
        }

        // 특정 곡으로 이동
        override fun onSkipToQueueItem(queueId: Long) {}

        // 특정 위치로 이동 (예: 30초로 이동)
        override fun onSeekTo(position: Long) {}

        // 미디어 ID로 재생
        override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {}

        // 일시정지
        override fun onPause() {}

        // 정지
        override fun onStop() {}

        // 다음 곡
        override fun onSkipToNext() {}

        // 이전 곡
        override fun onSkipToPrevious() {}

        // 사용자 정의 동작
        override fun onCustomAction(action: String?, extras: Bundle?) {}

        // 검색어로 재생 (예: "비틀즈 노래 틀어줘")
        override fun onPlayFromSearch(query: String?, extras: Bundle?) {}
    }

    // 서비스가 생성될 때 호출 (최초 1회)
    override fun onCreate() {
        super.onCreate()

        // MediaSession 생성
        session = MediaSessionCompat(this, "MyMusicService")

        // Session Token 설정 (다른 앱이 이 서비스를 찾을 수 있게 함)
        sessionToken = session.sessionToken

        // Callback 등록 (사용자 동작 처리)
        session.setCallback(callback)

        // 플래그 설정
        session.setFlags(
            // 미디어 버튼 처리 (헤드셋 버튼 등)
            MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
            // 전송 제어 처리 (재생, 일시정지 등)
            MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
        )
    }

    // 서비스가 종료될 때 호출
    override fun onDestroy() {
        // MediaSession 리소스 해제
        session.release()
    }

    /**
     * onGetRoot: 클라이언트가 서비스에 연결할 때 호출
     *
     * @param clientPackageName 연결 요청한 앱의 패키지명
     * @param clientUid 클라이언트 앱의 UID
     * @param rootHints 클라이언트가 제공한 힌트
     * @return BrowserRoot 객체 (연결 허용 시) 또는 null (거부 시)
     */
    override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): MediaBrowserServiceCompat.BrowserRoot? {
        // "root"라는 ID로 루트 반환 (모든 클라이언트 허용)
        // 실제 앱에서는 clientPackageName을 검사하여 보안 강화 필요
        return MediaBrowserServiceCompat.BrowserRoot("root", null)
    }

    /**
     * onLoadChildren: 미디어 항목 목록을 요청할 때 호출
     *
     * @param parentId 부모 미디어 항목의 ID
     * @param result 결과를 전달할 객체
     */
    override fun onLoadChildren(
        parentId: String,
        result: Result<MutableList<MediaItem>>
    ) {
        // 빈 리스트 반환 (실제로는 음악 목록을 반환해야 함)
        // TODO: 데이터베이스나 파일에서 음악 목록 가져오기
        result.sendResult(ArrayList())
    }
}

MediaBrowserService 구조

┌──────────────────────────┐
│   Android Auto / 자동차   │
│   (클라이언트)            │
└────────┬─────────────────┘
         │ 연결 요청
         ↓
┌──────────────────────────┐
│   MyMusicService         │
│   (서비스)               │
├──────────────────────────┤
│ onGetRoot() ← 연결 허용? │
│ onLoadChildren() ← 목록  │
├──────────────────────────┤
│   MediaSession           │
│   - callback             │
│   - onPlay()             │
│   - onPause()            │
│   - onSkipToNext()       │
└──────────────────────────┘
         ↓
    실제 음악 재생

4. R 클래스란?

R 클래스는 Android가 자동으로 생성하는 클래스로, 모든 리소스에 접근할 수 있는 ID를 제공합니다.

// R 클래스 구조 (자동 생성됨, 직접 수정 불가)
class R {
    class layout {
        const val activity_main = 0x7f030001
    }

    class id {
        const val main = 0x7f070001
        const val textView = 0x7f070002
    }

    class string {
        const val app_name = 0x7f0a0001
    }

    class drawable {
        const val ic_launcher = 0x7f020001
    }
}

// 사용 예
setContentView(R.layout.activity_main)       // 레이아웃 파일
val view = findViewById(R.id.textView)        // View ID
val appName = getString(R.string.app_name)   // 문자열 리소스

앱 빌드 및 실행 방법

1. Android Studio에서 실행

  1. 프로젝트 열기

    • Android Studio 실행
    • Open an Existing Project 선택
    • TsetApplication 폴더 선택
  2. Gradle 동기화

    • 프로젝트가 열리면 자동으로 Gradle 동기화 시작
    • 또는 File → Sync Project with Gradle Files
  3. 에뮬레이터 또는 실제 기기 연결

    • 에뮬레이터: Tools → Device Manager → Create Device
    • 실제 기기: USB 연결 + 개발자 옵션 활성화
  4. 앱 실행

    • 상단 툴바에서 실행 버튼 (▶) 클릭
    • 또는 Shift + F10 (Windows/Linux), Ctrl + R (Mac)

2. Gradle 명령어로 빌드

# Unix/Mac
./gradlew build

# Windows
gradlew.bat build

주요 Gradle 작업 (Tasks)

# 앱 빌드 (APK 생성)
./gradlew assembleDebug          # 디버그 APK
./gradlew assembleRelease        # 릴리스 APK

# 앱 설치 및 실행
./gradlew installDebug           # 디버그 APK 설치
./gradlew installRelease         # 릴리스 APK 설치

# 테스트 실행
./gradlew test                   # 단위 테스트
./gradlew connectedAndroidTest   # 기기 테스트

# 빌드 정리
./gradlew clean                  # 빌드 결과물 삭제

# 의존성 확인
./gradlew dependencies           # 의존성 트리 출력

3. APK 파일 위치

빌드가 완료되면 APK 파일이 다음 위치에 생성됩니다:

mobile/build/outputs/apk/debug/mobile-debug.apk

4. 로그 확인 (Logcat)

Android Studio 하단의 Logcat 탭에서 앱 로그를 실시간으로 확인할 수 있습니다.

// 코드에서 로그 출력
import android.util.Log

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 로그 출력
        Log.d("MainActivity", "onCreate 호출됨")  // Debug
        Log.i("MainActivity", "정보 로그")        // Info
        Log.w("MainActivity", "경고 로그")        // Warning
        Log.e("MainActivity", "에러 로그")        // Error
    }
}

Logcat 필터:

# 앱의 로그만 보기
package:com.example.tsetapplication

# 특정 태그만 보기
tag:MainActivity

# 에러만 보기
level:ERROR

추가 학습 자료

공식 문서

추천 학습 순서

  1. Kotlin 기본 문법 학습
  2. Activity와 Lifecycle 이해
  3. Layout과 View 학습
  4. RecyclerView로 리스트 구현
  5. ViewModel과 LiveData (MVVM 패턴)
  6. Room 데이터베이스
  7. Retrofit으로 네트워크 통신
  8. Coroutine으로 비동기 처리

문제 해결 (Troubleshooting)

1. Gradle 동기화 실패

File → Invalidate Caches / Restart → Invalidate and Restart

2. R 클래스를 찾을 수 없음

./gradlew clean
./gradlew build

3. SDK 버전 오류

local.properties 파일 확인:

sdk.dir=/Users/사용자명/Library/Android/sdk

4. 에뮬레이터가 느림

  • Hardware Acceleration 활성화 (Intel HAXM, AMD Hypervisor)
  • RAM 할당량 증가 (AVD Manager에서 설정)
  • x86 이미지 사용 (ARM보다 빠름)

마치며

이 가이드가 Android 개발을 시작하는 데 도움이 되기를 바랍니다. 궁금한 점이 있으면 공식 문서를 참고하거나, 커뮤니티에 질문해보세요!

행복한 코딩 되세요! 🚀

About

android study repo with kotlin

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages