티스토리 뷰

Service는 화면 없이 백그라운드에서 동작하며, 어플이 실행하고 있지 않은 상황에서도 주기적으로 데이터를 읽거나 쓰기 위해서 사용한다.

 

서비스는 액티비티와 함께 안드로이드 4대 구성요소 중 하나이기 때문에 새로 만든 서비스도 마찬가지로 매니페스트 파일에 등록해야 사용할 수 있다.

 

서비스는 실행된 상태를 계속 유지하기 위하여 서비스가 비정상적으로 종료되더라도 시스템이 자동으로 재실행된다. 

 

startService 메서드를 사용하여 액티비티에서 서비스를 실행시킬 수 있으며, 데이터를 주고 받기 위해 Intent 객체를 서비스에 전달한다.

또한 startService는 서비스가 이미 실행이 되고 있더라도 여러 번 호출이 가능하며 서비스는 그대로 메모리에 만들어진 상태로 유지된다. 때문에 startService는 서비스를 실행하는 것 뿐만 아니라, Intent 객체를 전달하는 용도로도 자주 사용된다.

 

바로 예제로 한번 확인해보자.


우선, 위와 같이 app 폴더에서 new -> service -> service를 클릭하여 새로운 서비스를 생성한다. 

 

새로운 서비스를 생성할 때 AndroidManifest 파일에도 자동으로 service 태그가 추가된다.

 

이후에 입력창 하나와 서비스를 실행시키기 위한 버튼 하나로 구성된 acivity_main을 먼저 구성한다.

 

◎activity_main.java

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    <Button
            android:text="서비스로 보내기"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:id="@+id/button"
            app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintHorizontal_bias="0.498" app:layout_constraintBottom_toBottomOf="parent"/>
    <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:ems="10"
            android:id="@+id/editText"
            app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/button"/>
</androidx.constraintlayout.widget.ConstraintLayout>

 

◎MainActivity.java

package com.example.sampleservice;

import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

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);
            }
        });
    }
}

다음으로 service로 넘어가서 자동으로 만들어진 생성자 메서드와 onBind() 메서드만 있기 때문에 생명주기와 관련된 overide메서드를 아래와 같이 추가한 뒤, onStartCommand() 메서드 또한 Intent 객체를 처리하기 위해 추가한다.

 

◎MyService.java

package com.example.sampleservice;

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

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 IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

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

        if(intent == null) {
            return Service.START_STICKY;
        } else {
            processCommand(intent);
        }

        return super.onStartCommand(intent, flags, startId);
    }

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

    private void processCommand(Intent intent) {
        String command = intent.getStringExtra("command");
        String name = intent.getStringExtra("name");

        for(int i = 0; i < 5; i++) {
            Log.d(TAG, "command: " + command + ", name: " + name);
            try{
                Thread.sleep(1000);
            } catch(Exception e) {
                Log.d(TAG, "Error: " + e.getMessage());
            }
        }
    }

}

onStartCommand() 메서드는 Intent 객체가 null인 경우에 Service.START_STICKY를 반환하는데, 해당 상수를 반환받은 서비스는 자동으로 재시작한다. 

다른 상수를 사용하여 서비스가 재시작되지 않게 할 수 있다.

 

Service 상수에 대한 자세한 설명은 여기를 확인하자.

 

onBind() 메서드는 서비스가 하나의 서버 역할을 하면서 액티비티와 연결될 수 있도록 만드는 메서드이다. 이는 나중에 다시 한번 다루도록 하겠다.

 

서비스에서 액티비티로 데이터를 전달하려면 반대로, startActivity메서드를 사용하면 된다. 

 

processCommand 메서드에서 for문이 전부 끝나면 액티비티쪽으로 데이터를 전달하는 코드를 추가하보면,

 

◎MyService.java

private void processCommand(Intent intent) {
    String command = intent.getStringExtra("command");
    String name = intent.getStringExtra("name");

    for(int i = 0; i < 3; i++) {
        Log.d(TAG, "command: " + command + ", name: " + name);
        try{
            Thread.sleep(1000);
        } catch(Exception e) {
            Log.d(TAG, "Error: " + e.getMessage());
        }
    }
    
    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", "show");
    showIntent.putExtra("name", name+"from service");
    startActivity(showIntent);
    
}

Flags에 대하여 설명을 좀 해보면, FLAG_ACTIVITY_NEW_TASK플래그는 startActivity메서드를 실행할 때마다 새로운 태스크를 생성하도록 하는 것이며, 서비스는 화면단을 가지고 있지 않기 때문에 액티비티와 같은 화면을 띄우기 위해서는 태스크가 필요하기 때문에 사용한다. 또한 MainActivity 객체가 이미 메모리에 있는 경우, 이를 재사용하기 위해 SINGLE_TOP, CLEAR_TOP 플래그를 사용한다. 

 

플래그에 대한 자세한 설명은 여기를 참조하자.

 

이제 서비스에서 액티비티로 전달한 데이터를 확인할 수 있는 코드를 작성한다.

 

◎MainActivity.java

package com.example.sampleservice;

import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

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);
            }
        });

        Intent passedIntent = getIntent();
        processIntent(passedIntent);
    }

    @Override
    protected void onNewIntent(Intent intent) {//MainActivity가 이미 메모리에 존재한다면, onCreate 호출하지 않기 때문에 사용한다.
        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();
        }
    }
}

 

'Mobile > Android' 카테고리의 다른 글

[Android] 위험 권한  (0) 2022.03.31
[Android] 브로드캐스트 수신자 이해하기  (0) 2022.03.30
[Android] ViewModel  (0) 2022.03.27
[Android] Navigation Drawer  (0) 2022.03.27
[Android] View Pager2  (0) 2022.03.25
Comments