Android System & AOSP Engineering/AOSP Framework & Custom Services

Android AOSP 가이드: AIDL 생성부터 Context.getSystemService() 등록까지 전 과정

임베디드 친구 2025. 5. 26. 21:59
반응형

안드로이드 기반의 단말기를 커스텀하다 보면, 내가 만든 하드웨어 모듈이나 전역 백그라운드 관리 기능을 일반 앱 개발자들이 아주 쉽게 갖다 쓸 수 있도록 OS 표준 API 형태로 배포해야 할 때가 있습니다. 구글이 만든 PowerManager나 LocationManager처럼 말이죠.

이를 달성하려면 단순히 자바 클래스를 만드는 데서 끝나지 않고, 리눅스 프로세스 경계를 넘어 통신할 수 있도록 AIDL 명세서를 작성해야 합니다. 또한 이를 SystemServer에 등록한 뒤, 앱 진입점인 Context.getSystemService() 프레임워크 레지스트리에 깔끔하게 바인딩해 주는 작업이 필수적입니다. 이 전체 파이프라인을 정확히 이해해야 앱 개발자에게 안전하고 직관적인 SDK 형태의 인터페이스를 제공할 수 있습니다. 이번 포스팅에서는 AIDL 정의부터 바인더 스텁 구현, 그리고 프레임워크 레지스트리 매핑까지 단계별 소스 코드와 함께 낱낱이 파헤쳐 보겠습니다.

Generated by Gemini AI.

📌 핵심 요약 3줄

  1. 사용자 정의 시스템 서비스는 프로세스 간 장벽을 넘기 위해 AIDL 명세서를 기반으로 Binder IPC 엔드포인트를 생성하는 것부터 시작합니다.
  2. 생성된 바인더 서비스 인스턴스는 부팅 시점에 SystemServer.java 내에서 ServiceManager.addService()를 통해 OS 전역에 등록됩니다.
  3. 앱단에서 context.getSystemService()로 우아하게 호출할 수 있도록 SystemServiceRegistry.java에 페처(Fetcher) 등록을 마쳐야 전체 사이클이 완성됩니다.

1. 전역 시스템 서비스 구현을 위한 4단계 아키텍처 파이프라인

나만의 커스텀 서비스를 안드로이드 프레임워크에 완전히 정착시키기 위한 핵심 단계별 작업과 산출물 요약입니다.

단계 수행 작업 내용 수정 및 추가되는 AOSP 소스 경로 실무 관점의 핵심 목적
1단계 AIDL 통신 인터페이스 정의 frameworks/base/core/java/android/os/

IYourService.aidl
프로세스 간 데이터 직렬화(Marshalling)를 위한 바인더 Stub 코드 생성
2단계 바인더 핵심 비즈니스 로직 구현 frameworks/base/services/core/java/

com/android/server/YourService.java
AIDL.Stub을 상속받아 단말기 제어 및 실제 연산 로직 구체화
3단계 SystemServer 전역 마스터 등록 frameworks/base/services/java/

com/android/server/SystemServer.java
OS 부팅 시 시스템 프로세스 메모리에 서비스를 상주시키고 토큰 등록
4단계 클라이언트용 매니저 API 바인딩 frameworks/base/core/java/

android/app/SystemServiceRegistry.java
Context.getSystemService() 호출 시 바인더 프록시를 매니저 객체로 변환 리턴

2. 1단계 & 2단계: AIDL 정의 및 서비스 로직 구현

2.1 인터페이스 명세서 작성 (IYourService.aidl)

향후 표준 SDK로의 확장성을 고려하여 안드로이드 코어 패키지 구조 내에 통신 명세서를 배치합니다.

Java
 
// frameworks/base/core/java/android/os/IYourService.aidl
package android.os;

/**
 * 시스템 전역에 제공할 사용자 정의 인터페이스
 */
interface IYourService {
    void yourMethod();
}

2.2 바인더 서비스 스텁 구체화 (YourService.java)

AOSP 컴파일러가 만들어준 IYourService.Stub 클래스를 상속받아 내부 상주 프로세스에서 돌아갈 실질적인 동작 코드를 채워 넣습니다.

Java
 
// frameworks/base/services/core/java/com/android/server/YourService.java
package com.android.server;

import android.os.IYourService;
import android.os.RemoteException;
import android.util.Slog;

public class YourService extends IYourService.Stub {
    private static final String TAG = "YourService";

    @Override
    public void yourMethod() throws RemoteException {
        // 일반 Log.d 대신 시스템 서버 전역 로그 버퍼를 쓰는 Slog를 사용합니다.
        Slog.d(TAG, "하부 시스템 제어를 위한 yourMethod()가 바인더를 통해 호출되었습니다.");
    }
}

3. 3단계: SystemServer 프로세스에 서비스 가동 등록

이제 안드로이드 부팅 시 이 서비스를 메모리에 올리고 전역 고유 이름표(your_service)를 붙여 상주시킵니다.

Java
 
// frameworks/base/services/java/com/android/server/SystemServer.java
import com.android.server.YourService;

public class SystemServer {
    private void startOtherServices() {
        // 시스템의 핵심 부트스트랩이 완료된 후 구동되는 부가 서비스 영역에 추가합니다.
        try {
            Slog.i(TAG, "사용자 정의 YourService 등록 프로세스 가동");
            YourService yourService = new YourService();
            
            // ServiceManager에 문자열 키값과 함께 바인더 객체를 등록합니다.
            ServiceManager.addService("your_service", yourService);
        } catch (Throwable e) {
            Slog.e(TAG, "YourService 가동 중 치명적 오류 발생", e);
        }
    }
}

4. 4단계: Context.getSystemService() 연동 매핑

앱 개발자들이 ServiceManager 같은 가려진 로우 레벨 코드를 직접 쓰지 않고 구글 표준 스타일로 우아하게 서비스를 호출할 수 있도록 프레임워크 레지스트리에 매핑해 줍니다. 먼저 개발자가 접근할 클라이언트 래퍼 매니저 클래스를 생성합니다.

4.1 클라이언트용 매니저 클래스 정의 (YourServiceManager.java)

Java
 
// frameworks/base/core/java/android/app/YourServiceManager.java
package android.app;

import android.content.Context;
import android.os.IYourService;
import android.os.RemoteException;

public class YourServiceManager {
    private final IYourService mService;

    // 레지스트리 페처에 의해 프록시 바인더 객체를 주입받습니다.
    public YourServiceManager(Context context, IYourService service) {
        mService = service;
    }

    public void callMethod() {
        try {
            if (mService != null) mService.yourMethod();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}

4.2 시스템 서비스 레지스트리 연결 (SystemServiceRegistry.java)

Java
 
// frameworks/base/core/java/android/app/SystemServiceRegistry.java
import android.app.YourServiceManager;
import android.os.IYourService;

final class SystemServiceRegistry {
    static {
        // 공용 static 초기화 블록 내부에 내 서비스를 등록해 줍니다.
        registerService("your_service", YourServiceManager.class,
                new CachedServiceFetcher<YourServiceManager>() {
            @Override
            public YourServiceManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                // 1. ServiceManager에서 바인더 토큰을 픽업합니다.
                IBinder b = ServiceManager.getServiceOrThrow("your_service");
                // 2. 바인더 인터페이스 프록시로 변환합니다.
                IYourService service = IYourService.Stub.asInterface(b);
                // 3. 앱 개발자가 쓸 매니저 클래스에 주입하여 리턴합니다.
                return new YourServiceManager(ctx.getOuterContext(), service);
            }});
    }
}

이제 모든 서드파티 및 시스템 앱 영역에서 아래 한 줄의 정석 코드로 내 시스템 기능을 조작할 수 있게 됩니다!

Java
 
// 일반 애플리케이션 액티비티 내부 호출 예시
YourServiceManager manager = (YourServiceManager) context.getSystemService("your_service");
manager.callMethod();

🛠️ 개발을 위한 팁 (Tips)

  1. ServiceManager.getServiceOrThrow 사용 권장: SystemServiceRegistry에서 바인더를 가져올 때는 단순히 getService()를 쓰는 것보다 getServiceOrThrow()를 쓰는 게 시스템 안정성에 유리합니다. 만약 부팅 타이밍 문제나 자원 고갈 때문에 내 커스텀 서비스가 정상 등록되지 않았을 경우, null을 반환받아 나중에 앱단에서 무심코 참조하다 NullPointerException으로 크래시가 나는 것보다 프레임워크 단에서 ServiceNotFoundException을 명확히 던지게 유도하는 것이 디버깅 아키텍처 측면에서 훨씬 깔끔합니다.
  2. bp 파일 컴파일 범위 체크: 새로운 자바 파일과 AIDL을 추가한 뒤에는 반드시 frameworks/base/Android.bp 스크립트를 열어 srcs 파일 그룹 리스트에 내 소스 파일 경로들이 누락 없이 기재되어 있는지 체크하세요. 간혹 소스 코드만 넣어두고 빌드 스크립트를 갱신하지 않아 Symbol Not Found 에러로 시간을 허비하는 경우가 많습니다.

⚠️ 흔히 하는 실수 (Common Mistakes)

  1. Context 객체 보관으로 인한 메모리 누수(Memory Leak): YourServiceManager를 구현할 때 생성자로 넘어오는 Context 객체를 통째로 클래스 내부 멤버 변수(private Context mContext;)에 강하게 잡아두는 실수를 많이 합니다. 만약 앱이 액티비티 컨텍스트(Activity Context)를 던졌을 경우, 해당 액티비티가 소멸되어도 매니저가 메모리를 물고 있어 시스템 전역에 메모리 누수가 누적됩니다. 컨텍스트가 꼭 필요하다면 반드시 ctx.getApplicationContext() 형태로 글로벌 어플리케이션 컨텍스트를 추출해서 보관해야 안전합니다.
  2. SELinux neverallow 정책 위반: SystemServer에 서비스를 등록하고 앱단 레지스트리 매핑까지 완벽히 마쳐도, 앱에서 호출하는 순간 avc: denied 라인과 함께 통신이 차단되는 현상을 보게 됩니다. 안드로이드의 강력한 커널 보안 정책인 SELinux 때문인데요, system/sepolicy/ 영역에 내 서비스 이름인 your_service를 시스템 서비스 타입(system_api_service)으로 선언해 주지 않으면 바인더 드라이버 단계에서 접근 권한 거부 에러가 떨어지니 반드시 자원 정의 파일(service.te 등)을 함께 패치해 주어야 합니다.

5. 결론

사용자 정의 프레임워크 서비스를 독자적으로 구축하고 이를 Context.getSystemService() 가이드라인에 완벽히 매핑하는 과정은 안드로이드 플랫폼 커스텀의 정점이라고 볼 수 있습니다.

단순히 독립된 프로세스 형태로 도는 데몬을 만드는 것을 넘어, 안드로이드 순정 OS의 아키텍처 프레임워크와 완벽히 융합된 하나의 '정식 서브시스템 API'를 탄생시키는 일이기 때문이죠. 오늘 공유해 드린 4단계 연동 시퀀스를 기반으로 코드를 배치하면 업그레이드가 용이한 견고한 OS 구조를 완성하실 수 있을 겁니다. AOSP 소스를 컴파일하는 도중 바인더 마샬링 단에서 막히거나, SystemServiceRegistry 수정 후 의존성 예외가 발생한다면 언제든 에러 로그와 함께 댓글 남겨주세요. 같이 원인을 짚어봅시다!

반응형