본문 바로가기

Android

Service & Thread

오늘은 안드로이드 서비스와 스레드에 대해서 알려드리겠습니다.


Service란?


-  일정한 간격으로 백그라운드에서 실행되는 프로세스 (장시간)

-  원격에서 실행될 수 있는 인터페이스 (IPC)

-  android.app.Service 클래스를 상속 받아 생명 주기에 맞게 구현

-  백그라운드에서 작업 / Activity와 데이터를 교환(Intent)하여 처리

-  AndroidManifest.xml에서 application 태그 내에 <service> 요소 등록하기 

-  서비스는 메인 스레드에서 실행되지만 구현되는 것은 sub-thread !



[ Manifest.xml ]



<service  

android:enabled="true”  android:name=“.service.MusicService”>

         <intent-filter>

             <action  android:name=“.service.MusicPlayAction" />

         </intent-filter>

</service>






처음에는 기존 Intent와 동일하게 선언한다. 

선언한 Intent는 startService(intent) 이런 식으로 들어가서 내용을 호출한다.

받는 스레드에서는 onStartCommand(Intent intent, int flags , int startd) 로 이동하게 됩니다. 




여기서는 크게 세 가지의 flags를 알고 가면 됩니다. 



1. START_STICKY 

: 서비스가 강제 종료가 될 경우 다시 시작할 때엔 intent 값을 null로 초기화하고 onStartCommand()를 다시 시작한다. 


2. START_REDELIVER_INTENT 

: 서비스가 stopSelf()전에 강제 종료된 후 라면 startService()로 전달된 intent가 다시 시작됨


3.  START_STICKY_COMPATIBILITY 

: 기존 onStart()와 동일하게 처리 되며, 그 전 버전과의 호환성 유지를 위해 제공


flags 인자 

-  START_FLAG_RETRY : 비정상 종료시에 재 시작 되는 flag 값

-  START_FLAG_REDELIVER : START_REDELIVER_INTENT flag 값


startId 

: stopSelfResult(int startId) 종료 시에 해당 서비스의 고유 값을 안드로이드 시스템에다가 전달할 값




메인 스레드에서는 UI 구성을 하는 것은 당연하지만, 작업 내용은 워커스레드로 돌려서 메인 스레드 위에서 스레드가 돌아갈 수 있도록 하는 

구조가 제일 낫다고 생각합니다.

워커스레드에서 작업을 하게 되면 메인스레드에서 쌓인 정보 때문에 ANR 문제를 해결할 수 있습니다. 


그렇다면 ANR이 무엇인지 부터 확인하겠습니다.





ANR 이란? 


Main Thread(UI Thread)에서 일정 시간 동안 어떠한 Task가 해결되지 않고 잡혀 있어서 제대로 실행 되지 않고 시간 내에 응답하지 못하였다는 

에러가 나오게 될 것 입니다.



ANR이 발생하는 이유

1. 어플리케이션이 UI 스레드에 어떠한 I/O 명령 (빈번한 네트워크 Access)으로 인해 막힐 때

2. 너무 많은 시간을 정교한 메모리 구조를 구축하는 데 들일 때 


예시

1. Input 이벤트에 5초안에 반응하지 않을 때

2. BroadcastReceiver가 10초 내로 실행을 끝내지 않을때 (UI 없는 브로드캐스트리시버 , 서비스도 결국엔 실행 주체가 메인 스레드이기 때문에

긴 시간을 소모하는 작업일 경우 ANR을 발생시킨다. 





이를 해결하려면 작업 시간 소모가 적은 워커스레드를 실행시키는 것인데, 다음 서비스와 스레드를 이용하여 코드를 하나 만들어 보겠습니다.



< MainActivity.java > 


public class MainActivity extends AppCompatActivity {

private Intent intent;

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

intent = new Intent(MainActivity.this,ServiceTest.class);
intent.putExtra("service",3);
findViewById(R.id.btnService).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startService(intent);
}
});
}
}



메인 액티비티에서는 intent를 만드는데, service를 키로 갖고 값을 3으로 갖는 intent를 하나 만들어서 startService로 보내는 부분이다.

이제 받는 액티비티에서는 Service를 상속받는 클래스여야 하고 onStartCommand() 에서 작동하게 될 것이다.





<ServiceTest.java>


public class ServiceTest extends Service {

// onPause : 백업할 때 !
private int extraValue;
private BackGroundSubThread subThread;


// tkwls

@Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "서비스 콜백 메소드 시작", Toast.LENGTH_LONG).show();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);

if (flags == START_FLAG_RETRY) {
// 정상 종료가 아닌 경우 반드시 실행되어야 하는 인텐트를 여기서 다시 진행
}
if (intent == null) {
// START_STICKY모드로 비정상 종료시 intent null로 만들고 처리해야 하는 업무
}
extraValue = intent.getIntExtra("service", 0);
subThread = new BackGroundSubThread("Thread1_test");
subThread.start();

// 서비스가 비정상적으로 종료할 경우 null 값을 인텐트에 넣어서 재시작함.
return START_STICKY; // START_STICKY 모드로 정상 처리
}


Service를 상속받는 클래스에서 startService 내용을 getIntent와 같은 방식으로 보낸 데이터를 받는다.

받은 데이터를 가지고 작업을 할 것인데, 메인 스레드에서는 직접 구현하지 않고 워커 스레드에서 돌릴 생각이기 때문에

onStartCommand() 메서드에서 스레드를 start() 한다.




public class BackGroundSubThread extends Thread {

private HashMap<String, String> newHashMap;
private Random random;

public BackGroundSubThread(String name) {
super(name);
newHashMap = new HashMap<>();
random = new Random(System.currentTimeMillis());
newHashMap.put("테스트1", "테스트 진행1");
newHashMap.put("테스트2", "테스트 진행2");
newHashMap.put("테스트3", "테스트 진행3");
newHashMap.put("테스트4", "테스트 진행4");
newHashMap.put("테스트5", "테스트 진행5");
}

@Override
public void run() {
while (!isInterrupted()) {
Message message = handler.obtainMessage();
message.what = extraValue;

if (extraValue == 0) {
message.obj = newHashMap.get("테스트1");
} else if (extraValue == 1) {
message.obj = newHashMap.get("테스트2");
} else if (extraValue == 2) {
message.obj = newHashMap.get("테스트3");

} else if (extraValue == 3) {
message.obj = newHashMap.get("테스트4");
} else {
message.obj = newHashMap.get("테스트4");
}
extraValue = random.nextInt(4);
try{
sleep(2000);
}catch(InterruptedException e){
message.obj = " sleep()";
currentThread().interrupt();
}
handler.sendMessage(message);
}
super.run();
}
}

호출한 스레드가 작동하면 내부에 메세지를 만든다. 메세지 내용은 핸들러에서 받은 메세지 내용으로 하며 수령인들이 확인할 수 있게 사용자 정의 메세지 코드로

인텐트로 보낸 값을 넣어 준다.


또한 이 값은 랜덤함수로 지속해서 돌려주기 때문에 테스트1~테스트4 의 경우가 번갈아가며 임의로 나오게 될 것이다.

그리고 핸들러를 통해 정의된 메세지는 보내지게 된다.


그렇다면 이제 Handler에 대해 확인해보자




@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {

// message 보낼 때
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
String newMessage = (String) msg.obj;
// 메인 액티비티에서 서비스로 보내면 여기 클래스로 들어와서
// onStartCommand() 로 들어온다.
// 서비스가 시작하면서 Thread를 시작한다.
// Thread가 돌아가면서 handler에 메세지를 보낸다.
// onStartCommand()에서 putExtra로 보낸 값을 가지고 있는다.
// 스레드를 시작시키면서 핸들러에서 가지고 있는 메시지를 스레드에서 받고
switch (msg.what){
case 0:
Toast.makeText(getApplicationContext(),"서비스 테스트1",Toast.LENGTH_LONG).show();
break;
case 1:
Toast.makeText(getApplicationContext(),"서비스 테스트2",Toast.LENGTH_LONG).show();
break;
case 2:
Toast.makeText(getApplicationContext(),"서비스 테스트3",Toast.LENGTH_LONG).show();
break;
case 3:
Toast.makeText(getApplicationContext(),"서비스 테스트4",Toast.LENGTH_LONG).show();
break;
}
}
};

수령인이 파악할 수 있게 사용자 정의 코드로 만들어진 메세지 코드 (msg.what)를 가지고 여러 경우를 나누어 진행하도록 만든다.

msg.what은 0~3 사이에 값이 나오게 될 것이다.

이유는 앞서 워커 스레드에서 바운더리가 4까지인 랜덤함수를 돌려서 extraValue에 넣었고 해당 값은 메세지의 사용자 코드로 등록되었기 때문이다.

다음은 케이스 별로 Toast 메세지를 보냈지만 본 내용은 워커스레드 하나에서 핸들러를 통해 전달되는 메세지 (수령인이 확인할 수 있는 사용자 정의

메세지 코드)를 가지고 각각 다른 작업을 하는 것을 보여준다. 토스트 메세지는 워커스레드에서 일어나는 행동이지만 결국 워커스레드도 메인스레드 위에서

일어나는 행동이라 생각하면 된다.


마지막에는 종료 시에 서비스 객체 자신의 서비스를 그만 두고 스레드를 인터럽트 시키는 동작을 구현해주면 된다.


@Override
public void onDestroy() {
super.onDestroy();
if (subThread != null && subThread.isAlive()) {
// 백그라운드 스레드가 존재할 경우
subThread.interrupt();
}
this.stopSelf(); // 서비스 종료
}

간단하게 서비스에 대해서 집고 넘어 갔고 메인스레드의 역할을 줄이기 위해 워커스레드에서 작업하는 것을 구현해 보았습니다.

다음에는 인텐트를 가지고 진행하는 것도 좋지만 브로드 캐스트 리시버를 활용해서 UI 컴포넌트가 작동하게 하도록 만들어보겠습니다.




코드

: https://github.com/Haamseongho/Android_study/tree/master/Service_Test

반응형

'Android' 카테고리의 다른 글

Firebase Messaging 관리  (2) 2023.01.17
Kotlin 문법2 - Class  (0) 2023.01.17
Kotlin 문법1 - 변수/Scope  (0) 2023.01.17
액티비티(Activity)  (0) 2023.01.17
구글 스토어에 등록하는 방법  (0) 2018.02.23