안드로이드 OS 소스를 수정해 시스템 레벨에 나만의 기능을 심으려고 할 때, 무턱대고 코드부터 짜기 시작하면 백전백패하기 쉽습니다. 내가 만들 서비스가 단말기 전체 프로세스를 대상으로 통신해야 하는지, 아니면 시스템 서버 내부에서만 조용히 돌아가는 독립 컴포넌트인지에 따라 아키텍처 설계 방향성이 완전히 달라지기 때문입니다.
특히 시스템 서비스를 설계할 때 가장 먼저 통과해야 하는 관문이 바로 'IPC(프로세스 간 통신)의 필요성 판단'과 '멀티스레드 환경에서의 자원 동기화'입니다. SystemServer라는 초특권 영역에 상주하는 코드는 수많은 애플리케이션의 요청을 동시에 받아내야 하므로, 사소한 설계 오류 하나가 단말기 전체의 버그나 먹통 현상으로 이어지기 십상이죠. 오늘은 커스텀 시스템 서비스 설계 시 꼭 따져봐야 할 IPC 인터페이스 결정 조건부터, 안전하게 데이터를 주고받는 런타임 바인딩 공정까지 완벽하게 정리해 보겠습니다.

📌 핵심 요약 3줄
- 타 프로세스나 서드파티 앱으로 서비스를 노출해야 한다면 AIDL 기반의 바인더 IPC가 필수적이며, 시스템 내부 통신용이라면 가벼운 로컬 싱글톤 구조가 유리합니다.
- 바인더 스레드 풀은 멀티스레드로 동작하므로, 시스템 서비스 전역 변수나 컬렉션을 다룰 때는 반드시 CopyOnWriteArrayList나 동기화 락을 적용해야 합니다.
- AIDL을 통해 주고받는 컬렉션 데이터는 메모리 주소 참조가 아닌 프로세스 간 '직렬화 복사(Deep Copy)' 방식으로 전송됨을 인지하고 설계해야 합니다.
1. 서비스 통신 아키텍처 선택 가이드: AIDL IPC vs 로컬 인터페이스
커스텀 시스템 서비스를 설계할 때 비즈니스 요구사항에 따라 어떤 통신 메커니즘을 채택해야 하는지 명확히 보여주는 설계 대조표입니다.
| 아키텍처 항목 | AIDL 기반 바인더 IPC 서비스 | 로컬 자바 인터페이스 서비스 |
| 통신 범위 제한 | 시스템 전역 (다른 프로세스, 외부 앱 접근 가능) | SystemServer 프로세스 내부(동일 공간 컴포넌트끼리만 가능) |
| 인터페이스 정의 | .aidl 파일 명세 작성 (자동 Stub 빌드) | 일반 순정 Java interface 선언 |
| 데이터 전송 방식 | 커널 바인더를 통한 마샬링 및 직렬화 복사 | JVM 힙 메모리 상의 객체 주소(참조) 직접 공유 |
| 성능 오버헤드 | 바인더 드라이버 컨텍스트 스위칭 비용 발생 (상대적으로 무겁고 제한적임) | 단순 메모리 호출로 오버헤드 제로 (지극히 가볍고 빠름) |
| 대표적인 유스케이스 | 배터리 제어 관리, 와이파이 상태 변경, 센서 허브 연동 | 시스템 서버 내부의 타 핵심 서비스 모니터링, 내부 라이프사이클 중계 |
2. AIDL을 적용한 스레드 세이프(Thread-Safe) 커스텀 서비스 구현
여러 앱이 비동기적으로 내 서비스를 노출하고 로그를 기록할 수 있도록, 멀티스레드 경합을 완벽히 차단한 커스텀 로그 시스템 서비스 예제입니다.
2.1 인터페이스 명세서 작성
// frameworks/base/core/java/com/example/customservice/ICustomLogService.aidl
package com.example.customservice;
import java.util.List;
interface ICustomLogService {
void logMessage(String message);
List<String> getLogs();
}
2.2 Thread-Safe 처리가 완료된 코어 시스템 서비스 클래스
// frameworks/base/services/core/java/com/android/server/logging/CustomLogService.java
package com.android.server.logging;
import android.content.Context;
import com.example.customservice.ICustomLogService;
import com.android.server.SystemService;
import android.util.Slog;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; // 멀티스레드 안전 동기화 컬렉션
public class CustomLogService extends SystemService {
private static final String TAG = "CustomLogServiceCore";
// [설계 혁신] 일반 ArrayList를 쓰면 여러 바인더 스레드가 동시 접근할 때 크래시가 납니다.
// 쓰기 작업 시 복사본을 만들어 스레드 안정성을 보장하는 CopyOnWriteArrayList를 채택합니다.
private final List<String> mLogs = new CopyOnWriteArrayList<>();
public CustomLogService(Context context) {
super(context);
}
@Override
public void onStart() {
Slog.i(TAG, "전역 커스텀 로그 수집 서비스 초기화 가동");
// ServiceManager 허브에 내 바인더 인프라 등록 완료
publishBinderService("custom_log_service", mBinderImpl);
}
// AIDL 인터페이스 실체화 및 멀티스레드 세이프 비즈니스 로직 맵핑
private final ICustomLogService.Stub mBinderImpl = new ICustomLogService.Stub() {
@Override
public void logMessage(String message) {
if (message != null) {
mLogs.add(message);
Slog.d(TAG, "새로운 전역 시스템 로그 적재 성공: " + message);
}
}
@Override
public List<String> getLogs() {
// AIDL 규격에 의해 이 리스트는 통째로 직렬화(Parcel)되어 앱 프로세스로 복사 전송됩니다.
return mLogs;
}
};
}
3. 클라이언트 앱에서의 런타임 서비스 바인딩 및 가동
시스템 서비스 적재가 완료되었다면 상위 레이어의 컨텍스트 앱 영역에서 바인더 주소를 인출하여 원격 기능을 호출합니다.
import android.os.IBinder;
import android.os.ServiceManager;
import com.example.customservice.ICustomLogService;
import android.util.Log;
import java.util.List;
public void executeGlobalLogging() {
try {
// 1. 커널 레지스트리 서비스매니저에서 고유 태그명으로 로우 바인더 핸들을 낚아챕니다.
IBinder binder = ServiceManager.getService("custom_log_service");
if (binder != null) {
// 2. 낚아챈 로우 바인더 객체를 AIDL 인터페이스 프록시 클래스로 트랜스폼합니다.
ICustomLogService logService = ICustomLogService.Stub.asInterface(binder);
// 3. 프로세스 경계를 넘어 원격 시스템 스레드 영역에 로그 기록 함수를 원격 구동합니다.
logService.logMessage("시스템 경보: 하부 컨트롤러 노드에서 이벤트가 발생했습니다.");
List<String> systemLogs = logService.getLogs();
Log.i("ClientApp", "현재까지 누적된 전역 시스템 로그 개수: " + systemLogs.size());
}
} catch (Exception e) {
Log.e("ClientApp", "바인더 원격 호출 도중 IPC 예외 트랜잭션 발생", e);
}
}
🛠️ 개발을 위한 팁 (Tips)
- 내부 통신 전용 서비스는 LocalServices 허브 활용: 만약 내가 만드는 커스텀 서비스가 외부 앱이나 다른 독립 프로세스와 통신할 필요가 전혀 없고, 오직 SystemServer 내부에서 동작하는 타 서비스(ActivityManagerService 등)와만 데이터를 주고받는 구조라면 AIDL을 설계할 필요가 전혀 없습니다. 프레임워크가 제공하는 내부 전용 레지스트리인 LocalServices.addService(MyLocalClass.class, mLocalInstance)에 등록하세요. 바인더 컨텍스트 스위칭 비용과 직렬화 오버헤드가 완전히 생략되므로 가상머신 내부 최고의 속도로 객체를 주고받을 수 있습니다.
- AIDL 아웃풋 리스트 용량 한계 모니터링: getLogs()처럼 리스트 구조를 AIDL로 넘길 때는 런타임에 데이터가 무한정 쌓이지 않도록 큐(Queue) 구조로 최대 개수를 제한(예: 최신 100개만 유지)해 두는 것이 좋습니다. 바인더 트랜잭션 버퍼는 약 1MB 내외의 크기 제한이 있으므로, 로그 텍스트가 수천 개 이상 쌓인 리스트를 한 번에 리턴하려고 하면 바인더 드라이버단에서 통신이 폭파되는 현상이 생기니 주기적인 클렌징 처리를 잊지 마세요.
⚠️ 흔히 하는 실수 (Common Mistakes)
- 바인더 Stub 내부의 동기화 메커니즘 누락 (ArrayList 불법 사용): 자바 개발 환경에 익숙한 분들이 가장 자주 놓치는 보안 결함입니다. 바인더 구현체 안에서 멤버 변수나 일반 ArrayList, HashMap 같은 컬렉션을 아무런 보호 장치 없이 제어하는 경우죠. 내 서비스를 호출하는 앱이 한두 개일 때는 티가 안 나지만, 단말 가동 후 수십 개의 앱이 백그라운드에서 동시에 내 서비스 함수를 난타하면 여러 스레드가 컬렉션 인덱스를 동시에 찢고 들어와 SystemServer 내부 메모리를 오염시키고 OS 자체를 크래시 내버립니다. 반드시 CopyOnWriteArrayList, ConcurrentHashMap을 쓰거나 임계 구역에 synchronized 처리를 꽁꽁 싸매어 보호해야 안전합니다.
- AIDL 리스트 리턴 시 메모리 참조 착각: AIDL 함수가 반환한 List<String> 객체를 클라이언트 앱단에서 가공한 뒤, 시스템 서비스 내부의 원본 데이터도 함께 바뀌었을 거라고 오해하는 실수입니다. 자바 내부 호출과 달리 프로세스 경계를 넘어가는 AIDL IPC는 모든 데이터를 바이트 단위로 쪼개 새로 조립하는 '직렬화 복사(Deep Copy)' 메커니즘으로 동작합니다. 즉, 앱이 받아 간 리스트는 완전한 별개의 복사본이므로 원본 데이터를 갱신하고 싶다면 반드시 updateLog() 같은 명시적인 AIDL 쓰기 함수를 추가로 정의해 호출해 주어야 합니다.
5. 결론
안드로이드 커스텀 시스템 서비스를 똑똑하게 설계하기 위한 첫 단추는 내 서비스가 마주할 통신 환경과 런타임 동기화 구조를 정확하게 파악하는 데 있습니다.
무조건 관습적으로 AIDL 파일부터 생성하기보다는 시스템 내부의 순수한 인터페이스 연동 구조인지, 혹은 글로벌 앱 프로세스들을 통제해야 하는 전역 API 인프라인지 명확한 선을 긋고 출발해야 성능 낭비 없는 날렵한 운영체제를 완성할 수 있습니다. 아울러 다중 스레드가 격돌하는 SystemServer 환경의 특성을 잊지 말고 스레드 세이프 처리를 아키텍처 뼈대에 단단히 이식해 주는 작업이 수반되어야 고품질 양산형 단말 코드를 배출할 수 있죠. 서비스를 구현하는 도중 멀티스레드 교착 상태(Deadlock)에 빠졌거나, AIDL 매개변수 마샬링 과정에서 원인 모를 런타임 에러를 만나 곤란을 겪고 계신다면 주저 말고 하단 댓글 창에 설계 구조를 남겨주세요. 로우 레벨 단에서 함께 디버깅 실마리를 찾아보겠습니다!
'Android System & AOSP Engineering > AOSP Framework & Custom Services' 카테고리의 다른 글
| AOSP 실무: Native Binder IPC 구현 및 Android.bp 기반 C++ 데몬 서비스 등록 가이드 (0) | 2025.06.07 |
|---|---|
| AOSP 펌웨어 개발: SystemService 프레임워크 표준 등록 및 서비스매니저 바인딩 가이드 (0) | 2025.06.06 |
| AOSP 개발 실무: SystemServer 서비스 아키텍처와 Android.bp 기반 커스텀 구현 (0) | 2025.06.04 |
| AOSP 입문: Custom System Service 개념부터 AIDL 바인더 등록까지 완벽 정리 (0) | 2025.06.03 |
| 임베디드 안드로이드 확장: AAOS 자동차 IVI 및 가전 OEM 커스텀 서비스 구현 실무 (0) | 2025.06.02 |