안드로이드 서비스 기능을 추가한 어플이 이상종료되는 증상이 발생한다.
코드 구현에서 고려하지 못한 사항이 있어서일까?
그래서 이번에 Service 에 대한 여러 자료를 참조하여 정리를 해둔다.
안드로이드 Application을 구성하는 4대 컴포넌트
1. Activity
2. Service : 백그라운드에서 동작하는 컴포넌트
3. Broadcast Receiver
4. Contents Provider
안드로이드에서 UI가 없어도 백그라운드에서 실행되는 동작이 필요할 때가 있다.
- 웹사이트 데이터 읽어오기 : AsyncTask 사용
- 음악재생, 전화수신 팝업 : Service 사용
Service는 Activity가 종료되어 있는 상태에서도 동작하기 위해서 만들어진 컴포넌트다.
앱이 종료되어도 서비스는 백그라운드에서 계속 실행된다.
만약 Service가 실행되고 있는 상태라면, 안드로이드 OS 에서는 해당 Process를 왠만하면 죽이지 않도록 방지하고 관리한다.
그렇기 때문에 메모리 부족이나, 특별한 경우를 제외하고는 Background 동작을 수행하도록 설계 되었다.
모든 컴포넌트(Activity, Service, Broadcast Receiver, Contents Provider)는 Main Thread(UI 작업을 처리해주는 Thread) 안에서 실행된다.
서비스 수명 주기.
왼쪽의 다이어그램은 서비스가 startService()로 생성된 경우의 수명 주기를 나타내며
오른쪽의 다이어그램은 서비스가 bindService()로 생성된 경우의 수명 주기를 나타낸다.
자세한 내용은 https://developer.android.com/guide/components/services.html 를 읽어보자.
- (액티비티와 같은) 컴포넌트가 startService()를 호출하면, 서비스는 "started" 상태가 된다.
서비스가 실행되면(started), 그 서비스를 실행한 컴포넌트가 종료되더라도 (할 일을 모두 마칠 때까지) 서비스는 종료되지 않는다.
- 일반적으로 서비스는 onCreate() → onStartCommand() → Service Running → onDestroy() 순으로 실행된다.
- onCreate() : 서비스에서 가장 먼저 최초 1번만 호출된다.
- Service 실행도중에 startService() 메서드를 실행하게 되면 Service의 onStartCommand() 메서드를 호출하게 한다.
- Service의 종료메서드인 stopService() 메서드를 호출하면 종료된다.
stopService() 메소드를 호출하지 않으면 프로세스가 종료되더라도 다시 살아난다.
서비스가 할 작업을 모두 마쳤을 때 stopSelf()나 stopService()를 호출하는 부분도 구현해야 한다.
- 프로세스에 의해 종료된 Service는 onCreate() -> onStartCommand() 순으로 실행된다.
- onStartCommand() 메서드의 3가지 return type
START_STICKY |
Service가 강제 종료되었을 경우 시스템이 다시 Service를 재시작시켜 주지만 intent 값을 null로 초기화 시켜서 재시작 |
START_NOT_STICKY |
시스템에 의해 강제로 종료된 Service가 재시작되지 않는다 |
START_REDELIVER_INTENT |
Service가 강제 종료되었을 경우 시스템이 다시 Service를 재시작시켜 주며, intent 값을 그대로 유지시켜 준다. |
- onDestroy() : 서비스가 종료될 때 실행되는 메소드
Service 구현
1. Service (*.java)를 만든다
2. AndroidManifest.xml 에 Service를 등록한다
3. Service 를 실행/중단하는 Intent 코드를 작성한다.
예제1.
- startService를 호출하면 서비스의 onStartCommand() 메소드가 동작된다.
- stopService 를 호출하면 서비스의 onDestroy() 메소드가 동작된다. 즉, 서비스를 종료시킨다.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 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/btn_servicestart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_vertical" android:layout_gravity="center" android:text="Service Start" android:textSize="14sp" android:textAllCaps="false" android:layout_marginTop="30dp"/>
<Button android:id="@+id/btn_servicestop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Service Stop" android:layout_gravity="center" android:gravity="center" android:textSize="14sp" android:textAllCaps="false" android:layout_marginTop="20dp"/> </LinearLayout>
|
import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); Context mContext; private Button btnServiceStart; private Button btnServiceStop;
private int mCount = 0; private Thread mThread;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = MainActivity.this;
btnServiceStart = findViewById(R.id.btn_servicestart); btnServiceStop = findViewById(R.id.btn_servicestop);
btnServiceStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(mContext, "서비스를 시작합니다.", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(mContext,MyService.class); startService(intent); } });
btnServiceStop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(mContext, "서비스를 종료합니다.", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(mContext,MyService.class); stopService(intent); } }); }
@Override protected void onDestroy() { Log.e(TAG,"MainActivity onDestroy"); stopService(new Intent(this,MyService.class)); // 현재 Activity가 종료되면 서비스 중지시킴 super.onDestroy(); } }
|
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.class.getSimpleName(); private int mCount = 0; private Thread mThread;
public MyService() { }
@Override public IBinder onBind(Intent intent) { return null; }
@Override public int onStartCommand(Intent intent, int flags, int startId) { if(mThread == null){ mThread = new Thread("My Thread"){ @Override public void run() { while (!Thread.currentThread().isInterrupted()){ try { mCount++; Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); this.interrupt(); } Log.e("My Thread", "서비스 동작 중 " + mCount); } } }; mThread.start(); } return START_STICKY; }
@Override public void onDestroy() { Log.e(TAG,"MyService onDestroy"); super.onDestroy(); if(mThread != null && mThread.isAlive() ){ mThread.interrupt(); mThread = null; mCount = 0; }
} }
|
실행결과
Stop 버튼 클릭시
Activity 종료시
Bound Service
startService() 메소드 대신 bindService() 메소드를 통해 시작되는 서비스를 서비스 바인딩이라 한다.
이 서비스는 Activity가 클라이언트 역할을 하고, 서비스가 서버 역할을 한다.
- Activity가 서비스에 어떤 요청을 하고, 서비스는 결과를 반환할 수 있다.
앱 컴포넌트가 bindService()를 호출하면, 서비스는 "bound" 상태(바인드 된 상태)가 된다.
바인드 된 서비스는, 앱 컴포넌트들이 서비스와 상호작용(요청 및 응답, 프로세스간 통신(IPC)) 할 수 있도록 해주는 클라이언트-서버 인터페이스를 제공해 준다.
- 하나의 서비스에 다수의 Activity(컴포넌트)가 바인드 될 수 있다.
- 서비스 바인딩은 연결된 Activity가 사라지면 서비스는 종료된다.
바인드 서비스 예제는 https://link2me.tistory.com/1763 에 있다.