Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

기록장

[안드로이드] Service(서비스) 이해하기 본문

안드로이드

[안드로이드] Service(서비스) 이해하기

edit0 2021. 1. 13. 21:45

안드로이드에서 Service는 화면 없이 동작하는 프로그램, 즉 '백그라운드 프로세스'라 한다. 서비스는 백그라운드에서 실행되므로 화면과 상관없이 계속 동작할 수 있다. 우리가 자주 사용하는 카카오톡도 꼭 켜놓지 않아도 알람이 오고 메시지를 받을 수 있는데 다 백그라운드 서비스가 있기 때문이다.

 

서비스는 단말이 항상 실행되어 있는 상태로 다른 단말과 데이터를 주고받거나 필요한 기능을 백그라운드에서 실행하는 것이다. 그래서 서비스가 비정상적으로 종료되더라도 시스템이 자동으로 재실행한다.

 

서비스를 실행시킬 때는 startService() 메소드를 호출하는데 Intent 객체를 파라미터로 전달한다. 이 Intent 객체에는 어떤 서비스를 실행할 것인지에 대한 정보를 담고 있다. 또한, 보내고 싶은 다른 데이터도 기본 인텐트를 사용하는 것처럼 함께 보낼 수 있다. 그리고 종료 시에는 stopService()로 종료시킬 수 있다.

 

서비스에는 주요 메소드 3가지가 있다.

  • onCreate(): 서비스 시작 시 제일 먼저 호출되는 메소드로 최초 서비스 시작 이후에는 쓰이지 않는다. 이유는 이미 메모리에 올려져 있는 서비스를 재실행하였을 시 이 메소드는 호출되지 않고 바로 onStartCommand() 메소드가 호출되기 때문이다.
  • onStartCommand(): onCreate() 다음으로 실행되는 메소드로 백그라운드에서 할 일들을 구현한다. 보통 넘어온 데이터를 처리하는 경우 이곳에서 처리한다.(onCreate()는 최초 1회만 호출되기 때문) 그리고 비정상 종료 시 재실행하거나 끝내거나 결정하는 코드를 구현하는 것도 이곳에서 한다.
  • onDestroy(): 서비스가 끝날 때 마지막으로 호출된다.

onStartCommand()에서 처리되는 서비스 상태에 대한 리턴 플래그 종류

  • START_STICKY: 프로세스가 강제 종료 되었을 시 서비스를 다시 실행시켜준다. 단 인텐트로 넘겨주는 데이터 값은 null 값으로 초기화되기 때문에 유의하여야 한다.
  • START_NOT_STICKY: 프로세스가 강제 종료 되었을 시 서비스가 다시 시작되지 않는다.
  • START_REDELIVER_INTENT: START_STICKY와 같이 비정상 종료 시 다시 실행시켜주며, 인텐트 값을 그대로 유지시켜준다.

* 서비스도 액티비티와 마찬가지로 앱의 구성 요소이기 떄문에, AndroidManifest.xml 파일에 등록해주어야 한다.

application 내에 등록

<service android:name=".myservice" />

 

코드

 

activity_main.xml

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

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="서비스 시작"
        android:id="@+id/button1"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="서비스 종료"
        android:id="@+id/button2"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textview1"
        android:textSize="20dp"/>

</LinearLayout>

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    Button b1,b2;
    TextView tv1;
    Intent intent;

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

        b1 = findViewById(R.id.button1);
        b2 = findViewById(R.id.button2);
        tv1 = findViewById(R.id.textview1);

        intent = new Intent(getApplicationContext(), myservice.class); //서비스 클래스를 인텐트로 생성

        b1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                intent.putExtra("Data", "Service");

                startService(intent); //서비스 시작
                //메모리에 있고 없고 상태를 비교해보고 싶으면 finish()로 비교해보면 된다.
                //finish();
            }
        });

        b2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(intent); //서비스 종료
            }
        });

        //메모리에 없는 상태에서 처음 생성된 액티비티라면 getIntent() 호출
        Intent return_data = getIntent();
        processIntent(return_data, "getIntent()");

    }

    //메모리에 이미 service가 실행 중일 경우 호출
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        processIntent(intent, "onNewIntent()");
    }

    void processIntent(Intent intent, String call){
        if(intent != null){
            String data1 = intent.getStringExtra("Back_data");
            String data2 = intent.getStringExtra("data");

            tv1.setText(data1 + " / " + data2 + " / " + call);
        }
    }
}

 

myservice.java

public class myservice extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    //서비스 실행 시 최초 호출, 메모리에 올려진 후부터는 호출되지 않음
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("태그", "onCreate()");
    }

    //메인 구현부분, 재호출 시 onStartCommand()로 호출
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("태그","onStartCommand");

        if(intent == null){
            Log.i("태그","Intent null");
            return Service.START_STICKY; //시스템 자동으로 재시작
        }
        else{
            processCommand(intent);
        }

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

    //서비스 종료 시 호출
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("태그","onDestroy()");
    }

    void processCommand(Intent intent){
        String data = intent.getStringExtra("Data");

        Log.i("태그", data);

	//화면이 없는 곳(service)에서 화면을 띄우려 하면 오류가 난다.
        //화면이 연속으로 뜰 수 있도록 만들어 놓은 것이 TASK이다.
        //화면이 없는 곳에서는 태스크 정보가 없기 때문에 오류가 난다.
        Intent back_intent = new Intent(getApplicationContext(), MainActivity.class);
        back_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        back_intent.putExtra("Back_data", "return_data");
        back_intent.putExtra("data",data);
        startActivity(back_intent);

    }
}

 

결과

 

코드 실행 시 보이는 부분이다. 처음에 processCommand() 메소드로 넘겨준 값이 있어서 null / null / getIntent()가 나온다.

 

서비스 시작을 누를 시 보이는 화면과 로그이다. onCreate() -> onStartCommand() 순으로 호출된 것을 볼 수 있다. 그리고 우리가 서비스 호출 시 같이 보냈던 데이터도 잘 갔다.

서비스에서 데이터를 받고 다시 리턴해서 보내는데,

finish()를 주석 처리 했을 경우 메모리에 이미 서비스가 있기 때문에 onNewIntent() 호출이 된 것을 볼 수 있다.

주석 처리를 하지 않았을 경우에는 getIntent() 가 찍히는 것을 볼 수 있다.

 

서비스 종료 버튼을 누를 시 onDestroy() 메소드가 호출되었다.

 

 

+ 백그라운드 음악 플레이

 

코드

 

activity_main.xml

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

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="음악 재생"
        android:id="@+id/button1"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="음악 정지"
        android:id="@+id/button2"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="음악 종료"
        android:id="@+id/button3"/>

</LinearLayout>

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    Button b1,b2,b3;
    Intent intent;

    Integer[] music_box;

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

        b1 = findViewById(R.id.button1);
        b2 = findViewById(R.id.button2);
        b3 = findViewById(R.id.button3);


        intent = new Intent(getApplicationContext(), myservice.class);

        b1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                intent.putExtra("start",true);
                startService(intent);
            }
        });

        b2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                intent.putExtra("start",false);
                startService(intent);
            }
        });

        b3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(intent);
            }
        });

    }
}

 

myservice.java

public class myservice extends Service {

    MediaPlayer mediaPlayer;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("태그", "onCreate()");
        mediaPlayer = MediaPlayer.create(this,R.raw.chacha);
        mediaPlayer.setLooping(false);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("태그","onStartCommand");

        boolean state = intent.getBooleanExtra("start",false);

        if(intent == null){
            Log.i("태그","Intent null");
            return Service.START_STICKY;
        }
        else if(state == true){
            mediaPlayer.start();
        }
        else if(state == false){
            if(mediaPlayer.isPlaying())
                mediaPlayer.pause();
        }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("태그","onDestroy()");
        mediaPlayer.stop();
    }
}

 

 

부족한 점, 피드백 환영합니다.

 

감사합니다.

 

 

참고

정재곤, 『Do it! 안드로이드 앱 프로그래밍 - 개정 6판』, 이지스퍼블리싱(주)