Android Service ex. (w/ AIDL)

2025. 6. 11. 10:32Frontend/Android

    목차
반응형

defininition

두 숫자를 입력으로 받아 합한 결과를 제공하는 간단한 서비스를 정의해 보겠습니다.
다음과 같이 add 메서드를 선언한 IMyAidlInterface라는 이름의 인터페이스를 정의합니다.

defines AIDL file

// IMyAidlInterface.aidl
package com.example.aidl;

interface IMyAidlInterface {
    int add(int x, int y);
}

Generated code

Build > Make Project를 수행하면 다음의 파일이 생성됩니다.

app/build/generated/aidl_source_output_dir/debug/out/com/example/aidl/IMyAidlInterface.java
생성된 파일의 내용은 다음과 같습니다.

package com.example.aidl;

public interface IMyAidlInterface extends android.os.IInterface {
    public int add(int x, int y) throws android.os.RemoteException;

    public static abstract class Stub extends android.os.Binder implements IMyAidlInterface {
        public Stub() { /* ... */ }

        // asInterface: IBinder → IMyAidlInterface로 변환
        public static IMyAidlInterface asInterface(android.os.IBinder obj) { /* ... */ }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        // onTransact: Binder IPC 메시지를 실제 메서드로 변환
        @Override
        protected boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
                throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION:
                    reply.writeString(DESCRIPTOR);
                    return true;
                case TRANSACTION_add:
                    data.enforceInterface(DESCRIPTOR);
                    int x = data.readInt();
                    int y = data.readInt();
                    int result = this.add(x, y);
                    reply.writeNoException();
                    reply.writeInt(result);
                    return true;
            }
            return super.onTransact(code, data, reply, flags);
        }
    }

    public static class Default implements IMyAidlInterface {
        @Override
        public int add(int x, int y) throws android.os.RemoteException {
            return 0;
        }
    }

    // Proxy: 클라이언트에서 실제로 Binder에 호출을 넘기는 역할
    public static class Proxy implements IMyAidlInterface {
        private android.os.IBinder mRemote;

        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }

        @Override
        public int add(int x, int y) throws android.os.RemoteException {
            android.os.Parcel data = android.os.Parcel.obtain();
            android.os.Parcel reply = android.os.Parcel.obtain();
            int result;
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                data.writeInt(x);
                data.writeInt(y);
                mRemote.transact(Stub.TRANSACTION_add, data, reply, 0);
                reply.readException();
                result = reply.readInt();
            } finally {
                data.recycle();
                reply.recycle();
            }
            return result;
        }

        @Override
        public android.os.IBinder asBinder() {
            return mRemote;
        }
    }
}

Usage

이를 상속 받아 서비스를 제공하는 코드를 작성합니다.

service side

class MyService : Service() {
    // Stub을 상속해서 메서드 구현
    private val binder = object : IMyAidlInterface.Stub() {
        override fun add(x: Int, y: Int): Int {
            return x + y
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }
}

AndroidManifest.xml에는 서비스를 등록합니다.

<service
    android:name=".MyService"
    android:exported="true">
    <intent-filter>
        <action android:name="com.example.aidl.IMyAidlInterface" />
    </intent-filter>
</service>

client side

ServiceConnection을 상속받는 객체를 생성하고 onServiceConnected callback이 불릴때 proxy 객체를 얻습니다.
이후 onCreate 시 myAidl?.add를 호출해서 서비스가 제공하는 기능을 RPC로서 수행을 요청합니다.

class MainActivity : AppCompatActivity() {
    private var myAidl: IMyAidlInterface? = null
    private var isBound = false

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            myAidl = IMyAidlInterface.Stub.asInterface(service)
            isBound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            myAidl = null
            isBound = false
        }
    }

    override fun onStart() {
        super.onStart()
        val intent = Intent("com.example.aidl.IMyAidlInterface")
        intent.setPackage("com.example.aidl") // 서비스 패키지명
        bindService(intent, connection, Context.BIND_AUTO_CREATE)
    }

    override fun onStop() {
        super.onStop()
        if (isBound) {
            unbindService(connection)
            isBound = false
        }
    }

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

        // 예시: 버튼 클릭 시 AIDL 메서드 호출
        findViewById<Button>(R.id.btn_add).setOnClickListener {
            if (isBound && myAidl != null) {
                val x = 10
                val y = 20
                try {
                    val result = myAidl?.add(x, y)
                    Toast.makeText(this, "Result: $result", Toast.LENGTH_SHORT).show()
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            } else {
                Toast.makeText(this, "서비스 연결 안 됨", Toast.LENGTH_SHORT).show()
            }
        }
    }
}
반응형