fragment에서 lifecycleScope를 사용해 데이터베이스에 접근하던 중 다음과 같은 오류가 발생했다.
class ModifyDialogFragment() : DialogFragment() {
private lateinit var binding : ModifyDialogBinding
private val viewModel : ViewModel by inject()
private var job : Job? = null
private var serialNum: Int = 0
constructor(serialNum: Int) : this() {
this.serialNum = serialNum
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = ModifyDialogBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
}
override fun onStart() {
super.onStart()
job = lifecycleScope.launch(Dispatchers.IO) {
val schedule = viewModel.getSchedule(serialNum)
}
}
override fun onStop() {
super.onStop()
job?.cancel()
}
}
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
이 오류는 메인 쓰레드에서 로컬 데이터베이스에 직접 접근할 때 봤던 오류다. 평상시 그냥 coroutineScope를 쓰다가 fragment의 생명주기 관리를 위해 lifecycleScope를 사용한건데, coroutineScope를 사용할 때는 아무 문제가 없다가 lifecycleScope를 사용하니 이런 문제가 발생했다. 두개는 용도만 다르지, 비동기 작업을 할 수 있는 것에선 동일한 코루틴 스코프가 아니였나? 😥 두 스코프의 차이를 구체적으로 보면 다음과 같다.
CoroutineScope
코루틴이 실행되는 범위로, 코루틴을 실행하고 싶은 lifecycle에 따라 원하는 scope를 생성해서 코루틴이 실행될 작업 범위를 지정할 수 있다. CoroutineScope가 취소되면 해당 스코프에 속한 모든 작업들도 취소된다.
LifecycleScope
Lifecycle 객체(Activity, Fragment, Serivce)를 대상으로 하며, LifeCycleOwner의 lifecycle에 엮여있으므로 lifecycle이 destroyed되면 코루틴 작업도 자동으로 취소된다. 기본적으로 Dispatcher.Main(메인 쓰레드)를 사용한다.
lifecycleScope.launch{}는 메인 쓰레드 내에서 작업을 수행한다. 따라서 비동기 작업을 요하는 데이터베이스 관련 작업을 lifecycleScope에서 수행하면 안되는게 당연했다. 따라서 withContext()를 사용해서 작업이 실행될 쓰레드를 변경해주니 문제가 해결되었다.
override fun onStart() {
super.onStart()
job = lifecycleScope.launch() {
withContext(Dispatchers.IO){
val schedule = viewModel.getSchedule(serialNum)
}
}
}
https://stackoverflow.com/questions/65224524/why-does-lifecyclescope-launch-in-a-fragment-block-the-ui-thread
https://0391kjy.tistory.com/49
https://stackoverflow.com/questions/59718077/lifecyclescope-launch-vs-coroutine-inside-onviewcreated
withContext(Dispatchers.IO){
val schedule = viewModel.getSchedule(serialNum)
}
안녕하세요 :)
포스트 잘 보고 있습니다.
데이터베이스 관련 IO 작업을 Fragment에서 작업하시는 이유가 뭘까요?