어플리케이션, 앱 (Application)/안드로이드 (Android)

안드로이드 jetpack compose 공부 정리 1일차 (코틀린, android 14 이후 버전)

sobal 2025. 3. 4. 20:45

jetpack compose 와 최근 업데이트된 여러 정보들을 기준으로 다시 안드로이드 개발을 총정리할 필요성을 느껴 겨울 방학 동안 강의를 들으면서 공부를 했다. 공부한 내용들을 따로 정리하는 게 좋을 것 같아 강의를 들으면서 공부한 내용을 정리해보았다. 

참고로 총 15일차까지 있다.

 


1. 개발 환경 및 도구

 

1.1. 안드로이드 스튜디오(Android Studio)

안드로이드 스튜디오는 안드로이드 앱 개발을 편리하게 할 수 있도록 만든 통합 개발 환경 (IDE) 이다. 아래의 공식 링크를 통해 안드로이드 스튜디오를 다운 받으면 일반적으로 Android Studio 설치 마법사가 최신 버전의 안드로이드 SDK도 같이 다운 받도록 안내한다.

Android Studio Flamingo(2022.3) 이후 버전은 Jetpack Compose 개발에 최적화된 기능을 강화했다.

https://developer.android.com/studio?hl=ko

 

Android 스튜디오 및 앱 도구 다운로드 - Android 개발자  |  Android Studio  |  Android Developers

Android Studio provides app builders with an integrated development environment (IDE) optimized for Android apps. Download Android Studio today.

developer.android.com

1.2. SDK (Software Development Kit)

안드로이드 SDK는 안드로이드 앱 개발에 필요한 모든 도구(컴파일러, 디버거, 에뮬레이터, 라이브러리, 샘플 코드, 문서 등)를 제공하는 개발자 도구 모음(툴 킷)이다.  

  • 핵심 구성 요소: 컴파일러, 디버거, 에뮬레이터, 라이브러리, API, 샘플 코드, 문서 등등
    • 컴파일러 : 컴파일러는 일반적으로 프로그래밍 언어(고급 언어)로 쓰여 있는 문서를 다른 프로그래밍 언어(저급 언어)로 옮기는 언어 번역 프로그램이다. 안드로이드 SDK에는 여러 컴파일러가 있는데, 이 컴파일러들이 복합적으로 작용하여 여러 소스코드를 최종적으로 실행 가능한 APK 파일로 변환한다.
    • javac (Java Compiler), kotlinc (Kotlin Compiler) 는 각각 자바와 코틀린으로 된 소스 코드(.java, .kt 파일)를 Java 바이트코드 (.class 파일)로 변환한다. 그리고 Android Resource Compiler (aapt2) 가 리소스를 컴파일하고 최적화하여 APK 파일에 포함될 수 있는 형태로 만들고, D8 혹은 DX가 바이트 코드를 DEX로 변환하고, R8이 코드 축소, 최적화, 난독화를 하고, C 혹은 C++ 코드가 있는 경우  Android NDK와 Clang 이 컴파일한다. 이 모든 과정은 Gradle과 Android Gradle Plugin  이 자동화하고 최종적으로 컴파일된 코드, 리소스, 네이티브 라이브러리 등 모든 구성 요소를 하나의 APK 파일로 압축하고 디지털 서명 과정을 통해 앱의 무결성을 보장한다. 
    • 디버거 : Android 앱을 구동하는 코드를 검사하여 버그를 검사하고 수정할 수 있도록 하는 필수 도구이다. 원하는 코드에 중단점을 지정해서 프로그램 실행을 정지하고 변수 값, 스택 트레이스 등을 검사할 수 있다. 기본적으로 F9 (Windows/Linux) / ⌘ + F8 (macOS) 단축키로 설정/해제를 할 수 있는데 자세한 안내 사항은 일단 공식 홈페이지를 참고하자.
    • https://developer.android.com/studio/debug?hl=ko
    • 애뮬레이터 : AVD (Android Virtual Device) 라고도 한다. 실제 기기 없이 앱을 테스트할 수 있도록 만든 가상의 기기(환경)이다. 다양한 Android 버전, 화면 크기, 하드웨어 구성 등을 지원한다. 다만 라이브러리의 버전과 애뮬레이터의 버전이 맞지 않거나 여러 이유로 인해 제대로 작동하지 않는 경우도 있어 본인은 주로 실제 기기를 이용해서 테스트한다. 
    • 라이브러리와 API : 안드로이드 플랫폼 기능을 활용할 수 있는 코드와 인터페이스이다. Android Framework API 는 안드로이드 운영체제의 핵심 기능을 제공하고 Android Support Library (지금은 주로 AndroidX를 쓴다)은 이전 버전의 안드로이드 플랫폼과의 호환성을 제공하고 최신 기능을 쉽게 사용할 수 있도록 돕는 라이브러리다. Google Play Services는 구글의 여러 서비스 (지도, firebase, 광고, 결제 등등)를 앱에 통합할 수 있도록 도와주는 API이고 Jetpack Compose는 선언형 UI 툴킷으로 과거의 XML 레이아웃을 대체한다.
    • 샘플 코드 및 문서 : 학습과 참고를 위한 자료이다.
  • API 레벨: 안드로이드 버전을 나타내는 숫자이다(예: API 레벨 30 = Android 11, 핵심 버전 몇 개만 외워놓으면 여러모로 도움이 된다). 앱이 특정 버전의 기능을 사용하려면 해당 레벨의 SDK를 설치해야 한다. 
  • NDK (Native Development Kit): C/C++ 코드를 사용하여 앱 성능을 높일 수 있는 도구. 일반적인 1인 앱 개발에서는 거의 안쓰이는 것 같긴한데 게임 개발 등 고성능이 필요한 앱, 혹은 오디오 처리나 암호화 라이브러리에서 사용될 수 있다고 한다. 
  • 아래는 Gradle 빌드 시스템과 AGP의 차이를 간단하게 정리한 표이다.

 

 

앱 디버그  |  Android Studio  |  Android Developers

Android 스튜디오의 기본적인 디버거 작업을 안내합니다.

developer.android.com

 

구분 Gradle (빌드 시스템) Android Gradle Plugin (AGP)
범위 범용적 (다양한 유형의 프로젝트 빌드 가능) 안드로이드 앱 빌드에 특화
역할 빌드 자동화 도구의 핵심 기능 제공 Gradle의 기능을 확장하여 안드로이드 앱 빌드에 필요한 기능 제공
사용 대상 Java, Kotlin, Groovy 등 다양한 프로젝트 안드로이드 앱 프로젝트
종속성 독립적 Gradle에 의존적 (Gradle 플러그인 형태로 동작)
설정 파일 build.gradle (Groovy) 또는 build.gradle.kts (Kotlin) build.gradle 파일 내에서 plugins { id 'com.android.application' } 등으로 적용

1.3. Gradle (https://gradle.org/)

복잡한 빌드 과정을 자동화해서 개발 생산성을 높여주는 강력하고 유연한 빌드 시스템이다.

  • Groovy/Kotlin DSL: build.gradle (Groovy) 또는 build.gradle.kts (Kotlin) 파일에 빌드 스크립트를 작성한다. 이 스크립트는 프로젝트의 구조, 빌드 단계, 의존성 등을 정의한다.
  • 플러그인: Gradle은 플러그인을 통해 기능을 확장하는데, 안드로이드 앱 개발에는 Android Gradle Plugin (AGP) 이 사용된다. Gradle에 안드로이드 앱 빌드에 특화된 기능을 추가하는데, 소스 코드 컴파일, 리소스 처리, DEX 변환, APK 패키징 등이 있다. build.gradle 파일에서 plugins { id 'com.android.application' } 또는 plugins { id 'com.android.library' } 와 같이 적용하는데, 다양한 플러그인을 조합하여 빌드 프로세스를 커스터마이징 할 수 있다.
  • 작업(Tasks): 빌드는 assembleDebug, assembleRelease, test, clean 등 여러 작업으로 구성된다. 각 작업은 특정 빌드 단계를 수행하고 작업 간의 의존성을 정의해서 실행 순서를 제어할 수 있다. gradlew tasks 명령어를 통해 프로젝트에서 사용 가능한 작업 목록을 확인할 수 있고 gradlew <taskName> (ex: gradlew assembleDebug) 명령어로 특정 작업을 실행할 수 있다. 또한 사용자 정의 작업(custom task)을 만들어 빌드 프로세스를 확장할 수도 있다.
  • 의존성 관리: 외부 라이브러리를 쉽게 추가하고 버전을 관리할 수 있다.
// build.gradle (Module: app)

dependencies {
    implementation "androidx.core:core-ktx:1.15.0" // 외부 라이브러리 추가 예시
    implementation "androidx.appcompat:appcompat:1.7.0"
    // ...
}

1.4. Manifest (AndroidManifest.xml)

AndroidManifest.xml 파일은 안드로이드 앱의 필수적인 구성 요소로, 앱에 대한 핵심 정보를 담고 있는 "신분증" 또는 "설명서"와 같다고 할 수 있다. 이 파일은 앱이 설치되고 실행되기 전에 안드로이드 시스템에 앱의 구조와 요구 사항을 알려주는 역할을 하는데 activity를 추가하거나 어떠한 권한을 필요로 할 때 여기에다 명시해야한다. 그리고 폰에 앱이 깔렸을 때 뜨는 아이콘이나 앱 이름도 여기다 명시한다.

  • 패키지 이름(Package Name): 앱의 고유 식별자 (예: com.example.myapp).
  • 컴포넌트(Components): Activity, Service, Broadcast Receiver, Content Provider 등 앱을 구성하는 주요 요소를 선언하는데 각 컴포넌트는 <activity>, <service>, <receiver>, <provider> 태그를 사용하여 정의한다. 컴포넌트의 이름, 속성, 인텐트 필터 등을 지정할 수 있다.
  • 권한(Permissions): 앱이 사용할 권한 (인터넷, 카메라 등)을 명시한다.
  • 인텐트 필터(Intent Filters): 컴포넌트가 응답할 수 있는 인텐트(Intent)를 정의한다. <intent-filter> 태그를 사용하여 지정하며, <action>, <category>, <data> 요소를 포함할 수 있다.
  • 본인은 기본 컴포넌트, 권한, 아이콘, 앱 이름, 액티비티 추가 관련 내용만 알아도 충분한 것 같은데 더욱 자세하게 알고 싶다면 아래의 공식 링크를 참고하자.
  • https://developer.android.com/guide/topics/manifest/manifest-intro?hl=ko
 

앱 매니페스트 개요  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 앱 매니페스트 개요 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 모든 앱 프로젝트는 프로젝트 소

developer.android.com

 

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

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApp">
        <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>
    </application>

</manifest>


1.5. Drawable

Drawable은 안드로이드 앱에서 사용되는 이미지, 아이콘, 배경, 버튼 모양 등 시각적 요소들을 정의하는 리소스이다. 단순히 이미지를 저장하는 폴더를 넘어서, 다양한 형태로 화면에 그려지는 모든 것을 포함하는 개념이라고 할 수 있다.
 

  • res/drawable/ 폴더 :
    • Drawable 리소스 파일들을 저장하는 기본 위치.
    • 다양한 해상도(density)에 대응하기 위해 drawable-mdpi, drawable-hdpi, drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi 등의 하위 폴더를 사용할 수 있다. (mdpi: medium density, hdpi: high density, xhdpi: extra high density, etc.) 
    • Android 시스템은 기기의 화면 밀도에 맞는 최적의 이미지를 자동으로 선택하여 사용한다.

  • 다양한 형식 지원 :
    • Bitmap (PNG, JPG): 픽셀 기반 이미지로, 사진이나 복잡한 그래픽을 표현할 때 주로 사용된다.
    • Vector Drawable: XML로 정의된 벡터 이미지로, 크기를 조정해도 품질이 떨어지지 않아 아이콘이나 단순한 일러스트에 적합하다.
    • Shape Drawable: XML로 사각형, 원 같은 간단한 도형을 만들 수 있어 버튼 배경이나 UI 요소의 모양을 정의할 때 유용하다.
    • Layer List: 여러 Drawable을 겹쳐서 하나의 복합 이미지로 구성할 수 있다.
    • State List: 버튼의 상태(예: 눌림, 초점 등)에 따라 다른 이미지를 표시할 수 있도록 설정한다.
  • 9-patch 이미지:
    9-patch 이미지는 크기가 변해도 깨지지 않는 특별한 이미지 형식이다. .9.png 확장자로 저장되며, 주로 버튼이나 배경처럼 크기가 유동적인 UI 요소에 사용된다. 이 형식은 이미지의 특정 영역을 늘리거나 줄이는 규칙을 정의해, 늘어날 때 깨지지 않고 자연스럽게 조정되도록 한다.
  • Vector Drawable (XML): XML로 벡터 이미지를 정의하면 해상도에 관계없이 선명하게 표시된다.
  •  참고로 XML 형식은 ai로 쉽게 원하는 시각적 요소를 구현할 수 있어 간단한 아이콘 같은 경우는 vector drawable를 이용하는게 편리하고 좋다.

 

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/>
</vector>

1.6. Version Control (Git)

Git은 코드 변경 이력을 관리하고, 여러 개발자와 효율적으로 협업하고, 안정적으로 코드를 유지보수할 수 있도록 돕는 분산 버전 관리 시스템(DVCS) 이다. 단순한 코드 백업을 넘어, 소프트웨어 개발의 핵심적인 도구라고 할 수 있다.

기본적으로 VCS 에서 git 관련 설정이 가능하다.

 

  • 분산 버전 관리 시스템 (DVCS):
    • 로컬 저장소와 원격 저장소를 모두 사용하여 효율적인 협업을 지원한다.
      • 로컬 저장소: 개발자 각자의 컴퓨터에 있는 저장소. 모든 버전 기록을 가지고 있으므로, 네트워크 연결 없이도 작업(커밋, 브랜치 생성, 과거 버전 확인 등)이 가능하다.
      • 원격 저장소: GitHub, GitLab, Bitbucket 등 서버에 있는 저장소. 다른 개발자와 코드를 공유하고 협업하는 데 사용된다.
    • 중앙 서버에 문제가 발생해도 각 개발자의 로컬 저장소에 전체 이력이 남아있으므로 복구가 용이하다.
  • 브랜치 (Branch):
    • 독립적인 개발 흐름을 만들 수 있다. (기능 개발, 버그 수정, 실험적인 기능 추가 등)
    • 기본 브랜치(main 또는 master)에서 분기(branch)하여 새로운 기능을 개발하고, 완료되면 다시 기본 브랜치에 병합(merge)한다.
    • 다른 개발자의 작업에 영향을 주지 않고 독립적으로 작업할 수 있다.
    • git branch <branch_name>: 새 브랜치 생성
    • git checkout <branch_name>: 특정 브랜치로 전환
    • git switch <branch_name>: 특정 브랜치로 전환 (최신 Git 버전에서 권장)
    • git branch -d <branch_name>: 브랜치 삭제 (병합된 브랜치만)
    • git branch -D <branch_name>: 브랜치 강제 삭제
  • 병합 (Merge):
    • 여러 브랜치의 변경 사항을 합치는 작업이다.
    • 일반적으로 기능 개발이 완료된 브랜치를 기본 브랜치(main 또는 master)에 병합한다.
    • git merge <branch_name>: 현재 브랜치에 다른 브랜치를 병합한다.
    • Fast-forward merge: 병합 대상 브랜치가 현재 브랜치보다 앞서 있는 경우, 단순히 포인터만 이동하여 병합한다. (히스토리가 깔끔쓰)
    • 3-way merge: 병합 대상 브랜치와 현재 브랜치가 공통 조상으로부터 분기된 경우, 각 브랜치의 변경 사항을 비교하여 병합한다. (새로운 merge commit 생성)
  • 충돌 (Conflict):
    • 병합 시, 동일한 파일의 동일한 부분을 서로 다르게 수정한 경우 충돌이 발생한다.
    • 충돌이 발생하면 Git은 해당 부분을 표시해주고, 개발자가 수동으로 충돌을 해결해야 한다.
    • 충돌 해결 후에는 다시 커밋해야 병합이 완료된다.
    • 팀 프로젝트를 할 때 신경을 많이 써야하는 부분이다.
  • Git 호스팅 서비스 (GitHub, GitLab, Bitbucket):
    • 원격 저장소를 제공하고, 코드 공유, 협업, 이슈 관리, 코드 리뷰 등 다양한 기능을 제공한다.
    • GitHub: 가장 널리 사용되는 Git 호스팅 서비스. 오픈 소스 프로젝트의 중심지이다. 개발자가 되고 싶으면 필수라고 할 수 있으니 신경 많이 쓰는게 좋다. 귀찮더라도 프로젝트들은 여기서 관리해보자.
    • GitLab: 자체 서버에 설치하여 사용할 수 있는(Self-Hosted) Git 호스팅 서비스. DevOps 플랫폼 기능도 제공한다.
    • Bitbucket: Atlassian 제품군(Jira, Confluence 등)과의 연동이 강점이다.
  • 주요 명령어:
    • git init: 현재 디렉토리를 Git 저장소로 초기화한다.
    • git add <file>: 변경된 파일을 Staging Area에 추가한다. (커밋할 준비)
    • git add . : 현재 디렉토리의 모든 변경사항을 추가한다.
    • git commit -m "Commit message": Staging Area에 있는 파일들을 커밋한다. (변경 이력을 기록)
      • -m 옵션: 커밋 메시지를 직접 입력.
      • 커밋 메시지는 변경 내용을 명확하게 설명하는 것이 중요하다.
    • git push <remote> <branch>: 로컬 저장소의 커밋을 원격 저장소에 업로드한다.
    • git pull <remote> <branch>: 원격 저장소의 변경 사항을 로컬 저장소로 가져와 병합한다.
    • git branch: 브랜치 목록을 확인한다.
    • git checkout <branch_name> / git switch <branch_name>: 다른 브랜치로 전환한다.
    • git merge <branch_name>: 다른 브랜치를 현재 브랜치에 병합한다.
    • git clone <repository_url>: 원격 저장소를 로컬에 복제한다.
    • git status: 현재 작업 중인 파일의 상태(변경된 파일, Staging Area에 있는 파일 등)를 확인한다.
    • git log: 커밋 히스토리를 확인한다.
    • git diff: 변경된 내용을 비교, 확인한다.
    • git remote: 원격 저장소 관련 명령어
    • git fetch: 원격 저장소의 변경사항을 가져오기만 하고, 병합은 하지 않는다.
  • .gitignore:
    • Git으로 관리하지 않을 파일이나 폴더를 지정하는 파일
    • 빌드 결과물, 임시 파일, 개인 설정 파일(ex: .idea/, *.iml) 등을 지정하여 불필요한 파일이 버전 관리에 포함되지 않도록 함.
    • api key 같은 중요 내용과 값들은 반드시 여기서 관리해야한다. 나중에 api key를 git에 올리는 불상사가 없도록 하자.
  • 추가적인 Git 개념:
    • Staging Area: 커밋하기 전에 변경 사항을 임시로 저장하는 영역.
    • Commit: 변경 이력의 스냅샷. 각 커밋은 고유한 해시 값을 가진다.
    • HEAD: 현재 작업 중인 브랜치의 최신 커밋을 가리키는 포인터.
    • Remote: 원격 저장소. (origin은 기본 원격 저장소 이름)
    • Tag: 특정 커밋에 이름을 붙여서 쉽게 참조할 수 있도록 하는 기능 (주로 릴리즈 버전에 태그를 붙임).
    • Pull Request (GitHub) / Merge Request (GitLab): 다른 개발자에게 코드 변경 사항을 검토하고 병합하도록 요청하는 기능.

 

1.7. Preview

Preview는 Android Studio에서 UI 코드를 작성할 때, 실제 기기나 에뮬레이터 없이도 실시간으로 화면을 확인하고 UI 요소와 상호작용할 수 있는 강력한 기능이다. XML 레이아웃과 Jetpack Compose 모두에서 사용할 수 있고, UI 개발 시간을 단축하고 생산성을 크게 향상시킨다. 특히 Jetpack Compose에서는 XML 없이 선언형으로 UI를 만들 수 있기 때문에 매우 유용하다고 할 수 있다.

 

  • XML 레이아웃 Preview:
    • Design 탭: Android Studio의 Layout Editor에서 Design 탭 또는 Split 탭(Code와 Design 뷰를 동시에 표시)을 통해 Preview를 확인할 수 있다.
      • Design 탭: 시각적인 디자인 뷰를 제공한다.
      • Split 탭: 코드와 디자인 뷰를 동시에 보면서 작업할 수 있다.
      • Code 탭: XML 코드만 표시한다.
    • Interactive Mode: Preview 창 상단의 눈 모양 아이콘을 클릭하여 Interactive Mode를 활성화하면, Preview에서 UI 요소와 상호작용(버튼 클릭, 텍스트 입력, 스크롤 등)할 수 있다. 실제 기기나 에뮬레이터 없이도 UI 동작을 빠르게 테스트할 수 있어서 매우 편하다.
    • 다양한 기기 및 설정:
      • Preview 창 상단의 기기 선택 드롭다운 메뉴에서 다양한 기기(Pixel, Nexus 등), 화면 크기, 방향(세로/가로), API 레벨 등을 선택하여 Preview를 확인할 수 있다.
      • UI 모드(Light/Dark), 시스템 UI 표시 여부 등도 설정할 수 있다.
      • Locale(언어 및 지역)을 변경하여 다국어 지원을 테스트할 수 있다.
    • 자동 업데이트: XML 코드를 수정하면 Preview가 실시간으로 업데이트되어 변경 사항을 즉시 확인할 수 있다.
    • Resource Qualifier: 특정 리소스 qualifier (ex: layout-land, values-ko)에 대한 preview도 가능하다.
    • Layout Validation: 다양한 화면 크기와 해상도에서 레이아웃이 어떻게 보이는지 한 번에 확인할 수 있는 기능이다. (View > Tool Windows > Layout Validation)
  • Jetpack Compose Preview:
    • @Preview 어노테이션: Jetpack Compose에서는 @Preview 어노테이션을 사용하여 Composable 함수의 Preview를 생성한다.
    • 파라미터:
      • showBackground: 배경색 표시 여부 (기본값: false)
      • backgroundColor: 배경색 지정 (16진수 ARGB 값)
      • widthDp, heightDp: Preview의 크기 지정 (dp 단위)
      • showSystemUi: 시스템 UI (상태 표시줄, 탐색 바) 표시 여부 (기본값: false)
      • device: 특정 기기 설정 사용 (예: Devices.PIXEL_4, Devices.NEXUS_7)
      • uiMode: UI 모드 (Light/Dark) 설정 (예: Configuration.UI_MODE_NIGHT_YES)
      • locale: 언어 설정 (예: "ko")
      • name: Preview 이름 지정 (여러 Preview를 구분하는 데 사용)
      • group: 여러 Preview들을 그룹으로 묶을 수 있다.
// Jetpack Compose Preview 예시
@Preview(showBackground = true, widthDp = 320, heightDp = 480)
@Composable
fun MyScreenPreview() {
    MyScreen()
}

 
 


2. 앱 구조 및 기본 개념

2.1. Class

클래스(Class)는 객체 지향 프로그래밍(OOP)의 핵심 개념으로, 데이터(멤버 변수/필드)기능(메서드) 을 묶어놓은 코드 블록, 즉 객체를 만들기 위한 설계도 또는 이라고 할 수 있다. 클래스를 통해 관련된 데이터와 기능을 하나의 논리적인 단위로 묶어 코드의 재사용성, 유지보수성, 가독성을 높일 수 있다.

  • 객체 (Object):
    • 클래스의 인스턴스(Instance). 실제로 메모리에 할당된 데이터와 기능의 묶음이다.
    • 클래스는 추상적인 개념(설계도)이고, 객체는 그 설계도를 바탕으로 만들어진 실체(건물)라고 생각할 수 있다.
    • 하나의 클래스로부터 여러 개의 객체를 생성할 수 있고, 각 객체는 고유한 상태(멤버 변수 값)를 가진다.
  • 멤버 변수 (Member Variable) / 필드 (Field):
    • 클래스 내부에 선언된 변수로, 객체의 상태(데이터)를 나타낸다.
    • 객체가 생성될 때 메모리에 할당된다.
    • 예: Person 클래스의 name, age
  • 메서드 (Method):
    • 클래스 내부에 정의된 함수로, 객체의 동작(기능)을 나타낸다.
    • 객체의 멤버 변수를 조작하거나, 다른 객체와 상호작용하는 등의 작업을 수행한다.
    • 예: Person 클래스의 greet(), walk(), eat()
  • 생성자 (Constructor):
    • 객체가 생성될 때 자동으로 호출되는 특별한 메서드이다.
    • 주로 객체의 초기화(멤버 변수 초기값 설정)를 담당한다.
    • 클래스 이름과 동일한 이름을 가진다.
    • 생성자를 명시적으로 정의하지 않으면, 컴파일러가 기본 생성자(default constructor, 파라미터가 없는 생성자)를 자동으로 생성한다.
  • 상속 (Inheritance):
    • 기존 클래스(부모 클래스/상위 클래스/슈퍼 클래스)의 기능을 확장하거나 재사용하는 방법.
    • 새로운 클래스(자식 클래스/하위 클래스/서브 클래스)는 부모 클래스의 멤버 변수와 메서드를 상속받고, 자신만의 멤버 변수와 메서드를 추가하거나 부모 클래스의 메서드를 재정의(override)할 수 있다.
    • 코드 중복을 줄이고, 클래스 간의 계층적인 관계를 표현할 수 있다.
    • open 키워드를 사용해서 상속이 가능한 클래스를 정의한다.
  • 다형성 (Polymorphism):
    • 같은 이름의 메서드가 객체의 타입에 따라 다르게 동작하는 것을 의미한다.
    • 상속과 메서드 오버라이딩(overriding)을 통해 다형성을 구현할 수 있다.
    • 인터페이스(interface)나 추상 클래스(abstract class)를 사용하여 다형성을 더 유연하게 구현할 수 있다.
  • 캡슐화 (Encapsulation):
    • 데이터(멤버 변수)와 기능(메서드)을 하나의 단위(클래스)로 묶고, 외부로부터의 직접적인 접근을 제한하는 것.
    • 접근 제한자 (Access Modifiers): public, protected, private, internal(Kotlin)을 사용하여 멤버 변수와 메서드의 접근 범위를 제어한다.
      • public: 모든 곳에서 접근 가능
      • protected: 같은 클래스 내부, 상속받은 클래스 내부에서 접근 가능
      • private: 같은 클래스 내부에서만 접근 가능
      • internal (Kotlin): 같은 모듈 내에서 접근 가능
    • 캡슐화를 통해 데이터의 무결성을 보장하고, 객체의 내부 구현을 숨겨(information hiding) 코드의 유연성과 유지보수성을 높일 수 있다.
    • Getter/Setter 메서드를 통해 private 멤버 변수에 간접적으로 접근하는 방식을 주로 사용한다.

추상화(Abstraction)

  • 객체에서 필요한 공통된 속성과 동작을 추출하여 클래스로 정의하는 과정이다.
  • 복잡한 현실 세계를 단순화하여 모델링하는 것.
패키지와 import들...

class CameraActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ColorValueTheme {
                CameraScreen()
            }
        }
    }

    @Composable
    fun CameraScreen() {
    	.....
    }
}


아래는 문법 설명을 위한 예시...

class Person(val name: String, var age: Int) { // 주 생성자 (Primary Constructor)

    // 멤버 변수 (name, age)는 주 생성자에서 선언과 동시에 초기화

    init { // 초기화 블록 (객체 생성 시 추가적인 작업 수행)
        println("Person 객체가 생성완료!")
    }

    fun greet() { 
        println("안녕, 제 이름은 $name 이고, $age 살이야.")
    }

    // 보조 생성자 (Secondary Constructor) - 필요에 따라 추가
    constructor(name: String) : this(name, 0) { // this()를 통해 주 생성자 호출
        println("이름만 지정된 Person 객체 생성")
    }
}

// 객체 생성
val person1 = Person("Koko", 20) // 주 생성자를 사용하여 객체 생성
person1.greet()

val person2 = Person("Sulyoon") // 보조 생성자를 사용하여 객체 생성
person2.greet()



// 상속 예시 
open class Animal(val name: String) { // open 키워드: 상속을 허용하는 클래스
    open fun makeSound() { // open 키워드: 오버라이딩을 허용하는 메서드
        println("...")
    }
}

class Dog(name: String) : Animal(name) { // Animal 클래스를 상속
    override fun makeSound() { // 부모 클래스의 메서드를 재정의 (override)
        println("왈왈왈왈왈!")
    }
}

val dog = Dog("김철두")
dog.makeSound()

2.2. MainActivity

MainActivity는 안드로이드 앱이 실행될 때 가장 먼저 실행되는 Activity 클래스이다. 사용자가 앱 아이콘을 터치했을 때 보이는 첫 화면을 담당하고, 앱의 진입점(entry point)이라고 보면 된다.

  • 앱의 시작점:
    • MainActivity는 앱의 주요 기능을 수행하는 시작점이 된다.
    • 일반적으로 앱의 메인 화면(UI)을 담당하고, 다른 액티비티를 시작하는 역할을 한다.
    • 사용자와의 상호작용을 처리하고, 필요한 데이터를 로드하며, 다른 컴포넌트(서비스, 브로드캐스트 리시버 등)를 호출할 수 있다.
  • AndroidManifest.xml에 등록:
    • MainActivity가 앱의 시작점으로 동작하려면, AndroidManifest.xml 파일에 <activity> 태그로 등록하고, <intent-filter>를 사용하여 android.intent.action.MAIN 액션과 android.intent.category.LAUNCHER 카테고리를 지정해야 한다.
    • `android.intent.action.MAIN`: 앱의 메인 액티비티임을 나타낸다. 
    • `android.intent.category.LAUNCHER`: 앱 런처(홈 화면)에 앱 아이콘이 표시되도록 한다. 
    • `android:exported="true"`: 다른 앱에서 이 액티비티를 시작할 수 있도록 허용한다. (이건 필수)
  • onCreate() 메서드:
    • MainActivity가 생성될 때 자동으로 호출되는 메서드이다. Activity 생명주기의 첫 번째 단계이.
    • onCreate() 메서드에서는 주로 다음과 같은 초기화 작업을 수행한다.
      • setContentView()를 사용하여 화면 레이아웃(XML 레이아웃 또는 Jetpack Compose UI)을 설정.
      • UI 요소(버튼, 텍스트 뷰 등)를 초기화하고 이벤트 리스너를 등록.
      • 필요한 데이터를 로드하거나, 다른 컴포넌트를 초기화.
      • savedInstanceState (Bundle 객체)를 사용하여 이전 상태를 복원 (화면 회전 등으로 인해 액티비티가 재생성될 때).
  • AppCompatActivity 상속:
    • MainActivity는 일반적으로 AppCompatActivity 클래스를 상속받는다.
    • AppCompatActivity는 이전 버전의 안드로이드와의 호환성을 제공하고, Material Design 테마 등 최신 기능을 쉽게 사용할 수 있도록 돕는 라이브러리이다.
  • 다른 액티비티 시작:
    • MainActivity에서 Intent를 사용하여 다른 액티비티를 시작할 수 있다. 
    •  
      • Jetpack Compose 사용 시:
        • XML 레이아웃 대신 Jetpack Compose를 사용하여 UI를 구성할 수 있다.
    • // 다른 액티비티 시작 예시
      val intent = Intent(this, OtherActivity::class.java)
      startActivity(intent)

MainActivity 선언 예시 - 기본적으로 되어있으니 크게 신경 쓸 필요는 없다.

2.3. Activity

앱 화면 하나를 담당하는 핵심 컴포넌트. 각 화면은 Activity 클래스로 구현되며, 사용자 인터랙션을 처리하고 화면을 구성한다.

  • UI 구성 및 사용자 인터랙션 처리:
    • Activity는 사용자에게 보여지는 UI를 구성하고, 사용자가 UI 요소(버튼, 텍스트 필드 등)와 상호작용할 때 발생하는 이벤트(터치, 키 입력 등)를 처리한다.
    • UI는 XML 레이아웃 파일(e.g., activity_main.xml)을 사용하거나, Jetpack Compose를 사용하여 Kotlin 코드로 직접 구성할 수 있다.
    • findViewById()와 binding (XML 레이아웃 사용 시) 또는 Composable 함수(Jetpack Compose 사용 시)를 통해 UI 요소에 접근하고 조작할 수 있다.
  • UI 스레드 (UI Thread):
    • Activity는 UI 스레드(메인 스레드)에서 실행된다. UI 스레드는 UI를 업데이트하고 사용자 이벤트를 처리하는 역할을 담당한다.
    • UI 스레드에서 오래 걸리는 작업(네트워크 요청, 파일 I/O, 복잡한 계산 등)을 수행하면 안 된다. 강의나 수업을 통해서 안드로이드 앱 개발을 공부할 때도 강사나 교수님이 매우 강조하는 부분이다. 간단하게 말하자면 백쪽에서 실행이 오래 걸린다고 UI가 제대로 안 보여지면 사용자 입장에서 매우 불편해진다. UI 스레드가 차단(block)되면 앱이 응답하지 않는 것처럼 보이고, 일정 시간(약 5초) 이상 응답이 없으면 ANR(Application Not Responding) 오류가 발생하여 앱이 강제 종료된다.
    • 해결 방법:
      • 별도의 스레드(Thread) 사용: Thread 클래스를 상속받거나 Runnable 인터페이스를 구현하여 새로운 스레드를 생성하고, 그 스레드에서 오래 걸리는 작업을 수행한다.
      • 코루틴(Coroutine) 사용 (권장!): Kotlin의 코루틴을 사용하면 비동기 작업을 더 쉽고 효율적으로 처리할 수 있다. 코루틴은 스레드보다 가볍고, 콜백(callback) 지옥을 피할 수 있으며, 구조화된 동시성(structured concurrency)을 지원한다.
      • AsyncTask: (deprecated) 백그라운드 작업과 UI 업데이트를 쉽게 처리하도록 돕는 클래스. 하지만, 생명주기 문제, 메모리 누수 위험 등으로 인해 현재는 권장되지 않는다고 한다.
  • 백 스택 (Back Stack):
    • Activity는 백 스택(Back Stack)이라는 자료 구조에 쌓인다. 사용자가 새로운 Activity를 시작하면, 새 Activity가 스택의 맨 위에 추가되고 이전 Activity는 중지(stopped) 상태가 된다.
    • 사용자가 뒤로 가기 버튼을 누르면, 스택의 맨 위에 있는 Activity가 제거(destroyed)되고 이전 Activity가 다시 시작(resumed)된다.
    • 백 스택은 LIFO(Last In, First Out) 방식으로 동작한다.
    • AndroidManifest.xml에서 <activity> 태그의 launchMode 속성을 사용하여 Activity의 백 스택 동작 방식을 제어할 수 있다. (standard, singleTop, singleTask, singleInstance)
    • taskAffinity를 이용해서 액티비티가 속할 task를 지정할 수 있다.
  • 생명주기 (Lifecycle):
    • Activity는 생성(created), 시작(started), 재개(resumed), 일시 중지(paused), 중지(stopped), 소멸(destroyed) 등 여러 상태를 거치며, 각 상태 변화에 따라 특정 생명주기 메서드가 호출된다. 생명주기는 앱의 메모리 관리나 화면 로드 순서 등등에 있어서 필수로 알고 신경 써야하는 부분이니 반드시 제대로 알고 가야한다.
    • 주요 생명주기 메서드:
      • onCreate(): Activity가 생성될 때 호출. UI 초기화, 데이터 로딩 등 한 번만 수행해야 하는 작업을 처리.
      • onStart(): Activity가 사용자에게 보여지기 직전에 호출.
      • onResume(): Activity가 사용자와 상호작용하기 직전에 호출 (포그라운드에 위치).
      • onPause(): 다른 Activity가 화면에 나타나 Activity가 일시 중지될 때 호출. 데이터 저장, 리소스 해제 등 수행.
      • onStop(): Activity가 더 이상 사용자에게 보여지지 않을 때 호출.
      • onDestroy(): Activity가 소멸될 때 호출.
      • onRestart(): Activity가 중지되었다가 다시 시작될 때 호출 (onStart() 전에 호출).
    • 각 생명주기 메서드에서 적절한 작업을 수행하여 앱의 안정성과 사용자 경험을 향상시켜야 한다. (예: onPause()에서 데이터 저장, onResume()에서 데이터 다시 로드)
  • Intent (인텐트):
    • Activity 간, 또는 앱의 다른 구성 요소(서비스, 브로드캐스트 리시버 등) 간의 통신을 위한 메커니즘이다.
    • 명시적 인텐트 (Explicit Intent): 실행할 컴포넌트(Activity)를 명확하게 지정. (클래스 이름 사용)
    • 암시적 인텐트 (Implicit Intent): 수행할 작업(action)과 데이터(data)를 지정. 안드로이드 시스템이 이 인텐트를 처리할 수 있는 컴포넌트를 찾아서 실행. (예: 웹 브라우저 열기, 전화 걸기)
    • startActivity(): 새로운 Activity를 시작.
    • startActivityForResult(): 새로운 Activity를 시작하고, 그 Activity로부터 결과값을 받기 위해 사용. (예: 카메라 앱에서 사진 촬영 후 결과 이미지 받기)
      • onActivityResult() 메서드에서 결과값을 처리. (최신 버전에서는 Activity Result API 사용 권장)
    • Intent 객체에 putExtra() 메서드를 사용하여 데이터를 추가하고, 받는 쪽에서는 getXXXExtra() 메서드 (예: getStringExtra(), getIntExtra())를 사용하여 데이터를 추출할 수 있다.
 
// 명시적 인텐트를 사용하여 다른 액티비티 시작 (Kotlin)
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("itemId", 123) // 데이터 추가
startActivity(intent)

2.4. onCreate() - 기본이니 코드 스니펫 한 번만 보고 가자.

안드로이드 스튜디오에서 프로젝트를 처음 생성하면 일반적으로 onCreate()는 아래와 같이 기본적으로 생성된다. 

// onCreate() 예시 (XML 레이아웃 사용)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main) // activity_main.xml 레이아웃을 화면으로 설정

    val textView = findViewById<TextView>(R.id.myTextView) // UI 요소 가져오기
    textView.text = "Hello, Android!" // 텍스트 설정
}

2.5. setContentView()

setContentView()는 Activity에서 사용자에게 보여질 화면 레이아웃(UI)을 설정하는 핵심 메서드이다. 이 메서드를 통해 XML 레이아웃 파일 또는 Jetpack Compose UI를 Activity에 연결하여 화면을 구성한다.

  • XML 레이아웃 설정:
    • setContentView(R.layout.xml_layout_name) 형태로 호출하며, R.layout.xml_layout_name은 res/layout/ 폴더에 있는 XML 레이아웃 파일(xml_layout_name.xml)을 가리킨다.
    • XML 레이아웃 파일에는 TextView, Button, ImageView 등 다양한 UI 요소(View)들이 정의되어 있으며, 이 요소들이 계층 구조(View Hierarchy)를 이루어 화면을 구성한다.
    • setContentView()는 XML 레이아웃 파일을 파싱하여 View 객체들을 생성하고, Activity의 content view로 설정한다.
// XML 레이아웃을 사용하는 경우 (Kotlin)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main) // res/layout/activity_main.xml 파일을 로드하여 화면 구성
}
  • View Binding (권장):
    • View Binding은 XML 레이아웃 파일의 각 View에 대한 참조를 쉽게 얻을 수 있도록 해주는 기능이다.
    • findViewById()를 사용하는 것보다 안전하고 간결하고 안전하다. (null safety, type safety)
    • build.gradle 파일에 viewBinding을 활성화해야 한다.

// build.gradle (Module: app)
android {
    // ...
    buildFeatures {
        viewBinding true
    }
}
// View Binding 사용 예시 (Kotlin)
private lateinit var binding: ActivityMainBinding // ActivityMainBinding은 자동으로 생성되는 클래스

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater) // View Binding 객체 생성
    setContentView(binding.root) // binding.root는 레이아웃의 최상위 뷰

    // binding을 사용하여 UI 요소에 접근
    binding.myTextView.text = "Hello, View Binding!"
    binding.myButton.setOnClickListener { /* ... */ }
}
  • Jetpack Compose UI 설정:
    • Jetpack Compose를 사용하여 UI를 구성하는 경우, setContent() 메서드 내에서 Composable 함수를 호출하여 화면을 정의한다.
// Jetpack Compose를 사용하는 경우 (Kotlin)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent { // setContent 블록 안에서 Composable 함수들을 호출
        MyTheme { // Material Design 테마 적용
            MyScreen() // Composable 함수 (UI 구성)
        }
    }
}
  • 여러 번 호출:
    • 일반적으로 setContentView()는 onCreate() 메서드에서 한 번만 호출되지만, 필요에 따라 여러 번 호출하여 화면을 동적으로 변경할 수도 있다. (하지만, 잦은 setContentView() 호출은 성능 저하를 유발할 수 있으므로 주의해야 한다.)

2.6. Surface (Compose)

Surface는 Jetpack Compose에서 Material Design의 Surface 개념을 구현하는 Composable 함수이다. UI 요소들을 그리는 배경 역할을 하며, 배경색, 모양(shape), 그림자(elevation), 테두리(border) 등을 설정하여 시각적인 깊이감과 계층 구조를 표현할 수 있다.

  • Material Design Surface:
    • Material Design에서는 UI를 여러 개의 Surface로 구성하고, 각 Surface에 고도(elevation)를 부여하여 시각적인 깊이감을 표현한다.
    • Surface는 빛과 그림자를 통해 사용자에게 UI 요소의 중요도와 상호작용 가능성을 전달한다.
  • Surface Composable 함수의 주요 파라미터:
    • modifier: Composable의 크기, 레이아웃, 동작 등을 변경하는 데 사용. Modifier 체이닝을 통해 여러 설정을 적용할 수 있다. (예: Modifier.padding(16.dp).fillMaxWidth())
    • shape: Surface의 모양을 지정. RectangleShape (기본값, 사각형), RoundedCornerShape (둥근 모서리), CircleShape (원) 등 사용.
      • RoundedCornerShape(size: Dp): 모든 모서리의 둥글기 정도를 동일하게 지정.
      • RoundedCornerShape(topStart: Dp, topEnd: Dp, bottomStart: Dp, bottomEnd: Dp): 각 모서리의 둥글기를 개별적으로 지정.
    • color: Surface의 배경색을 지정. Color 객체 또는 MaterialTheme.colorScheme의 색상 사용.
    • contentColor: Surface 위에 그려지는 내용(텍스트, 아이콘 등)의 기본 색상을 지정. Surface는 이 색상을 자식 Composable에 자동으로 전달.
    • border: Surface의 테두리를 지정. BorderStroke 객체를 사용.
      • BorderStroke(width: Dp, color: Color): 테두리의 두께와 색상을 지정.
      • BorderStroke(width: Dp, brush: Brush): 그라데이션 등 복잡한 브러시를 사용하여 테두리를 그릴 수 있다.
    • elevation: Surface의 그림자 높이(z-index)를 지정 (dp 단위). 그림자를 통해 UI 요소 간의 깊이감을 표현.
    • content: Surface 안에 포함될 Composable 함수 (UI 내용). 람다(lambda) 형태로 전달.
// Surface Composable 사용 예시 (Kotlin)
Surface(
    modifier = Modifier
        .padding(16.dp)
        .fillMaxWidth(), // 가로로 꽉 채움
    shape = RoundedCornerShape(8.dp), // 둥근 모서리 (8dp)
    color = MaterialTheme.colorScheme.surface, // Material Design 테마의 surface 색상
    contentColor = MaterialTheme.colorScheme.onSurface, // surface 위 내용의 색상 (자동으로 설정됨)
    border = BorderStroke(1.dp, Color.Gray), // 1dp 두께의 회색 테두리
    elevation = 4.dp // 그림자 높이 (4dp)
) {
    Text(
        text = "Hello, Compose Surface!",
        modifier = Modifier.padding(16.dp) // Surface 내부에도 padding 적용
    )
}
  • MaterialTheme과의 관계:
    • Surface는 MaterialTheme에 정의된 색상, 모양, 글꼴 등을 기본값으로 사용한다.
    • MaterialTheme.colorScheme을 통해 테마의 색상 팔레트에 접근할 수 있다. (예: MaterialTheme.colorScheme.surface, MaterialTheme.colorScheme.onSurface, MaterialTheme.colorScheme.primary)
    • MaterialTheme.shapes를 통해 테마의 모양(shape) 정의에 접근할 수 있다. (예: MaterialTheme.shapes.medium, MaterialTheme.shapes.small)
    • MaterialTheme.typography를 통해 테마의 글꼴 스타일에 접근.
  • 컨테이너 역할:
    • Surface는 다른 Composable 함수들을 감싸는 컨테이너 역할을 한다.
    • Surface 안에 여러 UI 요소들을 배치하여 그룹화하고, 일관된 스타일(배경색, 그림자 등)을 적용할 수 있다.
  • 중첩된 Surface:
    • Surface 안에 다른 Surface를 중첩하여 복잡한 UI 계층 구조를 표현할 수 있다.
    • 각 Surface는 자신만의 배경색, 모양, 그림자 등을 가질 수 있다.
  • 클릭 이벤트 처리:
    • Surface에 clickable modifier를 추가하면 클릭 이벤트를 처리할 수 있다.
Surface(
    modifier = Modifier
        .clickable { /* 클릭 시 수행할 작업 */ },
    // ... 다른 설정 ...
) {
    // ...
}

2.7. savedInstanceState (Bundle)

savedInstanceState는 Bundle 객체를 가리키며, Activity의 일시적인 상태 정보를 저장하고 복원하는 데 사용된다. 화면 회전, 시스템에 의한 프로세스 종료 등 Activity가 소멸되었다가 다시 생성될 때, savedInstanceState를 사용하여 이전 상태를 복원하여 사용자 경험을 향상시킬 수 있다.
 

  • Bundle 객체:
    • Bundle은 키-값(key-value) 쌍의 형태로 데이터를 저장하는 컨테이너이다.
    • 다양한 타입의 데이터(String, Int, Boolean, Parcelable 객체 등)를 저장할 수 있다.
    • Bundle은 Parcelable 인터페이스를 구현하므로, 프로세스 간 통신(IPC)에도 사용될 수 있다.
  • 데이터 저장 시점:
    • Activity가 소멸되기 전에, 시스템은 onSaveInstanceState(Bundle) 메서드를 호출한다.
    • onSaveInstanceState(Bundle) 메서드에서 Bundle 객체에 저장할 데이터를 추가한다.
    • 저장된 데이터는 시스템에 의해 보관된다.
 
// 데이터 저장 예시 (Kotlin)
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putString("myStringKey", "Hello, Bundle!") // String 값 저장
    outState.putInt("myIntKey", 123) // Int 값 저장
    // ... 다른 데이터 저장 ...
}
  • 데이터 복원 시점:
    • Activity가 다시 생성될 때, 시스템은 이전에 저장된 Bundle 객체를 onCreate(Bundle?) 또는 onRestoreInstanceState(Bundle) 메서드에 전달한다.
    • onCreate(Bundle?)에서 savedInstanceState 파라미터가 null이 아니면, 이전에 저장된 데이터가 있다는 의미이다.
    • onRestoreInstanceState(Bundle)는 onStart() 이후에 호출되며, UI 상태를 복원하는 데 주로 사용된다. (일반적으로는 onCreate()에서 복원하는 것이 더 간단하다)
// 데이터 복원 예시 (Kotlin) - onCreate()에서 복원
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    if (savedInstanceState != null) {
        val myString = savedInstanceState.getString("myStringKey")
        val myInt = savedInstanceState.getInt("myIntKey")
        // 복원된 데이터를 사용하여 UI 업데이트 등 수행
    }
}

// 데이터 복원 예시 (Kotlin) - onRestoreInstanceState()에서 복원
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)
    val myString = savedInstanceState.getString("myStringKey")
    val myInt = savedInstanceState.getInt("myIntKey")
      // 복원된 데이터를 사용하여 UI 업데이트 등 수행 (주로 View 관련 상태 복원)
}
  • 저장할 수 있는 데이터:
    • Bundle에는 작고 가벼운 데이터만 저장해야 한다. (큰 이미지, 복잡한 객체 등은 저장하지 않는 것이 좋다.)
    • 기본 타입(String, Int, Boolean, Float, Long, Double 등)과 Parcelable 인터페이스를 구현한 객체를 저장할 수 있다.
    • Serializable 인터페이스를 구현한 객체도 저장할 수 있지만, Parcelable보다 성능이 떨어진다.
  • onSaveInstanceState() 호출 시점:
    • onSaveInstanceState()는 Activity가 완전히 종료되기 전 (사용자가 뒤로 가기를 눌러 앱을 종료하는 경우 등)에는 호출되지 않을 수 있다.
    • onSaveInstanceState()는 주로 예기치 않은 상황(화면 회전, 시스템에 의한 프로세스 종료 등)에 대비하기 위한 것이다.
    • 영구적인 데이터(사용자 프로필, 설정 등)는 SharedPreferences, 파일, 데이터베이스 등에 저장해야 한다.
  • ViewModel과의 비교:
    • ViewModel은 화면 회전과 같이 구성 변경(configuration change)이 발생해도 데이터를 유지하는 데 사용된다.
    • savedInstanceState는 시스템에 의해 프로세스가 종료되었다가 다시 시작될 때도 데이터를 복원할 수 있다.
    • ViewModel과 savedInstanceState를 함께 사용하여 더 안정적으로 데이터를 관리할 수 있다. (SavedStateHandle 사용)
// onSaveInstanceState()와 onCreate()에서 상태 저장 및 복원 예시
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putString("myKey", "myValue") // 데이터 저장
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...
    if (savedInstanceState != null) {
        val myValue = savedInstanceState.getString("myKey") // 데이터 복원
    }
}

2.8. Composable

Composable 함수는 Jetpack Compose UI 툴킷의 가장 기본적인 구성 요소이다. UI를 선언적으로 정의하고, 상태(State) 변화에 따라 자동으로 UI를 업데이트(재구성, Recomposition)하는 기능을 제공한다.
 

  • 선언형 UI (Declarative UI):
    • UI가 어떻게 보여야 하는지를 기술한다. (명령형 UI(android 12 이전)와는 반대)
      • 명령형 UI (Imperative UI): UI를 변경하기 위해 어떻게 변경해야 하는지를 단계별로 지시하는 방식 (예: findViewById()로 View를 찾아서 setText()로 텍스트를 변경).
      • 선언형 UI: UI의 현재 상태를 정의하고, 상태가 변경되면 Compose가 자동으로 UI를 업데이트. (개발자는 UI 업데이트 방법을 직접 지시할 필요가 없음)
    • UI를 데이터(상태)의 함수로 표현. 즉, UI = f(state)
    • UI 코드가 더 간결하고 가독성이 좋아지며, 유지보수가 쉬워진다.
  • 상태 (State):
    • UI에 표시되는 데이터, 시간에 따라 변할 수 있는 값.
    • 상태가 변경되면, 해당 상태를 사용하는 Composable 함수가 자동으로 재구성(Recomposition)되어 UI가 업데이트된다.
    • remember: Composable 함수 내에서 상태를 유지하는 데 사용. recomposition이 일어나도 상태값이 유지.
    • mutableStateOf: 변경 가능한 상태를 생성. 이 상태의 값이 변경되면 recomposition이 발생.
    • State<T> / MutableState<T>: remember와 mutableStateOf가 반환하는 타입.
@Composable
fun Counter() {
    val count = remember { mutableStateOf(0) } // count는 MutableState<Int> 타입

    Button(onClick = { count.value++ }) { // 버튼 클릭 시 count 값 증가
        Text("Clicked ${count.value} times") // count 값을 텍스트로 표시
    }
}

 

 
  • 재구성 (Recomposition):
    • 상태가 변경된 Composable 함수만 다시 실행(재구성)되어 UI를 업데이트한다.
    • Compose는 변경된 부분만 효율적으로 다시 그리므로, UI 업데이트 성능이 뛰어나다.
    • 재구성은 Compose의 핵심 메커니즘이며, 불필요한 재구성을 최소화하는 것이 성능 최적화의 핵심이다. (ex: remember를 적절히 사용, Stable types 사용)
    • Side Effect가 없는 순수 함수 형태로 작성되어야 한다. (같은 입력에 대해 항상 같은 결과를 반환)
  • State Hoisting: 상태를 Composable 함수의 외부(상위 Composable 또는 ViewModel)로 이동시켜 여러 Composable에서 공유하고 재사용할 수 있도록 하는 패턴.
  • @Composable 어노테이션:
    • Composable 함수를 정의하는 데 사용.
    • @Composable 어노테이션이 붙은 함수는 다른 Composable 함수 내에서만 호출될 수 있다.
    • @Composable 함수는 UI를 반환(return)하지 않고, 암시적으로 UI 트리에 요소를 추가한다.
    • 함수 이름은 파스칼 케이스(PascalCase)를 사용하는 것이 관례이다.
 
@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}
  • Modifier:
    • Composable 함수의 외형(크기, 패딩, 배경색, 테두리 등)과 동작(클릭 이벤트, 스크롤 등)을 변경하는 데 사용된다.
    • Modifier 객체를 체이닝(chaining)하여 여러 설정을 적용할 수 있다.
    • Modifier는 Composable 함수의 첫 번째 파라미터로 전달하는 것이 일반적이다.
  • 주요 Modifier 종류:
    • 크기 및 레이아웃:
      • padding(): Composable의 안쪽 여백(padding)을 추가한다.
      • fillMaxWidth(), fillMaxHeight(), fillMaxSize(): Composable이 가로, 세로, 또는 전체 사용 가능한 공간을 채우도록 한다.
      • width(), height(), size(): Composable의 너비, 높이, 또는 크기(너비와 높이 모두)를 지정한다.
      • wrapContentWidth(), wrapContentHeight(), wrapContentSize(): Composable의 내용을 감싸는 크기로 설정한다.
      • offset(): Composable의 위치를 상대적으로 이동시킨다.
    • 배경 및 모양:
      • background(): Composable의 배경색 또는 배경 이미지를 설정한다.
      • border(): Composable의 테두리를 설정한다. (두께, 색상, 모양 지정 가능)
      • clip(): Composable의 모양을 자른다. (RoundedCornerShape, CircleShape 등 사용)
      • alpha(): Composable의 투명도를 조절한다. (0.0: 완전 투명, 1.0: 완전 불투명)
    • 이벤트 처리:
      • clickable(): Composable에 클릭 이벤트를 처리하는 리스너를 추가한다.
      • pointerInput(): 터치, 마우스 입력 등 포인터 이벤트를 처리한다.
    • 변환:
      • rotate(): Composable을 회전시킨다.
      • scale(): Composable의 크기를 확대/축소한다.
    • 접근성:
      • semantics(): Composable에 대한 접근성 정보(스크린 리더에서 읽을 내용, 역할 등)를 설정한다.
 
@Composable
fun MyButton() {
    Button(
        onClick = { /* ... */ },
        modifier = Modifier
            .padding(16.dp) // 패딩 추가
            .fillMaxWidth() // 가로로 꽉 채움
            .background(Color.Blue) // 배경색을 파란색으로 설정
    ) {
        Text("Click Me")
    }
}
 
  • Layout (레이아웃):
    • Composable 함수들을 화면에 어떻게 배치할 것인지를 결정한다.
    • Jetpack Compose는 미리 정의된 여러 Layout Composable을 제공한다.
    • 주요 Layout Composable:
      • Column: Composable들을 세로로 배치한다.
      • Row: Composable들을 가로로 배치한다.
      • Box: Composable들을 겹쳐서 배치한다. (FrameLayout과 유사)
      • ConstraintLayout: ConstraintLayout 라이브러리를 사용하여 복잡한 레이아웃을 구성. (체이닝, barriers, guidelines 등 사용)
      • LazyColumn / LazyRow: 스크롤 가능한 리스트를 효율적으로 표시. (RecyclerView와 유사)
      • Spacer: 빈 공간 생성
      • Arrangement: Column이나 Row 내에서 Composable들을 정렬 (정렬 방식 지정)
      • Alignment: Column, Row, Box 내에서 Composable들을 정렬 (위치 지정)
    • 더 자세한 사항은 다음 공식 페이지에서 확인 가능하다.
      https://developer.android.com/develop/ui/compose/layouts?hl=ko
 

Compose의 레이아웃  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose의 레이아웃 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Jetpack Compose를 사용하면 앱의 효율적

developer.android.com

 

코드 예시

// Column, Row, Box 예시
@Composable
fun MyLayout() {
    Column(
        modifier = Modifier.fillMaxSize(), // 전체 화면을 채움
        verticalArrangement = Arrangement.Center, // 세로 중앙 정렬
        horizontalAlignment = Alignment.CenterHorizontally // 가로 중앙 정렬
    ) {
        Text("Hello")
        Row {
            Button(onClick = { /* ... */ }) {
                Text("Button 1")
            }
            Spacer(modifier = Modifier.width(16.dp)) // 16dp 너비의 빈 공간
            Button(onClick = { /* ... */ }) {
                Text("Button 2")
            }
        }
        Box(modifier = Modifier.size(100.dp).background(Color.Red)) {
            Text("Box Content", color = Color.White)
        }
    }
}

 

참고 자료