순정 안드로이드 OS가 탑재된 레퍼런스 기기를 커스텀 보드나 특정 키오스크, 혹은 자동차 인포테인먼트(IVI) 환경에 맞추다 보면 OS 고유의 화면이나 핵심 기능을 뜯어고쳐야 하는 순간이 옵니다. 상단 상태 표시줄에 우리 장비만의 특수한 아이콘을 박아 넣거나, 앱 레이어에서 시스템 하드웨어를 제어할 수 있는 전용 API 통로를 열어주어야 하기 때문이죠.
이러한 작업을 하려면 안드로이드 소스 코드의 가장 핵심 영역인 frameworks/base 레이어를 직접 수정해야 합니다. 이번 포스팅에서는 안드로이드 14 소스를 바탕으로 상단 바를 담당하는 SystemUI를 커스텀하는 방법부터, 시스템 전체가 공유하는 '나만의 커스텀 시스템 서비스'를 프레임워크에 직접 심고 빌드하는 정석적인 과정을 공유해 보겠습니다.

📌 핵심 요약 3줄
- SystemUI 커스텀은 frameworks/base/packages/SystemUI/ 경로에서 진행되며, 상단 상태 표시줄이나 알림창 같은 핵심 컴포넌트를 제어합니다.
- 나만의 시스템 서비스를 프레임워크에 추가하려면 AIDL 정의 후 SystemServer.java에 서비스를 등록하여 부팅 시점에 구동시켜야 합니다.
- 변경된 소스는 부분 빌드(make SystemUI 등) 후 adb sync 또는 리플래싱을 통해 타겟 기기에 실시간으로 반영하며 디버깅합니다.
1. AOSP 핵심 레이어 구조 이해하기
소스를 무작정 수정하기 전에, 우리가 건드릴 코드들이 프레임워크 아키텍처 내부에서 각각 어떤 역할을 담당하고 서로 어떻게 연결되는지 표를 통해 직관적으로 정리해 보겠습니다.
| 컴포넌트 명칭 | 소스 코드 주요 위치 | 구동 프로세스 및 특징 | 수정 목적 예시 |
| SystemUI | frameworks/base/packages/SystemUI/ | com.android.systemui (단독 자바 애플리케이션으로 구동) |
상단 상태 표시줄 아이콘 추가, 네비게이션 바 형태 변경, 알림창 UI 커스텀 |
| System Server | frameworks/base/services/ | system_server (OS 부팅 시 Zygote가 직접 실행) |
윈도우 매니저, 배터리, 패키지 관리 등 핵심 시스템 서비스의 총괄 및 등록 |
| Framework API | frameworks/base/core/java/ | 다양한 시스템 서비스의 클라이언트 단 인터페이스 및 자바 스텁 생성 | 일반 앱 레이어에 개방할 커스텀 자바 API 인터페이스(AIDL 포함) 정의 |
2. AOSP 환경 구축 및 빌드 기본기
소스를 수정하기 위해 기본적으로 안드로이드 14 환경을 준비하고 브랜치를 동기화합니다.
# 1. 디렉토리 생성 및 안드로이드 14 레퍼런스 소스 초기화
mkdir aosp && cd aosp
repo init -u https://android.googlesource.com/platform/manifest -b android-14.0.0_r1
repo sync -j$(nproc)
# 2. 풀 빌드 환경 초기화 (최초 1회 컴파일 필요)
source build/envsetup.sh
lunch aosp_arm64-userdebug
make -j$(nproc)
3. 커스텀 기능 추가 및 SystemUI 수정
SystemUI는 OS 화면의 얼굴과 같습니다. 상단 상태 표시줄(Status Bar)에 커스텀 아이콘 엔진을 부착하는 시나리오를 예시로 들어보겠습니다.
3.1 StatusBar.java 컴포넌트 수정
파일 위치: frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
(주의: 안드로이드 13/14 이후 버전에서는 과거 StatusBar.java 구조의 많은 핵심 로직들이 CentralSurfaces 레이어로 이관되었습니다.)
@Override
public void start() {
// 기존 시스템 UI 컴포넌트 초기화 로직 수행
super.start();
// 제조사 전용 커스텀 아이콘 매니저 구동 로직 주입
Log.d("SystemUI_Custom", "커스텀 아이콘 매니저를 시작합니다.");
try {
IconManager.addCustomIcon();
} catch (Exception e) {
Log.e("SystemUI_Custom", "아이콘 주입 실패", e);
}
}
3.2 SystemUI 독립 빌드 및 반영
전체 소스를 다시 빌드하면 시간이 오래 걸리므로 SystemUI 모듈만 타겟팅하여 빌드한 뒤 adb를 이용해 기기에 밀어 넣습니다.
# SystemUI 패키지만 타겟 빌드
make SystemUI -j$(nproc)
# 쓰기 권한 확보 및 빌드된 APK 강제 주입
adb root
adb remount
adb push out/target/product/aosp_arm64/system/system_ext/priv-app/SystemUI/SystemUI.apk /system/system_ext/priv-app/SystemUI/
# 프로세스 즉시 반영을 위한 재부팅
adb reboot
4. Framework 레이어 확장: 나만의 시스템 서비스 추가
이번에는 앱 개발자들이 context.getSystemService() 형태로 호출해서 사용할 수 있는 커스텀 시스템 서비스를 OS 뼈대에 심어보겠습니다.
4.1 CustomService.java 구현
파일 위치: frameworks/base/services/core/java/com/android/server/custom/CustomService.java
package com.android.server.custom;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
// AIDL 스텁을 상속받아 실제 동작할 원격 시스템 서비스 본체를 구현합니다.
public class CustomService extends ICustomService.Stub {
private static final String TAG = "CustomService_Framework";
private final Context mContext;
public CustomService(Context context) {
mContext = context;
Log.i(TAG, "나만의 커스텀 서비스가 백그라운드에 성공적으로 로드되었습니다.");
}
@Override
public String getCustomData() throws RemoteException {
// 커스텀 보드의 센서 값이나 시스템 상태 문자열을 반환하는 구간
return "안형 프레임워크로부터 보내는 메시지입니다.";
}
}
4.2 SystemServer에 서비스 등록
부팅 시점에 구동되는 마스터 프로세스인 SystemServer에 우리 서비스를 명시적으로 등록해 주어야 자바 프레임워크가 서비스의 존재를 인지합니다.
파일 위치: frameworks/base/services/java/com/android/server/SystemServer.java
// startOtherServices() 메서드 내부 적절한 위치에 추가
try {
Slog.i("SystemServer", "커스텀 서비스(CustomService)를 시스템 매니저에 바인딩합니다.");
CustomService customService = new CustomService(mSystemContext);
ServiceManager.addService("custom_service", customService);
} catch (Throwable e) {
Slog.e("SystemServer", "커스텀 서비스 부팅 등록 실패", e);
}
4.3 Framework 레이어 빌드 및 적용
# 프레임워크 코어 자바 라이브러리 빌드
make framework -j$(nproc)
# 타겟 기기 리마운트 후 jar 파일 동기화
adb root && adb remount
adb push out/target/product/aosp_arm64/system/framework/framework.jar /system/framework/
adb reboot
🛠️ 개발을 위한 팁 (Tips)
- adb shell service list 명령어로 등록 확인: 커스텀 서비스를 프레임워크에 추가하고 기기를 재부팅했다면, 서비스가 정상적으로 살아 움직이는지 먼저 검증해야 합니다. 터미널에 adb shell service list | grep custom을 입력해 보세요. 우리가 SystemServer.java에 지정한 "custom_service"라는 이름과 함께 바인더 인터페이스 주소가 이쁘게 출력된다면 백그라운드 등록에 성공한 것입니다.
- framework-res.apk와 연동: 만약 SystemUI나 커스텀 서비스 내부에서 새로운 이미지 리소스나 문자열(String)을 사용해야 한다면, 소스 내부 폴더에 직접 넣는 것보다 frameworks/base/core/res/res/ 하위에 공통 리소스로 등록해 빌드하는 것이 프레임워크 내 다른 컴포넌트들과 리소스를 공유할 때 훨씬 깔끔합니다.
⚠️ 흔히 하는 실수 (Common Mistakes)
- SystemUI 단독 push 후 무한 부팅(Bootloop): 빌드된 SystemUI.apk 파일만 adb push로 기기에 밀어 넣었을 때, 간혹 간헐적인 크래시와 함께 기기가 벽돌이 되거나 무한 재부팅에 빠지는 경우가 있습니다. 안드로이드 최신 버전은 SystemUI와 프레임워크의 의존성이 강력해서 생기는 문제입니다. 가급적 모듈 단독 주입보다는 변경 사항이 생겼을 때 make 후 adb sync 명령어를 사용해 연관된 오디엑스(odex), vdex 파일까지 세트로 동기화해 주는 것이 안전합니다.
- AIDL 인터페이스 수정 후 update-api 누락: 커스텀 서비스를 위해 .aidl 인터페이스 파일을 새로 만들거나 기존 메서드 파라미터를 수정하면 컴파일 에러가 발생합니다. 구글 빌드 시스템은 공공 API 스펙의 변동을 감시하기 때문인데요, 프레임워크 단에서 인터페이스 명세가 바뀌었다면 자바 코드를 빌드하기 전에 반드시 make update-api 명령어를 수행하여 텍스트 시그니처 파일들을 동기화해 주어야 에러 없이 컴파일이 통과됩니다.
5. 마무리
안드로이드의 핵심 심장부인 frameworks/base 레이어와 SystemUI를 직접 제어하는 것은 단순 앱 개발을 넘어 안드로이드 플랫폼 엔지니어로 도약하기 위한 필수 코스입니다.
자바 서비스 레이어 아래에서 일어나는 바인더(Binder) IPC 메커니즘과 시스템 서버의 생명 주기를 깊이 이해할수록 더욱 견고하고 최적화된 커스텀 OS를 빌드할 수 있습니다. 오늘 공유해 드린 내용이 커스텀 프레임워크 개발의 뼈대를 잡는 데 도움이 되었기를 바라며, 구현 도중 발생하는 SELinux 권한 에러나 빌드 크래시 로그가 있다면 언제든 댓글로 남겨주세요. 함께 고민해 보겠습니다!
'Android System & AOSP Engineering > AOSP Framework & Custom Services' 카테고리의 다른 글
| Android 디버깅 가이드: Logcat, dumpsys, strace, systrace 실무 활용법 (0) | 2025.04.21 |
|---|---|
| Android 디바이스 포팅 가이드: BoardConfig 설정과 Device Tree 커널 구성 (0) | 2025.04.20 |
| AOSP 빌드 및 플래싱 총정리: 환경 설정부터 우분투 가이드까지 (0) | 2025.04.17 |
| Android System API 사용법과 Hidden API 접근 우회 및 커스텀 가이드 (0) | 2025.04.16 |
| Android IPC 기법 총정리: Broadcast부터 AIDL, AOSP 활용까지 (0) | 2025.04.15 |