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:s0vendor/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 함수가 정상적으로 실행되는 것을 확인할 수 있다.