Android 서비스(C) AIDL 예제

Android에서 C/C++로 AIDL을 사용하여 Android 서비스를 구현해 보았다.

전에 Android 서비스(Java) AIDL 예제 글에서는 Java로 Android 서비스를 구현하는 예제를 작성했었는데, 이번에는 C/C++을 사용하여 Android 서비스를 구현하는 예제를 작성해 보았다.

빌드 트리

전체 트리는 AOSP의 vendor/my/ 디렉토리 밑에 아래와 같이 구성하였다.

test
├── aidl
│   ├── aidl_api
│   │   └── vendor.my.test_service
│   │       └── current
│   │           └── vendor
│   │               └── my
│   │                   └── test_service
│   │                       └── ITestService.aidl
│   ├── vendor
│   │   └── my
│   │       └── test_service
│   │           └── ITestService.aidl
│   └── Android.bp
├── client
│   ├── TestClientExample.cpp
│   └── Android.bp
└── service
    ├── sepolicy
    │   ├── test_service.te
    │   ├── file_contexts
    │   └── service_contexts
    ├── TestService.cpp
    ├── Android.bp
    ├── test_framework_compatibility_matrix.xml
    ├── test_service.rc
    └── test_service_manifest.xml

빌드 구성

  • vendor/my/test/aidl/Android.bp 파일을 아래와 같이 작성한다.
    aidl_interface {
        name: "vendor.my.test_service",
        srcs: ["vendor/my/test_service/*.aidl"],
        stability: "vintf",
        vendor_available: true,
        backend: {
            cpp: { enabled: false },
            java: { enabled: false },
        },
        owner: "my",
    }
    

    위 예에서는 stability: "vintf"로 설정하였다. VINTF(Versioned Interface for Native HAL)로 설정하면 안정적인 시스템 인터페이스라고 명시하는 것이며 릴리즈 이후에는 변경 불가하고, 변경하려면 version up을 해야만 한다. 만약에 내부/테스트용으로 항상 변경 가능하게 하려면 "unstable"로 설정할 수 있다. (단, 서비스가 vendor/ 파티션에 위치하는 경우에는 “unstable”로 설정 안됨)

  • vendor/my/test/aidl/vendor/my/test_service/ITestService.aidl 파일을 아래와 같이 작성한다. 여기서는 단순 예제로 add 함수만 정의하였다.
    package vendor.my.test_service;
    
    @VintfStability
    interface ITestService {
        int add(int a, int b);
    }
    
  • 최초 빌드시에는 현재 버전에 대한 aidl 파일이 자동으로 생성되는데, 이후 API에 변경 사항이 생기면 m 명령으로 {aidl_interface service name}-update-api 타겟을 빌드하면 aidl 파일이 업데이트된다.
    예를 들어 아래와 같이 업데이트시킬 수 있다.
    $ m vendor.my.test_service-update-api
    

    결과로 vendor/my/test/aidl/aidl_api/vendor.my.test_service/current/vendor/my/test_service/ITestService.aidl 파일이 업데이트된다.

서비스 server 단 (C++로 구현)

  • vendor/my/test/service/Android.bp 파일을 아래와 같이 작성한다.
    cc_binary {
        name: "test-service",
        init_rc: ["test_service.rc"],
        vintf_fragments: ["test_service_manifest.xml"],
        vendor: true,
        srcs: [
            "TestService.cpp",
        ],
        cflags: [
            "-Wall",
            "-Wno-unused-parameter",
            "-Wno-unused-function",
        ],
        include_dirs: [
            "frameworks/native/include",
            "frameworks/native/libs/binder/include",
            "system/core/include",
        ],
        shared_libs: [
            "libbase",
            "libbinder",
            "libbinder_ndk",
            "libcutils",
            "liblog",
            "libutils",
            "vendor.my.test_service-V1-ndk",
        ],
        static_libs: [
            "libaidlcommonsupport",
        ],
    }
    
  • vendor/my/test/service/TestService.cpp 파일을 아래와 같이 작성한다. (여기서는 add 함수를 구현함)
    #include <android-base/logging.h>
    #include <android/binder_ibinder_platform.h>
    #include <android/binder_manager.h>
    #include <android/binder_process.h>
    
    #include "aidl/vendor/my/test_service/BnTestService.h"
    
    #define LOG_TAG "test-service"
    
    using namespace aidl::vendor::my::test_service;
    
    class TestService : public BnTestService {
    public:
        ndk::ScopedAStatus add(int32_t a, int32_t b, int32_t* _aidl_return) override {
            LOG(INFO) << "TestService::add(" << a << ", " << b << ")";
            *_aidl_return = a + b;
            return ndk::ScopedAStatus::ok();
        }
    };
    
    int main() {
        LOG(INFO) << "Starting ACAS service...";
    
        ABinderProcess_setThreadPoolMaxThreadCount(0);
        std::shared_ptr<TestService> service = ndk::SharedRefBase::make<TestService>();
    
        const char* serviceName = "vendor.my.test_service.ITestService/default";
        if (AServiceManager_addService(service->asBinder().get(), serviceName) != STATUS_OK) {
            LOG(ERROR) << "Failed to register service: " << serviceName;
            return -1;
        }
    
        LOG(INFO) << "Service registered as " << serviceName;
        ABinderProcess_joinThreadPool();
        return 0;
    }
    
  • 그런데 위 서비스는 Android SE policy에 의해 거부되므로, 필요한 권한을 할당해 주어야 한다.
    먼저 vendor/my/test/service/test_service_manifest.xml 파일은 아래와 같이 작성한다.
    <manifest version="1.0" type="device">
        <hal format="aidl">
            <name>vendor.my.test_service</name>
            <version>1</version>
            <interface>
                <name>ITestService</name>
                <instance>default</instance>
            </interface>
        </hal>
    </manifest>
    

    vendor/my/test/service/test_framework_compatibility_matrix.xml 파일을 아래와 같이 작성한다.

    <compatibility-matrix version="1.0" type="framework">
        <hal format="aidl">
            <name>vendor.my.test_service</name>
            <version>1</version>
            <interface>
                <name>ITestService</name>
                <instance>default</instance>
            </interface>
        </hal>
    </compatibility-matrix>
    

    vendor/my/test/service/sepolicy/test_service.te 파일을 아래와 같이 작성한다.

    type vendor_test_service, domain, hal_service_type, service_manager_type;
    type vendor_test_service_exec, exec_type, vendor_file_type, file_type;
    
    init_daemon_domain(vendor_test_service)
    vndbinder_use(vendor_test_service)
    allow vendor_test_service servicemanager:binder { call transfer };
    allow vendor_test_service vendor_test_service:service_manager { add find };
    

    vendor/my/test/service/sepolicy/file_contexts 파일을 아래와 같이 작성한다.

    /vendor/bin/test-service u:object_r:vendor_test_service_exec:s0
    

    vendor/my/test/service/sepolicy/service_contexts 파일을 아래와 같이 작성한다.

    vendor.my.test_service.ITestService/default u:object_r:vendor_test_service:s0
    
  • Android 실행시 자동으로 이 서비스가 실행되도록 vendor/test/service/test_service.rc 파일을 아래와 같이 작성한다.
    on init
        start test-service
    
    service test-service /vendor/bin/test-service
        class hal
        user system
        group system
    

서비스 클라이언트 단 (C++로 구현 예제)

  • vendor/my/test/client/Android.bp 파일을 아래와 같이 작성한다.
    cc_binary {
        name: "test_client_example",
        srcs: [
            "TestClientExample.cpp",
        ],
        shared_libs: [
            "libbase",
            "libbinder",
            "libbinder_ndk",
            "liblog",
            "libutils",
            "vendor.my.test_service-V1-ndk",
        ],
        vendor: true,
    }
    
  • vendor/my/test/client/TestClientExample.cpp 파일을 아래와 같이 작성한다. (테스트로 서비스가 제공하는 add 함수를 호출하여 결과를 출력함)
    #include <android-base/logging.h>
    #include <android/binder_ibinder_platform.h>
    #include <android/binder_manager.h>
    #include <android/binder_process.h>
    
    #include "aidl/vendor/my/test_service/ITestService.h"
    
    #define LOG_TAG "test-client"
    
    using aidl::vendor::my::test_service::ITestService;
    
    int main() {
        LOG(INFO) << "Connecting to ExampleService...";
        std::cout << "Connecting to ExampleService..." << std::endl;
    
        std::shared_ptr<ITestService> service;
        AServiceManager_waitForService("vendor.my.test_service.ITestService/default");
        service = ITestService::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("vendor.my.test_service.ITestService/default")));
        if (!service) {
            LOG(ERROR) << "Failed to get TestService";
            std::cout << "Failed to get TestService" << std::endl;
            return -1;
        }
    
        int32_t result;
        ndk::ScopedAStatus status;
        status = service->add(3, 5, &result);
        if (status.isOk()) {
            LOG(INFO) << "Result of add(3, 5): " << result;
            std::cout << "Result of add(3, 5): " << result << std::endl;
        } else {
            LOG(ERROR) << "add() call failed";
            std::cout << "add() call failed" << std::endl;
        }
    
        return 0;
    }
    
  • 빌드에 포함시키기
    vendor 경로에서 빌드에 포함되는 mk 파일에 아래 내용을 추가한다.
    PRODUCT_PACKAGES += \
        vendor.my.test_service \
        test-service \
        test_client_example
    
    DEVICE_PRODUCT_COMPATIBILITY_MATRIX_FILE += \
        vendor/my/test/service/test_framework_compatibility_matrix.xml
    
    BOARD_VENDOR_SEPOLICY_DIRS += \
        vendor/my/test/service/sepolicy
    
  • 서비스 테스트
    Android 부팅 후에, 아래와 같이 “service list” 명령으로 해당 서비스가 정상적으로 실행 중인지 확인할 수 있다.
    # service list | grep test_service
    255     vendor.my.test_service.ITestService/default: [vendor.my.test_service.ITestService]
    
  • 서비스 클라이언트 테스트
    아래와 같이 테스트해보면 기대대로 동작한다.
    $ test_client_example
    Connecting to ExampleService...
    Result of add(3, 5): 8
    

서비스 클라이언트 단 (Java로 구현 예제)

이번에는 서비스 클라이언트만 Java로 구현해 본다.

  • Android.bp 파일을 아래와 같이 작성한다.
    android_app {
        name: "MyClientApp",
        certificate: "platform",
        srcs: ["java/**/*.kt"],
        resource_dirs: [
            "res",
        ],
        platform_apis: true,
        static_libs: [
            "androidx.core_core-ktx",
            "androidx.appcompat_appcompat",
            "com.google.android.material_material",
            "androidx-constraintlayout_constraintlayout",
            "androidx.navigation_navigation-fragment-ktx",
            "androidx.navigation_navigation-ui-ktx",
            "vendor.my.test_service-V1-java",
        ],
        required: ["privapp-permissions-recop"],
    }
    
  • AndroidManifest.xml 파일을 아래와 같이 작성한다.
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.my.test_client_test" >
    
        <application
            android:allowBackup="true"
            android:dataExtractionRules="@xml/data_extraction_rules"
            android:fullBackupContent="@xml/backup_rules"
            android:icon="@drawable/pacifier"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.MyClientAidlApp"
            tools:targetApi="31" >
            <activity
                android:name=".MainActivity"
                android:exported="true"
                android:label="@string/app_name"
                android:theme="@style/Theme.TestClientTest.NoActionBar" >
                <meta-data
                    android:name="android.app.lib_name"
                    android:value="" />
    
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    </manifest>
    
  • MainActivity.kt 파일을 아래와 같이 작성한다.
    package com.my.test_client_test
    
    import android.os.Bundle
    import com.google.android.material.snackbar.Snackbar
    import androidx.appcompat.app.AppCompatActivity
    import android.util.Log
    import android.os.Environment
    import android.os.Binder
    import android.os.IBinder
    import android.os.ServiceManager
    
    class MainActivity : AppCompatActivity() {
        private var binder: IBinder? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            getAidlInterface()
        }
    
        private fun isExternalStorageReadOnly(): Boolean {
            val extStorageState : String = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(extStorageState)) {
                return true;
            }
            return false;
        }
    
        private fun isExternalStorageAvailable(): Boolean {
            val extStorageState : String = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED.equals(extStorageState)) {
                return true;
            }
            return false;
        }
    
        private fun getAidlInterface() {
            binder = ServiceManager.getService(HAL_AIDL_SERVICE_NAME)
            binder?.let{
                iAcasService = ITestService.Stub.asInterface(binder)
                Log.d(LOG, "App is bound to my_service")
                Log.d(LOG, "Result of add(3, 5): " + iAcasService.add(3, 5))
            }
        }
    
        companion object{
            val HAL_AIDL_SERVICE_NAME = "vendor.my.test_service.ITestService/default"
            val LOG = "MyClientApp"
        }
    }
    
  • 빌드된 앱을 실행해보면 서비스의 add 함수가 정상적으로 실행되는 것을 확인할 수 있다.

카테고리:

업데이트: