-
728x90
오늘은 서비스에 대해서 알아볼게요.
서비스는 백그라운드 작업을 할 때 사용하는 컴포넌트에요.
지금까지는 우리가 알고 있는 스레드로 백그라운드 작업을 했었죠? 액티비티에서 백그라운드 작업을 하는건 굉장히 위험해요
그 이유를 한 번 보고 서비스에 대해 알아볼게요
서비스 사용의 필요성
<layout/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" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/start_btn" android:text="시작" android:layout_width="match_parent" android:layout_height="100dp"/> <Button android:id="@+id/end_btn" android:text="종료" android:layout_width="match_parent" android:layout_height="100dp"/> </LinearLayout>
<MainActivity.java>
package kr.co.sample; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { Button startBtn, endBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startBtn = findViewById(R.id.start_btn); endBtn = findViewById(R.id.end_btn); startBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 스레드 이름을 MyTaskThread 로 지정 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { Log.d("MyTask", String.valueOf(i)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "MyTaskThread").start(); // 앱종료 finish(); } }); endBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); } }
결과
start 버튼을 클릭하면 100까지 세는 스레드가 만들어지고, finish() 하여 해당 액티비티는 종료됩니다.
그럼에도 불구하고 MyTaskThread는 여전히 살아있고, 로그도 여전히 찍히고 있는걸 볼 수 있습니다.
"액티비티가 종료되어도 스레드는 종료되지 않는다."
그럼 질문할 수 있겠죠 "onDestroy() 메소드에 스레드 종료 작업을 하면 되지 않나요?"
네 맞습니다. 일단 해두긴 해야하는데 안드로이드 메모리가 부족해져서 갑자기 종료시켜야 할 때는 onDestroy() 메소드가 반드시 호출된다는 보장이 없기 때문에 위험한건 여전합니다.
이런 문제를 해결하려면 서비스를 사용해야합니다.
1. 서비스는 백그라운드 스레드를 관리하기 위해 필요하다.
2. 서비스는 백그라운드 작업을 다른 앱과 공유하는 "서버"의 역할도 담당한다.
서비스의 종류
스타티드 서비스(Started Service)
스타티드 서비스는 특정 작업을 동작시켜두는데 목적을 둔 서비스입니다.
스타티드 서비스를 시작하고 종료하는 작업, 딱 두 가지 제어 밖에 할 수 없습니다.
지정한 작업을 마치게 되면 서비스는 자동으로 종료되게 됩니다.
다운로드 작업이 주로 스타티드 서비스로 구현 됩니다. 다운로드 시작, 다운로드 종료 딱 두 가지 액션밖에 없기 때문이죠
바인딩 서비스(Binding Service)
서비스의 메소드를 마치 라이브러리 함수 호출하듯이 끌어다 쓸 수 있습니다.
음악 서비스가 재생과 종료만 제공한다면 사용자는 다음곡으로, 이전곡으로 이동할 수 없으니 굉장히 불편하겠죠
이런 경우 바인딩 서비스로 구현합니다.
+ 바인딩 서비스는 서버/클라이언트 개념을 갖고 있어서 클라이언트가 종료되면 바인딩 서비스도 종료되고, 작업을 다 마쳤다고 하더라도 클라이언트가 종료되지 않으면 바인딩 서비스도 종료되지 않고 연결을 유지합니다.
물론 바인딩 서비스와 스타티드 서비스를 모두 이용하는 앱들도 많습니다.
바인딩 서비스와 스타티드 서비스의 관계
바인딩 서비스는 스타티드 서비스가 실행이 되어야지만 클라이언트가 서비스에 바인딩될 수 있습니다.
그래서 첫 번째 바운드는 실패했던 것입니다.
우리가 PC를 켜지도 않고 게임에 접속하려고 하면 당연히 안되는 것처럼 서비스도 먼저 start가 되고 bind가 가능합니다.
바인딩, 스타티드 여러 개념이 나와서 헷갈리실텐데 예제 보면서 차근차근 보면 어렵지 않으니 집중해주세요!
예제
서비스 생성
저는 서비스 이름을 CountService 로 하겠습니다.
<MainActivity.java>
package kr.co.sample; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { Button startBtn, endBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startBtn = findViewById(R.id.start_btn); endBtn = findViewById(R.id.end_btn); startBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 서비스 시작 Intent intent = new Intent(MainActivity.this, CountService.class); startService(intent); } }); endBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 서비스 종료 Intent intent = new Intent(MainActivity.this, CountService.class); stopService(intent); } }); } }
<CountService.java>
package kr.co.sample; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class CountService extends Service { public CountService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); Log.d("CountService", "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d("CountService", "onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.d("CountService", "onDestroy"); } }
결과
시작 종료 버튼을 여러번 막 눌러보시면 서비스의 생명주기에 맞춰서 로그가 뜰거에요
서비스의 생명주기
되게 간단하죠? 액티비티에서 엄청 복잡했던 생명주기에 비하면 서비스는 아무것도 아니에요 (설명할게 없을 정도..)
onCreate() : 서비스가 최초로 생성될 때 한 번 호출됩니다. = startService() 호출 시 최초 1번
onStartCommand() : startService() 호출 시 매번 호출됩니다.
onDestory() : 서비스가 종료될 때 한 번 호출됩니다. = stopService() 호출 시 종료되며 호출
예제2
<CountService.java>
package kr.co.sample; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class CountService extends Service { public CountService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); Log.d("CountService", "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d("CountService", "onStartCommand"); for (int i = 0; i < 100; i++) { Log.d("CountService", String.valueOf(i)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.d("CountService", "onDestroy"); } }
결과
ANR이 발생했어요. 왜그럴까요?
안드로이드 시스템은 서비스가 20초 이상 메인스레드를 잡고 있으면 ANR을 발생시키기 때문이에요
즉 서비스를 만들었다고 작업 스레드를 만들 필요가 없는게 아니에요. 서비스 내부에서 작업 스레드를 만들어주는게 중요합니다.
< CountService 수정 >
package kr.co.sample; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class CountService extends Service { private Thread thread = null; public CountService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); Log.d("CountService", "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (thread == null) { thread = new Thread(new Runnable() { @Override public void run() { Log.d("CountService", "onStartCommand"); for (int i = 0; i < 100; i++) { Log.d("CountService", String.valueOf(i)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); break; } } } }); thread.start(); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.d("CountService", "onDestroy"); // 서비스 종료 시 스레드도 종료 if (thread != null) { thread.interrupt(); thread = null; } } }
위 처럼 수정하시면 잘 동작하고 로그도 잘 뜰거에요
'안드로이드' 카테고리의 다른 글
안드로이드 서비스 (3) (0) 2020.05.21 안드로이드 서비스(2) (0) 2020.05.21 안드로이드 HandlerThread (0) 2020.05.13 안드로이드 CountDownTimer (0) 2020.05.13 Android AsyncTask (0) 2020.05.10