[Android] Android AIDL 통신 (작성중)
2026. 1. 19. 13:49네, AIDL을 이용해서 권한 문제를 해결할 수 있습니다. 소켓 통신의 권한 문제를 AIDL로 우회하는 방법을 알려드리겠습니다.
문제 상황 분석
Android 앱이 소켓을 열지 못하는 일반적인 원인:
SELinux 정책: 앱이 특정 소켓 파일에 접근 불가
파일 권한: /dev/socket/ 디렉토리 접근 권한 부족
Network Security Config: Android 9+ 에서 일반 소켓 제한
Targetless 소켓: Unix domain socket 생성/접근 권한 부족
해결 방안: AIDL 브리지 패턴
기존 C 프로그램은 그대로 두고, System Service를 중간 브리지로 사용합니다.
Android App → AIDL → System Service (권한 있음) → Socket → C Program
구현 예제
1. 아키텍처
[Android App]
↓ (AIDL - 권한 문제 없음)
[System Service]
↓ (Socket - System 권한으로 접근 가능)
[기존 C Program]
2. AIDL 인터페이스 정의
frameworks/base/core/java/android/os/ISocketBridge.aidl
package android.os;
/**
* 소켓 브리지 서비스
* @hide
*/
interface ISocketBridge {
/**
* C 프로그램으로 데이터 전송
*/
boolean sendData(in byte[] data);
/**
* C 프로그램에서 데이터 수신
*/
byte[] receiveData();
/**
* 요청-응답 패턴
*/
byte[] sendAndReceive(in byte[] request);
/**
* 연결 상태 확인
*/
boolean isConnected();
}
3. System Service 구현 (Java + JNI)
frameworks/base/services/core/java/com/android/server/SocketBridgeService.java
package com.android.server;
import android.content.Context;
import android.os.ISocketBridge;
import android.util.Slog;
public class SocketBridgeService extends ISocketBridge.Stub {
private static final String TAG = "SocketBridgeService";
private static final String SOCKET_PATH = "/dev/socket/my_c_program";
private final Context mContext;
// Native 메서드
private native boolean native_init(String socketPath);
private native boolean native_sendData(byte[] data);
private native byte[] native_receiveData();
private native byte[] native_sendAndReceive(byte[] request);
private native boolean native_isConnected();
private native void native_cleanup();
static {
System.loadLibrary("socketbridge_jni");
}
public SocketBridgeService(Context context) {
mContext = context;
// 소켓 연결 초기화
if (!native_init(SOCKET_PATH)) {
Slog.e(TAG, "Failed to initialize socket connection");
} else {
Slog.i(TAG, "Socket bridge initialized successfully");
}
}
@Override
public boolean sendData(byte[] data) {
if (data == null || data.length == 0) {
Slog.w(TAG, "Invalid data");
return false;
}
// 권한 체크 (필요시)
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
"Need INTERNET permission"
);
return native_sendData(data);
}
@Override
public byte[] receiveData() {
return native_receiveData();
}
@Override
public byte[] sendAndReceive(byte[] request) {
if (request == null) {
return null;
}
return native_sendAndReceive(request);
}
@Override
public boolean isConnected() {
return native_isConnected();
}
@Override
protected void finalize() throws Throwable {
native_cleanup();
super.finalize();
}
}
4. JNI 구현 (소켓 통신)
frameworks/base/services/core/jni/com_android_server_SocketBridgeService.cpp
#define LOG_TAG "SocketBridgeServiceJNI"
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include <utils/Log.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
namespace android {
static int gSocketFd = -1;
static const int MAX_BUFFER_SIZE = 4096;
// 소켓 초기화
static jboolean native_init(JNIEnv* env, jobject clazz, jstring socketPath) {
const char* path = env->GetStringUTFChars(socketPath, nullptr);
ALOGI("Initializing socket connection to: %s", path);
// 기존 연결 정리
if (gSocketFd >= 0) {
close(gSocketFd);
gSocketFd = -1;
}
// Unix domain socket 생성
gSocketFd = socket(AF_UNIX, SOCK_STREAM, 0);
if (gSocketFd < 0) {
ALOGE("Failed to create socket: %s", strerror(errno));
env->ReleaseStringUTFChars(socketPath, path);
return JNI_FALSE;
}
// 소켓 주소 설정
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
// C 프로그램에 연결
if (connect(gSocketFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
ALOGE("Failed to connect to socket %s: %s", path, strerror(errno));
close(gSocketFd);
gSocketFd = -1;
env->ReleaseStringUTFChars(socketPath, path);
return JNI_FALSE;
}
ALOGI("Socket connected successfully");
env->ReleaseStringUTFChars(socketPath, path);
return JNI_TRUE;
}
// 데이터 전송
static jboolean native_sendData(JNIEnv* env, jobject clazz, jbyteArray data) {
if (gSocketFd < 0) {
ALOGE("Socket not connected");
return JNI_FALSE;
}
jsize len = env->GetArrayLength(data);
jbyte* bytes = env->GetByteArrayElements(data, nullptr);
// 데이터 크기 먼저 전송 (프로토콜)
uint32_t dataLen = htonl(len);
if (send(gSocketFd, &dataLen, sizeof(dataLen), 0) != sizeof(dataLen)) {
ALOGE("Failed to send data length: %s", strerror(errno));
env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
return JNI_FALSE;
}
// 실제 데이터 전송
ssize_t sent = send(gSocketFd, bytes, len, 0);
env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
if (sent != len) {
ALOGE("Failed to send data: %s", strerror(errno));
return JNI_FALSE;
}
ALOGD("Sent %d bytes", len);
return JNI_TRUE;
}
// 데이터 수신
static jbyteArray native_receiveData(JNIEnv* env, jobject clazz) {
if (gSocketFd < 0) {
ALOGE("Socket not connected");
return nullptr;
}
// 데이터 크기 수신
uint32_t dataLen;
if (recv(gSocketFd, &dataLen, sizeof(dataLen), 0) != sizeof(dataLen)) {
ALOGE("Failed to receive data length: %s", strerror(errno));
return nullptr;
}
dataLen = ntohl(dataLen);
if (dataLen > MAX_BUFFER_SIZE) {
ALOGE("Data too large: %u bytes", dataLen);
return nullptr;
}
// 데이터 수신
uint8_t* buffer = new uint8_t[dataLen];
ssize_t received = recv(gSocketFd, buffer, dataLen, 0);
if (received != (ssize_t)dataLen) {
ALOGE("Failed to receive data: %s", strerror(errno));
delete[] buffer;
return nullptr;
}
// Java byte array로 변환
jbyteArray result = env->NewByteArray(dataLen);
env->SetByteArrayRegion(result, 0, dataLen, (jbyte*)buffer);
delete[] buffer;
ALOGD("Received %u bytes", dataLen);
return result;
}
// 요청-응답
static jbyteArray native_sendAndReceive(JNIEnv* env, jobject clazz,
jbyteArray request) {
// 데이터 전송
if (!native_sendData(env, clazz, request)) {
return nullptr;
}
// 응답 수신
return native_receiveData(env, clazz);
}
// 연결 상태 확인
static jboolean native_isConnected(JNIEnv* env, jobject clazz) {
if (gSocketFd < 0) {
return JNI_FALSE;
}
// 소켓이 유효한지 확인
int error = 0;
socklen_t len = sizeof(error);
if (getsockopt(gSocketFd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
return JNI_FALSE;
}
return error == 0 ? JNI_TRUE : JNI_FALSE;
}
// 정리
static void native_cleanup(JNIEnv* env, jobject clazz) {
if (gSocketFd >= 0) {
close(gSocketFd);
gSocketFd = -1;
ALOGI("Socket connection closed");
}
}
// JNI 메서드 매핑
static const JNINativeMethod gMethods[] = {
{"native_init", "(Ljava/lang/String;)Z", (void*)native_init},
{"native_sendData", "([B)Z", (void*)native_sendData},
{"native_receiveData", "()[B", (void*)native_receiveData},
{"native_sendAndReceive", "([B)[B", (void*)native_sendAndReceive},
{"native_isConnected", "()Z", (void*)native_isConnected},
{"native_cleanup", "()V", (void*)native_cleanup},
};
int register_android_server_SocketBridgeService(JNIEnv* env) {
return AndroidRuntime::registerNativeMethods(env,
"com/android/server/SocketBridgeService",
gMethods, NELEM(gMethods));
}
} // namespace android
5. SystemServer에 등록
frameworks/base/services/java/com/android/server/SystemServer.java
import com.android.server.SocketBridgeService;
private void startOtherServices() {
// ... 기존 코드 ...
try {
Slog.i(TAG, "Socket Bridge Service");
ServiceManager.addService("socket_bridge",
new SocketBridgeService(context));
} catch (Throwable e) {
reportWtf("starting Socket Bridge Service", e);
}
// ... 기존 코드 ...
}
6. Android App에서 사용
MyApplication.java
package com.example.myapp;
import android.app.Application;
import android.os.IBinder;
import android.os.ISocketBridge;
import android.os.ServiceManager;
import android.os.RemoteException;
import android.util.Log;
public class MyApplication extends Application {
private static final String TAG = "MyApp";
private ISocketBridge mSocketBridge;
@Override
public void onCreate() {
super.onCreate();
// Socket Bridge 서비스에 연결
IBinder binder = ServiceManager.getService("socket_bridge");
mSocketBridge = ISocketBridge.Stub.asInterface(binder);
if (mSocketBridge == null) {
Log.e(TAG, "Failed to get socket bridge service");
}
}
// 데이터 전송 헬퍼
public boolean sendToC(byte[] data) {
try {
if (mSocketBridge != null) {
return mSocketBridge.sendData(data);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to send data", e);
}
return false;
}
// 데이터 수신 헬퍼
public byte[] receiveFromC() {
try {
if (mSocketBridge != null) {
return mSocketBridge.receiveData();
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to receive data", e);
}
return null;
}
// 요청-응답 헬퍼
public byte[] sendAndReceive(byte[] request) {
try {
if (mSocketBridge != null) {
return mSocketBridge.sendAndReceive(request);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to send and receive", e);
}
return null;
}
}
MainActivity.java
package com.example.myapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.util.Log;
import java.nio.charset.StandardCharsets;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private MyApplication mApp;
private TextView mResponseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mApp = (MyApplication) getApplication();
mResponseText = findViewById(R.id.responseText);
Button btnSend = findViewById(R.id.btnSend);
btnSend.setOnClickListener(v -> sendMessage());
}
private void sendMessage() {
new Thread(() -> {
try {
// C 프로그램에 메시지 전송
String message = "Hello from Android!";
byte[] request = message.getBytes(StandardCharsets.UTF_8);
Log.d(TAG, "Sending: " + message);
// 요청 전송 및 응답 수신
byte[] response = mApp.sendAndReceive(request);
if (response != null) {
String responseStr = new String(response,
StandardCharsets.UTF_8);
Log.d(TAG, "Received: " + responseStr);
runOnUiThread(() -> {
mResponseText.setText("Response: " + responseStr);
});
} else {
Log.e(TAG, "No response received");
runOnUiThread(() -> {
mResponseText.setText("Error: No response");
});
}
} catch (Exception e) {
Log.e(TAG, "Communication error", e);
runOnUiThread(() -> {
mResponseText.setText("Error: " + e.getMessage());
});
}
}).start();
}
}
7. SELinux 정책
system/sepolicy/private/system_server.te
# System server가 C 프로그램 소켓에 접근
allow system_server my_c_program_socket:sock_file write;
allow system_server my_c_program:unix_stream_socket connectto;
system/sepolicy/public/app.te
# 앱이 socket_bridge 서비스 사용
allow { appdomain -isolated_app } socket_bridge_service:service_manager find;
binder_call({ appdomain -isolated_app }, system_server)
system/sepolicy/private/service_contexts
socket_bridge u:object_r:socket_bridge_service:s0
8. C 프로그램 (기존 코드 - 변경 최소화)
기존 C 프로그램의 소켓 코드 예시
// 기존 코드는 그대로 유지
// 다만 SELinux 레이블만 조정
int create_server_socket(const char* socket_path) {
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
unlink(socket_path); // 기존 소켓 제거
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
// 권한 설정 (system_server가 접근 가능하도록)
chmod(socket_path, 0666);
listen(sockfd, 5);
return sockfd;
}
핵심 포인트
권한 문제 해결: System Service는 높은 권한을 가지므로 소켓 접근 가능
앱 코드 수정 최소화: 소켓 코드 대신 AIDL 호출로 간단히 변경
C 프로그램 수정 불필요: 기존 소켓 서버 코드 그대로 사용
SELinux 관리 용이: System Service 정책만 설정하면 됨
추가 권장 사항
재연결 로직 추가:
public void reconnect() {
try {
if (mSocketBridge != null && !mSocketBridge.isConnected()) {
// 재연결 로직
}
} catch (RemoteException e) {
Log.e(TAG, "Reconnect failed", e);
}
}
이 방식을 사용하면 앱의 권한 문제 없이 System Service의 권한으로 C 프로그램과 통신할 수 있습니다.
네, 같은 Android.bp 파일에 Java App과 C++ 서비스 데몬을 함께 정의할 수 있습니다. 실제로 이런 패턴은 Android에서 자주 사용됩니다.
Android.bp에서 여러 타입 모듈 정의
1. 통합 Android.bp 예제
packages/apps/MyApp/Android.bp
// ============================================
// AIDL 인터페이스 정의
// ============================================
aidl_interface {
name: "com.mycompany.myapp.aidl",
local_include_dir: "aidl",
srcs: [
"aidl/com/mycompany/myapp/*.aidl",
],
//stability: "vintf",
backend: {
java: {
sdk_version: "system_current",
enabled: true,
},
cpp: {
enabled: false,
},
ndk: {
enabled: true, // C++ 서비스용
},
},
// unstable로 설정하면 버전관리 안 함
unstable: true,
// versions_with_info: [
// {
// version: "1",
// imports: [],
// },
// ],
}
// ============================================
// C++ 네이티브 데몬 서비스
// ============================================
cc_binary {
name: "myapp_service_daemon",
// 설치 위치
vendor: true, // vendor 파티션
// 또는
// system_ext: true, // system_ext 파티션
relative_install_path: "hw", // /vendor/bin/hw/ 또는 /system_ext/bin/hw/
// 소스 파일
srcs: [
"native/src/main.cpp",
"native/src/MyAppService.cpp",
"native/src/DataProcessor.cpp",
],
// 헤더 경로
local_include_dirs: [
"native/include",
],
// 컴파일 플래그
cflags: [
"-Wall",
"-Werror",
"-Wextra",
],
// C++ 표준
cpp_std: "c++17",
// 공유 라이브러리 의존성
shared_libs: [
"libbase",
"libbinder_ndk",
"liblog",
"libutils",
],
// 정적 라이브러리 의존성
static_libs: [
"com.mycompany.myapp.aidl-V1-ndk", // AIDL 생성 라이브러리
],
// Init RC 스크립트
init_rc: ["native/myapp_service_daemon.rc"],
// VINTF manifest (HAL인 경우)
vintf_fragments: ["native/myapp_service_daemon.xml"],
}
// ============================================
// Java 라이브러리 (앱에서 사용할 클라이언트)
// ============================================
java_library {
name: "myapp-service-client",
srcs: [
"src/com/mycompany/myapp/client/**/*.java",
],
static_libs: [
"com.mycompany.myapp.aidl-V1-java", // AIDL Java 바인딩
],
sdk_version: "system_current",
}
// ============================================
// Android App (APK)
// ============================================
android_app {
name: "MyApp",
// 앱 타입
platform_apis: true,
privileged: true, // system/priv-app에 설치
certificate: "platform", // 플랫폼 서명
// 소스 파일
srcs: [
"src/**/*.java",
],
// 리소스
resource_dirs: ["res"],
// Manifest
manifest: "AndroidManifest.xml",
// 의존성
static_libs: [
"androidx.appcompat_appcompat",
"com.google.android.material_material",
"myapp-service-client", // 위에서 정의한 클라이언트 라이브러리
],
// JNI 라이브러리 (필요시)
jni_libs: [
// "libmyapp_jni",
],
optimize: {
enabled: false,
},
dex_preopt: {
enabled: false,
},
}
// ============================================
// JNI 라이브러리 (필요한 경우)
// ============================================
cc_library_shared {
name: "libmyapp_jni",
srcs: [
"jni/MyAppJNI.cpp",
],
shared_libs: [
"liblog",
"libnativehelper",
],
static_libs: [
"com.mycompany.myapp.aidl-V1-ndk",
],
cflags: [
"-Wall",
"-Werror",
],
sdk_version: "current",
}
// ============================================
// 선택사항: 네이티브 테스트
// ============================================
cc_test {
name: "myapp_service_test",
srcs: [
"native/test/MyAppServiceTest.cpp",
],
shared_libs: [
"libbase",
"libbinder_ndk",
"liblog",
],
static_libs: [
"com.mycompany.myapp.aidl-V1-ndk",
"libgmock",
"libgtest",
],
test_suites: ["device-tests"],
}
2. 디렉토리 구조
packages/apps/MyApp/
├── Android.bp # 통합 빌드 파일
├── AndroidManifest.xml # Java App Manifest
│
├── aidl/ # AIDL 인터페이스
│ └── com/mycompany/myapp/
│ ├── IMyAppService.aidl
│ └── IMyAppCallback.aidl
│
├── src/ # Java App 소스
│ └── com/mycompany/myapp/
│ ├── MainActivity.java
│ ├── MyApplication.java
│ └── client/
│ └── ServiceClient.java
│
├── res/ # Android 리소스
│ ├── layout/
│ ├── values/
│ └── drawable/
│
├── native/ # C++ 데몬 소스
│ ├── include/
│ │ ├── MyAppService.h
│ │ └── DataProcessor.h
│ │
│ ├── src/
│ │ ├── main.cpp
│ │ ├── MyAppService.cpp
│ │ └── DataProcessor.cpp
│ │
│ ├── myapp_service_daemon.rc # Init RC
│ └── myapp_service_daemon.xml # VINTF manifest
│
├── jni/ # JNI (필요시)
│ └── MyAppJNI.cpp
│
└── test/ # 테스트
└── MyAppServiceTest.cpp
3. AIDL 인터페이스
aidl/com/mycompany/myapp/IMyAppService.aidl
package com.mycompany.myapp;
import com.mycompany.myapp.IMyAppCallback;
interface IMyAppService {
/**
* 데이터 처리 요청
*/
String processData(String input);
/**
* 설정 변경
*/
boolean setConfig(in byte[] config);
/**
* 콜백 등록
*/
void registerCallback(IMyAppCallback callback);
/**
* 상태 조회
*/
int getStatus();
}
aidl/com/mycompany/myapp/IMyAppCallback.aidl
package com.mycompany.myapp;
oneway interface IMyAppCallback {
void onDataChanged(String newData);
void onStatusChanged(int status);
}
4. C++ 데몬 구현
native/include/MyAppService.h
#pragma once
#include <aidl/com/mycompany/myapp/BnMyAppService.h>
#include <aidl/com/mycompany/myapp/IMyAppCallback.h>
#include <memory>
#include <mutex>
#include <vector>
namespace aidl {
namespace com {
namespace mycompany {
namespace myapp {
class MyAppService : public BnMyAppService {
public:
MyAppService();
virtual ~MyAppService();
// IMyAppService 인터페이스 구현
ndk::ScopedAStatus processData(
const std::string& input,
std::string* _aidl_return) override;
ndk::ScopedAStatus setConfig(
const std::vector<uint8_t>& config,
bool* _aidl_return) override;
ndk::ScopedAStatus registerCallback(
const std::shared_ptr<IMyAppCallback>& callback) override;
ndk::ScopedAStatus getStatus(int32_t* _aidl_return) override;
private:
std::mutex mMutex;
std::vector<std::shared_ptr<IMyAppCallback>> mCallbacks;
int32_t mStatus;
void notifyDataChanged(const std::string& data);
void notifyStatusChanged(int32_t status);
};
} // namespace myapp
} // namespace mycompany
} // namespace com
} // namespace aidl
native/src/MyAppService.cpp
#define LOG_TAG "MyAppService"
#include "MyAppService.h"
#include <android-base/logging.h>
namespace aidl {
namespace com {
namespace mycompany {
namespace myapp {
MyAppService::MyAppService() : mStatus(0) {
LOG(INFO) << "MyAppService created";
}
MyAppService::~MyAppService() {
LOG(INFO) << "MyAppService destroyed";
}
ndk::ScopedAStatus MyAppService::processData(
const std::string& input,
std::string* _aidl_return) {
LOG(DEBUG) << "processData: " << input;
std::lock_guard<std::mutex> lock(mMutex);
// 실제 데이터 처리 로직
std::string result = "Processed: " + input;
// 콜백 알림
notifyDataChanged(result);
*_aidl_return = result;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus MyAppService::setConfig(
const std::vector<uint8_t>& config,
bool* _aidl_return) {
LOG(DEBUG) << "setConfig: " << config.size() << " bytes";
std::lock_guard<std::mutex> lock(mMutex);
// 설정 적용 로직
// ...
*_aidl_return = true;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus MyAppService::registerCallback(
const std::shared_ptr<IMyAppCallback>& callback) {
if (!callback) {
return ndk::ScopedAStatus::fromServiceSpecificError(
-1, "Callback is null");
}
std::lock_guard<std::mutex> lock(mMutex);
// 중복 체크
for (const auto& cb : mCallbacks) {
if (cb->asBinder() == callback->asBinder()) {
LOG(WARNING) << "Callback already registered";
return ndk::ScopedAStatus::ok();
}
}
mCallbacks.push_back(callback);
LOG(INFO) << "Callback registered, total: " << mCallbacks.size();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus MyAppService::getStatus(int32_t* _aidl_return) {
std::lock_guard<std::mutex> lock(mMutex);
*_aidl_return = mStatus;
return ndk::ScopedAStatus::ok();
}
void MyAppService::notifyDataChanged(const std::string& data) {
for (auto it = mCallbacks.begin(); it != mCallbacks.end(); ) {
auto status = (*it)->onDataChanged(data);
if (!status.isOk()) {
LOG(WARNING) << "Callback failed, removing";
it = mCallbacks.erase(it);
} else {
++it;
}
}
}
void MyAppService::notifyStatusChanged(int32_t status) {
mStatus = status;
for (auto it = mCallbacks.begin(); it != mCallbacks.end(); ) {
auto st = (*it)->onStatusChanged(status);
if (!st.isOk()) {
it = mCallbacks.erase(it);
} else {
++it;
}
}
}
} // namespace myapp
} // namespace mycompany
} // namespace com
} // namespace aidl
native/src/main.cpp
#define LOG_TAG "MyAppServiceDaemon"
#include "MyAppService.h"
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
using aidl::com::mycompany::myapp::MyAppService;
int main(int argc, char** argv) {
// 로깅 초기화
android::base::InitLogging(argv, android::base::LogdLogger());
LOG(INFO) << "Starting MyApp Service Daemon";
// Binder 스레드 풀 설정
ABinderProcess_setThreadPoolMaxThreadCount(4);
ABinderProcess_startThreadPool();
// 서비스 생성
std::shared_ptr<MyAppService> service =
ndk::SharedRefBase::make<MyAppService>();
// 서비스 등록
const std::string instance = std::string() +
MyAppService::descriptor + "/default";
binder_status_t status = AServiceManager_addService(
service->asBinder().get(), instance.c_str());
if (status != STATUS_OK) {
LOG(ERROR) << "Failed to register service: " << status;
return 1;
}
LOG(INFO) << "Service registered: " << instance;
// 메인 스레드를 Binder 스레드로 전환
ABinderProcess_joinThreadPool();
return 0;
}
5. Init RC 파일
native/myapp_service_daemon.rc
service myapp_service_daemon /vendor/bin/hw/myapp_service_daemon
class hal
user system
group system
disabled
on boot
start myapp_service_daemon
on property:sys.boot_completed=1
start myapp_service_daemon
6. Java App에서 사용
src/com/mycompany/myapp/client/ServiceClient.java
package com.mycompany.myapp.client;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import com.mycompany.myapp.IMyAppService;
import com.mycompany.myapp.IMyAppCallback;
public class ServiceClient {
private static final String TAG = "ServiceClient";
private static final String SERVICE_NAME =
"com.mycompany.myapp.IMyAppService/default";
private IMyAppService mService;
private final IMyAppCallback mCallback = new IMyAppCallback.Stub() {
@Override
public void onDataChanged(String newData) {
Log.d(TAG, "Data changed: " + newData);
// UI 업데이트 등
}
@Override
public void onStatusChanged(int status) {
Log.d(TAG, "Status changed: " + status);
}
@Override
public int getInterfaceVersion() {
return IMyAppCallback.VERSION;
}
@Override
public String getInterfaceHash() {
return IMyAppCallback.HASH;
}
};
public boolean connect() {
try {
IBinder binder = ServiceManager.waitForService(SERVICE_NAME);
if (binder == null) {
Log.e(TAG, "Service not found");
return false;
}
mService = IMyAppService.Stub.asInterface(binder);
mService.registerCallback(mCallback);
Log.d(TAG, "Connected to service");
return true;
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect", e);
return false;
}
}
public String processData(String input) {
if (mService == null) {
Log.e(TAG, "Service not connected");
return null;
}
try {
return mService.processData(input);
} catch (RemoteException e) {
Log.e(TAG, "Failed to process data", e);
return null;
}
}
public boolean setConfig(byte[] config) {
if (mService == null) return false;
try {
return mService.setConfig(config);
} catch (RemoteException e) {
Log.e(TAG, "Failed to set config", e);
return false;
}
}
public int getStatus() {
if (mService == null) return -1;
try {
return mService.getStatus();
} catch (RemoteException e) {
Log.e(TAG, "Failed to get status", e);
return -1;
}
}
}
src/com/mycompany/myapp/MainActivity.java
package com.mycompany.myapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.mycompany.myapp.client.ServiceClient;
public class MainActivity extends Activity {
private ServiceClient mServiceClient;
private EditText mInputText;
private TextView mResultText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mInputText = findViewById(R.id.inputText);
mResultText = findViewById(R.id.resultText);
Button btnProcess = findViewById(R.id.btnProcess);
// 서비스 연결
mServiceClient = new ServiceClient();
new Thread(() -> mServiceClient.connect()).start();
// 데이터 처리 버튼
btnProcess.setOnClickListener(v -> {
String input = mInputText.getText().toString();
new Thread(() -> {
String result = mServiceClient.processData(input);
runOnUiThread(() -> {
if (result != null) {
mResultText.setText(result);
}
});
}).start();
});
}
}
7. SELinux 정책
sepolicy/myapp_service.te
type myapp_service, domain;
type myapp_service_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(myapp_service)
binder_use(myapp_service)
binder_call(myapp_service, servicemanager)
add_service(myapp_service, myapp_service_service)
# 필요한 권한 추가
핵심 포인트
하나의 Android.bp: 여러 타입의 모듈을 하나의 파일에 정의 가능
AIDL 공유: Java와 C++ 모두 같은 AIDL 인터페이스 사용
독립적 빌드: 각 모듈은 독립적으로 빌드되지만 의존성 관리됨
IPC 통신: Binder 기반 AIDL로 프로세스 간 통신
모듈화: 클라이언트 라이브러리를 분리하여 재사용 가능
이 구조를 사용하면 Java 앱과 C++ 데몬을 효율적으로 관리하고 IPC를 통해 통신할 수 있습니다.
'Android > Android' 카테고리의 다른 글
| [Android] Android.mk와 AIDL(작성중) (0) | 2026.01.21 |
|---|---|
| [Android] te 파일에 대해 (Sepolicy) (0) | 2026.01.21 |
| [ADB] 각종 ADB shell 명령어 + Android settings 명령어 (0) | 2025.08.19 |
| [Android] Android ACTs (0) | 2025.08.19 |
| [Android] seapp_contexts 예제 형식 (0) | 2024.03.06 |
