본문 바로가기

MOBILE/android

[Android] Android 4대 구성요소: Activity, Service, BroadcastReceiver, ContentProvider

서비스

: 시간이 오래 걸리는 작업들은 백그라운드 서비스로 실행시켜야한다.

안드로이드 서비스는 액티비티에 종속되어있다. 따라서 별개의 thread가 아니라 main thread에서 실행된다.

Activity와 달리 UI가 없는게 특징이다.

이때 안드로이드 서비스가 액티비티에 종속되어 메인 스레드에서 실행되기 때문에

서비스에서 deadlock이 걸리게 되면 메인 액티비티도 중지된다.

또는 서비스에서 오래 걸리는 작업을 별개의 thread에서 실행하지 않으면

메인 스레드의 UI 또한 중지되므로 os는 ANR 즉 android not responding 발생시켜 앱 프로세스가 강제종료된다.

 

요약하자면 서비스는 시간이 오래 걸리는 작업을 백그라운드에서 진행할 때 사용하는데

별개의 thread가 아닌 메인 스레드에서 실행되므로 시간이 아주 오래 걸리는 작업이라면 ANR을 발생시킬 가능성이 있으므로 thread를 별개로 만들어 실행해야 한다.

 

서비스의 작동

서비스는 크게 두가지로 구분한다.

- 시작된 서비스

- 바인드된 서비스

 

1. 시작된 서비스

: 클라이언트가 인텐트를 실어서 직접 실행시킨다. 데이터를 서비스로 주고, 처리가 끝나면 서비스가 끝난다.

(1) startService()로 클라이언트에서 인텐트를 실어서 서비스를 실행시키면

(2) 서비스의 onCreate()로 entry point가 설정되어 진입하고

(3) onStartCommand()에서 실제로 작업이 활성화된다.

(4) 서비스에서 stopSelf(), 클라이언트에서 stopService()를 호출하여 서비스를 중지하면 onDestroy()로 서비스가 종료된다.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if(intent.getAction().equals("com.example.servicetest,PLAYMUSIC")){
        new MusicThread().start();
    }
    else if(intent.getAction().equals("com.example.servicetest.DOWNLOAD")){
        new DownloadThread().start();
    }
    return super.onStartCommand(intent, flags, startId);
}

// Thread 생성해서 실행
class MusicThread extends Thread{
    @Override
    public void run() {
        Log.d("started","MusicThread start");
        try{
            Thread.sleep(2000);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
        Log.d("started","MusicThread stop");
    }
}
class DownloadThread extends Thread{
    @Override
    public void run() {
        Log.d("started","DownloadThread start");
        try{
            Thread.sleep(1000);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
        Log.d("started","DownloadThread stop");
    }
}

 

2. 바인드된 서비스

: 서비스는 이미 실행되어 있고, 클라이언트와 서비스의 연결을 생성해서 데이터를 주고 받는 통신을 한다. 즉 결과를 return 한다.

(1) bindService로 실행되어 있는 서비스에 인텐트를 실어 클라이언트와 연결을 생성한다.

(2) 서비스의 onCreate()로 entry point가 설정되어 진입하고

(3) onBind()에서 실제로 작업이 활성된다. 

(4) 명시적 인텐트를 실어서 stopService를 호출하면 서비스가 중지된다.

(5) 만약 클라이언트가 모두 unbindService() 를 호출해서 연결을 끊어서 연결된게 하나도 없다면

(6) onDestroy()로 서비스가 종료된다.

 

바인드된 서비스를 사용할 때는 추가 객체가 필요하다.

- service에서 Binder 클래스를 상속받는 내부 클래스가 필요하다.

이 바인더 클래스는 getService() 등의 메서드를 하나 추가해서 현재의 서비스 객체를 return해야한다. (.this)

- onBind() 메서드에서 바인더 객체를 return 하면 클라이언트에서 bindService()를 호출해서 이 객체를 얻는다.

- 그 후 서비스 메서드는 원하는대로 작성하면 된다.

 

클라이언트에서는 인터페이스를 구현해야한다.

- ServiceConnection

이 객체는 서비스와의 연결을 확인한다. 이 객체를 bindService()에 넘겨서 서비스와의 연결 상황을 확인한다.

인터페이스에서는 두가지 메서드를 오버라이딩한다.

- onServiceConnected(): 서비스와의 연결이 성공적일때 호출되어 바인더 객체가 return됨. 

- onServiceDisconnected(): 서비스와의 연결이 끊겼을 때 호출된다.

 

예를 들어 두 정수의 곱을 바인드 서비스로 실행한 후 계산 결과를 리턴한다면

<service>

// Service 
@Override
public IBinder onBind(Intent intent) {
	// binder 객체를 return
    return new LocalBinder();
}

// 내부 바인더 클래스를 생성해서 현재의 서비스 객체를 return 한다.
public class LocalBinder extends Binder{
    BindedService getService(){
        return BindedService.this;
    }
}

// 서비스에서 수행하기 원하는 메서드를 정의한다. 여기서는 간단하게 두 정수의 곱
public int Calculate(int a, int b){
    return a*b;
}

 

<main>

// main

// 계산하는 메서드를 정의한다. 이걸 리스너 내부에 쓰면 됨
    public void calculate(){
        EditText text1=findViewById(R.id.edit1);
        EditText text2=findViewById(R.id.edit2);

        if(text1.length()>0 && text2.length()>0){
            int num1=Integer.parseInt(text1.getText().toString());
            int num2=Integer.parseInt(text2.getText().toString());

		// 바인드된 서비스 객체를 통해서 원하는 서비스의 메서드를 호출
            int result=bindedService.Calculate(num1,num2);
            Toast.makeText(this, "result = "+result, Toast.LENGTH_SHORT).show();
        }

    }
    
    	// ServiceConnection 인터페이스를 구현하여 객체를 생성한다.
        ServiceConnection serviceConnection=new ServiceConnection() {
        
        // 서비스와의 연결이 성공적일 때 호출됨
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        	
            // 내부 바인더 객체의 메서드로 바인드된 서비스 객체를 얻는다.
            bindedService=((BindedService.LocalBinder) iBinder).getService();
            Bound=true;
        }

		// 서비스와 연결을 끊겼을 때 호출됨
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Bound=false;
        }
    };
    
        @Override
    protected void onStart() {
        super.onStart();
        // 명시적 인텐트를 생성하고 bindService로 서비스와 연결한다.
        // 이때 두번째 파라미터에 serviceconnection 객체를 넘긴다.
        Intent intent=new Intent(this,BindedService.class);
        bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if(Bound){
        	// unbindservice로 서비스와의 연결을 해제한다.
            unbindService(serviceConnection);
            Bound=false;
        }

    }

근데 서비스를 왜 쓰냐?

: 어짜피 서비스가 메인 스레드라면 그냥 thread를 새로 만드는게 낫지 않냐?

서비스는 백그라운드에서 실행되는 작업을 위한 것이다. 즉 굳이 애플리케이션과 상호작용하지 않아도 관계없이 해당된다.

만약 메인 스레드 외부에서 실행되어야하고, 사용자가 애플리케이션과 상호작용을 할때만 실행되어도 된다면 thread를 생성하는게 좋다.

새 스레드에서 작업을 실행시키는 방법은

1. Thread class를 상속받거나

2. Runnable 인터페이스를 구현한다.

비동기로 처리하는 방법은 또 다르다.