728x90

안드로이드 바인딩 서비스 개요

startService() 메소드 대신 bindService() 메소드를 통해 시작되는 서비스를 서비스 바인딩이라 한다.

바인딩 서비스는 Activity가 클라이언트 역할을 하고, 서비스가 서버 역할을 한다.

startService() 메소드 대신 bindService() 메소드를 통해 시작되는 서비스를 서비스 바인딩이라 한다.

이 서비스는 Activity가 클라이언트 역할을 하고, 서비스가 서버 역할을 한다.



출처: https://link2me.tistory.com/1343 [소소한 일상 및 업무TIP 다루기]

BindService는 startService()를 통해 시작되는 UnBound Service와는 다르게
Activity / Fragment와 서비스간에 데이터를 주고 받을 수 있으며, 프로세스간의 통신에도 사용된다.

백그라운드에서 무한히 실행되지 않도록 Activity 종료시 자동으로 서비스를 종료시킬 수 있도록 구현해야 한다.

서비스 바인딩은 연결된 Activity가 사라지면 서비스도 소멸된다.
하나의 서비스에 다수의 Activity 연결이 가능하다.
서비스에 연결된 Activity(컴포넌트)가 하나도 남아있지 않으면 서비스는 종료된다.

서비스 바인딩 및 구현 동작

연결을 유지하고 데이터를 전송 받기 위한 ServiceConnection() 객체와 IBinder 인터페이스 객체가 필요하다.

ServiceConnection() 는 Service를 호출하는 Activity 에 만들어야 하고, IBinder 는 Service 에서 생성한 후 리턴해야 한다.


startService()를 호출하여 서비스를 시작하고 이를 통해 서비스가 무한히 실행되도록 할 수 있으며, bindService()를 호출하면 클라이언트(Activity)가 해당 서비스에 바인딩되도록 할 수 있다는 의미다.


서비스가 시작되고 바인딩되도록 허용한 다음, 서비스가 실제로 시작되면 시스템은 Activity가 모두 바인딩을 해제해도 서비스를 소멸시키지 않는다. 그 대신 서비스를 직접 확실히 중단해야 한다. 그러려면 stopSelf() 또는 stopService()를 호출하면 된다.


보통은 onBind() 또는 onStartCommand() 중 한 가지만 구현하지만, 둘 모두 구현해야 할 때도 있다.
클라이언트는 bindService()를 호출하여 서비스에 바인딩된다.
이때 반드시 서비스와의 연결을 모니터링하는 ServiceConnection의 구현을 제공해야 한다.
Android 시스템이 클라이언트와 서비스 사이에 연결을 설정하면 ServiceConnection에서 onServiceConnected()을 호출한다.
onServiceConnected() 메서드에는 IBinder 인수가 포함되고 클라이언트는 이를 사용하여 바인딩된 서비스와 통신한다.
바인딩된 서비스를 구현할 때 가장 중요한 부분은 onBind() 콜백 메서드가 반환하는 인터페이스를 정의하는 것이다.

바인딩 서비스 예제

안드로이드 서비스 예제를 통해 개념을 익히고 활용할 수 있도록 예제를 테스트하고 적어둔다.

https://link2me.tistory.com/1343 에 기본 서비스 예제가 있으며, 비교하여 달라진 점을 확인해 볼 수 있다.

bindService를 추가하면 서비스 종료시에 반드시 unbindService(mConnection) 처리를 해줘야만 제대로 종료가 됨을 확인할 수 있다.


layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_servicestart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:layout_gravity="center"
        android:text="서비스 시작"
        android:textSize="14sp"
        android:textAllCaps="false"
        android:layout_marginTop="30dp"/>

    <Button
        android:id="@+id/btn_servicestop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="서비스 종료"
        android:layout_gravity="center"
        android:gravity="center"
        android:textSize="14sp"
        android:textAllCaps="false"
        android:layout_marginTop="20dp"/>

    <Button
        android:id="@+id/btn_bindservice"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="바인드 값"
        android:layout_gravity="center"
        android:gravity="center"
        android:textSize="14sp"
        android:textAllCaps="false"
        android:layout_marginTop="20dp"/>

</LinearLayout>



MyService.java

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    private static final String TAG = MyService.class.getSimpleName();
    private Context context;
    private int mCount = 0;
    private Thread mThread;
    public static boolean SERVICE_CONNECTED = false;

    public MyService() {
    }

    private IBinder mBinder = new MyBinder();
    public class MyBinder extends Binder {
        public MyService getService(){
            Log.e(TAG, "MyService MyBinder return.");
            return MyService.this;  // 서비스 객체를 리턴
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "MyService IBinder onBind");
        // Service 객체와 (화면단 Activity 사이에서) 데이터를 주고받을 때 사용하는 메서드
        // Activity에서 bindService() 를 실행하면 호출됨
        // 데이터를 전달할 필요가 없으면 return null;
        return mBinder; // 리턴한 mBinder 객체는 서비스와 클라이언트 사이의 인터페이스를 정의한다.
    }

    public int getCount(){
        return mCount;
    }

    @Override
    public void onCreate() {
        Log.e(TAG, "MyService Started");
        this.context = this;
        MyService.SERVICE_CONNECTED = true;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "MyService onStartCommand startID === "+startId); // 계속 증가되는 값
        if(mThread == null){
            mThread = new Thread("My Thread"){
                @Override
                public void run() {
                    while (!Thread.currentThread().isInterrupted()){
                        try {
                            mCount++;
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            this.interrupt();
                        }
                        Log.e("My Thread", "서비스 동작 중 " + mCount);
                    }
                }
            };
            mThread.start();
        }
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.e(TAG,"MyService onDestroy");
        super.onDestroy();
        if(mThread != null && mThread.isAlive() ){
            mThread.interrupt();
            mThread = null;
            mCount = 0;
        }
        SERVICE_CONNECTED = false;
    }
}


MainActivity.java

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Set;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    Context mContext;
    private Button btnServiceStart;
    private Button btnServiceStop;
    private Button btnBindService;

    private MyService myService;
    private boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;

        btnServiceStart = findViewById(R.id.btn_servicestart);
        btnServiceStop = findViewById(R.id.btn_servicestop);
        btnBindService = findViewById(R.id.btn_bindservice);

        btnServiceStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "서비스를 시작합니다.", Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(mContext,MyService.class);
                startService(intent);
            }
        });

        btnServiceStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "서비스를 종료합니다.", Toast.LENGTH_SHORT).show();
                Log.e(TAG,"MainActivity ServiceStop Button Clicked.");
                if(mBound){
                    unbindService(mConnection);
                    mBound = false;
                }
                stopService(new Intent(mContext,MyService.class));
            }
        });

        btnBindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mBound){
                    Toast.makeText(mContext, "카운팅 : " + myService.getCount(), Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        Log.e(TAG,"MainActivity onDestroy");
        if(mBound){
            unbindService(mConnection);
            mBound = false;
        }
        stopService(new Intent(this,MyService.class));
        super.onDestroy();
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this,MyService.class);
        bindService(intent,mConnection,BIND_AUTO_CREATE); //
startService 버튼 클릭시 시작
//        startService(MyService.class, mConnection,null); // Activity 시작시
startService 자동 실행
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MyService.MyBinder binder = (MyService.MyBinder) service;
            myService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 예기치 않은 종료(안드로이드 OS에 의한 종료)

            mBound = false;
        }
    };

    private void startService(Class<?> service, ServiceConnection serviceConnection, Bundle extras) {
        // onStart 메소드에서 서비스 자동 실행 처리할 목적
        if (!MyService.SERVICE_CONNECTED) {
            Intent startService = new Intent(this, service);
            if (extras != null && !extras.isEmpty()) {
                Set<String> keys = extras.keySet();
                for (String key : keys) {
                    String extra = extras.getString(key);
                    startService.putExtra(key, extra);
                }
            }
            startService(startService);
        }
        Intent bindingIntent = new Intent(this, service);
        bindService(bindingIntent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
}


Log 를 통해서 동작 순서에 대한 이해를 할 수 있다.

Activity가 실행되면서 자동으로 서비스를 실행하도록 한 경우의 Log 메시지다.


블로그 이미지

Link2Me

,