1. 안드로이드 OS의 아키텍처 구조

- Linux Kernel
- 가장 하위 계층
- 파워를 관리하고 프로세스나 스레드, 메모리 등의 시스템 자원도 커널에서 관리
- USB나 카메라 등 각종 디바이스의 드라이버가 위치. 기기의 기본 HW와 통신하는 역할\
- HAL(Hardware Abstraction Layer)
- Kernel위 레이어
- 각종 하드웨어 기능을 활용할 수 있는 인터페이스를 상위 레이어에 제공
- Native C/C++ Libraries(네이티브 데몬 및 라이브러리)
- HAL위 레이어
- 커널 또는 다른 인터페이스와 직접 상호작용하며 HAL에 의존하지 않음
- 네이티브 라이브러리의 기능을 상위 레이어에 제공
- 게임 엔진이나 미디어 처리 등 성능이 중요한 기능을 개발할 때 활용
- C나 C++ 코드가 필요할 때, NDK(Native Development Kit)를 사용해 구현 가능
- 미디어 코덱, 그래픽, 카메라 같은 연산은 C/C++이 빠름
- JNI로 연결되어 자바와 HAL 연결을 고속처리 => (Java API Framework -> JNI -> Native Library -> HAL -> Kernel)
- ART(Android RunTime)
- HAL위 레이어
- Java 애플리케이션 실행의 중추적인 역할을 하는 가상머신
- 일반적으로 자바를 통해 작성된 프로그램은 JVM에서 실행 됨 => ART는 Android를 위해 맞춰진 안드로이드용 JVM
- Java API Framework
- ART/NDK위 레이어
- 앱 개발을 위한 핵심 Java API를 제공
- 대표적으로 View System, Activity Manager, Resource Manager, Content Provider 등
- System Apps
- 최상단에 위치한 레이어
- 실제 유저들이 사용하는 앱들이 위치
- ex) 이메일, 캘린더, 카메라
Linux Kernel에서 하드웨어를 관리하고,그 위 HAL이 하드웨어 접근을 추상화합니다.
Native C/C++ 라이브러리와 ART는 앱 실행 환경을 구성하고, Java Framework가 개발자에게 API를 제공하며,
최상위에는 System App과 일반 앱이 위치합니다.
2. ART(Android RunTime)의 발전 과정과 장점
- 기본적으로 Java 코드는 바이트 코드로 변환되어 JVM에서 실행
- 왜 JVM안쓰고 따로 런타임을 사용할까?
- 라이센스 문제 : 자바가 안드로이드에서 개발하는 것이 아니기 때문에 라이센스 문제가 있음.
- 메모리 효율성 : JVM은 스택 기반 모델로 많은 메모리를 요구하지만, DVM은 레지스터 기반 모델로 적은 메모리에 최적화(JVM에 비해 명령이 단순하고 처리속도가 빠르기때문)되어 있어, 여러 개 VM 인스턴스를 실행할 수 있고, 프로세스 독립성, 메모리 관리 & 스레딩을 지원한다.
- 안드로이드용 JVM = 1. Dalvik(JIT Compiler) 2. ART(AOT Compiler)
- 초창기에는 Dalvik을 통해 JIT 컴파일 방식 사용
=> JIT 컴파일 시 배터리와 하드웨어 부하 + 앱 프로세스 전체를 RAM에 올려둬야 하는 문제 발생
이를 해결하기 위해 5.0부터 ART 등장 - ART => 앱 설치 시 한 번에 미리 컴파일 해 바이트 코드로 변환해두고 앱 실행시 읽음 => 실행 속도가 빨라짐
하지만, 설치시간이 느리고 용량 많이 씀 - 이를 해결하기 위해 7.0부터 최초 설치 시에는 JIT을 사용하고, 기기를 사용하지 않는 시간에 일부분 컴파일 해 AOT 방식으로 바꿔나감
결론적으로 ART는 JIT과 AOT 컴파일러의 장점을 합친 안드로이드 가상 머신
3. Android DEX 파일과 구성
- Dalvik EXceutable
- 자바 컴파일러를 통해 변환된 바이트 코드를 ART가 실행할 수 있게 바꾼 실행파일 포맷
- 안드로이드에서 실행되는 경량화된 바이트 코드 포맷
- 헤더와 섹션으로 구분한다.
4. DEX 파일 관련 컴파일러는?
1) DX
3.1이하 버전 Dex 변환 도구. 현재는 D8로함
2) D8
Dex 변환 도구. 바이트 코드 기반 파일을 실행 가능한 Dex 파일로 변환 ex) .class -> .dex
3) R8
Dex 변환 도구. 코드 축소 및 최적화, 난독화까지 지원. Proguard 대체
=> proguard-rules에 예외를 설정하는 등의 방법을 통해 변환 과정에서의 버그를 방지할 수 있음
5. Android의 APK 파일 구성
- DEX 파일 + Manifest + resource
- AndroidManifest.xml 파일을 통해 설정사항을 확인할 수 있음
- Resource 파일에는 각종 asset과 정적 데이터들이 포함됨
DEX, APK, R8 흐름
Java 코드
↓ javac
.class (bytecode)
↓ D8 / R8
.dex (Dalvik EXecutable)
↓ APK Packager
APK / AAB 파일 생성
6. Enum을 사용하면 안되는 이유는?
- enum은 내부적으로 클래스처럼 동작해 일반 상수보다 메모리 사용량이 더 많음
- 내부적으로 자동 생성되는 메소드가 많아 DEX 파일 생성 시, dex 메소드 수가 많아 파일 사이즈에 영향을 줌
- D8, R8에서 많이 개선되었지만, 기본 타입보다는 비효율적이라 성능 민감한 안드로이드에서는 비효율적
- 대체 방법 :
@intDef/@StringDef,Sealed Class
7. Compiler와 Build의 차이점
- 빌드 : 소스코드를 실행 가능한 산출물까지 변환하는 일련의 과정. (컴파일 => DEX 변환 => 리소스 패킹 => 서명 => APK/AAB )
- 컴파일 : 이런 빌드 과정의 일부. 소스코드(.java, .kt)를 바이트 코드(.class)로 변환하는 과정
컴파일 예시
- Java 컴파일러는 자바 코드를 바이트 코드로 변환
- D8/R8과 같은 DEX Compiler는 이런 바이트 코드를 DEX 파일로 변환하는 역햘
빌드 예시
- 실제 실행 가능한 API 파일까지 만드는 일련의 과정을 총괄하는 개념
8. 컴파일 에러와 빌드 실패는 같은 개념인가
- 다름
- 컴파일 에러는 문법이나 타입 에러 등에 의해 발생
- 빌드 실패는 종속성 오류나 리소스 충돌 등 더 다양한 원인을 포함
9. 안드로이드 최신 버전과 API레벨은
- stable로 출시된 최신 안드로이드 버전 = 15
- API 레벨은 35
- 16은 아직 베타 릴리즈 상태
10. Build gradle에서 minSDK, targetSDK, compileSDK 버전의 차이는?
- minSDK : 해당 앱을 설치할 수 있는 최소 안드로이드 API 레벨.
- targetSDK : 해당 앱을 '개발'할 때 적용할 안드로이드 API 레벨.
- compileSDK : 해당 앱을 "컴파일"하거나 "빌드"할 때 적용할 안드로이드 API 레벨
14. ANR이란?
- Application Not Responding
- 앱이 일정시간 이상 응답하지 않아 사용자가 조작할 수 없는 상태를 나타내는 오류
[발생 조건]
- UI ANR : 사용자 입력 이벤트에 5초 이내로 응답하지 못할 때
- BroadcastReceiver ANR : 브로드캐스트 리시버가 설정해둔 timeout 내에 실행 완료하지 못할 때
15. ANR 예방 방법
- UI 쓰레드가 차단되지 않도록 함
- 오래 걸리는 로직x. 오래 걸릴 것으로 예상되면 별도의 쓰레드 활용
- 브로드캐스트 리시버의 실행 시간 줄이기
- 코루틴 등 별도의 비동기 솔루션 활용
16. 안드로이드에서 Task란?
애플리케이션에서 액티비티를 관리하는 스택 구조이다. 즉, 한 앱을 사용하는 동안 액티비티의 이동 흐름을 기록하는 스택
- LIFO 형태.
- back을 눌러 상위 액티비티가 없어지면, 나중에 들어온 액티비티부터 사용됨
- 가장 먼저 들어온 액티비티를 Root Activity, 마지막에 들어온 액티비티를 Top Activity라고 함
17. 안드로이드에서 멀티 스레딩을 위해 사용되는 솔루션은 무엇이 있는가?
- 쓰레드
- UI 쓰레드 외 별도의 스레드에서 작업 수행 가능
- Thread 클래스를 열고 로직을 명시한 뒤 start로 실행
- 자바 기본 스레드, Runnable과 같이 사용, 일회성에 적합
- Looper
- 쓰레드 안에서 메시지 큐를 계속 돌면서 처리하는 무한루프
- 메인 스레드에는 루퍼가 연결되어 있지만, 별도로 만든 스레드에는 looper가 없기 때문에 필요 시 직접 만들어야함
- Handler Thread 사용 시 한번에 해결 가능
- 쓰레드 안에서 메시지 큐를 계속 돌면서 처리하는 무한루프
- Handler
- 안드로이드에서 사용할 수 있는 스레드 통신 방법. 특정 Looper와 연결되어 메시지를 보내는 역할
- 특정 Looper와 연결되어 있어야 동작 => 그냥 생성 시, 해당 스레드의 루퍼와 메시지 큐에 자동 연결
- Message나 Runnable 객체를 보내 작업을 예약하거나 전달 ex)
Handler.post(),sendMessage()
- Thread Pool
- 대기 상태의 스레드를 유지해 많은 병렬 작업을 실행할 때, 성능 향상
- 코루틴
- 코틀린 기반의 경량 스레드
- 가장 많이 사용되는 현대적 멀티 스레드 방식
- 코틀린 기반의 경량 스레드
18. Thread를 만들 때, 왜 굳이 Looper를 연결해 사용하는가
쓰레드는 한번 실행 후 사라진다. 그러나 루퍼를 이용하면 계속 살아있는 스레드가 되어 계속 이벤트를 받아 처리할 수 있게됨
- 일회성으로 작업하고 UI 변경사항 등을 적용하는 정도라면 별도의 루퍼 없이 Thread만으로 충분
- 하지만 별도의 Thread를 만들고 이 스레드를 계속해서 재활용해야한다면, Thread + Looper + Handler 구조가 유리하다
19. Thread + Looper + Handler로 기존 스레드를 재활용하는 이유와 새로운 스레드를 만드는 것과의 차이
작업마다 새 스레드를 만들면 비용이 크다
- 쓰레드마다 Stack 메모리가 별도 할당되어 낭비가 발생하고, Context Switching 비용 발생
=> OOM(Out of Memory: 메모리 부족) 초래 - 순서를 보장할 수 없음. 뒤에 있던 쓰레드가 먼저 처리될 수도 있음
Looper + Handler를 사용하면,
- 하나의 쓰레드를 계속 재활용
- 순차 처리 보장
- 메모리 안정성 증가
- 메시지를 큐에 넣어 처리 가능
20. 백그라운드 작업 솔루션에는 어떤 것이 있을까?
WorkManager, JobScheduler, AlarmManager, Foreground Service
- WorkManager
- jekpack의 공식 백그라운드 작업 처리도구. 가장 권장되는 방식
- 지연 작업, 네트워크 조건, 충전 중 등 조건 기반 작업 가능
- 앱이 꺼저도 실행, 재부팅 후에도 실행 가능
- JobScheduler
- 안드로이드 시스템 기본 스케쥴러(API 21 이상에서 사용 가능)
- WorkManager 내부가 JobScheduler 기반으로 되어 있음
- AlarmManager
- 정확한 시간이나 주기적 작업 실행을 위한 시스템 서비스
- 앱 종류나 재부팅에도 작동 가능
- Foreground Service
- 4대 컴포넌트 Service의 일종
- 알림을 띄우며 실행되는 서비스
- 앱이 켜져있을 때만 유효
- 음악 재생, 네비게이션 등
21. 백 버튼으로 앱을 종료하면, 프로세스는 종료되는가
종료되지 않을 수 있다.
- 백 버튼으로 종료하면, Root Activity를 Pop 하는 상황
- 실제로 해당 액티비티의 생명주기는
onDestory()를 호출하지만, 프로세스는 메모리에 남을 수 있음 - 이 경우 앱 다시 실행 시, 안드로이드가 프로세스를 캐싱하여 빠르게 재실행 시킬 수 있음
언제 진짜 종료될까?
- 메모리 부족해 Low Memory Killer가 프로세스를 메모리에서 제거한다(kill)
- 개발자가 직접
finishAffinity()혹은killProcess()호출