[Do it! 안드로이드 앱 프로그래밍 #17] 서비스
Android

[Do it! 안드로이드 앱 프로그래밍 #17] 서비스

17 서비스

앱이 실행되어 있다고 해서 항상 화면이 보이는 것은 아니다. 예를 들어, 카카오톡은 앱이 실행되어 있지 않거나 화면이 사용자에게 보이지 않는 상태에서도 다른 사람이 보낸 메시지를 받을 수 있다. 이는 화면 없이 백그라운드에서 실행되는 서비스(Service)가 있기 때문이다. 화면 뒤의 공간을 뒷단 또는 백그라운드라고 부른다. 서비스도 앱의 구성 요소이기 때문에 매니페스트 파일에 꼭 등록해야 한다.

 

 

17-1 서비스의 실행 원리와 역할

서비스를 실행하려면 메인 액티비티에서 startService 메서드를 호출하면 된다. 서비스는 실행된 상태를 계속 유지하기 위해 서비스가 비정상적으로 종료되더라도 시스템이 자동으로 재실행한다.

 

서비스를 시작시키기 위해 startService 메서드를 호출할 때 인텐트 객체를 파라미터로 전달한다. 이 인텐트 객체는 어떤 서비스를 실행할 것인지에 대한 정보를 담고 있으며 시스템은 서비스를 시작시킨 후 인텐트 객체를 서비스에 전달한다. 이렇게 실행된 서비스는 startService 메서드를 여러 번 호출해도 이미 메모리에 만들어진 상태로 유지된다. 따라서 startService 메서드는 서비스를 시작하는 목적 외에 인텐트를 전달하는 목적으로도 자주 사용된다.

 

처음 startService 메서드를 호출할 때 시스템이 호출하는 메서드는 onCreate 메서드이고 서비스가 이미 메모리에 만들어진 상태일 때 호출하는 메서드는 onStratCommand 메서드이다.

 

 

17-2 로그 사용하여 인텐트의 부가 데이터 출력하기

새로운 프로젝트를 만들고 app > 우클릭 > New > Service > Finish로 서비스를 만든다. 새로운 서비스가 만들어지면 자바 소스 코드가 만들어지는 것뿐만 아니라 AndroidManifest.xml 파일 안에 <service> 태그도 추가된다. 서비스는 시스템에서 관리하므로 매니페스트에 넣어 앱 설치 시에 시스템이 알 수 있게 해야 한다.

 

인텐트를 사용해서 액티비티에서 서비스로, 서비스에서 액티비티로 데이터를 전달할 수 있는데, 우선 액티비티에서 서비스로 데이터를 전달하는 실습을 먼저 해보겠다.

 

activity_main.xml 파일을 열고 입력상자와 버튼을 추가하고, 버튼을 누르면 입력상자의 데이터를 서비스에 전달하는 코드를 추가한다.

 

▼ 버튼을 누르면 입력상자의 데이터 서비스에 전달하기

public class MainActivity extends AppCompatActivity {
    EditText editText;

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

        editText = findViewById(R.id.editText);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name = editText.getText().toString();

                // 인텐트 객체 만들고 부가 데이터 넣기
                Intent intent = new Intent(getApplicationContext(), MyService.class);
                intent.putExtra("command", "show");
                intent.putExtra("name", name);

                // 서비스 시작하기
                startService(intent);
            }
        });
    }
}

 

이제 액티비티로부터 전달받은 데이터를 서비스에서 출력하는 코드를 추가한다.

 

▼ 액티비티로부터 전달받은 데이터 서비스에서 출력하기

public class MyService extends Service {
    private static final String TAG = "MyService";

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate() 호출됨.");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand() 호출됨.");

        // 인텐트 객체가 널이 아니면 processCommand() 메서드 호출하기
        if(intent == null){
            return Service.START_STICKY;
        }else{
            processCommand(intent);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    private void processCommand(Intent intent){
    
        // 인텐트에서 부가 데이터 가져오기
        String command = intent.getStringExtra("command");
        String name = intent.getStringExtra("name");

        Log.d(TAG, "command: " + command + ", name: " + name);

        for(int i=0; i<5; i++){
            try{
                Thread.sleep(1000);
            }catch(Exception e){};
            Log.d(TAG, "Waiting " + (i+1) + " seconds.");
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

서비스는 시스템에 의해 자동으로 다시 시작될 수 있기 때문에 객체가 null이면 onStartCommand 메서드는 Service.START_STICKY을 반환한다. 그리고 이 값을 반환하면 서비스가 비정상 종료되었다는 의미이므로 시스템이 자동으로 재시작한다.

 

서비스가 서버 역할을 하면서 액티비티와 연결될 수 있도록 만드는 것을 바인딩(Binding)이라고 한다. 이를 위해서는 onBind 메서드를 재정의 해야 하지만 여기서는 바인딩 기능을 사용하지 않으므로 다음으로 넘어간다.

 

여기까지가 액티비티에서 서비스로 데이터를 전달하는 방법이었고, 이제 서비스에서 액티비티로 데이터를 전달해보도록 하겠다. 우선 서비스에서 액티비티로 데이터를 전달하는 코드를 processCommand 메서드에 추가한다.

 

▼ 데이터 액티비티에 전달하기

private void processCommand(Intent intent){

    // 인텐트 객체 만들기
    Intent showIntent = new Intent(getApplicationContext(), MainActivity.class);

    // 인텐트에 플래그 추가하기
    showIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    showIntent.putExtra("command", command);
    showIntent.putExtra("name", name + " from service.");
    startActivity(showIntent);
}

화면이 없는 서비스에서 화면이 있는 액티비티를 띄우려면 새로운 태스크를 만들어야 한다. FLAG_ACTIVITY_NEW_TASK 플래그를 인텐트에 추가한다. 그리고 MainActivity 객체가 이미 메모리에 만들어져 있을 때 재사용하도록 FLAG_ACTIVITY_SINGLE_TOP과 FLAG_ACTIVITY_CLEAR_TOP 플래그도 인텐트에 추가한다.

 

이제 서비스로부터 전달받은 데이터를 액티비티에서 출력하는 코드를 추가한다.

 

▼ 서비스로부터 전달받은 데이터 액티비티에서 출력하기

@Override
protected void onCreate(Bundle savedInstanceState) {
    
    // 중략

    // 액티비티가 새로 만들어질 때 전달된 인텐트 처리하기
    Intent passedIntent = getIntent();
    processIntent(passedIntent);
}

@Override
protected void onNewIntent(Intent intent) {

    // 액티비티가 이미 만들어져 있을 때 전달된 인텐트 처리하기
    processIntent(intent);

    super.onNewIntent(intent);
}

private void processIntent(Intent intent){
    if(intent != null){
        String command = intent.getStringExtra("command");
        String name = intent.getStringExtra("name");

        Toast.makeText(this, "command: " + command + ", name: " + name, Toast.LENGTH_LONG).show();
    }
}

서비스와 액티비티 간에 데이터 주고받기
서비스와 액티비티 간에 데이터 주고받기