안드로이드 순정 OS가 탑재된 스마트폰을 넘어 자동차 가전(IVI), 스마트 홈 허브, 로봇 전용 단말기 등 임베디드 하드웨어를 다루다 보면 표준 안드로이드 SDK가 제공하지 않는 특수한 커스텀 기능이 간절해질 때가 있습니다. 예를 들면 공장 제어용 특수 GPIO 핀을 전역에서 제어하거나, 제조사만의 독자적인 보안 암호화 칩과 통신하는 기능 같은 것들이죠.
일반적인 앱 개발 환경에서는 권한과 샌드박스 제약 때문에 이러한 로우 레벨 제어가 불가능합니다. 결국 AOSP(Android Open Source Project) 소스 코드를 열고, 시스템의 심장부인 SystemServer 프로세스 위에 나만의 사용자 정의 프레임워크 서비스(Custom Framework Service)를 직접 설계해 심어야 합니다. 이번 포스팅에서는 커스텀 서비스를 설계할 때 거쳐야 하는 요구사항 분석부터, 통신 방식(AIDL vs 내부 인터페이스)의 손익 계산, 그리고 SystemServer 등록과 Android.bp 빌드 스크립트 수정까지 실무 환경에서 바로 쓸 수 있는 빌드 파이프라인을 자세히 다뤄보겠습니다.

📌 핵심 요약 3줄
- 사용자 정의 프레임워크 서비스는 시스템 전반에 독자적인 기능을 배포하기 위해 SystemServer 프로세스 생명 주기와 연동하여 자바 계층에 구현합니다.
- 다수의 서드파티 앱 프로세스와 안전하게 연격 통신(IPC)을 주고받아야 하는 구조라면 Java Interface가 아닌 AIDL 기반의 바인더 통신 설계가 필수입니다.
- 서비스 구현 후 SystemService 구조를 통해 SystemServer.java에 정식 등록하고, Android.bp 컴파일 소스 트리에 추가해야 최종 이미지에 병합됩니다.
1. 서비스 설계 핵심: AIDL 방식 vs Java Interface 방식 비교
새로운 시스템 서비스를 설계할 때 가장 먼저 결정해야 하는 것은 "누가 이 서비스를 호출할 것인가?"입니다. 통신 범위에 따라 설계 아키텍처가 완전히 달라집니다.
| 비교 항목 | AIDL 인터페이스 방식 (추천) | 일반 Java 인터페이스 방식 |
| 통신 스코프 | IPC (Inter-Process Communication) | IPC 불가능 (동일 프로세스 한정) |
| 주요 타겟 클라이언트 | 일반 서드파티 앱, 시스템 UI, 외부 프로세스 전반 | system_server 내부에서 도는 타 시스템 서비스들 |
| 구현 메커니즘 | 리눅스 바인더(Binder) 드라이버 경유 | 단순 자바 객체 참조 및 다이렉트 메서드 호출 |
| 아키텍처 장점 | 프로세스 간 경계를 허물어 전역 API 형태로 배포 가능 | 바인더 마샬링 오버헤드가 없어 연산 속도가 극도로 빠름 |
| 주요 용도 | 커스텀 전원 제어, 커스텀 가속 데몬 API 제공 | 시스템 서버 내부용 헬퍼 모듈, 데이터 베이스 매니저 |
2. Custom Framework Service 설계 및 구현 3단계
가장 범용적이고 실무에서 자주 쓰이는 AIDL 기반 전역 프레임워크 서비스를 기준으로 구현 단계를 쪼개어 보겠습니다.
2.1 1단계: 바인더 통신 통로 개설 (IMyCustomService.aidl)
먼저 앱 영역과 프레임워크 서비스 영역이 서로 알아들을 수 있는 바인더 인터페이스 규칙을 정의합니다.
// frameworks/base/core/java/android/os/IMyCustomService.aidl
package android.os;
/**
* 외부 애플리케이션이 바인더를 통해 원격 호출할 메서드 명세서
*/
interface IMyCustomService {
void triggerCustomHardware();
int getHardwareStatus();
}
2.2 2단계: 비즈니스 로직 및 시스템 서비스 클래스 구현
AIDL로 생성된 Stub을 구체화하는 실질적인 기능 구현 단계입니다. 안드로이드 프레임워크 표준 규격에 맞추어 SystemService 클래스를 뼈대로 삼아 작성합니다.
// frameworks/base/services/core/java/com/android/server/MyCustomService.java
package com.android.server;
import android.content.Context;
import android.os.IMyCustomService;
import android.os.RemoteException;
import com.android.server.SystemService;
public class MyCustomService extends SystemService {
private final Context mContext;
public MyCustomService(Context context) {
super(context);
mContext = context;
}
@Override
public void onStart() {
// SystemServiceManager에 의해 서비스가 실행될 때 바인더 엔드포인트를 전역에 퍼블리싱합니다.
publishBinderService("my_custom_service", new BinderImpl());
}
// AIDL Stub을 구현한 실질적인 바인더 통신 처리부
private final class BinderImpl extends IMyCustomService.Stub {
@Override
public void triggerCustomHardware() throws RemoteException {
// 커스텀 로우 레벨 제어 로직 기술
}
@Override
public int getHardwareStatus() throws RemoteException {
return 1; // 정상 상태 리턴
}
}
}
2.3 3단계: SystemServer에 서비스 안착시키기
안드로이드 자바 스택의 모태가 되는 SystemServer.java 소스를 수정하여 시스템 부팅 시 시퀀스 스레드에 내 서비스를 태웁니다.
// frameworks/base/services/java/com/android/server/SystemServer.java
import com.android.server.MyCustomService;
public class SystemServer {
private void startOtherServices() {
// 부트스트랩 및 코어 서비스가 안정화된 후 구동되는 기타 서비스 영역에 배치합니다.
t.traceBegin("StartMyCustomService");
try {
// SystemServiceManager를 통해 서비스를 실행하면 내부 onStart()가 트리거됩니다.
mSystemServiceManager.startService(MyCustomService.class);
} catch (Throwable e) {
reportWtf("Starting MyCustomService failed", e);
}
t.traceEnd();
}
}
3. AOSP 빌드를 위한 Android.bp 환경 구성
코드를 모두 작성했다면 안드로이드 빌드 시스템인 Soong에게 컴파일 대상을 알려주어야 합니다. 프레임워크 서비스 코드는 대개 services.core 내부에 병합하는 것이 정석입니다.
3.1 AIDL 컴파일 타겟 등록
우선 내가 만든 .aidl 파일이 빌드 시스템에 인식되도록 프레임워크 코어 빌드 스크립트에 경로를 추가해 줍니다.
// frameworks/base/Android.bp (기존 파일 내부 srcs 그룹에 추가)
filegroup {
name: "framework-core-sources",
srcs: [
// 기존 소스 리스트들 사이에 내 커스텀 aidl 경로를 명시합니다.
"core/java/android/os/IMyCustomService.aidl",
],
}
3.2 서비스 소스 컴파일 등록
실제 자바 코드가 컴파일되도록 서비스 코어 빌드 그룹에 추가해 줍니다.
// frameworks/base/services/core/Android.bp
java_library_static {
name: "services.core",
srcs: [
// 기존 서비스 자바 파일 경로 그룹 내부
"java/com/android/server/MyCustomService.java",
],
}
3.3 전적 빌드 가동
환경 설정 스크립트를 로드하고 전체 컴파일을 가동하여 시스템 이미지를 뽑아냅니다.
$ source build/envsetup.sh
$ lunch aosp_x86_64-userdebug
$ m services.core framework
🛠️ 개발을 위한 팁 (Tips)
- publishBinderService 패턴을 무조건 활용하기: 과거 레거시 안드로이드 코드에서는 ServiceManager.addService("이름", new 서비스())를 직접 호출하는 방식을 썼지만, 구글은 최신 AOSP에서 SystemService 구조를 상속받아 publishBinderService() 인터페이스를 쓰는 것을 강력히 권장하고 있습니다. 이 방식을 쓰면 시스템 서버 내부의 라이프사이클 이벤트를 똑같이 공유받아(onBootPhase() 등), 다른 핵심 시스템 서비스들이 완벽히 부팅되었는지 타이밍을 안전하게 체크한 뒤 내 서비스의 실질적인 하드웨어 제어를 가동할 수 있습니다.
- AIDL 데이터 전송 시 Parcelable 규격 준수: 내 커스텀 서비스의 메서드 매개변수로 단순 기본형(int, String)이 아니라 커스텀 자바 객체(클래스)를 프로세스 간 주고받고 싶다면, 해당 클래스는 반드시 android.os.Parcelable 인터페이스를 구현해야 합니다. 바인더는 메모리 주소를 넘기는 게 아니라 데이터를 바이트 스트림으로 잘게 쪼개어 직렬화(Serialization)한 뒤 상대방 프로세스에 던지기 때문입니다.
⚠️ 흔히 하는 실수 (Common Mistakes)
- publishBinderService와 ServiceManager.addService 중복 호출: 처음 플랫폼 서비스를 만드는 엔지니어들이 가장 많이 하는 실수입니다. MyCustomService 내부에서 publishBinderService("my_service", ...)를 실행해 놓고, 기연가민가해서 SystemServer.java 소스에 또다시 ServiceManager.addService("my_service", ...) 코드를 직접 수동으로 타이핑해 넣는 경우가 있습니다. 이렇게 되면 부팅 시점에 바인더 유일 이름 이름(Unique Name) 충돌이 발생해 SystemServer가 예외를 뿜으며 크래시가 나고, 단말기가 무한 부팅 링크(Bootloop)에 빠지게 되니 한 가지 방식만 선택해야 합니다.
- SELinux 매핑 누락으로 인한 자바 NullPointerException: 소스 컴파일도 완벽히 끝났고 이미지 플래싱도 잘 되었는데, 막상 앱에서 내 서비스를 호출하려고 ServiceManager.getService("my_custom_service")를 치면 null이 반환되는 경우가 허다합니다. 이는 안드로이드 강제 보안 정책인 SELinux에서 내 서비스 이름을 미등록 서비스로 간주해 바인더 연결을 강제로 드롭(Drop)시켰기 때문입니다. device/제조사/보드/sepolicy/ 혹은 system/sepolicy/private/service_contexts 파일에 반드시 my_custom_service u:object_r:my_custom_service:s0 형태로 보안 문맥을 선언해 주어야 정상 구동됩니다.
4. 결론
안드로이드 AOSP 아키텍처에 사용자 정의 프레임워크 서비스를 온전히 이식하는 것은 안드로이드 운영체제의 커널 유저 공간과 자바 프레임워크의 브릿지를 완벽히 연결하는 플랫폼 엔지니어링의 정수입니다.
단순히 소스 코드를 추가하는 작업에 그치지 않고, 시스템 전체의 라이프사이클 의존성을 고려해 적절한 컴파일 타겟팅(Android.bp)을 지정하고 보안 정책(SELinux)까지 올바르게 세팅해 주어야 비로소 디바이스 전역을 지배하는 강력한 커스텀 API가 완성됩니다. 소스 트리 수정 도중 컴파일 의존성 오류가 발생하거나 빌드된 이미지를 올렸는데 디바이스가 로고에서 넘어가지 않는 현상이 있다면 주저하지 말고 에러 로그나 덤프를 댓글로 남겨주세요. 같이 원인을 분석해 나가겠습니다!
'Android System & AOSP Engineering > AOSP Framework & Custom Services' 카테고리의 다른 글
| AOSP 빌드 및 디버깅 가이드: Custom Framework Service 플래싱부터 dumpsys 검증까지 (0) | 2025.05.27 |
|---|---|
| Android AOSP 가이드: AIDL 생성부터 Context.getSystemService() 등록까지 전 과정 (0) | 2025.05.26 |
| AOSP 소스 분석: SystemServer의 서비스 초기화 과정과 3대 핵심 서비스 구조 (0) | 2025.05.23 |
| Android Framework Service 구현 가이드: AIDL부터 Binder IPC 등록까지 (0) | 2025.05.22 |
| Android AOSP 아키텍처 이해: Framework Service vs System Service 차이점 (0) | 2025.05.20 |