안드로이드 루퍼

PEPPERMINT100·2021년 1월 8일
0
post-thumbnail

서론

저번 글에서 안드로이드에서 스레드와 핸들러에 대해 알아보았다. 간단하게 정리하자면 안드로이드는 메인 스레드와 작업 스레드로 나뉘고 메인 스레드는 앱이 시작될 때 생성되어 실행되며 모든 UI 작업은 여기서 처리하도록 되어있다. 그리고 나머지 스레드를 전부 작업 스레드라고 하는데, 작업 스레드에서는 핸들러를 사용하여 작업 스레드의 UI 작업을 메인 스레드에게 넘겨서 UI 작업을 처리할 수 있도록 도와준다.

저번 글에서 확인한 것처럼 스레드는 이런 순서를 통해 동작한다. 여기서 루퍼가 잠깐 등장했었는데, 스레드는 생성이 되면 들어오는 메시지를 순서대로 처리하는데, 근데 그 처리를 핸들러를 통해 보내고 받아올 수 있다. 즉 핸들러는 스레드의 메시지를 보내는 것뿐만이 아닌 메시지를 통해 처리된 결과를 실행(excute)하는 역할도 하게 된다. 그렇다면 저 메시지 큐의 순서 관리는 누가 하게 될까? 물론 메시지 큐의 순서도 핸들러가 메시지 큐의 처음에 작업을 넣던지 아니면 중간에 넣던지 선택할 수 있지만 그 메시지 큐를 도는 작업을 루퍼가 하게 된다.

아래 코드를 잠시 확인해보자.

    inner class MyThread: Thread(){
        override fun run() {
                var now = System.currentTimeMillis()
                Log.d(TAG, "MyThread - run: now : $now ");
                SystemClock.sleep(1000)
                var msg = Message()
                msg.what = 0
                msg.obj = now
                Handler().post(object: Runnable {
                    override fun run() {
                        myText.text = "now : ${msg.obj}"
                })
            }
        }
    }

어제 작성한 코드와 비슷한데, 메시지를 생성하고 스레드 내에서 핸들러를 생성한 다음 UI를 변경시키는 Runnable로 넣어준다. 참고로 핸들러의 post 메소드는 UI 스레드로 Runnable 작업을 넘기는 역할을 하게 된다. 하지만 이 코드는 동작하지 않고 에러 로그를 보면

  java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

위와 같은 에러를 확인할 수 있다. 해석해보면 루퍼가 준비되지 않았다고 쓰여있다. 이유는 사실 저번 글에서 쓴 코드와 다르기 때문인데, 지정된 핸들러를 사용하지 않고 새로운 핸들러를 생성해서 사용하였기 때문이다.

다시 한 번 말하지만 스레드를 제외하고 나머지 다른 메시지 큐, 루퍼, 핸들러는 안드로이드 고유의 개념이다.(다른 곳에도 개념 자체는 사용되지만 저 객체들은 안드로이드에 속해있다.)

이 작업이 실행되지 않는 이유는 핸들러는 메시지 큐를 관리하는 객체여야 하는데, 메시지 큐가 없기 때문이다. 이 메시지 큐는 루퍼를 생성해주면 함께 메시지 큐를 생성하게 된다.

루퍼

    inner class MyThread: Thread(){
        lateinit var handler: Handler
        override fun run() {
            Log.d(TAG, "MyThread - run: ");
                Looper.prepare()
                handler = Handler()
                SystemClock.sleep(1000)
                Looper.loop()
                Log.d(TAG, "MyThread - terminated: ");
        }
    }

위 처럼 코드를 생성해주면 에러가 고쳐진다. 핸들러 생성 이전 루퍼를 준비하여 메시지 큐와 핸들러를 연결해주고 루퍼의 루프를 돌린다. 이렇게 하면 위 사진처럼 루퍼가 계속 메시지 큐를 끝없이 돌게 된다.

버튼을 하나 더 생성하고 이름을 workButton이라고 해보자. 그리고 아래와 같이 이벤트 리스너를 추가해준다.

        workButton.setOnClickListener {
            myThread.handler.post{
                for(i in 1..5){
                    Log.d(TAG, "MainActivity - onCreate: i is $i ");
                    SystemClock.sleep(1000)
                }
            }
        }

이렇게 하면 myThread의 핸들러에 post 메소드 안의 작업을 메시지 큐에 추가하도록 한다. 만약 이 버튼을 여러 번 누르게 되면 새로운 Runnable 객체를 계속해서 메시지 큐에 넣게 되므로 계속해서 for문을 돌게 되는 것을 로그에서 확인할 수 있다.

원래는 핸들러를 통해 메인 스레드에 접근하여 UI 작업을 하였지만 이번엔 메인 스레드에서 핸들러에 접근하여 메시지 큐를 추가하도록 한 것이다.

이번엔 myThread의 루퍼에도 접근할 수 있도록 myThread 코드를 조금 변형시켜보자.

    inner class MyThread: Thread(){
        lateinit var handler: Handler
        lateinit var looper: Looper
        override fun run() {
            Log.d(TAG, "MyThread - run: ");
                Looper.prepare()
                looper = Looper.myLooper()!!
                handler = Handler()
                SystemClock.sleep(1000)
                Looper.loop()
                Log.d(TAG, "MyThread - terminated: ");
        }
    }

그리고 startButtonstopButton의 이벤트 리스너를 변경해준다.

        startButton.setOnClickListener{
            Log.d(TAG, "MainActivity - onCreate: 스타트 버튼 클릭");
            myThread.start()
        }

        stopButton.setOnClickListener {
            Log.d(TAG, "MainActivity - onCreate: 스탑 버튼 클릭 ");
            myThread.looper.quit()
        }

이제 start를 누르면 스레드를 시작하여 루퍼를 돌게 하고 stop을 누르면 루퍼에 접근하여 루프를 멈추게 한다.

        workButton.setOnClickListener {
              myThread.handler.post{
                for(i in 1..5){
                    Log.d(TAG, "MainActivity - onCreate: i is $i ");
                    SystemClock.sleep(1000)
                }
            }
        }

이제 workButton을 누르면 myThread에서 새로운 메시지 큐를 실행하게 되기 때문에 1부터 5까지 로그캣에 출력이 되는 것을 볼 수 있고, 다시 workButton을 누르면 새로운 Runnable이 메시지 큐에 등록이 되므로 다시 1부터 5까지 출력이 되는 것을 볼 수 있다. 그리고 stop 버튼을 누르면 looper 자체를 종료시키므로 더이상 workButton을 누르더라도 작동을 하지 않는 것을 확인할 수 있다.

결론

안드로이드 공부 순서를 구글링해서 보면 스레드, 핸들러, 루퍼의 원리를 아는 것이 중요하다고 작성된 글을 많이 접했다. 이러한 작업은 비동기를 처리하기 위함인데, 핸들러와 루퍼를 이용해서 안드로이드에서 가장 원시적인 방법으로 비동기를 처리했다고 보면 된다. 다음에는 모던 안드로이드에서 비동기를 어떻게 처리하는지에 대해 알아보도록 하겠다.

profile
기억하기 위해 혹은 잊어버리기 위해 글을 씁니다.

0개의 댓글