Remote Service는 App의 Main process와 다른 Process에서 실행되는 Service를 말합니다. MainActivity의 Process가 A process에서 실행이된다면, Service는 B process에서 실행됩니다. A와 B 프로세스는 서로 통신을 하여 동작을 해야 하기 때문에, 이 부분에 대해서 구현이 따로 필요합니다.
안드로이드에서는 RPC(Remote Procedure Call)를 Binder를 통해서 지원을 하는데, Binder로 매번 RPC를 구현하기는 귀찮고 번거롭기 때문에 좀 더 간단한 두가지 방법을 제공해주고 있습니다.
- AIDL(Android Interface Definition Language)
- Messenger Class
이 글에서는 Messenger class를 이용하여 Activity와 Service가 통신하는 샘플앱을 구현해보겠습니다.
샘플앱은 다음과 같은 동작을 합니다.
- Activity는 Service에 이벤트를 전달
- Service는 이벤트를 받고 어떤 동작을 수행
- Service는 Activity로 결과 이벤트를 전달
예제코드는 GitHub에서 확인할 수 있습니다.
Messenger를 이용하여 RPC를 구현
액티비티와 서비스가 다른 프로세스에서 실행되었을 때, 이 두개의 객체가 서로 메시지를 주고 받으려면 RPC를 구현해야 합니다. Messenger는 RPC를 쉽게 구현할 수 있도록 도와줍니다.
Messenger 객체를 생성하는데 필요한 것은 Binder와 Handler입니다. Binder를 이용하여 다른 Process와 binding하며 다른 Process에서 보낸 event는 Handler로 전달됩니다.
먼저 Service와 Activity를 구현하고, Messenger를 이용하여 두개의 Component들이 서로 통신할 수 있도록 만들겠습니다.
Remote Service 구현
아래 코드는 Remote Service를 구현한 코드입니다.
이 클래스는 Service를 상속하고 멤버변수로 Messenger 객체를 저장하는 mClientCallbacks
를 갖고 있습니다.
mClientCallbacks
리스트의 Messenger 객체들은, 서비스에 바인딩하는 Activity 등의 객체들이 전달해주는 것입니다. 서비스가 어떤 작업을 수행하고 그 결과 값을 전달해줄 때 이 Messenger 객체를 통해서 이벤트를 전달할 수 있습니다. (우리는 1개의 액티비티만 서비스에 바인딩할 것이기 때문에 리스트에는 1개의 객체만 추가됩니다)
반면에, 멤버변수 mMessenger
는 액티비티가 서비스에 이벤트를 전달할 때 사용하는 객체입니다. 액티비티가 서비스에 바인딩할 때, 서비스는 mMessenger
객체의 IBinder를 전달해 줍니다. 액티비티가 서비스로 전달한 이벤트들은 CallbackHandler 내부에서 처리가 됩니다.
public class RemoteService extends Service{
private final String TAG = "RemoteService";
public static final int MSG_CLIENT_CONNECT = 1;
public static final int MSG_CLIENT_DISCONNECT = 2;
public static final int MSG_ADD_VALUE = 3;
public static final int MSG_ADDED_VALUE = 4;
private ArrayList<Messenger> mClientCallbacks = new ArrayList<Messenger>();
final Messenger mMessenger = new Messenger(new CallbackHandler());
int mValue = 0;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
@Override
public void onDestroy(){
super.onDestroy();
}
private class CallbackHandler extends Handler {
@Override
public void handleMessage( Message msg ){
switch( msg.what ){
case MSG_CLIENT_CONNECT:
Log.d(TAG, "Received MSG_CLIENT_CONNECT message from client");
mClientCallbacks.add(msg.replyTo);
break;
case MSG_CLIENT_DISCONNECT:
Log.d(TAG, "Received MSG_CLIENT_DISCONNECT message from client");
mClientCallbacks.remove(msg.replyTo);
break;
case MSG_ADD_VALUE:
Log.d(TAG, "Received message from client: MSG_ADD_VALUE");
mValue += msg.arg1;
for (int i = mClientCallbacks.size() - 1; i >= 0; i--) {
try{
Log.d(TAG, "Send MSG_ADDED_VALUE message to client");
Message added_msg = Message.obtain(
null, RemoteService.MSG_ADDED_VALUE);
added_msg.arg1 = mValue;
mClientCallbacks.get(i).send(added_msg);
}
catch( RemoteException e){
mClientCallbacks.remove( i );
}
}
break;
}
}
}
}
Messenger를 통해 액티비티로부터 이벤트를 받을 때, Messenger는 서비스에 구현된 CallbackHandler
로 이벤트를 던저준다고 생각하시면 좋습니다.
그래서 우리가 구현해야할 것은 Handler에 이벤트를 추가하는 것 뿐입니다.
Remote Service 설정
동일한 앱에서 Activity와 Service가 다른 프로세스에서 실행되게 만드려면 AndroidManifest.xml에서 서비스를 remote로 동작하도록 설정해줘야 합니다.
Remote Service로 실행되게 하려면, AndroidManifest.xml에서 내가 만든 Service를 정의하고 android:process
속성을 Remote Service로 설정하여야 합니다.
<service
android:name=".RemoteService"
android:process=":remote"/>
Service TAG에서 android:process=":remote" 로 선언해야 Main process와는 다른 Process에서 Service가 실행됩니다.
Activity 구현
Activity는 멤버변수로 두개의 Messenger 객체를 갖고 있습니다.
- "mServiceCallback"은 서비스로부터 전달받는 객체입니다. 위의 서비스 코드에서 바인딩할 때 던져주는 IBinder로 만들어진 Messenger 객체입니다.
- "mClientCallback"는 서비스로 전달해주는 객체입니다. 서비스에서 액티비티로 결과를 리턴하기 위해 사용되는 Messenger 객체입니다.
mClientCallback
도 마찬가지로 CallbackHandler
객체를 생성자에 전달합니다.
서비스가 액티비티로 이벤트를 전달하면 Messenger 객체는 이벤트를 CallbackHandler
에 던져줍니다.
public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
private Messenger mServiceCallback = null;
private Messenger mClientCallback = new Messenger(new CallbackHandler());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnAddValue = (Button) findViewById(R.id.btn_add_value);
btnAddValue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mServiceCallback != null) {
// request 'add value' to service
Message msg = Message.obtain(
null, RemoteService.MSG_ADD_VALUE);
msg.arg1 = 10;
try {
mServiceCallback.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
Log.d(TAG, "Send MSG_ADD_VALUE message to Service");
}
}
});
Log.d(TAG, "Trying to connect to service");
Intent intent = new Intent(getApplicationContext(), RemoteService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected");
mServiceCallback = new Messenger(service);
// connect to service
Message connect_msg = Message.obtain( null, RemoteService.MSG_CLIENT_CONNECT);
connect_msg.replyTo = mClientCallback;
try {
mServiceCallback.send(connect_msg);
Log.d(TAG, "Send MSG_CLIENT_CONNECT message to Service");
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected");
mServiceCallback = null;
}
};
private class CallbackHandler extends Handler
{
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case RemoteService.MSG_ADDED_VALUE:
Log.d(TAG, "Recevied MSG_ADDED_VALUE message from service ~ value :" + msg.arg1);
break;
}
}
}
}
좀 더 자세한 설명
위의 코드를 부분적으로 나누어서 설명하겠습니다.
Activity에서 Service 바인딩
아래처럼 ServiceConnection을 정의하고 bindService를 통해 Service에 bind합니다.
바인딩이 되면 ServiceConnection
으로 IBinder 객체가 전달되고 그것으로 Messenger
객체를 만들 수 있습니다.
사실 Messenger 객체는 서비스로부터 받은 것은 아니지만, 서비스로부터 받은 IBinder로 만든 Messenger 객체입니다. 이 객체로 서비스에 이벤트를 전달할 수 있습니다. (그냥 서비스로부터 받은 Messenger객체라고 생각하시는게 이해하기에는 편합니다..)
아직 액티비티는 서비스에게 Messenger객체를 전달하지 않았습니다. 전달해주는 코드는 onServiceConnected()
에 있습니다.
바인딩으로 서비스로부터 Messenger객체를 받았고, 그 객체를 통해서 액티비티의 Messenger객체를 서비스에 전달하게 만들었습니다.
protected void onCreate(Bundle savedInstanceState) {
...
Log.d(TAG, "Trying to connect to service");
Intent i = new Intent(getApplicationContext(), MsgService.class);
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
...
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected");
mServiceCallback = new Messenger(service);
// connect to service
Message connect_msg = Message.obtain( null, MsgService.MSG_CLIENT_CONNECT);
connect_msg.replyTo = mClientCallback;
try {
mServiceCallback.send(connect_msg);
Log.d(TAG, "Send MSG_CLIENT_CONNECT message to Service");
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected");
mServiceCallback = null;
}
};
액티비티로부터 전달된 Messenger 객체
액티비티는 서비스로 Messenger객체를 전달하였습니다.
아래 코드는 서비스의 CallbackHandler
입니다. Messenger는 MSG_CLIENT_CONNECT
이벤트를 CallbackHandler로 던져줍니다.
Handler 코드를 보시면 Messenger 객체를 받아서 mClientCallbacks 리스트에 추가하고 있습니다.
private class CallbackHandler extends Handler {
@Override
public void handleMessage( Message msg ){
switch( msg.what ){
case MSG_CLIENT_CONNECT:
Log.d(TAG, "Received MSG_CLIENT_CONNECT message from client");
mClientCallbacks.add(msg.replyTo);
break;
...
Activity <=> Service 간의 이벤트 전달
지금까지 액티비티와 서비스의 Messenger 객체를 서로 교환하는 코드를 살펴보았습니다. Messenger의 도움으로 두개의 Component는 이제 서로에게 이벤트를 전달할 수 있습니다.
이제 일(Job)을 시켜보죠.
저는 샘플앱에서 액티비티가 서비스로 숫자를 전달하고, 서비스에서 누적된 숫자를 액티비티로 다시 리턴하는 내용을 구현하였습니다.
다음은 액티비티의 버튼이 눌렸을 때 동작하는 코드입니다. 버튼이 눌리면 숫자 10과 함께 MSG_ADD_VALUE
이벤트를 서비스로 전달합니다.
Button btnAddValue = (Button) findViewById(R.id.btn_add_value);
btnAddValue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mServiceCallback != null) {
// request 'add value' to service
Message add_msg = Message.obtain(
null, MsgService.MSG_ADD_VALUE);
add_msg.arg1 = 10;
try {
mServiceCallback.send(add_msg);
} catch (RemoteException e) {
e.printStackTrace();
}
Log.d(TAG, "Send MSG_ADD_VALUE message to Service");
}
}
});
다음은 서비스에서 MSG_ADD_VALUE
이벤트를 받는 코드입니다. 숫자를 받으면 멤버변수 mValue에 누적하고, 누적된 값을 액티비티로 전달합니다.
코드를 보시면 모든 callback 객체들에게 결과를 전달하고 있습니다. 바인딩된 액티비티가 10개라면 10개의 액티비티에 이벤트를 모두 전달하게 됩니다.
private class CallbackHandler extends Handler {
@Override
public void handleMessage( Message msg ){
switch( msg.what ){
...
case MSG_ADD_VALUE:
Log.d(TAG, "Received message from client: MSG_ADD_VALUE");
mValue += msg.arg1;
for (int i = mClientCallbacks.size() - 1; i >= 0; i--) {
try{
Log.d(TAG, "Send MSG_ADDED_VALUE message to client");
Message added_msg = Message.obtain(null, RemoteService.MSG_ADDED_VALUE);
added_msg.arg1 = mValue;
mClientCallbacks.get(i).send(added_msg);
}
catch( RemoteException e){
mClientCallbacks.remove( i );
}
}
break;
실행 결과
다음은 앱을 실행하고 버튼을 3번 눌렀을 때 출력되는 로그입니다. MainActivity와 RemoteService의 PID가 다른 것을 볼 수 있습니다.
10-29 21:27:14.815 5279 5279 D MainActivity: Trying to connect to service
10-29 21:27:15.123 5279 5279 D MainActivity: onServiceConnected
10-29 21:27:15.124 5279 5279 D MainActivity: Send MSG_CLIENT_CONNECT message to Service
10-29 21:27:15.129 5312 5312 D RemoteService: Received MSG_CLIENT_CONNECT message from client
10-29 21:27:27.394 5279 5279 D MainActivity: Send MSG_ADD_VALUE message to Service
10-29 21:27:27.394 5312 5312 D RemoteService: Received message from client: MSG_ADD_VALUE
10-29 21:27:27.394 5312 5312 D RemoteService: Send MSG_ADDED_VALUE message to client
10-29 21:27:27.402 5279 5279 D MainActivity: Recevied MSG_ADDED_VALUE message from service ~ value :10
10-29 21:27:52.284 5279 5279 D MainActivity: Send MSG_ADD_VALUE message to Service
10-29 21:27:52.285 5312 5312 D RemoteService: Received message from client: MSG_ADD_VALUE
10-29 21:27:52.285 5312 5312 D RemoteService: Send MSG_ADDED_VALUE message to client
10-29 21:27:52.297 5279 5279 D MainActivity: Recevied MSG_ADDED_VALUE message from service ~ value :20
10-29 21:27:53.623 5279 5279 D MainActivity: Send MSG_ADD_VALUE message to Service
10-29 21:27:53.624 5312 5312 D RemoteService: Received message from client: MSG_ADD_VALUE
10-29 21:27:53.624 5312 5312 D RemoteService: Send MSG_ADDED_VALUE message to client
10-29 21:27:53.630 5279 5279 D MainActivity: Recevied MSG_ADDED_VALUE message from service ~ value :30
정리
지금까지 Messenger를 이용하여 두개 이상의 Component가 서로 이벤트를 주고 받는 RPC를 구현하였습니다. Messenger를 이용하는 이유는 RPC 구현을 매우 쉽게 할 수 있기 때문입니다. (AIDL으로 구현하는게 더 편하다고 느끼실 수 있겠지만..)
포인트는 두개의 Component가 서로 Messenger 객체를 교환해야 한다는 것입니다. 서로 상대방으로 이벤트를 전달할 Messenger객체를 갖고 있으면 Handler에 이벤트를 구현해주는 것만 작성하면 더 구현할 것이 없습니다.
참고
- 예제코드는 GitHub에 있습니다
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력