안녕하세요! 개발하는 머리입니다. 안드로이드 탑재 장비를 개발하거나 하드웨어 제어용 프레임워크를 설계하다 보면, 구글이 기본으로 제공하는 API 외에 우리 제품만을 위한 새로운 기능이 필요할 때가 많습니다. 예를 들면 특정 하드웨어 드라이버와 직접 통신하거나, 프레임워크 레벨에서 전역적으로 상태를 관리해야 하는 경우죠.
이럴 때 활용할 수 있는 가장 정석적인 방법이 바로 AOSP(Android Open Source Project)에 커스텀 시스템 서비스(System Service)를 추가하는 것입니다. 이번 포스팅에서는 AIDL 정의부터 SystemServer 등록, 그리고 앱 레이어에서 접근할 수 있는 Manager 클래스 구현까지 전체적인 프레임워크 확장 프로세스를 차근차근 살펴보겠습니다.

📌 핵심 요약 3줄
- 안드로이드 시스템 서비스는 SystemServer 프로세스 위에서 동작하며, 프로세스 간 통신(IPC)을 위해 AIDL 인터페이스 정의가 선행되어야 합니다.
- 구현된 서비스는 SystemServer.java에 등록하여 부팅 시점에 구동되도록 설정하고, Android 10 이상 버전부터는 Android.bp 등 빌드 시스템 파일에도 AIDL을 명시해야 합니다.
- 앱 레이어에서 서비스에 쉽게 접근할 수 있도록 CustomManager를 구현하고, 프레임워크의 Context 구조에 바인딩해 주는 것이 정석적인 구조입니다.
1. 안드로이드 시스템 서비스 아키텍처 개요
새로운 서비스를 만들기 전에 안드로이드 프레임워크가 시스템 서비스를 관리하는 구조를 표로 먼저 정리해 보겠습니다. 안드로이드는 커널 공간 위에 자바 프레임워크 레이어가 얹혀 있는 구조라, 아래와 같은 역할 분담이 확실하게 이루어집니다.
| 구성 요소 | 주요 역할 | 위치 (AOSP 소스 트리 기반) |
| AIDL (.aidl) | 클라이언트(앱)와 서비스 간의 바인더(Binder) IPC 인터페이스 정의 | frameworks/base/core/java/ 또는 별도 AIDL 경로 |
| SystemService | 백그라운드에서 실제 비즈니스 로직을 수행하는 본체 (Stub 구현체) | frameworks/base/services/core/java/com/android/server/ |
| SystemServer | 부팅 시점에 등록된 모든 시스템 서비스(AMS, WMS 등)를 초기화하고 실행 | frameworks/base/services/java/com/android/server/SystemServer.java |
| Manager API | 앱 개발자가 context.getSystemService() 형태로 서비스를 편하게 쓰도록 감싸주는 래퍼(Wrapper) | frameworks/base/core/java/android/os/ 또는 android/app/ |
2. AIDL을 이용한 시스템 서비스 구현 단계
2.1 AIDL 인터페이스 정의
가장 먼저 프로세스 간 통신을 위한 규격을 정의해야 합니다. frameworks/base/core/java/android/os/ICustomService.aidl 경로에 파일을 생성합니다.
// ICustomService.aidl
package android.os;
/** @hide */
interface ICustomService {
void customMethod();
}
Tip: 내부 시스템용 API이므로 다른 앱에 무분별하게 노출되지 않도록 자바독에 /** @hide */ 태그를 추가해 주는 것이 AOSP의 관례입니다. 또한, 최신 AOSP 버전에서는 해당 AIDL 파일 인터페이스를 frameworks/base/Android.bp 파일의 srcs 항목에 추가해 주어야 빌드 시스템이 인식합니다.
2.2 시스템 서비스 클래스 구현
이제 AIDL이 만들어준 Stub 클래스를 상속받아 진짜 기능을 수행할 서비스 본체를 작성합니다. frameworks/base/services/core/java/com/android/server/CustomService.java 파일을 생성합니다.
package com.android.server;
import android.os.ICustomService;
import android.os.RemoteException;
import android.util.Slog;
public class CustomService extends ICustomService.Stub {
private static final String TAG = "CustomService";
public CustomService() {
Slog.i(TAG, "CustomService가 성공적으로 생성되었습니다.");
}
@Override
public void customMethod() throws RemoteException {
// 하드웨어 제어나 시스템 전역 로직이 실행되는 구간입니다.
Slog.i(TAG, "customMethod() 시스템 서비스 함수가 호출되었습니다.");
}
}
2.3 SystemServer에 서비스 등록
안드로이드가 켜질 때 이 서비스가 자동으로 메모리에 올라가도록 SystemServer.java에 등록해 주어야 합니다.
파일 위치: frameworks/base/services/java/com/android/server/SystemServer.java
import com.android.server.CustomService;
import android.os.ServiceManager;
...
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
// ... 기존 코드들 ...
t.traceBegin("StartCustomService");
Slog.i(TAG, "CustomService 시작 중...");
try {
// "custom_service"라는 고유 이름으로 바인더 컨텍스트에 등록합니다.
ServiceManager.addService("custom_service", new CustomService());
} catch (Exception e) {
Slog.e(TAG, "CustomService 등록 실패", e);
}
t.traceEnd();
// ... 기존 코드들 ...
}
3. 클라이언트용 System API (Manager) 추가 방법
이제 일반 앱 레이어나 다른 시스템 컴포넌트에서 이 서비스를 편하게 가져다 쓸 수 있도록 래퍼 클래스인 CustomManager를 만들어 줍니다.
파일 위치: frameworks/base/core/java/android/os/CustomManager.java
package android.os;
import android.content.Context;
import android.util.Log;
public class CustomManager {
private static final String TAG = "CustomManager";
private final ICustomService mService;
private final Context mContext;
public CustomManager(Context context, ICustomService service) {
mContext = context;
mService = service;
}
public void customMethod() {
try {
if (mService != null) {
mService.customMethod();
} else {
Log.w(TAG, "CustomService 인스턴스가 존재하지 않습니다.");
}
} catch (RemoteException e) {
Log.e(TAG, "customMethod 호출 중 바인더 예외 발생", e);
}
}
}
정석적인 시스템 등록 팁: 원래 안드로이드 환경에서는 CustomManager를 직접 new로 생성하기보다, Context.getSystemService()를 통해 싱글톤으로 가져오는 것이 올바른 구조입니다. 이를 위해 frameworks/base/core/java/android/app/SystemServiceRegistry.java 파일에 아래와 같이 매니저 등록 코드를 추가해 줍니다.
JavaregisterService(Context.CUSTOM_SERVICE, CustomManager.class, new CachedServiceFetcher<CustomManager>() { @Override public CustomManager createService(ContextImpl ctx) throws ServiceNotFoundException { IBinder b = ServiceManager.getServiceOrThrow(Context.CUSTOM_SERVICE); ICustomService service = ICustomService.Stub.asInterface(b); return new CustomManager(ctx, service); } });
4. 클라이언트(App)에서 서비스 활용 및 빌드
시스템에 등록이 완료되었다면 일반 앱 코드에서는 다음과 같이 아주 직관적으로 서비스를 호출해 사용할 수 있게 됩니다.
// SystemServiceRegistry에 등록한 정석적인 호출 방법
CustomManager customManager = (CustomManager) context.getSystemService("custom_service");
if (customManager != null) {
customManager.customMethod();
}
이제 변경된 프레임워크 소스코드를 전체적으로 빌드하고 타겟 디바이스에 반영합니다.
source build/envsetup.sh
lunch aosp_arm-eng # 개발하시는 타겟 보드 또는 에뮬레이터 환경에 맞게 선택
make -j8
빌드가 끝나고 이미지를 플래싱한 뒤, 터미널에서 바인더 서비스 목록을 조회해 보면 우리가 추가한 서비스가 정상 등록된 것을 확인할 수 있습니다.
adb shell service list | grep custom_service
[출력 결과]
custom_service: [android.os.ICustomService]
🛠️ 개발을 위한 팁 (Tips)
- SELinux 정책(Policy) 업데이트 필수: 안드로이드 최신 버전은 보안이 매우 엄격합니다. 코드 상으로 완벽하게 시스템 서비스를 추가했더라도, SELinux 정책(system/sepolicy/)에 서비스명을 등록(type custom_service, service_manager_type;)해 주지 않으면 Permission Denied 에러와 함께 바인더 접근이 차단되니 주의하세요.
- update-api 명령어 실행: frameworks/base 하위의 자바 코드나 AIDL을 수정하면 공개 API 변경 사항을 체크하는 빌드 시스템 때문에 빌드가 멈출 수 있습니다. 이럴 때는 make update-api 명령어를 먼저 수행하여 자바 API 시그니처 텍스트 파일들을 갱신해 주어야 정상적으로 전체 빌드가 진행됩니다.
⚠️ 흔히 하는 실수 (Common Mistakes)
- Android.bp 빌드 스크립트 누락: 최신 AOSP 프레임워크 환경에서는 소스 파일만 던져두면 빌드 대상에서 제외됩니다. 새로 만든 .java 파일과 .aidl 파일이 속한 디렉토리의 Android.bp에 소스가 제대로 명시되어 있는지 반드시 더블 체크해야 합니다.
- 메인 스레드 블로킹: 시스템 서비스의 메서드는 기본적으로 바인더 스레드 풀(Binder thread pool)에서 비동기적으로 실행되지만, 만약 서비스 내부에서 시간이 오래 걸리는 I/O 작업이나 블로킹 연산을 수행하면 SystemServer 전체가 멈춰 watchdog 하드 리셋이 걸릴 수 있습니다. 무거운 작업은 반드시 별도의 핸들러 스레드(HandlerThread)를 파서 처리하세요.
5. 결론
이번 포스팅에서는 안드로이드 AOSP 프레임워크 개발의 핵심이라고 할 수 있는 커스텀 시스템 서비스 추가 과정을 알아봤습니다. 전체적인 흐름이 규격화되어 있어서 한 두 번만 직접 빌드하고 디버깅해 보면 금방 익숙해질 수 있는 영역입니다.
단순히 앱 개발을 넘어 안드로이드 플랫폼 자체를 커스텀하고 제어하는 임베디드 영역으로 나아가기 위한 첫 단추이니, 꼭 직접 코드를 넣고 에뮬레이터나 개발 보드에서 adb shell service list 결과를 확인해 보시기 바랍니다. 개발하다가 막히는 에러 로그가 있다면 언제든 댓글로 남겨주세요!
'Android System & AOSP Engineering > AOSP Framework & Custom Services' 카테고리의 다른 글
| Android IPC 기법 총정리: Broadcast부터 AIDL, AOSP 활용까지 (0) | 2025.04.15 |
|---|---|
| Android 프레임워크 동작 원리: 계층 구조부터 Context와 Binder IPC까지 (0) | 2025.04.14 |
| 안드로이드 바인더 구조 분석: Binder IPC 작동 원리와 1-Copy 메모리 매핑 메커니즘 (0) | 2025.04.12 |
| 안드로이드 PMS 구조 분석: PowerManagerService의 WakeLock 제어와 배터리 도즈 모드 메커니즘 (0) | 2025.04.11 |
| 안드로이드 PMS 구조 분석: PackageManagerService의 APK 설치 메커니즘과 AOSP 소스코드 해부 (0) | 2025.04.10 |