-
Managing Threads and Custom ServicesAndroid 2022. 2. 4. 18:21
Managing Threads and Custom Services을 요약, 정리하였다.
Understanding the Main Thread
application이 시작되면 시스템은 main이라고 하는 application 실행 스레드를 생성한다. 이 스레드는 이벤트를 전달하고 사용자 인터페이스를 렌더링하는 역할을 하기 때문에 매우 중요하며 일반적으로 UI 스레드라고 한다. 모든 구성 요소(activities, services 등)와 실행 코드는 동일한 프로세스에서 실행되며 기본적으로 UI 스레드에서 인스턴스화된다.
UI 스레드에서 네트워크 액세스 또는 데이터베이스 쿼리와 같은 긴 작업을 수행하면 전체 앱 UI가 응답하지 않는다. UI 스레드가 차단되면 그리기 이벤트를 포함하여 이벤트를 전달할 수 없다.사용자의 관점에서 application은 정지된 것처럼 보인다. 또한 Android UI toolkit은 스레드로부터 안전하지 않으므로 백그라운드 스레드에서 UI를 조작해서는 안 된다.
요컨대, 이 가이드 전체에서 두 가지 중요한 규칙을 염두에 두자.
- 메인 스레드에서 긴 작업을 실행하지 말자(UI 차단을 피하기 위해)
- 백그라운드 스레드에서 UI를 변경하지 말자(메인 스레드에서만 변경)
UI 스레드가 구성되는 방식에 대한 자세한 내용은 핸들러 및 루퍼 섹션을 참조하자.
다음으로 서비스와 스레딩의 연결을 살펴보자.Services and Threading
서비스와 스레드 관리는 밀접하게 관련되어 있지만 별개의 주제이다. 서비스는 사용자가 앱과 상호 작용하지 않을 때도 백그라운드에서 실행할 수 있는 구성 요소이다. 앱이 열려 있지 않은 상태에서 작업을 수행해야 하는 경우에만 서비스를 생성해야 한다.스레드 관리는 기본적으로 application의 메인 스레드에서 여전히 custom service가 실행되기 때문에 이해하는 것이 중요하다. custom service를 생성하는 경우 IntentService를 활용하지 않는 한 백그라운드 스레드를 수동으로 관리해야 한다.
Thread Management
위에 설명된 UI 스레드 차단과 관련된 주요 문제의 결과로 모든 Android 앱은 백그라운드 스레드를 활용하여 디스크에서 읽거나 디스크에 쓰기 또는 네트워크 작업 수행과 같은 모든 장기 실행 작업을 수행해야 한다. 그러나 Android 프레임워크에서 스레드를 관리하기 위한 몇 가지 다른 추상화가 있다. 다음 표에서는 백그라운드 작업을 실행하기 위한 가장 실용적인 옵션을 분류한다.Type Description Built On AsyncTask UI를 업데이트하는 짧은 작업을 순차적으로 실행 ThreadPoolExecutor HandlerThread 단일 스레드에서 작업을 순차적으로 실행 Handler, Looper ThreadPoolExecutor 스레드 풀을 사용하여 작업을 동시에 실행 Executor, ExecutorService HandlerThread 사용
HandlerThread는 작업을 순차적으로 실행하는 새 작업자 스레드를 시작하기 위한 편리한 클래스이다. 코드를 실행하거나 메시지가 도착하는 순서대로 처리할 수 있는 루프를 시작하는 단일 백그라운드 스레드가 필요한 경우 사용하는 도구이다.HandlerThread는 Runnable 또는 Message 개체를 처리하기 위해 Thread 내에서 Looper를 시작하는 편리한 클래스이다. Handler는 Runnable 또는 Message 객체를 Looper의 큐에 삽입하는 데 사용된다.
// Create a new background thread for processing messages or runnables sequentially HandlerThread handlerThread = new HandlerThread("HandlerThreadName"); // Starts the background thread handlerThread.start(); // Create a handler attached to the HandlerThread's Looper mHandler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { // Process received messages here! } };
이제 메시지를 큐에 넣어 데이터를 전달하거나 Runnable을 처리하여 코드를 실행할 수 있다. 두 경우 모두 대기열에 있는 객체는 Handler를 통해 처리를 위해 Looper의 내부 MessageQueue에 추가된다.
HandlerThread에서 Runnable 실행하기
HandlerThread가 시작되면 Handler를 통해 worker thread에서 코드를 실행할 수 있다.
// Execute the specified code on the worker thread mHandler.post(new Runnable() { @Override public void run() { // Do something here! } });
위의 코드에서 Hander의 post 메소드를 호출하여 Runnable이 최대한 빨리 실행되도록 대기열에 넣는다. Handler 클래스는 Runnable이 미래에 처리되도록 예약하는 몇 가지 다른 방법을 지원한다.
Method Description post Runnable을 즉시 대기열에 넣어 실행 postAtTime Runnable을 대기열에 넣고 millisecond 단위로 지정된 절대 시간에 실행 postDelayed Runnable을 대기열에 넣고 지정된 지연(millisecond)후에 실행 postAtFrontQueue 실행될 Runnable을 즉시 queue 맨 처음에 넣는다. HandlerThread에서 메시지 처리
위와 같이 Runnable을 사용하여 백그라운드 스레드에서 임의의 코드를 실행하는 대신 Handler는 bundled information로 메시지를 대기열에 넣을 수도 있다. Handler를 통해 worker thread에 메시지를 보내려면
// Secure a new message to send Message message = handler.obtainMessage(); // Create a bundle Bundle b = new Bundle(); b.putString("message", "foo"); // Attach bundle to the message message.setData(b); // Send message through the handler mHandler.sendMessage(message); // or instead send an empty message with // mHandler.sendEmptyMessage(0);
위의 코드에서 Handler의 sendMessage 메소드를 호출하여 가능한 한 빨리 처리될 메시지를 큐에 넣는다. Handler 클래스는 메시지가 미래에 처리되도록 예약하는 몇 가지 다른 방법을 지원한다.
Method Description sendMessage(Message msg) Message 객체 전달. 메시지 큐의 가장 마지막에 msg 추가 sendMessageDelayed(Message msg, long delayMillis) 현재 시각에서 delayMillis 만큼의 시간 후에, Message 객체 전달. sendMessageAtTime(Message msg, long uptimeMillis) uptimeMillis에 지정된 시각에, Message 객체 전달. sendEmptyMessage(int what) Message 클래스 변수 중 what 멤버만 채워진 Message 객체 전달. sendEmptyMessageDelayed(int what, long delayMillis) 현재 시각에서 delayMillis 만큼의 시간 후에, what 멤버만 채워진 Message 객체 전달. sendEmptyMessageAtTime(int what, long uptimeMillis) uptimeMillis에 지정된 시각에, what 멤버만 채워진 Message 객체 전달. "Empty message"는 여전히 메시지 유형을 나타내는 단일 int 값을 포함한다. Empty messages는 추가 데이터가 필요하지 않은 "refresh" 또는 "notify" 유형 메시지를 수신하는 simple handler에 사용할 수 있다.
Messages vs Runnables?
종종 Runnable과 Message를 모두 받아들이는 Handler의 목적에 의문이 생긴다. Runnable은 단순히 코드 블록을 저장하는 메시지이며 Runnable과 Message 둘 다 동일한 MessageQueue에 포함되어 있음을 명심하자.
HandlerThread 중지
woker thread는 다음과 같이 즉시 중지할 수 있다.
handlerThread.quit(); // quits immediately
API >= 18에서는 종료하기 전에 대기 중인 메시지 처리를 완료하기 위해 대신 quitSafely()를 사용해야 한다.
HandlerThread 주의 사항
HandlerThread는 스레드에서 선형으로(순차적으로) 작업을 실행하는 데 적합하며 기본 Looper 및 Handler에 대한 액세스를 노출하여 메시지와 실행 가능 파일을 처리하는 방법과 시기를 개발자가 제어할 수 있다. 그러나 작업을 서로 동시에 실행할 수 있는 기능이 필요한 경우 스레드 풀을 관리하여 작업을 병렬로 실행하는 데 도움이 되는 ThreadPoolExecutor를 사용해야 한다.
ThreadPoolExecutor 사용
ThreadPoolExecutor는 제한된 스레드 풀 내에서 여러 스레드에서 병렬화된 작업을 실행하는 좋은 방법이다. 작업을 동시에 실행하고 작업 실행 방식에 대한 제어를 유지하기 위한 도구이다.
ThreadPoolExecutor 구성
ThreadPoolExecutor 사용은 스레드 풀을 구성하는 많은 인수와 함께 새 인스턴스를 구성하는 것으로 시작한다.
// Determine the number of cores on the device int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); // Construct thread pool passing in configuration options // int minPoolSize, int maxPoolSize, long keepAliveTime, TimeUnit unit, // BlockingQueue<Runnable> workQueue ThreadPoolExecutor executor = new ThreadPoolExecutor( NUMBER_OF_CORES*2, NUMBER_OF_CORES*2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() );
Service 내에서 ThreadPoolExecutor를 초기화하는 경우 onStartCommand() 내에서 생성해야 한다. onCreate()에 넣으면 RequestRejectedException 오류가 발생할 수 있다.
ThreadPoolExecutor에서 Runnable 실행하기
다음을 사용하여 풀의 스레드에서 실행할 Runnable 코드 블록을 큐에 넣을 수 있다.
// Executes a task on a thread in the thread pool executor.execute(new Runnable() { public void run() { // Do some long running operation in background // on a worker thread in the thread pool! } });
모든 스레드가 현재 사용 중이면 Executor는 스레드를 사용할 수 있을 때까지 새 작업을 큐에 넣는다.
ThreadPoolExecutor는 매우 유연하며 개발자에게 작업 실행 방법의 모든 측면에 대한 상당한 제어 권한을 제공한다.
ThreadPoolExecutor 중지
스레드 풀은 shutdown 명령을 사용하여 언제든지 종료할 수 있다.
executor.shutdown();
이렇게 하면 모든 runnables이 처리되면 executor가 안전하게 종료된다. executor를 즉시 종료하려면 대신 executor.shutdownNow()를 사용한다.
Threading Glossary
AsyncTask, HandlerThread 및 ThreadPoolExecutor를 포함한 Android 내의 모든 스레딩 관리 옵션은 모두 Android OS 내에서 스레딩을 지원하는 여러 기본 클래스를 기반으로 한다.Name Description Runnable 모든 스레드에서 실행할 수 있는 코드를 나타낸다. Thread Runnable에 지정된 코드를 실행하는 동시 실행(Concurrent) 단위 Message Handler를 통해 보내거나 받을 수 있는 데이터를 나타낸다. Handler 스레드에서 Runnable 또는 Message 개체를 처리한다. Looper Runnable 또는 Message 객체를 처리하고 Handler로 보내는 Loop MessageQueue Looper가 발송한 Runnable 또는 Message 객체의 목록을 저장한다. Runnable
Runnable은 일반적으로 Handler를 통해 예약된 스레드에서 실행할 수 있는 코드를 나타낸다. Runnables는 구현할 run 메서드가 있는 추상 클래스이다.
Runnable taskToRun = new Runnable() { @Override public void run() { // The code to execute when the runnable is processed by a thread } };
Thread
스레드는 Runnable에 지정된 코드를 실행하는 동시 실행 단위이다. taskToRun 위에 정의된 Runnable은 스레드를 사용하여 실행할 수 있다.
// Start a new thread to execute the runnable codeblock Thread thread = new Thread(taskToRun); thread.start();
Handler와 Loopers
Handler는 수신 메시지를 지속적으로 MessageQueue에 넣고 처리하는 Looper에 대한 Message(데이터) 또는 Runnable(코드) 개체의 전송 및 처리를 관리한다. Handler가 작동하려면 Looper가 필요하며 일반적으로 다음 순서가 발생한다.
- Handler는 Message 또는 Runnable 객체를 MessageQueue에 큐에 넣는다.
- Looper는 순차적으로 MessageQueue에서 메시지를 빼낸다.
- Looper는 처리할 Handler에 Message 또는 Runnable을 발송한다.
앱 내에서 기본 스레드인 UI 스레드는 들어오는 모든 view 관련 이벤트를 처리하는 singleton looper다. UI Looper는 Looper.getMainLooper()를 사용하여 언제든지 액세스할 수 있다. 따라서 Handler를 사용하여 실행 중인 다른 스레드의 메인 스레드에서 실행할 코드를 게시할 수도 있다.
// Create a handler attached to the UI Looper Handler handler = new Handler(Looper.getMainLooper()); // Post code to run on the main UI Thread (usually invoked from worker thread) handler.post(new Runnable() { public void run() { // UI code goes here } });
UI 스레드의 handler에 액세스하는 이 패턴은 Activity 내에서 매우 일반적이므로 Activity.runOnUiThread(Runnable action) 메서드는 위의 코드를 훨씬 더 단순화한다.
// From within an Activity, // usually executed within a worker thread to update UI runOnUiThread(new Runnable(){ public void run() { // UI code goes here } });
Handler는 짧은 지연 후 또는 지정된 미래 시간에 실행 가능한 코드 블록을 실행하기 위해 추가 "scheduling" 명령을 지원한다. Handler는 앱 내에서 주기적 작업(예: 새 업데이트 폴링)을 반복하기 위해 자신을 재귀적으로 호출할 수도 있다.
Custom Services
Custom Services 정의
먼저, Service를 확장하는 클래스를 애플리케이션 내에 정의하고 이 인텐트가 실행될 때 수행할 작업을 설명하는 onStartCommand를 정의한다.import android.app.Service; public class MyCustomService extends Service { @Override public void onCreate() { super.onCreate(); // Fires when a service is first initialized } @Override public int onStartCommand(Intent intent, int flags, int startId) { // Fires when a service is started up, do work here! // ... // Return "sticky" for services that are explicitly // started and stopped as needed by the app. return START_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { // Cleanup service before destruction } }
onStartCommand는 services를 트리거할 때 트리거되는 메서드입니다. onStartCommand 메소드에는 "동작 모드"를 나타내는 int가 필요하다. 두 가지 주요 작동 모드가 추가로 있다. START_STICKY는 필요에 따라 명시적으로 시작 및 중지되는 서비스에 사용되는 반면 START_NOT_STICKY 또는 START_REDELIVER_INTENT는 전송된 명령을 처리하는 동안에만 실행 상태를 유지해야 하는 서비스에 사용된다.
핵심적으로 이것이 서비스의 골격을 정의하는 데 필요한 전부이다. 그러나 Custom Services는 기본적으로 앱의 기본 스레드 및 프로세스에서 실행된다. 서비스 내에서 작업을 실행할 백그라운드 스레드를 관리해야 한다.서비스 등록
각 서비스는 AndroidManifest.xml에 있는 앱의 매니페스트에 등록해야 한다.<application android:icon="@drawable/icon" android:label="@string/app_name"> <service android:name=".MyCustomService" android:exported="false"/> <application/>
Threading within the Service
Custom Services를 만드는 경우 이 가이드의 앞부분에서 설명한 스레딩 관리 옵션을 사용하여 백그라운드 스레딩을 직접 관리해야 한다.- 순차: 서비스가 단일 작업자 스레드를 실행하여 작업을 순차적으로 처리하도록 하려면 HandlerThread를 사용한다.
- 동시성: 서비스가 스레드 풀 내에서 동시에 작업을 실행하도록 하려면 ThreadPoolExecutor를 사용한다.
예를 들어 아래 HandlerThread를 사용하여 서비스 백그라운드에서 작업을 처리한다.
import android.os.Handler; import android.os.Looper; public class MyCustomService extends Service { private volatile HandlerThread mHandlerThread; private ServiceHandler mServiceHandler; // Define how the handler will process messages private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } // Define how to handle any incoming messages here @Override public void handleMessage(Message message) { // ... // When needed, stop the service with // stopSelf(); } } // Fires when a service is first initialized public void onCreate() { super.onCreate(); // An Android handler thread internally operates on a looper. mHandlerThread = new HandlerThread("MyCustomService.HandlerThread"); mHandlerThread.start(); // An Android service handler is a handler running on a specific background thread. mServiceHandler = new ServiceHandler(mHandlerThread.getLooper()); } // Fires when a service is started up @Override public int onStartCommand(Intent intent, int flags, int startId) { // ... return START_STICKY; } // Defines the shutdown sequence @Override public void onDestroy() { // Cleanup service before destruction mHandlerThread.quit(); } // Binding is another way to communicate between service and activity // Not needed here, local broadcasts will be used instead @Override public IBinder onBind(Intent intent) { return null; } }
Running Tasks in the Service
위의 코드는 백그라운드 작업을 수행할 수 있도록 하는 HandlerThread를 설정한다. 다음으로 메시지를 보내거나 Runnable을 처리하여 백그라운드에서 코드를 실행할 수 있다.
public class MyCustomService extends Service { private volatile HandlerThread mHandlerThread; private ServiceHandler mServiceHandler; // ... // Fires when a service is started up @Override public int onStartCommand(Intent intent, int flags, int startId) { // Send empty message to background thread mServiceHandler.sendEmptyMessageDelayed(0, 500); // or run code in background mServiceHandler.post(new Runnable() { @Override public void run() { // Do something here in background! // ... // If desired, stop the service stopSelf(); } }); // Keep service around "sticky" return START_STICKY; } // ... }
Starting the Service
서비스를 정의했으면 서비스를 트리거하고 서비스 데이터를 전달하는 방법을 살펴보겠습니다. 이것은 우리가 이미 친숙한 동일한 Intent 시스템을 사용하여 수행된다.
public class MainActivity extends Activity { // Call `launchTestService` to initiate the service public void launchTestService() { // Construct our Intent specifying the Service Intent i = new Intent(this, MyCustomService.class); // Add extras to the bundle i.putExtra("foo", "bar"); // Start the service startService(i); } }
application 중 언제든지 모든 activity 또는 fragment에서 서비스를 시작할 수 있다. startService()를 호출하면 서비스가 onStartCommand() 메서드를 실행하고 서비스가 명시적으로 종료될 때까지 실행된다.
서비스 중지
서비스가 시작되면 서비스를 시작한 구성 요소와 독립적인 수명 주기가 있으며 서비스를 시작한 구성 요소가 파괴되더라도 서비스는 백그라운드에서 무기한으로 실행될 수 있다. 따라서 서비스는 stopSelf()를 호출하여 작업이 완료될 때 자체적으로 중지해야 한다. 그렇지 않으면 activity(또는 다른 구성 요소)가 stopService()를 호출하여 서비스를 중지할 수 있다.
'Android' 카테고리의 다른 글
data binding : providing values in Include layout (0) 2021.11.05 WorkManager(1) (0) 2021.10.04 Fragment (0) 2021.08.16 Replace findViewById with View Binding (0) 2021.08.06 ANR(Application Not Responding) (0) 2021.07.19