본문 바로가기
카테고리 없음

RxAndroid - 안드로이드 스레드 대체(TimerTask)

by JeongUPark 2020. 2. 17.
반응형

[출처 - RxJava 프로그래밍 : 리액티브 프로그래밍 기초부터 안드로이드 까지 한번에]

본 글은 'RxJava 프로그래밍 : 리액티브 프로그래밍 기초부터 안드로이드 까지 한번에' 를 학습하면서 정리한 글입니다.


안드로이드에서 주기적으로 실행하는 동작을 구현할 때 보통 Timer클래스나 Handler 클래스를 습니다. Timer 클래스는 schedule() 메서드를 이용하여 지연 시간을 설정하거나 특정 시간에 동작을 실행, 고정된 시간을 통한 동작 반복을 실행 할 수 있습니다. Handler클래스는 postDelayed() 매서들르 사용합니다.

간단하게 Timer클래스의 사용을 확인해 보겠습니다.

 

xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/main_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/stop_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Activity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*

class MainActivity : AppCompatActivity() {
    private val TAG = "RxAndroid Async Test"
    var mAndroidText : TextView? = null
    val DELAY = 0
    val PERIOD = 1000
    var count = 0
    val mTimer = Timer()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mAndroidText = main_text
        stop_btn.setOnClickListener {
            timerStop()
        }
        timerStart()


    }

    fun timerStart(){
        mTimer.scheduleAtFixedRate(object : TimerTask(){

            override fun run() {
                runOnUiThread {
                    mAndroidText!!.text = count.toString()
                }
                count++
            }
        }, DELAY.toLong(),PERIOD.toLong())
    }

    fun timerStop(){
        mTimer.cancel()
    }


}

 

결과

위와 같이 1초마다 시간이 올라가고 stop 버튼을 누르면 멈춥니다. 이게 Timer를 사용한 간단한 code 였습니다.

이런 Timer 클래스는 다음과 같은 메서드를 사용할 수 있습니다.

메서드 이름 설명
schedule(TimerTask task, Date time) 지정된 시간에 한번 실행
schedule(TimerTask task, Date firstTime, long period) 지정된 시간에 실행을 시작 후 주기적으로 실행
schedule(TimerTask task, long delay) 설정된 지연 시간 후 한번 실행
schedule(TimerTask task, long delay, long period) 설정된 지연 시간 후 주기적으로 실행
scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 지정된 시간에 실행을 시작후 고정된 간격으로 주기적으로 실행
scheduleAtFixedRate(TimerTask task, long delay, long period) 설정된 지연 시간 후 고정된 간격으로 주기적으로 실행
cancel() 현재 예약 된 작업을 삭제하고이 타이머를 종료
purge() 이 타이머의 작업 대기열에서 취소 된 모든 작업을 제거

위와 같은 Timer 클래스 이외에도 CounterDownTimer클래스(실행 횟수 제한시 사용) 또는 Handler 클래스의 postDelayed를 사용할 수 도 있습니다.

 

RXAndroid 를 이용해 Timer 클래스를 대체하면 두가지 방법으로 구현가능합니다.(더 있을 수도 있죠)

첫번쨰는 interval 함수 입니다.

 

xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/add_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        tools:ignore="MissingConstraints" />
    <Button
        android:id="@+id/stop_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/add_btn"
        tools:ignore="MissingConstraints" />
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/main_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/stop_btn"/>


</androidx.constraintlayout.widget.ConstraintLayout>

Activity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import java.util.concurrent.TimeUnit


class MainActivity : AppCompatActivity() {
    private val TAG = "RxAndroid Async Test"
    private val INITIAL_DELAY = 0L
    private val PERIOD = 3L
    private val mAdapter = MyViewAdapter()
    lateinit var ob : Disposable
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        main_recyclerview.layoutManager = LinearLayoutManager(this)
        main_recyclerview.adapter = mAdapter

        add_btn.setOnClickListener {
            startPolling()
        }
        stop_btn.setOnClickListener {
            Log.d(TAG,"stop btn click")
            ob.dispose()
        }
    }

    private fun startPolling() {

       ob = Observable.interval(INITIAL_DELAY, PERIOD, TimeUnit.SECONDS)
            .flatMap({ o -> Observable.just("polling #1 " + o.toString()) })
           .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe{it->addItme(it)}



    }
    private fun addItme(str : String){
        mAdapter.updateItems(str)
        mAdapter.notifyDataSetChanged()
    }
}

Adapter & Holder

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class MyViewAdapter () : RecyclerView.Adapter<MyViewHolder>(){

    private val mItems : ArrayList<String> = ArrayList<String>()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.i_test_item,parent,false))
    }

    override fun getItemCount(): Int {
        return mItems.size
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val item = mItems[position]
        holder.nameTView.text = item
    }

    fun updateItems(item: String){
        mItems.add(item)
    }
}
package jeongu.test.rx_async

import android.view.View
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observable
import kotlinx.android.synthetic.main.i_test_item.view.*

class MyViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) {

    val nameTView = itemView.i_name


}

결과

add를 누르면 3초마다 polling #1 n이 나타나고 , stop을 누르면 멈춥니다. 그리고 다시 add를 누르면 다시 시작합니다.

 

그리고 repeatWhen과 delay를 이용하여서 다음과 같이 만들어도 똑같은 결과를 볼 수 있습니다.(stop_btn.setOnClickListener와 startPolling 그리고 INITIAL_DELAY 앞에 val->var로 만 수정 하였습니다.)

package jeongu.test.rx_async

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import java.sql.Time
import java.util.concurrent.TimeUnit


class MainActivity : AppCompatActivity() {
    private val TAG = "RxAndroid Async Test"
    private var INITIAL_DELAY = 0L
    private val PERIOD = 3L
    private val mAdapter = MyViewAdapter()
    lateinit var ob : Disposable
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        main_recyclerview.layoutManager = LinearLayoutManager(this)
        main_recyclerview.adapter = mAdapter

        add_btn.setOnClickListener {
            startPolling()
        }
        stop_btn.setOnClickListener {
            Log.d(TAG,"stop btn click")
            INITIAL_DELAY = 0
            ob.dispose()
        }
    }

    private fun startPolling() {

       ob = Observable.just("polling #2")
           .repeatWhen({o->o.delay(PERIOD, TimeUnit.SECONDS)})
           .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe{it->addItme("$it ${INITIAL_DELAY++}")}

    }
    private fun addItme(str : String){
        mAdapter.updateItems(str)
        mAdapter.notifyDataSetChanged()
    }
}

 

 

AndroidSchedulers.mainThread 함수는 스케줄러 내부에서 직접 MainLooper에 바인딩합니다. 반면 form을 사용하여 개발자가 직접 임의의 Looper 객체를 설정할 수 있습니다. form을 사용하면 mainThread함수와 똑같이 하려면

AndroidSchedulers.form(Looper.getMainLooper())로 사용하면 됩니다. (MainLoop의 내용은 여기서 확인하시면 됩니다.)

반응형