[Android] IPC를 위한 3가지 방법 소개, 각 방법의 특징과 차이점은?

mhyun, Park·2023년 1월 8일
2

App을 개발하다 보면, 서로 다른 Process와 통신해야 할 필요가 있다.
특히, Middle-Ware 성격의 App을 개발하다 보면 IPC의 중요성과 필요성은 더욱 커지게 되는데
Android는 이러한 IPC mechanism을 위해 아래와 같은 세 가지 방식들을 제공하고 있다.

[1] BroadCastReceiver

BroadcastReceiverpublish-subscribe Design Pattern와 같이
등록된 Receiver들에 대해 특정 이벤트에 대한 Broadcast를 보내 IPC를 구현하는 방법으로써 예를 들어,

  • 시스템이 부팅되었을 때
  • 디바이스가 충전을 시작했을 때
  • 새로운 데이터가 다운로드 됐다거나 하는 상황 등에 이용된다.

즉, BroadcastReceiver비동기로 동작되며 Event를 전달받은 onReceive()UI Thread에서 처리되고 있다.
그리고 기본적으로 Android OS를 통한 1:多 단방향(one-way) 통신이기에 보안에 위협되는 data전달 시에는 고려되지 않는 방법이다.

BroadcastReceiver 에 대한 보안 취약점 대응을 위해 Android는 package 설정, permission 설정, LocalBroadcastReceiver 이용과 같이 BroadcastReceiver 범위를 제한하도록 권고하고 있으며 자세한 내용은 BroadcastRecevier 보안 고려사항 및 권장사항에서 확인할 수 있다.

Android BroadcastReceiver Example Tutorial
https://www.digitalocean.com/community/tutorials/android-broadcastreceiver-example-tutorial

[2] Messagner

Messenger는 객체 생성시 파라미터로 전달한 Handler를 통해 MessageQueue에 Message를 전달함으로써 IPC를 구현하는 방법이다.
사실 Handler의 경우 한 Process 내 Thread들간의 통신 역할을 담당하고있지만, Messenger의 경우 Parcelable을 상속받고 있고 전달받은 Handler를 wrapping 함으로써 해당 Local Handler를 다른 프로세스에서 이용 할 수 있는 환경을 조성하고 있다.

/**
 * Reference to a Handler, which others can use to send messages to it.
 * This allows for the implementation of message-based communication across
 * processes, by creating a Messenger pointing to a Handler in one process,
 * and handing that Messenger to another process.
 *
 * <p>Note: the implementation underneath is just a simple wrapper around
 * a {@link Binder} that is used to perform the communication.  This means
 * semantically you should treat it as such: this class does not impact process
 * lifecycle management (you must be using some higher-level component to tell
 * the system that your process needs to continue running), the connection will
 * break if your process goes away for any reason, etc.</p>
 */
public final class Messenger implements Parcelable {
    private final IMessenger mTarget;
    /**
     * Create a new Messenger pointing to the given Handler.  Any Message
     * objects sent through this Messenger will appear in the Handler as if
     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
     * been called directly.
     * 
     * @param target The Handler that will receive sent messages.
     */
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
    ...
}

Messenger를 통해 원격 프로세스에서 보낸 MessageMessageQueue를 통해 다른 Process의 Local Handler로 전달된다.
그리고 MessageQueue는 Single Thread에서 한번에 하나의 Message를 처리하기에 Thread-Safe하며 비동기로 동작된다.

또한, MessagereplyTo property를 통해 Message를 보낸 Messenger에 대한 instance를 얻을 수 있다.
이는 Server가 Client의 Message를 받고 reply할 수 있는 two-way communication 환경을 조성해준다.

/**
 * Defines a message containing a description and arbitrary data object that can be sent to a Handler. 
 * This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.
 * 
 * While the constructor of Message is public, 
 * the best way to get one of these is to call Message.obtain() 
 * or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.
**/
public final class Message implements Parcelable {
    public int what;
    public int arg1;
    public int arg2;
    public Object obj;
    
    /**
     * Optional Messenger where replies to this message can be sent.
     * The semantics of exactly how this is used are up to the sender and receiver.
     */
    public Messenger replyTo;
    ...
}

Android Messenger Example Tutorial
https://proandroiddev.com/ipc-techniques-for-android-messenger-3e8555a32167

[3] AIDL (Android Interface Definition Language)

AIDL(Android Interface Definition Language)은 Client와 Service가 모두 동의한 프로그래밍 Interface를 정의하여 해당 Interface를 통해 1:1 Process 간 통신(IPC)을 구현하는 방법이다.

[ IRemoteService.aidl ]

package com.example.android

interface IRemoteService {
    /** 
     * Request a calculatation to the Service. 
     */
    int calculate(a:Int, b:Int, c: Int);
}

앞서 설명한 [1] BroadcastReceiver[2] Messenger 의 경우 원격 process에서 전달한 data을 이용하기 위해선 Marshaling을 해줘야하지만 AIDL을 이용할 경우, 공통된 interface에서 주고 받을 data type을 토대로 Android OS가 Marshaling 작업을 대신 수행해주고 있기 때문에 App단에선 더욱 깔끔하고 명확한 RPC 통신을 구현할 수 있다.

또한, AIDL 통한 method 호출은 Direct Call이다. 즉, 여러 원격 Process가 호출할 경우에 대한 순서를 보장하지 못한다.
이 때문에 개발자는 Concurrency를 고려하여 AIDL에 대한 Interface 구현해야 하며
Server 측 Binder 연결이 끊어진 상태에서 Client가 AIDL api를 호출하면 DeadObjectException 예외를 발생시키기에 개발자는 Client 측에서 해당 예외가 발생할 수 있음을 염두해야 한다

01-07	16:02:17.953   	22273	22273	W	System.err:	android.os.DeadObjectException
01-07	16:02:17.981   	22273	22273	I	binder:	22273:22273 transaction failed 29189/-22, size 108-0 line 3289
01-07	16:02:17.958   	22273	22273	W	System.err:		at android.os.BinderProxy.transactNative(Native Method)
01-07	16:02:17.958   	22273	22273	W	System.err:		at android.os.BinderProxy.transact(BinderProxy.java:662)
01-07	16:02:17.958   	22273	22273	W	System.err:		at com.example.aidl_service.aidlInterface$Stub$Proxy.calculateData(aidlInterface.java:92)
01-07	16:02:17.958   	22273	22273	W	System.err:		at com.example.aidl_client.MainActivity.verifyAndCalculate(MainActivity.java:95)

그리고 AIDL은 정의된 API의 return Type을 통해 two-way communication을 지원하며 기본적으로 Sync call이다.
하지만 만약, aidl call을 비동기적으로 진행하고 싶은 경우엔 aidl method keyword에 oneway 를 붙여주고 return type은 void로 지정하면 된다.

[IRemoteService.aidl]

package com.example.android

interface IRemoteService {
    /** 
     * Request initialization to the Service. 
     */
    oneway void initialize();

    /** 
     * Request a calculatation to the Service. 
     */
    int calculate(a:Int, b:Int, c: Int);
}

혹은, 위와 같은 비동기 작업에대한 결과를 Client가 알아야 할 경우 AIDL Callback을 함께 정의함으로써
위의 방법과 마찬가지로 비동기식 RPC를 구현할 수 있다.

[IRemoteService.aidl]

package com.example.android

import com.example.android.IRemoteServiceCallback;

interface IRemoteService {
    /** 
     * Request initialization to the Service. 
     */
    oneway void initialize(IRemoteServiceCallback callback);

    /** 
     * Request a calculatation to the Service. 
     */
    int calculate(a:Int, b:Int, c: Int);
}

[IRemoteServiceCallback.aidl]

package com.example.android;

oneway interface IRemoteServiceCallback {
    /** 
     * Notify the Service is initialized. 
     */
	void onInitialized();
}

마지막으로 AIDLin, out, inout 3 가지의 parameter keyword를 제공한다.
primitive type은 in만 지원하며, keyword를 정의하지 않았을 경우의 default keyword는 in이다.
각 keyword에 대한 대략적인 설명은 다음과 같다.

  • in - object is transferred from client to service only used for inputs
  • out - object is transferred from client to service only used for outputs.
  • inout - object is transferred from client to service used for both inputs and outputs.

즉, out keyword를 parameter에 사용할 경우 Callee인 Service 측에선 해당 data에 대한 write 권한을 갖게 된다.
예를 들어, 아래와 같은 aidl이 있을 경우 api 주석에 명시한대로 운용되며
비동기식 RPC를 지원하는 oneway type의 method는 out, inout keyword를 사용할 수 없다.

[IMemberManageService.aidl]

package com.example.android;

import com.example.android.Member;

interface IMemberManageService {
    /**
    * Service에선 parameter로 전달받은 member를 write 할 수 있다.
    */
	void getMember(out Member member);
    
    /**
    * Service에선 parameter로 전달받은 member를 read/write 할 수 있다.
    */
	void updateMember(inout Member member, in boolean isLeader);
    
    /**
    * Service에선 parameter로 전달받은 member를 read만 할 수 있다.
    */
	oneway void removeMember(in Member member);
}

마무리

이렇게 이번 포스팅에선 IPC를 위해 Android에서 제공하는 3가지 방식에 대해 알아봤다.
포스팅의 주된 주제는 AIDL 이지만, 각각의 방식의 특징과 차이점은 분명하기에 마주하는 상황에 맞춰 IPC를 구현하면 될 것 같다.

  • BroadcaseReceiver - one-way 비동기 IPC 구현 (구현 난이도 : 하)
  • Messenger - two-way 비동기 IPC 구현 (구현 난이도 : 중)
  • AIDL - 명확한 API를 통한 two-way 동기/비동기 IPC 구현 (구현 난이도 : 상)


Reference

1. 안드로이드 멀티스레딩5 — 프로세스 간 통신
2. AIDL을 이용한 Service <-> Activity간 Callback통신
3. "In/out/inout" in a AIDL interface parameter value

profile
Android Framework Developer

0개의 댓글