[출처 - 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의 내용은 여기서 확인하시면 됩니다.)