Android - 다른 앱의 Service에 바인딩

AIDL을 이용하여 Remote Service 구현에서 AIDL으로 Remote Service를 구현하였습니다. 이 예제는 동일한 앱에서 정의한 서비스로 바인딩하는 예제였습니다.

만약 다른 앱에서 이 서비스로 바인딩하려면 어떻게 해야 할까요?

다른 앱의 서비스에 바인딩하는 것은, 자신의 앱의 서비스에 바인딩하는 것과 거의 동일합니다. 하지만, 다음과 같은 내용을 추가로 구현해야 합니다.

  • 서비스를 제공하는 앱에서 누구나 서비스에 바인딩할 수 있도록 선언 (exported="true")
  • 서비스에 바인딩하는 앱이 IRemoteService, IRemoteServiceCallback를 참조할 수 있도록 AIDL 산출물을 라이브러리로 제공

이 글에서 사용되는 앱은 RemoteService 앱과 Client 앱 입니다. RemoteService에 서비스가 구현되어있으며, AIDL을 이용하여 Remote Service 구현에서 소개된 샘플 앱입니다.

RemoteService 앱의 프로젝트에 AIDL 라이브러리와 Client 앱을 추가할 것입니다.

이 글에서 만든 프로젝트는 GitHub - RemoteService and Client App(Kotlin)에서 확인할 수 있습니다.

Client 앱에서 IRemoteService, IRemoteServiceCallback 참조

Client는 ServiceConnection을 통해서 서비스의 Binder 객체를 얻을 수 있지만, 이 바인더 객체를 IRemoteService, IRemoteServiceCallback으로 변환하려면 앱이 이 클래스들을 참조하고 있어야 합니다.

RemoteService에서 구현된 바인더 인터페페이스를 Client에서 참조하려면 다음과 같은 방법이 있습니다.

  1. RemoteService와 Client 앱에서 동일한 코드의 AIDL파일을 각각 생성하고 컴파일
  2. AIDL 인터페이스에 대한 Jar 라이브러리를 만들고, 그것을 RemoteService와 Client 앱에서 참조

여기서는 2의 방법으로 구현하는 것을 소개합니다.

Client 앱 및 AIDL 라이브러리 모듈 추가

앞으로 AIDL을 이용하여 Remote Service 구현에서 소개된 RemoteService 프로젝트에 Client 앱과 AIDL 라이브러리를 추가할 것입니다.

먼저 이 프로젝트를 다운로드받고 안드로이드 스튜디오에서 실행해주세요.

[File]->[New]->[New Module]를 클릭하여 다음과 같이 Client앱을 추가합니다. client app client app

AIDL 라이브러리는 Android Library로 추가합니다. aidl library package name은 RemoteService와 동일한 com.example.remoteservice로 설정하시면 됩니다. aidl library

RemoteService 앱의 Service를 Public으로 만들기

기본적으로 AndroidManifest에 서비스를 정의하면 자신의 앱에서만 실행할 수 있습니다.

다른 앱에서 실행할 수 있도록 만드려면 Service TAG에 exported="true"를 추가해야 합니다.

com.example.remoteservice 앱의 Manifest에 다음과 같이 service를 정의하면 됩니다. 또한, 다른 앱에서 이 서비스를 찾기 쉽도록 하려고 MY_SERVICE라는 Action으로 IntentFilter를 등록하였습니다.

<service android:name=".RemoteService" android:process=":remote"
         android:exported="true">
    <intent-filter>
        <action android:name="com.example.remoteservice.MY_SERVICE" />
    </intent-filter>
</service>

이제 Client 앱에서 MY_SERVICE Action으로 이 서비스를 찾을 수 있고, 실행시킬 수도 있습니다.

AIDL Module 구현

aidllib 모듈의 main/aidl/com/example/remoteservice 경로에 RemoteService에서 구현한 AIDL 파일 두개를 이동시킵니다.

RemoteService도 aidllib을 참조할 것이기 때문에 AIDL 파일은 삭제해도 됩니다.

aidllib 모듈을 Build를 하면 IRemoteService와 IRemoteServiceCallback 클래스가 생성되며 다른 앱에서 참조할 수 있습니다.

RemoteService와 Client에서 aidllib 라이브러리 참조

RemoteService와 Client 앱에서 aidllib을 의존성으로 추가해야 클래스를 참조할 수 있습니다.

[File] -> [Project Structure]를 누르면 다음과 같은 화면이 보입니다. RemoteService와 Client 앱의 Module dependency에 aidllib을 추가하면 앱에서 이 라이브러리의 코드를 참조할 수 있습니다. add dependency

add dependency

Client에서 Remote Service에 바인딩

Client 앱의 MainActivty는 RemoteService의 MainActivity와 이름이 같기 때문에, MainActivity2로 변경하였습니다.

다음은 MainActivity2에서 RemoteService 앱의 Service에 바인딩하는 코드입니다.

class MainActivity2 : AppCompatActivity() {

    companion object {
        const val TAG = "MainActivity2"
    }

    private var bound = false;
    private var iRemoteService: IRemoteService? = null

    private var callback = object : IRemoteServiceCallback.Stub() {
        override fun onItemAdded(name: String?) {
            Log.d(TAG, "onItemAdded: $name")
            bound = true
        }

        override fun onItemRemoved(name: String?) {
            Log.d(TAG, "onItemRemoved: $name")
            bound = false
        }
    }

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            Log.d(TAG, "onServiceConnected: $className")
            iRemoteService = IRemoteService.Stub.asInterface(service)
            iRemoteService!!.addCallback(callback)
        }

        override fun onServiceDisconnected(className: ComponentName) {
            Log.d(TAG, "onServiceDisconnected: $className")
            iRemoteService = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()
        if (!bound) {
            val intent = Intent("com.example.remoteservice.MY_SERVICE")
            intent.setPackage("com.example.remoteservice")
            bindService(intent, connection, Context.BIND_AUTO_CREATE);
        }
    }

    override fun onStop() {
        super.onStop()
        if (bound) {
            iRemoteService!!.removeCallback(callback)
            unbindService(connection)
        }
    }
}

대부분의 코드는 AIDL을 이용하여 Remote Service 구현에서 소개한 MainActivity의 코드와 동일합니다.

다른 부분은 인텐트 객체를 생성할 때 MY_SERVICE Action과 setPackage()com.example.remoteservice 패키지를 설정한 것 입니다. RemoteService 앱의 서비스는 MY_SERVICE Action을 갖고 있는 인텐트 필터로 등록되었기 때문에, 아래와 같은 인텐트로 서비스를 찾을 수 있습니다.

val intent = Intent("com.example.remoteservice.MY_SERVICE")
intent.setPackage("com.example.remoteservice")
bindService(intent, connection, Context.BIND_AUTO_CREATE);

QUERY 퍼미션 설정

Android 11에서는 Package visibility로, Target SDK API가 30 이상인 앱은 기본적으로 다른 앱의 정보를 검색할 수 없습니다. 다른 앱을 찾고 싶다면 AndroidManifest에 <Query> Syntax로 패키지 이름을 설정하거나 다음과 같이 QUERY_ALL_PACKAGES 퍼미션을 선언해야 합니다.

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

Client 앱의 Target API가 30 이상으로 설정되어 있다면 위의 퍼미션을 꼭 선언하셔야 합니다. API 30 미만인 앱은 Package visibility가 적용되지 않기 때문에 퍼미션을 추가하지 않으셔도 됩니다.

실행

Client 앱을 실행해보면 Remote Service에 바인딩되면서 아래와 같이 로그가 출력하게 됩니다.

12-20 14:05:19.987  5754  5754 D MainActivity2: onServiceConnected: ComponentInfo{com.example.remoteservice/com.example.remoteservice.RemoteService}
12-20 14:05:19.989 20138 20156 D RemoteService: Add callback : com.example.remoteservice.IRemoteServiceCallback$Stub$Proxy@882781d
12-20 14:05:24.977  5754  5772 D MainActivity2: onItemAdded: new item
12-20 14:05:24.977  5754  5772 D MainActivity2: onItemRemoved: old item
12-20 14:05:29.980  5754  5772 D MainActivity2: onItemAdded: new item
12-20 14:05:29.984  5754  5772 D MainActivity2: onItemRemoved: old item

로그에서 보이는 PID로 프로세스를 확인하면 Client와 RemoteService 앱이라는 것을 다시 확인할 수 있습니다.

js@js:~/codechacha$ adb shell ps -ef | grep com.example
u0_a156        5754    276 1 14:05:18 ?     00:00:00 com.example.client
u0_a153       20112    276 0 10:54:57 ?     00:00:05 com.example.remoteservice
u0_a153       20138    276 0 10:54:57 ?     00:00:36 com.example.remoteservice:remote

정리

Client 앱에서 RemoteService에 구현된 Service로 바인딩하는 과정을 알아보았습니다. 서비스 구현과 별개로, 두개의 앱에서 IRemoteService, IRemoteServiceCallback 클래스를 사용하기 때문에 라이브러리 모듈로 만들고 함께 참조하도록 하였습니다.

만약 프로젝트가 다른 두개의 앱에서 IRemoteService 등의 바인더 인터페이스에 접근이 필요하다면 AIDL의 산출물을 Jar 파일로 만들어 각각의 앱에 의존성으로 추가되도록 만들어야 합니다.

Loading script...

Related Posts

codechachaCopyright ©2019 codechacha