안드로이드 스레드 예제. 스레드로 고민해보기. (Android Thread UseCase)

2019. 2. 1. 19:57


1. 스레드(Thread)로 고민해보기.

지난 글 [안드로이드 스레드(Android Thread)]에서, 스레드(Thread)의 개념에 대해 설명하고, 안드로이드에서 스레드를 만들고 실행하는 방법에 대해 살펴보았습니다. 그리고 프로세스의 시작과 함께 반드시 실행되는 메인 스레드(Main Thread)에 대해 언급하고, 안드로이드 메인 스레드의 중요한 역할(루프 실행과 화면 그리기)에 대해 설명하였습니다.


아마도 [안드로이드 스레드(Android Thread)]를 미리 살펴봤다면, 스레드에 대한 기본 개념 정도는 이해하게 되었으리라 생각되는데요. 이제 간단한 앱 개발을 통해 스레드를 사용하는 이유와 방법에 대해 알아보도록 하겠습니다.


참고로, 예제에서 보여주는 앱의 구현 과정은 오직 스레드 사용법에 대한 설명을 위해 임의로 채택한 방법입니다. 그러므로 예제와 유사한 기능을 구현하기 위한 목적으로 본문의 예제 소스를 사용하는 것은 추천하지 않습니다.


자, 그럼 시작해 볼까요?

2. 앱 만들어보기.

2.1 디지털 시계 앱 구상.

예제에서 만들고자 하는 앱은 "디지털 시계"입니다. 먼저, 아래와 같이, 앱을 구상하는 상황을 가정해보겠습니다. 가볍게 읽어주세요.


[단계-1] 앱 아이디어.
어느 날 갑자기, 멋진(?) 앱에 대한 아이디어가 떠올랐습니다. 바로 "디지털 시계" 인데요. 
보통 스마트폰을 구매하면 기본적으로 설치된 앱 중에 하나이지만, 
마음에 드는 디자인이 없는 관계로 직접 만들기로 결정합니다. 
이름은... 디지털(Digital)과 시계(Watch)라는 단어를 적절히 조합하여 디와치(DiWatch)로 정했네요.

2.2 요구사항 정의

어떤 앱을 만들지 정했으니, 이제 간단하게나마 디와치(DiWatch) 앱에 대한 요구사항을 정리해 보겠습니다.


[단계-2] 요구사항 정의.
R1. 현재 시각을 숫자 형식으로 화면에 표시한다.
R2. 표시 형식은 "시:분:초"로 표시한다.
R3. 시간은 24시간 기준(0~23), 분은 60분(0~59), 초는 60초(0~59) 범위로 표시한다.
R4. 화면 갱신은 1초 단위로 수행한다. (즉, 1초마다 한번씩 화면을 다시 그린다.)
R5. 현재 시각 정보는 화면의 중앙에 표시한다.
R6. 시각 표시는 앱 시작과 동시에 수행한다.

2.3 앱 화면 구성 설계.

앱 실행 화면은 요구사항에 따라 아래 그림처럼 간단하게 구성합니다. (참고로, 여기서는 스토리보드를 따로 작성하지 않았습니다. 예제가 너무 간단하여 스토리보드에 넣을만한 내용이 없네요.)


[단계-3] 앱 화면 설계.


그리고 이제 앱을 어떻게 만들지 SW구조를 설계해야 하는데요. 이 과정을 먼저, 스레드 없이 접근해보도록 하겠습니다.

3. 간단한 접근. 스레드 없이 무작정 만들어보기.

3.1 앱 구조 설계

앞서 정리한 요구사항에 따라 아래와 같이 SW구조를 설계해봤는데요. 정해진 형식없이 간단하게 작성한 내용이니, 처리 흐름을 이해하는 정도로만 참고하시면 될 것 같습니다.


[단계-4] 앱 구조 설계. (스레드 없이 무작정 만들어보기)

앱 구조 설계(스레드 없이 무작정 만들어보기)


3.2 구현. (스레드 없이 무작정 만들어보기.)

자, 이제 설계에 따라 앱을 구현해보도록 하겠습니다. [2.3 앱 화면 설계] 단계에서 설계한 화면대로, 메인액티비티의 레이아웃은 아래와 같이 작성합니다.


[단계-5.1] 메인액티비티 레이아웃 작성.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="48sp"
        android:text="00:00:00"
        android:id="@+id/clock"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

이제 1초마다 현재 시각을 얻어와서 화면에 표시하는 자바 코드를 작성하겠습니다. 앱이 시작됨과 동시에 시각을 표시하는 작업이 수행되어야 하므로, 앞서 설계한 내용대로, 액티비티의 초기화가 수행되는 onCreate() 메서드에 구현하면 될 것 같습니다. while 루프를 돌며 1초 마다 한번씩 현재 시각을 구해온 다음, 텍스트뷰에 표시하도록 만들겠습니다.


[단계-5.2] onCreate()에서 1초 마다 시각을 갱신하는 코드 작성.
public class MainActivity extends AppCompatActivity {

    TextView clockTextView ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...

        clockTextView = findViewById(R.id.clock) ;
        
        Calendar cal = Calendar.getInstance() ;
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss") ;
        while (true) {
            String strTime = sdf.format(cal.getTime());
            clockTextView.setText(strTime) ;

            try {
                Thread.sleep(1000) ;
            } catch (Exception e) {
                e.printStackTrace() ;
            }
        }
    }
}
class MainActivity : AppCompatActivity() {

    var clockTextView: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        clockTextView = findViewById(R.id.clock)

        val cal = Calendar.getInstance()
        val sdf = SimpleDateFormat("HH:mm:ss")

        while (true) {
            val strTime = sdf.format(cal.getTime())
            clockTextView?.setText(strTime)

            Thread.sleep(1000)
        }
    }

3.3 실행 결과. (스레드 없이 무작정 만들어보기.)

앱을 빌드하고 실행해보겠습니다. 결과가 어떻게 나올까요? 디지털 시계가 1초마다 한번씩 표시되나요?

스레드 없이 무작정 만들어보기 예제 실행 화면


어라, 화면에 디지털 시계는 고사하고, 메인 액티비티 레이아웃에 배치했던 텍스트뷰(00:00:00)조차 표시되지 않습니다. 아예 화면은 갱신조차 되지 않고, 앱 실행은 멈춰버린 것 같습니다. 왜 그럴까요?


원인을 찾기 위해, 한 가지 질문을 던져보겠습니다.


일반적으로, 개발자가 안드로이드 앱의 초기화 코드를 작성하는 메서드인 onCreate() 메서드는
어디서 호출, 실행되는 것일까요?


onCreate() 메서드가, 안드로이드 스튜디오에서 액티비티를 추가할 때 자동으로 추가되고, 앱이 실행된 다음 액티비티가 만들어지는 시점에 무조건 호출되는 메서드라는 것은 막연히 알고 있지만, 프레임워크 내부에서 onCreate() 메서드가 어떤 과정을 거쳐 호출되는지에 대해서는 큰 관심을 가지지 않았을 것입니다.


하지만 [안드로이드 스레드]를 미리 살펴보고 내용을 이해했다면, onCreate()가 어떠한 이벤트 메시지가 수신되었을 때 실행되는 메서드이고, 안드로이드 앱의 메인 스레드에서 실행된다는 것을 어렴풋이 눈치챌 수 있을 것입니다.


그런데 위에서 작성한 코드를 보면, onCreate() 메서드에서 무한 루프(while(true))를 실행해 버렸습니다. 메인 스레드 입장에서는 onCreate() 메서드에서 초기화 작업을 마치고나서, 다시 메시지 큐를 확인하여 다른 이벤트(화면 그리기, 터치 입력 처리 등)들을 처리해야 하는데, onCreate() 메서드가 끝나지 않게 된 것이죠.


이런 이유 때문에, 안드로이드 앱의 메인 스레드에서는 무한 루프나 실행 시간이 긴 작업, 또는 Thread.sleep()을 통한 과도한 대기 등의 코드 작성을 피해야 합니다.


자, 어쨌든 문제가 무엇인지 파악했으니, 이제 코드를 수정해야겠군요. 스레드를 사용하겠습니다.

4. 스레드 사용. 일단 스레드로 만들어보기.

앞서 작성한 코드의 문제는, 지속적으로 실행되어야 하는 작업을 메인 스레드(onCreate())에서 실행했기 때문에, 메인 스레드의 다른 코드가 더 이상 실행되지 않는 것이었습니다. 사용자 입력도 처리하지 못하고, 화면 갱신도 수행하지 못하게 되어버렸죠.


그럼 이제 메인 스레드와 분리되어 동시적으로(Concurrently) 실행되어야 하는 작업(1초 마다 현재 시각 표시)을 별도의 스레드로 작성해보겠습니다.


4.1 스레드를 적용한 앱 구조 설계

이제 스레드를 적용한 처리 흐름을 작성해보겠습니다. 이 또한, 처리 흐름을 이해하는 정도로만 참고하시면 될 것 같습니다.


[단계-4] 앱 구조 설계. (스레드 적용)

앱 구조 설계(스레드 적용)


4.2 구현. (스레드 적용)

메인 액티비티의 레이아웃은 앞서 [3.2 구현]에서 작성한 XML 코드를 그대로 사용하고, 자바 코드만 새로 작성하겠습니다.


[단계-5.2] 스레드에서 1초 마다 현재 시각을 갱신하는 코드 작성.
public class MainActivity extends AppCompatActivity {

    TextView clockTextView ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...

        clockTextView = findViewById(R.id.clock) ;

        class NewRunnable implements Runnable {
            Calendar cal = Calendar.getInstance() ;
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss") ;

            @Override
            public void run() {
                while (true) {
                    String strTime = sdf.format(cal.getTime());
                    clockTextView.setText(strTime) ;

                    try {
                        Thread.sleep(1000) ;
                    } catch (Exception e) {
                        e.printStackTrace() ;
                    }
                }
            }
        }

        NewRunnable nr = new NewRunnable() ;
        Thread t = new Thread(nr) ;
        t.start() ;
    }
}
class MainActivity : AppCompatActivity() {

    var clockTextView: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        clockTextView = findViewById(R.id.clock)

        thread(start = true) {
            var cal = Calendar.getInstance()
            var sdf = SimpleDateFormat("HH:mm:ss")

            while (true) {
                val strTime = sdf.format(cal.time)
                clockTextView?.setText(strTime)

                Thread.sleep(1000)
            }
        }
    }

[안드로이드 스레드(Android Thread) - 2.2 Runnable 인터페이스 구현]에서 스레드를 만드는 방법에 대해 설명했듯이, 새로운 스레드를 만들기 위해 Runnable인터페이스를 구현(implements)한 클래스를 생성하였습니다. 그리고 run() 메서드를 추가한 다음, 1초마다 현재 시각을 가져와서 화면에 표시하도록 만들었습니다.


코드 자체는 onCreate() 메서드 내부에 작성되어 있지만, 메인 스레드와 분리된, 새로 생성한 스레드에서 실행됩니다. 그래서 while(1) 루프로 인해 메인 스레드의 루프가 실행되지 않는 문제가 해결되었죠. 이제 메인 스레드가 정상적으로 다른 이벤트(화면 그리기, 터치 이벤트 등)를 처리할 수 있게 되었습니다.

4.3 실행 결과. (스레드 적용)

자, 이제 스레드를 사용하여 1초 단위로 시각을 표시하도록 만들었으니, 앱을 실행하여 결과를 확인하겠습니다.

스레드 적용 예제 실행 화면


이런, 현재 시각이 한번 제대로 표시되는 것 같더니 앱이 종료되어 버렸습니다. 작성한 코드에 문제는 없는 것 같은데, 앱이 왜 종료된 것일까요? 무엇이 원인일까요? 문제에 대한 원인과 해답을 찾기 위해, 앱이 중지될 때 출력된 로그 메시지를 확인해 보겠습니다.

Only the original thread that created a view hierarchy can touch its views


음, 에러 메시지 중에 눈에 띄는 메시지가 있네요.


"Only the original thread that created a view hierarchy can touch its views."

대충 번역해보면, "뷰 계층을 생성한 원래 스레드만이 해당 뷰를 건드릴 수 있습니다."라는 의미인데요. 어떤 의미인지 한번에 이해가 되나요? 이해하기 쉽게 문장을 살짝 바꿔볼까요? "뷰 계층을 생성하지 않은 스레드는 해당 뷰를 건드릴 수 없습니다.". 새로 만든 스레드와 뷰의 문제인 것은 확실하네요.


소스에서, 화면의 텍스트뷰(clockTextView)에 현재 시각을 표시하기 위해, 새로 만든 스레드 내에서 텍스트뷰의 setText() 메서드를 호출하였습니다. 하지만 이는 잘못된 구현입니다.


TextViewsetText() 메서드는 텍스트뷰에 표시될 문자열을 변경합니다. 즉, UI 그리기 기능이 실행되는 것인데요, 메인 스레드가 아닌 다른 스레드에서 화면을 그리는 기능이 실행되었기 때문에 에러가 발생한 것입니다. [안드로이드 스레드(Android Thread) - 3.3 안드로이드 메인 UI스레드의 중요한 역할:화면 그리기]에서 설명했던 내용을 다시 상기시켜 볼까요?


"그리기 기능"은 반드시 메인 UI 스레드에서 실행되어야 합니다.

또 다시, 문제의 원인을 찾아내었습니다. 수정해야겠군요.

5. 스레드 적용. 스레드 간 통신 적용하기.

앞서 작성한 코드를 통해, 안드로이드 메인 스레드가 아닌 스레드에서 뷰(View)에 대한 직접적인 접근의 문제에 대해 살펴보았습니다. 다행히도, 그리고 친절하게도, 안드로이드 프레임워크에서 해당 문제에 대한 에러 메시지를 띄워준 덕분에 문제점을 쉽게 찾을 수 있었죠.


문제를 해결하기 위한 방법은 여러가지가 있지만, 여기서는 가장 기본적인 접근 방법, "스레드 간 통신"을 적용하겠습니다.


"스레드 간 통신"의 핵심은 간단합니다. 하나의 스레드에서 다른 스레드로 메시지를 보내는 것입니다. 예제에서는, 새로 만든 스레드(1초 마다 메시지 전달)에서 메인 스레드(현재 시각 화면에 표시)로 메시지를 보내면 됩니다.


5.1 스레드 간 통신을 적용한 앱 구조 설계

수정해야 할 내용은 명확한데요, 새로 생성한 스레드에서 뷰를 직접 다루지 않고, 메인 스레드에서 접근하도록 만들면 됩니다. 그리고 이를 위해 새로운 스레드에서 메인 스레드로 메시지를 보내는 것이죠. 아래 그림과 같은 구조로 처리될 수 있습니다.


[단계-4] 스레드 간 통신을 적용한 앱 구조 설계.

앱 구조 설계(스레드 간 통신을 적용)


5.2 구현. (스레드 간 통신 적용하기.)

일단, 자세한 설명은 뒤로 하고, 아래 코드처럼 구현할 수 있습니다.


[단계-5.2] 스레드 간 통신으로 메인 스레드에서 현재 시각을 갱신하는 코드 작성.
public class MainActivity extends AppCompatActivity {

    TextView clockTextView ;
    private static Handler mHandler ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...

        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Calendar cal = Calendar.getInstance() ;

                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
                String strTime = sdf.format(cal.getTime());

                clockTextView = findViewById(R.id.clock) ;
                clockTextView.setText(strTime) ;
            }
        } ;

        class NewRunnable implements Runnable {
            @Override
            public void run() {
                while (true) {
                    
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace() ;
                    }

                    mHandler.sendEmptyMessage(0) ;
                }
            }
        }

        NewRunnable nr = new NewRunnable() ;
        Thread t = new Thread(nr) ;
        t.start() ;
    }
}
class MainActivity : AppCompatActivity() {

    var clockTextView: TextView? = null
    private var mHandler: Handler? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        @SuppressLint("HandlerLeak")
        mHandler = object : Handler() {
            override fun handleMessage(msg: Message) {
                val cal = Calendar.getInstance()

                val sdf = SimpleDateFormat("HH:mm:ss")
                val strTime = sdf.format(cal.time)

                clockTextView = findViewById(R.id.clock)
                clockTextView?.setText(strTime)
            }
        }

        thread(start = true) {
            while (true) {
                Thread.sleep(1000)
                mHandler?.sendEmptyMessage(0)
            }
        }
    }

여기서 눈여겨 볼 부분은 핸들러를 만들고(new Handler) 수신 메시지를 처리하는 코드(handleMessage), 그리고 메인 스레드의 핸들러를 통해 메시지를 전달(sendEmptyMessage)하는 코드인데요. 지금 당장 상세한 설명을 덧붙이지 않아도, 코드의 흐름이 어떻게 흘러가는지 어렴풋이 이해될 거라 생각합니다.


그럼, 실행 결과를 확인해보죠.

5.3 실행 결과. (스레드 간 통신 적용하기.)

스레드 간 통신 적용 예제 실행 화면


자, 이제 드디어 원하는 기능이 동작되도록 만들었습니다.

6. 정리

[안드로이드 스레드(Android Thread)]의 스레드에 대한 기본적인 설명에 이어, 이 글에서는 다소 억지스런(?) 예제를 통해 스레드를 사용하는 이유와 사용법, 그리고 몇 가지 주의 사항에 대해 살펴보았습니다.


간단히 요약하자면,

  • 메인 스레드와 병행적으로(Concurrently) 실행되어야 할 작업은 스레드로 작성한다.
  • 메인 스레드에서는 실행 시간 또는 대기 시간이 긴 작업의 실행을 피해야 한다.
  • UI를 변경하는 작업은 반드시 메인 스레드에서 실행되어야 한다.
  • 핸들러(Handler)를 사용하여 메인 스레드로 메시지를 보낼 수 있다.

정도로 정리될 수 있겠네요.


본문의 내용을 꼼꼼하게 살펴보고 예제 코드를 직접 작성해 본다면, 아마도 스레드의 기본 개념과 사용법에 대해 조금 더 깊이 이해할 수 있지 않을까 생각합니다.


7. 참고.

.END.


ANDROID 프로그래밍/THREAD , , , , , , , , , , , , ,

  1. Blog Icon
    KingKong

    설연휴 우울한 취준생한테 가뭄끝 단비같은 글이네여!!!
    굿굿!!올만에 와봤는데 역시나 입니다!!

  2. 에고.. 취업 준비로 마음 고생이 많으신가 보네요.
    하지만 연휴에도 개발 관련 자료 찾으시는 걸 보니, 곧 좋은 직장에 들어가실거라 생각됩니다.

    새해 복 많이 받으세요.

    감사합니다.

  3. 대학생부터 취준생이된 지금까지 도움 많이됐습니다 감사합니다

  4. 누군가에게 도움이 되는 건 정말 기분좋은 일이네요. ^^

    꼭 좋은 직장 들어가셔서, 해피해피 라이프 즐기실거라 생각합니다!

    방문해 주셔서 감사합니다.

  5. Blog Icon
    초보 개발자 1호

    회사에 들어가고 나서, 모르는 걸 공부할 때 언제나 큰 도움이 됩니다.
    마침 쓰레드의 개념이 잘 이해가 가지 않았었는데, 좋은 예제와 설명 감사드립니다.
    그런데 핸들러라는 건 결국 쓰레드끼리(서브, 메인 쓰레드 등) 메시지를 주고받는 매개체 역할을 하는 건가요?

  6. 음, "스레드가 어떤 동작을 수행하기 위해 외부에서 메시지를 받을 필요가 있을 때 사용하는 구현 요소"라는 표현이 좀 더 정확할 것 같아요.

    "스레드끼리"라는 표현에는... 쌍방간 통신 개념이 들어가니까요.

    모든 스레드가 핸들러를 가지는 것은 아닙니다. 필요한 스레드만 사용하면 되죠. 물론, 핸들러를 사용하기 위한 루퍼를 생성하는 게 먼저이긴 한데, 관련 내용은 따로 정리해볼게요.

    "스레드"를 처음 접하면 쉽게 이해되지 않는 부분이 있을거에요. 하지만 천천히 개념들을 습득하고 구현 과정을 거치다보면, 점점 익숙해지실겁니다.

    언제든 궁금한 점 있으면 질문 남겨주세요.

    감사합니다.

  7. Blog Icon
    초보 개발자 1호

    그렇군요. 친절한 답변 감사드립니다.

  8. 자주 찾아주셔서 감사합니다.

  9. Blog Icon
    낡은컴쟁이

    이렇게 정성을 들여 만든 가르침에 있다는 것에 경외갑을 느낍니다..

    위의 에제가 실행은 아무 이상이 없이 잘 됩니다만..

    이런 메시지가 나타납니다..
    This Handler class should be static or leaks might occur
    이 문제 좀 해결 부탁드려요

  10. 문제의 원인은,

    핸들러(new Handler())가 MainActivity의 inner 클래스로 선언되었고, 이는 핸들러 객체가 MainActivity와 참조되는 것을 의미합니다.

    그런데 이렇게 inner 클래스로 만들어진 경우, outer 클래스인 MainActivity를 GC되지 않게 만드는 문제가 생길 수 있습니다. 핸들러는 해당 스레드의 Looper, MessageQueue와 연관되는데, 그의 outer 클래스인 MainAcitivy가 GC될 타이밍에 핸들러가 스레드 내부에서 참조가 유효하다면 MainActivity가 GC되지 못하는 것이죠.

    이 문제를 해결할 방법은 핸들러를 static 클래스로 선언하여 MainActivity와 WeakReference가 되게 만들면 됩니다.

  11. Blog Icon
    서지민

    요구사항 정의서까지 있다니 정리가 잘 되어 있네요. 감사합니다. 도움이 많이 되었습니다.

  12. 요구사항 정의서..라고 하기엔 걍 대충대충 정리한거라 큰 도움이 될까 모르겠네요.
    그래도 무턱대고 예제 코드만 올려놓는 것보단 이해하기 더 좋을 것 같아서 정리해봤네요.

    도움이 되셨다니 다행입니다.

    감사합니다.

  13. Blog Icon
    사랑합니다

    21세기 최고의 글이네요. 스티브 잡스의 프레젠테이션이 떠오르는데 너무 빛이나서 제대로 읽기가 힘드네요.. 잘보고 갑니다.

  14. 시력 보호를 위해 모니터의 밝기를 조절하심이.... 흐흐.
    너무 과찬의 말씀을 적어주셨네요.

    누가보면 제가 쓰고 제가 답변하는 것처럼 보일 듯...

    어쨌든 좋은 말씀 감사합니다.

  15. Blog Icon
    이재홍

    안드로이드 공부를 하고있는 사람입니다. 이 블로그를 제 교과서로 시작하려구요. 감사합니다!

  16. 아이고. 교재라뇨... ㅜㅜ
    다른 좋은 책과 강좌가 많이 있습니다.
    두루 두루 살펴보시면서 공부하시면 더 효과가 좋을 것 같습니다!!

    방문해 주셔서 감사합니다.

  17. 글 읽으면서 공부 중입니다! ㅎㅎ 좋은 내용 다시 한 번 감사합니다.
    간단한 질문 하나 드려도 될까요?

    Handler의 handleMessage 함수 안에 cal, sdf 프로퍼티 선언하면 윗 예제 코드처럼 잘 작동함을 확인했습니다.

    혹시나 궁금해서 handleMessage 외부, onCreate() 안에 선언해봤더니 한 번만 사용되고 타이머가 멈췄습니다. 이유를 생각해 본 결과, MainThread가 onCreate()를 끝내 버렸기 때문에 onCreate() 안의 프로퍼티는 더 이상 유효하지 않다고 결론을 내렸는데, 이 이유가 맞는지 궁금합니다!

    항상 좋은 글 감사합니다 ㅎㅎ 많이 보고 공부하겠습니다!

  18. 답이 많이 늦었네요. 죄송합니다.

    개발자동동님께서 어떻게 작업하신 건지.. 쉽게 감이 오지 않네요.
    딱히 타이머가 멈출 이유는 없을 것 같은데, 멈췄다고 하니... 코드를 봐야 원인과 결론에 대한 확인이 가능할 것 같습니다.

    조금 더 깊게 살펴보시려면, 변수 Scope와 Life time에 대해 이해하시면 좋을 것 같습니다.

    늦었지만, 추가적인 내용 올려주시면 도움드리기 더 수월할 것 같습니다.

    감사합니다.

  19. Blog Icon
    놉놉

    안드로이드 공부하면서 여기저기 찾아봤는데 이해하기 쉽고 잘 정리된 글같아요. 감사합니다

  20. 도움이 되셨다니 다행이네요.
    방문해 주셔서 감사합니다.

  21. Blog Icon
    강태

    쓰레드를 만들때
    메인엑티비티에서 만들어야한다는거잖아요?
    메인엑티비티에서 서브엑티비티를 불러오려면 메인액티비티에서 핸들러를 써서
    메인엑티비티에 불러오면 되는건가요?

    어... 그러니까 제말은....
    메인액티비티 <- 핸들러(서브1,서브2,서브3) 이렇게 핸들러를 써서 메인으로 불러온다는거죠?

  22. 글의 내용을 잘못 이해하신 것 같습니다.

    쓰레드를 메인액티비티에서 만들어야 하는 것이 아닙니다. 쓰레드는 어디서든 만들 수 있습니다.

    메인액티비티에서 서브액티비티를 불러온다.. 라는 것도 앞 뒤가 맞지 않는 질문이고요.

    메인액티비티에서 서브 액티비티를 실행하는 것과 스레드는 직접적으로 관련이 없습니다.

    아마... 액티비티 하나에 스레드가 하나가 실행된다... 고 이해하신 거 같은 느낌이 드는데...

    관련 내용을 조금 더 유심히 살펴보시고, 어떤 기능을 만드려고 하시는지 정리하여 질문 남겨주세요.

    지금 질문글만으로는 어떻게 답을 남겨드려야 할지 결정하기가 쉽지 않네요.

    감사합니다.

  23. Blog Icon
    강태

    제가 만들려는게 일단 어플이
    이런식으로 만들건데요
    메인화면->블루투스연결화면->아두이노에 연결된스마트폰 카메라화면->종료->메인화면 인데요.

    다른화면으로 넘어가도 블루투스는 계속 연결되어있고 그런걸 만들려고하는데요..
    그래서 스레드로 구현하면 될것같아서 공부중이었습니다...

  24. 네. 방향을 제대로 잡으신 것 같습니다.

    메인스레드와 관계없이 계속 실행되어야 할 기능을 구현하기 위해 스레드를 사용한다는 접근이 매우 타당하다고 생각합니다.

    블루투스 통신을 별도의 스레드로 만들어 실행하고, 메인스레드와의 통신을 위해 핸들러를 사용하면 원하는 기능을 만드는데 큰 어려움 없으실거라 생각합니다.

    블로그 내용들을 잘 참고해보시고요, 궁금한 내용이 있으면 질문글 다시 남겨주세요.

    질문글의 내용이 구체적이고 상세할수록 더 자세한 답변 드릴 수 있다는 점, 참고 부탁드려요.

    감사합니다.

  25. Blog Icon
    버터

    자바 스레드랑 같은 개념이니까 자바 스레드로 공부해도 무방한가요??

  26. 네. 무방합니다. 안드로이드에서 제공하는 스레드가 곧 자바 스레드입니다.

    안드로이드 앱을 자바로 개발할 때, Thread 클래스는 아래의 패키지 경로에 존재합니다.

    java.lang.Thread

    이것이 의미하는 것은 Thread 클래스가 java sdk에서 제공하는 패키지에 포함되어 있다는 것이죠.

    즉, 자바 스레드랑 같은 개념... 이 아닌,
    "자바 스레드"입니다.

    하지만 주의하셔야 할 게, Handler의 경우는 패키지 경로가 android.os.Handler 입니다. 즉, java sdk에 포함된 API가 아닌, android sdk에 포함된 API라는 것이죠.

    답변이 되었으면 좋겠네요.

    감사합니다.

  27. Blog Icon
    도치

    안녕하세요, 좋은 강의에 많은 도움 받고 있습니다.

    현재 여러개의 스레드를 돌리는 앱을 제작중인데요, 대부분이 while()을 이용한 무한 루프입니다.

    socket으로 바이트를 전송받고 실시간으로 영상처리를 해야해서 실시간으로 여러개의 스래드를 단발성이 아닌 무제한으로 돌리게 되는데요.

    동시에 돌리는 스레드가 늘어날 수록(4개 스레드) 스레드들의 속도가 줄어드는게 보입니다.

    단발성 스레드가 아닌 만큼 forkjoin 이나 threadpool이 큰 의미가 없을 것으로 생각 되는데요

    해결방안이나 제안해주실 수 있는 방법이 있을까요?

  28. 제가 해결방법을 제시해드릴 수 있는 내용은 아닌 것 같고요.

    먼저, 현재 상황을 정리하고, 원인을 분석하여 동작 가능성을 예측한 다음, 해결 방법을 찾으시는 게 순서인 것 같습니다.

    소켓으로 실시간으로 데이터를 처리하는데 있어 스레드를 사용하는 것 자체는 문제가 생길만한 내용이 없어 보입니다.
    어쨌든 데이터를 받아야 하고, 실시간으로(병행적으로) 처리해야 하고.. 이러한 작업을 스레드로 하는 건 잘못된 접근법이 아니죠.

    하지만 시스템 성능에 만족되는가에 대한 것은 조금 더 검토와 시험이 필요한 내용입니다.

    예를 들자면,
    4개의 스레드를 실행했을 때 성능에 문제가 생긴다 => 반드시 RX 당 하나의 스레드를 써야 하는가? => 실시간 요구사항을 100% 만족하지 않더라도 시스템 성능에 영향을 미치지 않을 정도로 스레드를 돌리면 안되는가.... 등으로 생각을 돌려볼 필요도 있을 것 같고요.

    아니면 소켓을 통해 전달받는 데이터를 줄이는 방법 등에 대해서도 고민해볼 수 있고요.

    질문 하신 내용에 대해서는 단순히 스레드 사용에 대한 접근보다는 처리하고자 하는 데이터의 특성과 시스템 성능 측면에서 더 많이 고민되어야 할 문제 같네요. 최악의 경우, 현재 사용하고 계시는 H/W에서 제대로 실행할 수 없는 상황도 가정해야 할지도 모르고요.

    어느 정도의 요구사항을 가지고 계신지 몰라서 도움드리기가 쉽지 않네요. 양해바랍니다.

    감사합니다.

  29. Blog Icon
    노마드

    안녕하세요 좋은 글 잘 보았습니다.

    혹시 스레드나 러너블 꼭 MainActivity에 있어야 하나요? 다른 view에서 실행해도 되는지요?

  30. 스레드가 반드시 MainActivity에 있을 필요는 없습니다.
    언제든 필요할 때 필요한 곳에서 실행하면 됩니다.

    감사합니다.

  31. 숨 넘어갑니다 119좀 불러주세요.....

    뽀따님 글 거의 다 보고 있는데 최곱니다.

  32. 볼품없는 블로그에 자주 방문해 주시고,
    칭찬 댓글까지 남겨주셔서 정말 감사합니다.

  33. ppottasoft@gmail.com 감사 메일 하나 보냈습니다!

  34. 보내주신 메일 확인했습니다.

    에고.. 참.. 이 블로그가 뭐라고 그런 걸 보내셨나요... ㅜㅜ
    안드로이드 공식 홈페이지에 잘 정리되어 있고, 구글링 해보면 쉽게 찾을 수 있는 내용들 나열되어 있는 정도인데...

    취미삼아 하는 거라,
    자주 확인하지도 못하고 관리도 제대로 되지 않는 블로그인데요..

    오히려, 이렇게 자주 찾아주시고, 관심가져 주셔서, 제가 고마울 따름입니다.

    감사합니다.

  35. Blog Icon
    sgo8308

    안녕하세요
    저번 글을 보고 저는 핸들러가 onCreate같은 메소드를 실행시키는 주체인 줄 알았는데 onCreate가 핸들러 자체인 것인가요?? 핸들러는 클래스이고 onCreate는 메소드인데.. 뭔가 헷갈리네요..!
    또 저는 oncreate안에 레이아웃을 인플레이트하는 과정에서 화면이 그려진다고 생각했는데 그게 아니라
    oncreate가 되고 루프가 다시 한 번 돌면서 화면이 그려지게 되는 그런것인가요??
    지금 이해한 것을 정리해보면 메세지큐에 oncreate를 하라는 메세지가 추가되고 루프가 체크한 후에 oncreate가 실행되고 그 oncreate 안에서 다시 뷰를 찾고 setText하라는 메세지를 메세지큐에 보내면 다시 루프가 그 메세지를 체크해서 메인 쓰레드가 화면을 그리는 건가?? 라고 지금 이해했는데 맞는지 잘 모르겠습니다 !

  36. 일단 용어 사용에서 조금 혼동을 드린 것 같아 죄송합니다.

    "안드로이드 Handler 객체를 의미하는 핸들러"와 "어떤 액션이 일어났을 때 그것을 처리하는 함수인 핸들러"를 본문에서 혼용해서 사용해서 헷갈리신 것 같아요.

    onCreate가 Handler 객체인 것은 아니고, 어떤 핸들러가 수신될 때 액티비티에서 실행되는 메서드가 맞습니다.

    그리고 질문글의 후반부에서 이해하신 내용은 딱히 문제가 없어 보입니다.

    감사합니다.

  37. Blog Icon
    정지원

    안녕하세요 글이너무 좋아서 출처를 남기고 블로그에 포스팅하고싶은데 괜찮으실까요?

  38. 내용의 일부를 발췌하고 링크를 남겨주시는 것은 괜찮을 것 같습니다.
    감사합니다.

  39. Blog Icon
    James

    사랑합니다. 항상 동기부여 많이 되고있습니다.

  40. 감사합니다.