
Manifest Android Interview 책을 읽고 Practical Questions 에 대한 답변을 작성해보고, 카테고리 내에 특정 개념에 대한 딥다이브 및 스터디 시간에 얘기 나누면 좋을 내용들을 적어보는 글입니다.
답변 정리는 LLM의 도움을 받았습니다.
Q) 0. Android란 무엇인가?
Q) 안드로이드 플랫폼 아키텍처는 Linux Kernel, Android Runtime (ART), Hardware Abstraction Layer (HAL) 등 여러 계층으로 구성된다. 이 구성 요소들이 애플리케이션 실행과 하드웨어와의 상호 작용을 위해 어떻게 작동하는지 설명하라
안드로이드 플랫폼 아키텍처 동작 방식:
1. 애플리케이션 실행 과정:
앱 실행 → ART(Android Runtime)에서 앱 코드를 네이티브 코드로 컴파일/실행
시스템 서비스 호출 → Application Framework를 통해 필요한 시스템 기능 요청
하드웨어 접근 → Framework가 HAL(Hardware Abstraction Layer)을 통해 하드웨어 제어
2. 계층별 역할:
Linux Kernel: 메모리 관리, 프로세스 스케줄링, 디바이스 드라이버 제공
HAL: 하드웨어별 차이를 추상화하여 상위 계층에 표준 인터페이스 제공
ART: 앱의 바이트코드를 기계어로 변환하고 실행
Application Framework: 시스템 서비스(ActivityManager, WindowManager 등)를 통해 앱과 시스템 연결
3. 상호작용 흐름:
앱 → Framework → Native Libraries/HAL → Kernel → 하드웨어
이렇게 계층화된 구조로 앱은 하드웨어 종류에 관계없이 일관된 방식으로 동작할 수 있음.
Q) 1. Intent란 무엇인가?
Intent는 수행할 작업에 대한 추상적인 설명.
이는 Activity, Service, BroadcastReceiver 간의 통신을 가능하게 해주는 메시징 객체로서, 보통은 다른 액티비티를 시작하거나, 브로드캐스트를 보내거나, 서비스를 시작할 때 사용.
또한 컴포넌트 간에 데이터를 전달할 수 있기 때문에, Android의 컴포넌트 기반 아키텍처에서 매우 핵심적인 역할을 수행.
Q1) 명시적 인텐트와 암시적 인텐트의 주요 차이점은 무엇이며, 각각을 어떤 상황에서 사용하는가?
명시적 vs 암시적 인텐트:
명시적 인텐트: 특정 컴포넌트를 직접 지정 (클래스명 명시)
암시적 인텐트: 수행할 작업만 명시, 시스템이 적합한 앱 선택
사용 시나리오:
명시적: 같은 앱 내 액티비티 전환, 특정 서비스 시작
암시적: 이메일 전송, 웹페이지 열기, 사진 촬영 등 외부 앱 기능 활용
Q2) Android 시스템은 암시적 인텐트를 처리할 앱을 어떻게 결정하며, 적합한 애플리케이션이 없을 때는 어떻게 되는기?
암시적 인텐트 처리 과정:
Intent Filter 매칭: action, category, data 기준으로 적합한 앱 검색
여러 앱 발견 시: 사용자에게 앱 선택 다이얼로그 표시
적합한 앱 없을 시: ActivityNotFoundException 발생
예외 처리:
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
// 적절한 앱이 없을 때 처리
}
Q) 2. PendingIntent의 목적은 무엇인가?
PendingIntent는 일반 Intent와 달리, 현재 앱이 아닌 다른 앱이나 시스템 컴포넌트가 나중에 정의된 Intent를 대신 실행할 수 있도록 권한을 위임하는 객체.
앱의 생명주기를 벗어난 시점(예: 알림 클릭 시점, 예약된 작업 등)에도 작업이 수행되도록 할 수 있게 함
Q1) PendingIntent란?
PendingIntent는 다른 애플리케이션(예: 알람 관리자, 시스템, 외부 앱 등)이 앱의 권한으로 정의된 Intent를 나중에 실행할 수 있도록 허가하는 래퍼 객체
Q2) Intent와의 차이점

Q3) PendingIntent가 반드시 필요한 대표 시나리오
예시: 알람 기능 (AlarmManager)
val intent = Intent(context, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
triggerTimeMillis,
pendingIntent
)
AlarmManager는 시스템 서비스로, 앱 외부의 시스템 컴포넌트가 인텐트를 실행해야 하므로 PendingIntent를 필수로 요구합.그 외 사용 예시
Notification에서 알림 클릭 시 특정 액티비티를 실행할 때AppWidget에서 버튼 클릭 처리할 때JobScheduler, Broadcast, Service 등을 타이밍이나 외부 이벤트에 따라 트리거할 때Q) 3. Serializable과 Parcelable의 차이는 무엇인가?

Q) Android에서 Serializable과 Parcelable의 주요 차이점은 무엇이며, 컴포넌트 간 데이터 전달 시 Parcelable이 일반적으로 선호되는 이유는 무엇인가?
차이점 위에 언급
Parcelable이 선호되는 이유:
성능 우위: 10-100배 빠른 처리 속도
메모리 효율성: 가비지 컬렉션 부담 최소화
Android 최적화: 시스템 레벨에서 최적화됨
사용 권장사항:
Parcelable: Intent, Bundle 등 Android 컴포넌트 간 데이터 전달
Serializable: 파일 저장, 네트워크 전송 등 간단한 영속화
결론: Android 환경에서는 성능과 메모리 효율성 때문에 Parcelable 사용이 강력히 권장됨
Q) 4. Context란 무엇이며 어떤 종류가 있는가?
Context는 안드로이드 앱의 환경 정보를 담고 있는 추상 클래스로, 앱의 현재 상태와 시스템 리소스에 접근할 수 있는 기능을 제공(앱과 안드로이드 시스템 간의 브릿지 역할)
public abstract class Context {
// 추상 메서드들
public abstract Object getSystemService(String name);
public abstract String getPackageName();
public abstract Resources getResources();
// ... 기타 추상 메서드들
}
주요 종류:
Activity Context: Activity의 생명주기와 연결된 Context
Application Context: 앱 전체 생명주기와 연결된 Context
Service Context: Service의 생명주기와 연결된 Context
Q1) 왜 Context를 잘못 사용하면 메모리 누수가 발생할 수 있는가?
Activity Context를 오래 살아남는 객체(static 변수, 싱글톤, companion object, object 선언 등)에 저장하면, Activity가 종료되어도 참조가 유지되어 GC에서 회수되지 않아 메모리 누수가 발생
그외 Handler, AsyncTask, Inner Class, 람다 표현식에서 Activity를 참조하는 경우도 해당
Q2) Activity Context와 Application Context의 차이는 무엇이며, 언제 각각을 사용해야 하는가?
생명 주기의 차이,
Activity Context: UI 관련 작업, 테마 적용, Dialog 생성 시 사용
Application Context: 앱 전체 수명 동안 유지되는 작업, 싱글톤 패턴, 시스템 서비스 접근 시 사용
Q) 5. Application 클래스란 무엇인가
Application 클래스는 앱의 전역 상태를 관리하는 클래스
앱 시작 순서:
ContentProvider.onCreate() (가장 먼저)
Application.onCreate() (그 다음)
Activity/Service/BroadcastReceiver (필요에 따라)
목적과 특징:
앱 전체에서 공유되는 데이터와 설정 관리
앱 시작 시 초기화 작업 수행
전역 상태 저장 및 관리
Q) Application 클래스의 목적은 무엇이며, 생명주기나 리소스 관리 측면에서 Activity와 어떤 차이가 있는가?
생명주기: Application은 앱이 완전히 종료될 때까지 유지, Activity는 사용자 상호작용에 따라 생성/소멸
리소스 관리: Application은 앱 전체 리소스 관리, Activity는 개별 화면 리소스 관리
Q) 6. AndroidManifest 파일의 목적은 무엇인가
AndroidManifest.xml은 Android 앱과 운영체제 간의 중요한 연결고리 역할을 하는 핵심 설정 파일
Q) AndroidManifest의 intent filter는 어떻게 앱 상호작용을 가능하게 하며, Activity 클래스가 AndroidManifest에 등록되지 않으면 어떤 일이 발생하는가?
Intent Filter의 앱 상호작용:
Intent Filter는 앱 간 통신의 관문 역할. 시스템이 특정 Intent를 받으면 매칭되는 Intent Filter를 찾아 해당 컴포넌트를 실행.
<activity android:name=".ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
이 설정으로 다른 앱에서 텍스트 공유 시 이 Activity가 선택 옵션에 나타남
Activity 미등록 시 결과:
// 등록되지 않은 Activity 실행 시도
startActivity(Intent(this, UnregisteredActivity::class.java))
// 결과: ActivityNotFoundException 발생, 앱 크래시
주요 결과:
런타임 크래시: ActivityNotFoundException으로 앱이 강제 종료
Intent 매칭 실패: 외부 앱에서 해당 Activity 호출 불가
백스택 문제: 등록되지 않은 Activity는 백스택에서 관리되지 않음
AndroidManifest 등록은 필수사항이며, 등록되지 않은 Activity는 시스템에 존재하지 않는 것으로 간주됨
Q) 7. Activity 생명주기를 설명하라
onCreate() → onStart() → onResume() → onPause() → onStop() → onDestroy()
Q) onPause()와 onStop()의 차이는 무엇이며, 고성능 리소스를 처리할 때 각각을 어떻게 사용해야 하는가?
onPause(): Activity가 부분적으로 가려질 때 호출 (Dialog, 반투명 Activity)
onStop(): Activity가 완전히 보이지 않을 때 호출
고성능 리소스 처리:
onPause(): 카메라, 센서, 애니메이션 등 즉시 중단해야 하는 리소스 해제
onStop(): 네트워크 연결, 데이터베이스 연결 등 비용이 큰 리소스 해제
Q) 8. Fragment의 생명주기를 설명하라
onCreate() → onCreateView -> onViewCreated() -> onViewStateRestored -> onStart() → onResume() → onPause() → onStop() → onSaveInstanceState() -> onDestroyView() -> onDestory()
Q) onCreateView()와 onDestroyView()의 목적은 무엇이며, 이 메서드들에서 View 관련 리소스를 적절히 처리하는 것이 왜 중요한가?
onCreateView()의 목적:
Fragment의 UI 레이아웃을 생성하고 반환하는 메서드.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// 레이아웃 인플레이션
return inflater.inflate(R.layout.fragment_example, container, false)
}
onDestroyView()의 목적:
Fragment의 View가 소멸될 때 호출되어 View 관련 리소스를 정리.
override fun onDestroyView() {
super.onDestroyView()
// View 참조 해제, 리스너 제거 등
_binding = null // ViewBinding 해제
}
적절한 리소스 처리의 중요성:
메모리 누수 방지:
class MyFragment : Fragment() {
private var _binding: FragmentExampleBinding? = null
private val binding get() = _binding!!
override fun onCreateView(...): View {
_binding = FragmentExampleBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // 중요: View 참조 해제
}
}
주요 이유:
메모리 누수 방지: View 참조를 해제하지 않으면 GC되지 않음
크래시 방지: Fragment 재생성 시 null View 참조로 인한 크래시 방지
리소스 최적화: 애니메이션, 리스너, Observer 등 정리로 성능 향상
Fragment는 View보다 오래 살 수 있어서 View 생명주기와 Fragment 생명주기를 분리해서 관리해야 합니다.재시도Claude는 실수를 할 수 있습니다. 응답을 반드시 다시 확인해 주세요.
Q) 9. Service 란 무엇인가?
Service는 백그라운드에서 장시간 실행되는 작업을 수행하는 컴포넌트
Q1) Android에서 Started Service와 Bound Service의 차이점은 무엇이며, 각각을 언제 사용해야 하는가?
Started Service: startService()로 시작, 독립적으로 실행, 명시적으로 stopSelf()나 stopService()로 종료
Bound Service: bindService()로 바인딩, 클라이언트와 상호작용, 모든 클라이언트가 unbind되면 자동 종료
사용 시기:
Started Service: 파일 다운로드, 음악 재생 등 독립적 작업
Bound Service: 클라이언트와 지속적 통신이 필요한 작업
Q) 10. BroadcastReceiver 란 무엇인가?
BroadcastReceiver는 시스템이나 앱에서 발생하는 브로드캐스트 메시지를 수신하는 컴포넌트
Q2) 브로드캐스트의 유형에는 어떤 것들이 있으며, 시스템 브로드캐스트와 커스텀 브로드캐스트는 기능과 사용 방식에서 어떻게 다른가?
브로드캐스트 유형:
System Broadcast: 시스템에서 발생 (배터리 부족, 화면 꺼짐 등)
Custom Broadcast: 개발자가 정의한 브로드캐스트
Ordered Broadcast: 우선순위에 따라 순차적으로 전달
Sticky Broadcast: 한 번 발송되면 시스템에 유지 (API 21에서 deprecated)
차이점:
시스템 브로드캐스트: 시스템 이벤트 감지, 자동으로 발생
커스텀 브로드캐스트: 앱 간 통신, 개발자가 직접 발송
Q) 11. ContentProvider 란 무엇이며, 어떻게 앱 간 데이터를 안전하게 공유할 수 있게 하는가?
ContentProvider는 앱 간 데이터를 안전하게 공유할 수 있게 하는 컴포넌트로, 표준화된 인터페이스를 제공
Q) ContentProvider URI 의 주요 구성 요소는 무엇인가?
URI 주요 구성 요소:
content://com.example.provider/table/id
- scheme: content://
- authority: com.example.provider
- path: table
- id: 특정 레코드 식별자
Q) ContentResolver 는 ContentProvider 와 어떻게 상호작용하여 데이터를 조회하거나 수정하는가?
ContentResolver가 URI를 분석하여 해당 ContentProvider를 찾음
query(), insert(), update(), delete() 메서드를 통해 데이터 조작
ContentProvider는 요청을 받아 내부 데이터베이스나 파일에 접근하여 결과 반환
Q) 12. 구성 변경(Configuration Changes)은 어떻게 처리해야 하는가?
Q) 구성 변경 시 Activity 재생성으로 인해 데이터 손실이 발생하는 것을 어떻게 방지할 수 있는가? 일시적 상태와 영속적 상태를 각각 어떻게 다뤄야 하는가?
데이터 손실 방지 방법:
일시적 상태: onSaveInstanceState()와 Bundle 사용
영속적 상태: ViewModel, 데이터베이스, SharedPreferences 사용
Q) android:configChanges 속성을 사용하면 Activity 생명주기에 어떤 영향이 생기며, 어떤 상황에서 onConfigurationChanged()를 수동으로 구현해야 하는가?
android:configChanges 속성:
설정 시 해당 구성 변경에 대해 Activity 재생성 방지
onConfigurationChanged() 메서드가 호출됨
레이아웃 변경이 필요한 경우 수동으로 처리해야 함
사용 상황:
게임이나 동영상 재생 시 화면 회전으로 인한 중단 방지
복잡한 초기화 작업이 있는 경우
Q) 13. Android 는 메모리를 어떻게 관리하며, 메모리 누수를 어떻게 방지할 수 있는가?
Q) 앱에서 흔히 발생하는 메모리 누수 원인은 무엇이며, 개발자는 이를 어떻게 방지할 수 있는가?
흔한 메모리 누수 원인:
Activity Context를 static 변수, companion object, object 선언, 전역 변수에 저장
Handler, AsyncTask, Coroutine에서 Activity 참조
Inner Class에서 외부 Activity 참조 (람다 표현식 포함)
리스너, 콜백 등록 후 해제하지 않음
잘못된 Coroutine Scope 사용 (GlobalScope 등)
방지 방법:
WeakReference 사용
Static Inner Class 사용
생명주기에 맞는 리소스 해제
Application Context 사용
Q) 안드로이드의 가비지 컬렉션은 어떻게 작동하며, 메모리 누수 분석과 수정을 위한 도구로는 무엇이 있는가?
가비지 컬렉션:
ART에서 Concurrent Copying GC 사용
참조가 없는 객체를 자동으로 메모리에서 해제
분석 도구:
LeakCanary, Memory Profiler, MAT(Memory Analyzer Tool)
Q) 14. ANR(Application Not Responding)의 주요 원인은 무엇이며, 이를 방지하는 방법은 무엇인가?
주요 원인:
Main Thread에서 긴 작업 수행 (5초 이상)
BroadcastReceiver에서 긴 작업 수행 (10초 이상)
데드락 발생
Q) 어떻게 ANR을 감지하고 진단할 수 있으며, 앱의 성능을 개선하기 위한 도구와 전략은 무엇인가?
방지 방법:
무거운 작업을 Background Thread에서 수행
AsyncTask, ExecutorService, WorkManager 사용
Thread 간 동기화 주의
감지 및 진단:
Logcat에서 ANR 로그 확인
StrictMode를 통한 성능 모니터링
Systrace, Method Tracing 사용
Q) 15. 어떻게 딥링크를 처리하는가?
딥링크 처리 방법
1. AndroidManifest.xml 설정:
<activity android:name=".DeepLinkActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="myapp.com"
android:pathPrefix="/product" />
</intent-filter>
</activity>
2. Activity에서 딥링크 처리:
class DeepLinkActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Intent에서 딥링크 데이터 추출
val data = intent.data
if (data != null) {
val productId = data.getQueryParameter("id")
val category = data.pathSegments.getOrNull(1)
// 딥링크 기반 화면 구성
navigateToProduct(productId)
}
}
}
Q) Android에서 딥링크를 어떻게 테스트하며, 다양한 기기와 시나리오에서 딥링크가 올바르게 작동하는지 확인하기 위한 일반적인 디버깅 기법은 무엇인가?
딥링크 테스트 방법
1. ADB 명령어 테스트:
# HTTP/HTTPS 딥링크 테스트
adb shell am start \
-W -a android.intent.action.VIEW \
-d "https://myapp.com/product?id=123" \
com.example.myapp
# 커스텀 스키마 테스트
adb shell am start \
-W -a android.intent.action.VIEW \
-d "myapp://product/123" \
com.example.myapp
2. Android Studio Deep Link 도구:
// Tools > App Links Assistant 사용
// URL Mapping 테스트
// App Links 검증
3. 브라우저/이메일을 통한 실제 테스트:
<!-- 테스트용 HTML 파일 -->
<a href="https://myapp.com/product?id=123">앱에서 상품 보기</a>
<a href="myapp://share/text?content=hello">앱으로 공유하기</a>
디버깅 기법
1. Intent 데이터 로깅:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 딥링크 디버깅을 위한 상세 로깅
Log.d("DeepLink", "Intent action: ${intent.action}")
Log.d("DeepLink", "Intent data: ${intent.data}")
Log.d("DeepLink", "Intent extras: ${intent.extras}")
intent.data?.let { uri ->
Log.d("DeepLink", "Scheme: ${uri.scheme}")
Log.d("DeepLink", "Host: ${uri.host}")
Log.d("DeepLink", "Path: ${uri.path}")
Log.d("DeepLink", "Query: ${uri.query}")
}
}
2. App Links 검증:
# Digital Asset Links 검증
adb shell pm verify-app-links --re-verify com.example.myapp
# 검증 상태 확인
adb shell pm get-app-links com.example.myapp
3. 다양한 시나리오 테스트:
// 테스트 케이스들
class DeepLinkTest {
@Test
fun testProductDeepLink() {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://myapp.com/product?id=123"))
// 앱이 올바르게 실행되는지 검증
}
@Test
fun testInvalidDeepLink() {
// 잘못된 딥링크 처리 테스트
}
@Test
fun testDeepLinkWithoutNetwork() {
// 네트워크 없는 상황에서 딥링크 처리
}
}
4. 기기별 테스트 고려사항:
Android 버전별 차이: Android 6.0+ App Links vs 이전 버전
제조사별 커스터마이징: 삼성, LG 등 제조사별 딥링크 처리 차이
기본 브라우저 설정: Chrome, Samsung Browser 등에 따른 동작 차이
앱 설치 상태: 앱 미설치 시 Play Store 리다이렉션 테스트
5. 디버깅 도구:
// 딥링크 디버깅용 개발자 옵션
if (BuildConfig.DEBUG) {
// 딥링크 정보를 Toast로 표시
Toast.makeText(this, "DeepLink: ${intent.data}", Toast.LENGTH_LONG).show()
// 딥링크 히스토리 저장
saveDeepLinkHistory(intent.data.toString())
}
딥링크는 다양한 진입점과 상황을 고려해야 하므로, 체계적인 테스트와 로깅이 필수.
Q) 16. Task와 Back Stack이란 무엇인가?
Task: 사용자가 특정 작업을 수행할 때 상호작용하는 Activity들의 집합
Back Stack: Task 내에서 Activity들이 쌓이는 후입선출(LIFO) 스택
Q1) singleTask와 singleInstance Launch Mode의 차이점은 무엇이며, 각각 어떤 상황에서 사용하는가?
Launch Mode 차이:
singleTask: 새 Task에서 Activity 시작, 기존 인스턴스 있으면 재사용
singleInstance: 새 Task에서 단독으로 실행, 다른 Activity와 공유 불가
Q2) Activity의 Launch Mode는 Task 및 Back Stack에 어떤 영향을 미치는가?
standard: 새 인스턴스 생성
singleTop: 스택 최상단에 있으면 재사용
singleTask: Task 내에서 유일한 인스턴스
singleInstance: 시스템 전체에서 유일한 인스턴스
Q) 17. Bundle의 사용 목적은 무엇인가?
Q1) onSaveInstanceState()에서 Bundle을 활용하여 구성 변경 중 UI 상태를 어떻게 보존하는가?
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("key", value)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.getString("key")?.let { value ->
// 복원 로직
}
}
Q2) Bundle에 저장 가능한 데이터의 타입에는 어떤 것들이 있는가?
기본 타입 (int, String, boolean 등)
Parcelable, Serializable 객체
배열, ArrayList
다른 Bundle
Q) 18. Activity 또는 Fragment 간 데이터를 어떻게 전달하는가?
Bundle(Key-Value 형태로 데이터를 저장하는 컨테이너)로 전달
Q1) Fragment 간에 ViewModel을 공유하면 어떤 장점이 있고, Bundle을 사용하는 것보다 더 나은점이 무엇인가?
Fragment 간 ViewModel 공유 장점:
구성 변경 시 데이터 유지
생명주기 인식으로 메모리 누수 방지
코드 간소화 및 유지보수성 향상
실시간 데이터 동기화
Q2) Fragment Result API는 언제 사용하는 것이 적절하며, 어떤 구조적 장점을 제공하는가?
Fragment Result API:
Fragment 간 결과 전달 시 사용
타입 안전성 제공
생명주기 인식
코드 가독성 향상
Q) 19. 구성 변경 시 Activity는 어떻게 동작하는가?
Q) 구성 변경으로 액티비티가 다시 생성될 때 데이터 손실을 방지하는 방법과, 일시적 상태 및 영구적 상태를 처리하는 방법들에는 어떤 것들이 있는가?
데이터 손실 방지 방법:
onSaveInstanceState(): 일시적 UI 상태 저장
ViewModel: 구성 변경에 살아남는 데이터 저장
Persistent Storage: 데이터베이스, SharedPreferences 사용
상태 처리 구분:
일시적 상태: 스크롤 위치, 입력 필드 내용 등
영구적 상태: 사용자 설정, 앱 데이터 등
Q) 20. ActivityManager 란 무엇인가?
ActivityManager는 앱과 시스템의 Activity 및 프로세스 정보를 관리하는 시스템 서비스
Q) ActivityManager.getMemoryInfo()는 앱 성능을 어떻게 최적화하는 데 사용될 수 있으며, 시스템이 저메모리 상태에 진입했을 때 개발자가 취해야 할 조치는 무엇인가?
getMemoryInfo() 활용:
val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
val memoryInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memoryInfo)
if (memoryInfo.lowMemory) {
// 메모리 사용량 줄이기
// 캐시 정리, 불필요한 객체 해제
}
저메모리 상태 대응:
캐시 데이터 정리
불필요한 서비스 중단
메모리 사용량 최적화
Q) 21. SparseArray를 사용하는 장점은 무엇인가?
Q1) 어떤 상황에서 SparseArray를 HashMap 대신 사용하는 것이 적절한가?
SparseArray vs HashMap:
SparseArray: int 키 전용, 메모리 효율적, 작은 데이터셋에 최적화
HashMap: 모든 타입 키 지원, 큰 데이터셋에 빠름
사용 시기:
키가 int이고 데이터가 적을 때(< 1000개)
메모리 사용량이 중요한 경우
자주 삽입/삭제하지 않는 경우
Q2) 성능과 메모리 측면에서 각각 어떤 트레이드오프가 있는가?
트레이드오프:
SparseArray: 메모리 효율적, 작은 데이터셋에서 빠름, 키 제한
HashMap: 큰 데이터에 빠름, 메모리 오버헤드 존재
Q) 22. 런타임 권한(Runtime Permission)은 어떻게 처리하는가?
Q) Android 의 런타임 권한 시스템은 사용자 프라이버시를 어떻게 개선하며, 민감 권한 요청 전 앱은 어떤 절차를 거쳐야 하는가?
런타임 권한의 프라이버시 개선:
사용자가 권한 허용/거부를 런타임에 결정
최소 권한 원칙 구현
권한 사용 이유 명확히 설명
권한 요청 절차:
권한 필요성 체크
권한 설명 (rationale) 제공
권한 요청
결과 처리
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
if (shouldShowRequestPermissionRationale(permission)) {
// 권한 설명 표시
}
requestPermissions(arrayOf(permission), REQUEST_CODE)
}
Q) 23. Looper, Handler, HandlerThread의 역할은 무엇인가?
Looper: 메시지 큐를 지속적으로 모니터링하고 메시지를 순차 처리
Handler: 스레드 간 메시지 전달과 비동기 작업 실행
HandlerThread: 백그라운드에서 메시지를 순차 처리하는 작업 스레드 역할
Q1) Handler는 Looper와 어떻게 작동하여 스레드 간 통신을 가능하게 하는가?
Handler와 Looper 동작:
Handler가 메시지를 Looper의 MessageQueue에 전송
Looper가 메시지를 순차적으로 꺼내서 Handler에 전달
Handler가 handleMessage()에서 메시지 처리
Q2) HandlerThread는 직접 Looper를 설정한 Thread보다 어떤 면에서 더 간편하고 안전한가?
HandlerThread 장점:
Looper 설정이 자동화됨
안전한 초기화 보장
quit() 메서드로 안전한 종료
Q) 24. 예외(Exception)를 추적하는 방법은?
Q) 개발 환경에서 Logcat을 통한 예외 디버깅과, 운영 환경에서 Crashlytics를 사용하는 방식은 어떻게 다르며, 각각 어떤 방식으로 예외를 해결할 수 있는가?
개발 환경 (Logcat):
실시간 로그 확인
스택 트레이스 직접 분석
breakpoint 설정 가능
운영 환경 (Crashlytics):
원격 크래시 수집
사용자 영향도 분석
크래시 발생 조건 추적
우선순위 기반 버그 수정
해결 방식:
개발: 즉시 디버깅 및 수정
운영: 데이터 분석 후 패치 배포
Q) 25. Build Variant와 Product Flavor란 무엇인가?
Q) Build Type과 Product Flavor의 차이점은 무엇이며, 이 둘은 어떻게 조합되어 Build Variant를 형성하는가?
Build Type vs Product Flavor:
Build Type: 빌드 설정 (debug, release)
Product Flavor: 앱 변형 (free, paid, staging)
Build Variant 형성:
Build Variant = Product Flavor + Build Type
예: freeDebug, paidRelease
활용:
서로 다른 API 엔드포인트
다른 리소스 및 설정
빌드 설정 최적화
Q) 26. 안드로이드 앱에서 접근성을 어떻게 보장하는가?
Q) 사용자 글꼴 설정을 반영하면서 레이아웃이 깨지지 않도록 하려면 어떤 접근이 필요한가?
글꼴 설정 반영:
sp 단위 사용 (사용자 글꼴 크기 설정 반영)
android:layout_width="0dp"와 weight 사용
최소/최대 크기 제한 설정
텍스트 줄바꿈 허용
Q) 접근성을 위한 포커스 내비게이션은 어떻게 설계해야 하며, 어떤 도구를 통해 테스트할 수 있는가?
포커스 내비게이션:
android:focusable="true" 설정
논리적 포커스 순서 구성
android:nextFocusDown 등으로 명시적 순서 지정
테스트 도구:
TalkBack
Accessibility Scanner
Espresso 접근성 테스트
Q) 27. 안드로이드 파일 시스템이란 무엇인가?
Android 파일 시스템은 Android 디바이스에서 데이터를 저장, 조직, 관리하는 구조화된 환경
이 시스템은 Linux 기반 파일 시스템 구조 위에 구축되어 있으며, 앱과 사용자가 효율적으로 파일을 저장하고 접근할 수 있도록 함. Android는 애플리케이션의 개인 저장소와 공유 저장소를 모두 제공하며, 보안과 권한 체계를 엄격하게 적용
Q1) Android 파일 시스템에서 앱 간 데이터 격리는 어떻게 보장되며, 이를 위해 어떤 메커니즘들이 작동하는가?
앱 간 데이터 격리:
각 앱은 고유한 UID/GID를 가짐
앱별 전용 디렉터리 할당
파일 권한 시스템으로 접근 제어
SELinux 정책 적용
Q2) Android 10에서 도입된 Scoped Storage는 기존 모델과 비교하여 어떤 차별점이 있으며, 어떤 API를 통해 대응할 수 있는가?
Scoped Storage (Android 10+):
앱별 샌드박스 강화
MediaStore API를 통한 미디어 파일 접근
SAF(Storage Access Framework)를 통한 문서 접근
외부 저장소 직접 접근 제한
대응 API:
MediaStore API
Storage Access Framework
File Provider
Q) 28. Android Runtime (ART), Dalvik, Dex Compiler란 무엇인가?
Android 애플리케이션은 특수한 런타임 환경과 컴파일 과정을 통해 기기에서 실행됨
이 과정에서 Android Runtime (ART), Dalvik, Dex Compiler는 성능 최적화와 메모리 효율성을 보장하는 데 핵심적인 역할을 수행
Q) ART의 AOT 컴파일은 Dalvik의 JIT 컴파일과 어떤 차이가 있으며, 이로 인해 앱 시작 속도와 CPU 사용량에 어떤 영향을 미치는가?
ART vs Dalvik:
ART (AOT): 설치 시 네이티브 코드로 컴파일, 빠른 실행 속도, 큰 저장 공간
Dalvik (JIT): 런타임에 바이트코드 해석, 느린 실행 속도, 작은 저장 공간
영향:
앱 시작 속도: ART가 더 빠름
CPU 사용량: ART가 더 적음
메모리 사용량: ART가 더 많음
배터리 수명: ART가 더 절약
Q) 29. APK 파일과 AAB 파일의 차이점은 무엇인가?

Q) AAB 형식은 기기별 APK 최적화를 어떻게 수행하며, 기존 APK 형식 대비 어떤 장점이 있는가?
AAB (Android App Bundle) 장점:
Dynamic Delivery: 사용자 기기에 필요한 부분만 다운로드
언어별 분할: 사용자 언어에 맞는 리소스만 포함
밀도별 분할: 화면 밀도에 맞는 이미지만 포함
아키텍처별 분할: CPU 아키텍처에 맞는 네이티브 라이브러리만 포함
기기별 최적화:
Google Play가 사용자 기기 정보를 바탕으로 최적화된 APK 생성 및 전달
Q) 30. R8 최적화란 무엇인가?
R8은 Android의 빌드 프로세스에 내장된 코드 축소 및 최적화 도구
이전의 ProGuard를 대체하며, APK 또는 AAB 파일의 크기를 줄이고 실행 성능을 향상시키기 위해 사용
Q) R8은 앱 크기 및 실행 성능 향상에 어떤 방식으로 기여하며, 과도한 축소를 방지하기 위해 어떤 설정이 필요한가?
R8 기여 방식:
코드 축소: 사용하지 않는 코드 제거
리소스 축소: 사용하지 않는 리소스 제거
난독화: 코드 보안 강화
최적화: 성능 향상을 위한 코드 변환
Q) R8과 ProGuard의 차이점은 무엇이며, R8이 제공하는 구체적인 이점은 무엇인가?
설정 방법:
# proguard-rules.pro
-keep class com.example.MyClass { *; }
-keepclassmembers class com.example.MyClass {
public <init>(...);
}
R8 vs ProGuard:
R8: 더 빠른 빌드, 더 나은 최적화, 공식 지원
ProGuard: 레거시 도구, 별도 설치 필요
Q) 31. 앱 크기를 줄이는 방법은 무엇인가?
Q) 고해상도 이미지로 인해 앱 크기가 과도하게 커졌다면, 시각적 품질을 유지하면서 이를 어떻게 최적화하겠는가? 어떤 이미지 포맷을 사용하겠는가?
이미지 최적화:
WebP 포맷 사용 (기존 JPEG/PNG 대비 25-35% 크기 감소)
Vector Drawable 사용 (아이콘 등)
적응형 이미지 제공 (여러 밀도별 리소스)
이미지 압축 도구 사용
Q) 앱 기능 중 일부만 자주 사용된다면, 초기 다운로드 크기를 줄이면서 전체 기능을 제공하려면 어떻게 설계하시겠습니까?
기능별 최적화:
Dynamic Feature Modules 사용
필수 기능만 기본 APK에 포함
부가 기능은 필요 시 다운로드
App Bundle과 Play Feature Delivery 활용
Q) 32. 안드로이드에서 프로세스란 무엇이며 운영체제가 이를 어떻게 관리하는가?
Android에서 프로세스(process) 는 애플리케이션의 컴포넌트들이 실행되는 실행 환경
각 Android 앱은 기본적으로 고유한 프로세스 내에서 실행되며, 단일 스레드(메인 스레드) 를 기반으로 함
Q1) 서로 다른 컴포넌트를 다른 프로세스에서 실행시키려면 AndroidManifest에서 어떻게 구성해야 하는가?
다른 프로세스 실행 설정:
<service
android:name=".MyService"
android:process=":remote" />
<activity
android:name=".MyActivity"
android:process="com.example.process" />
Q2) Android 시스템은 메모리가 부족할 때 어떤 기준으로 프로세스를 종료하며, 중요한 프로세스가 종료되지 않도록 하기 위해 개발자가 할 수 있는 전략은 무엇인가?
프로세스 우선순위 계층:
Foreground Process: 사용자가 직접 상호작용
Visible Process: 사용자에게 보이지만 포커스 없음(직접 상호작용 X)
Service Process: 백그라운드 서비스 실행
Cached Process: 현재는 사용되지 않지만 빠른 재시작을 위해 메모리에 남아 있는 프로세스
중요 프로세스 보호 전략:
Foreground Service 사용
Persistent Notification 표시
Partial Wake Lock 활용
알람 관리자를 통한 주기적 작업
Process 우선순위 관리
앱 시작 순서:
ContentProvider.onCreate() (가장 먼저)
Application.onCreate() (그 다음)
Activity/Service/BroadcastReceiver (필요에 따라)
이러한 특성 때문에 많은 라이브러리들(Firebase, WorkManager, LeakCanary 등)이 ContentProvider를 이용해 자동 초기화를 구현함
ContentProvider를 사용하면 개발자가 별도로 Application 클래스에서 초기화 코드를 작성할 필요 없이 자동으로 라이브러리가 초기화됨
class MyLibraryInitProvider : ContentProvider() {
override fun onCreate(): Boolean {
// Application.onCreate()보다 먼저 실행됨
val context = context ?: return false
MyLibrary.initialize(context)
return true
}
// 다른 메서드들은 null 반환
override fun query(...) = null
override fun insert(...) = null
// ... 기타 메서드들
}
AndroidManifest.xml:
<provider
android:name=".MyLibraryInitProvider"
android:authorities="${applicationId}.MyLibraryInitProvider"
android:exported="false"
android:enabled="true" />
하지만 ContentProvider를 많이 사용하면 앱 시작 시간이 느려지는 문제가 있어서, 구글은 이를 해결하기 위해 App Startup 라이브러리를 제공.
이 라이브러리는 여러 초기화 작업을 하나의 ContentProvider에서 처리하여 성능을 개선
<provider android:name="com.firebase.FirebaseInitProvider" ... />
<provider android:name="com.workmanager.WorkManagerInitProvider" ... />
<provider android:name="com.crashlytics.CrashlyticsInitProvider" ... />
<provider android:name="com.analytics.AnalyticsInitProvider" ... />
각 ContentProvider는:
시스템이 개별적으로 인스턴스화
각각의 onCreate() 메서드 호출
평균 2-3ms의 오버헤드 (ContentProvider당)
10개 라이브러리 = 20-30ms 추가 지연
// 시스템이 순차적으로 실행
FirebaseInitProvider.onCreate() // 예상 소요 시간: 3ms
WorkManagerInitProvider.onCreate() // 2ms
CrashlyticsInitProvider.onCreate() // 4ms
AnalyticsInitProvider.onCreate() // 2ms
// 총 11ms + 각 Provider 생성 오버헤드
모든 ContentProvider.onCreate()는 메인 스레드에서 실행
사용자가 앱 화면을 보기 전까지 블로킹
초기화가 길어질수록 "앱이 느리다"고 느껴짐
<!-- App Startup: 하나의 ContentProvider로 통합 -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false">
<!-- 모든 초기화 작업을 메타데이터로 등록 -->
<meta-data android:name="com.example.WorkManagerInitializer"
android:value="androidx.startup" />
<meta-data android:name="com.example.FirebaseInitializer"
android:value="androidx.startup" />
</provider>
ContentProvider 생성 오버헤드: 10개 → 1개
시작 시간 단축: 약 18-20ms 절약 (평균적으로)
class WorkManagerInitializer : Initializer<WorkManager> {
override fun create(context: Context): WorkManager {
// WorkManager 초기화
return WorkManager.getInstance(context)
}
// 의존성 명시: Firebase가 먼저 초기화되어야 함
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf(FirebaseInitializer::class.java)
}
}
기존 문제: ContentProvider는 무작위 순서로 초기화
App Startup 해결: 명시적 의존성 그래프로 올바른 순서 보장
// 즉시 초기화가 필요하지 않은 경우
AppInitializer.getInstance(context)
.initializeComponent(WorkManagerInitializer::class.java)
효과:
앱 진입시 필수적이지 않은 라이브러리는 나중에 초기화
-> 앱 시작 시간에서 해당 초기화 시간 완전 제거
사용자가 기능을 실제 사용할 때 초기화
이렇게 App Startup은 단순히 ContentProvider를 하나로 합치는 것이 아니라, 의존성 관리, Lazy Loading을 통해 근본적인 성능 개선을 제공
TL;DR: Bundle의 1MB 제한은 Binder IPC의 물리적 한계이며,
전체 시스템의 성능과 안정성을 위한 설계 결정
IPC란 무엇인가?
IPC(Inter-Process Communication)는 서로 다른 프로세스 간에 데이터를 주고받는 메커니즘.
Android에서는 보안과 안정성을 위해 각 앱이 독립된 프로세스에서 실행되므로, 앱 간 통신을 위해서는 IPC가 필요하고, Android는 Binder IPC를 사용
왜 Binder IPC를 채택했는지는 해당 글 참고
안드로이드의 프로세스 격리 철학
App A Process App B Process
┌─────────────┐ ┌─────────────┐
│ Memory │ │ Memory │
│ Space A │ ≠≠≠ │ Space B │
│ │ │ │
└─────────────┘ └─────────────┘
안드로이드는 프로세스 격리를 기본 원칙으로 함
각 앱은 별도의 프로세스에서 실행
메모리 공간이 완전히 분리됨(직접적인 메모리 공유 불가능)
// ❌ 이런 식으로는 불가능 (다른 프로세스라서)
val myObject = MyClass()
otherApp.directAccess(myObject) // 메모리 주소가 다름!
// ✅ Bundle을 통한 직렬화된 데이터 전송
val bundle = Bundle().apply {
putString("key", "value")
}
intent.putExtras(bundle)
Bundle은 이 IPC 과정에서 데이터를 안전하게 직렬화하여 전송하는 컨테이너 역할을 수행.
직렬화 컨테이너: 메모리 객체 → 바이트 스트림
타입 안전성: 각 데이터 타입별 메서드 제공
효율적 파싱: Key-Value 구조로 빠른 접근
IPC 통신을 위해서는 데이터를 직렬화해야 하고, 직렬화할 수 없는 타입들이 존재하기 때문에 Bundle도 동일한 타입 제한을 갖게 됨
Intent with Bundle → Parcel → Binder Driver → Parcel → Bundle
(App A) (직렬화) (커널 공간) (역직렬화) (App B)
Binder 특징:
동기/비동기 통신 지원
보안: UID/PID 기반 권한 검증
효율성: 복사 최소화 (copy-once)
안정성: 커널 레벨 메모리 보호
// Android 소스코드: frameworks/native/libs/binder/ProcessState.cpp
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
// 실제로는 1MB에서 페이지 크기 * 2를 뺀 값
메모리 효율성
전체 시스템 메모리 사용량 = Binder 버퍼 × 프로세스 개수
예시:
응답성 보장
// 1MB 전송 시간 (대략적)
// CPU 직렬화: ~2-5ms
// Binder 전송: ~1-3ms
// 역직렬화: ~2-5ms
// 총 시간: ~5-13ms (사용자가 느끼지 못함)
// 10MB라면?
// 총 시간: ~50-130ms (사용자가 버벅임을 느낌)
안정성
큰 트랜잭션은 시스템 데드락 위험 증가
OOM(Out of Memory) 방지
실제 사용 가능 크기
fun getAvailableBundleSize(): Int {
val parcel = Parcel.obtain()
return try {
// 시스템 오버헤드 고려
// - Intent 메타데이터: ~100-200 bytes
// - Bundle 헤더: ~50-100 bytes
// - 기타 Binder 오버헤드: ~100-200 bytes
1024 * 1024 - 500 // 약 1MB - 500bytes
} finally {
parcel.recycle()
}
}
Android IPC는:
대용량 데이터 전송 방법
1. 파일 기반 전송
2. Content Provider 활용
3. Shared Memory (Android 8.0+)
4. 메모리 매핑
Android Binder IPC
Parcelable vs Serializable
지원자 95%가 틀리는 startActivity() 라이프사이클, 당신도 예외는 아닙니다
읽어보면 좋을 참고 자료
Java GC 동작 방식과 WeakReference의 이해
이후 카테고리에 언급될 예정, 읽어보면 좋을 참고 자료
BaselineProfile, 앱 시작 시간 최적화하기 (Android)
참고)
Java GC 동작 방식과 WeakReference의 이해
[Android] IPC , RPC ,Binder 알아보기
오우 마쉿네용 ㅎㄷㄷ...!