Android - 프래그먼트 생애주기 백 스택 관리 (Kotlin)

업데이트:

  • 연구주제 : Android - 프래그먼트 생애주기 백 스택 관리 (Kotlin)
  • 연구목적 : 안드로이드에서의 코틀린 활용
  • 연구일시 : 2020년 04월 15일 09:00~17:00
  • 연구자 : 이재환 ljh951103@naver.com
  • 연구장비 : HP EliteDesk 800 G4 TWR, Kotlin, Android studio, IntelliJ
  • 관련연구 : Java, Android, Kotlin, BottomNavigationView, BackStack


서론

프래그먼트 백 스택 관리에 대한 내용은 사실 이전에 같은 내용으로 포스팅한 적이 있습니다.
그러나 프래그먼트가 중복적으로 호출되어도 백 스택에서 제거되지 않아, 자원낭비가 심한 경우를 해결하지 못했습니다.
이번에는 그 부분을 해결하고, 전체적인 코드를 리뷰 해보겠습니다.


본론

우선 하나의 리사이클뷰를 공유하는 5개의 프레임이 존재하여 바텀 내비게이션 뷰을 통해 프래그먼트를 이동합니다.

image

즉, 다섯 개의 프래그먼트 클래스가 있고, 하나의 레이아웃을 돌려쓰면서 관리한다는 말이 됩니다.
바텀 내비게이션 뷰나 리사이클뷰를 구성하는 방법은 이전 포스팅을 참고해주시면 됩니다.


다음 코드는 내비게이션 아이템을 선택할 때 마다 프래그먼트를 이동하는 부분입니다.

여기서 addToBackStack은 프래그먼트를 생성하면서 백스택을 생성한다는 뜻입니다.
백스택이 없을 경우, 프래그먼트를 많이 옮겨다녀도 백버튼 한번에 앱이 종료될 수 있기 때문에 백스택을 저장하게끔 합니다.

popBackStack은 해당 프래그먼트를 백스택에서 제거한다는 코드입니다. 그 구분은 태그로 합니다.
각각의 분기마다 이동, 저장, 삭제 모두 태그가 지정되어 있기 때문에 해당 태그에 대해서만 연산을 진행합니다.

    override fun onNavigationItemSelected(p0: MenuItem): Boolean {
        val fm = supportFragmentManager
        val transaction: FragmentTransaction = fm.beginTransaction()

        when(p0.itemId){
            R.id.menu_name -> {
                fm.popBackStack("name", FragmentManager.POP_BACK_STACK_INCLUSIVE)
                val fragmentA = NameFragment(appbar)
                transaction.replace(R.id.frame_layout,fragmentA, "name")
                transaction.addToBackStack("name")
            }
            R.id.menu_tag -> {
                fm.popBackStack("tag", FragmentManager.POP_BACK_STACK_INCLUSIVE)
                val fragmentB = TagFragment(appbar)
                transaction.replace(R.id.frame_layout,fragmentB, "tag")
                transaction.addToBackStack("tag")
            }
            R.id.menu_cal -> {
                fm.popBackStack("cal", FragmentManager.POP_BACK_STACK_INCLUSIVE)
                val fragmentC = DateFragment(appbar)
                transaction.replace(R.id.frame_layout,fragmentC, "cal")
                transaction.addToBackStack("cal")
            }
            R.id.menu_location -> {
                fm.popBackStack("location", FragmentManager.POP_BACK_STACK_INCLUSIVE)
                val fragmentD = LocationFragment(appbar)
                transaction.replace(R.id.frame_layout,fragmentD, "location")
                transaction.addToBackStack("location")
            }
        }

        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
        transaction.commit()
        transaction.isAddToBackStackAllowed
        return true
    }


다음은 프래그먼트를 클릭했을 때, 자동적으로 바텀 내비게이션 뷰의 아이콘도 변하게 하도록 합니다.
이 메소드는 뒤로가기 버튼을 눌렀을 경우 호출합니다.

private fun updateBottomMenu(navigation: BottomNavigationView) {
        val tag1: Fragment? = supportFragmentManager.findFragmentByTag("name")
        val tag2: Fragment? = supportFragmentManager.findFragmentByTag("tag")
        val tag3: Fragment? = supportFragmentManager.findFragmentByTag("cal")
        val tag4: Fragment? = supportFragmentManager.findFragmentByTag("location")

        if(tag1 != null && tag1.isVisible) {navigation.menu.findItem(R.id.menu_name).isChecked = true }
        if(tag2 != null && tag2.isVisible) {navigation.menu.findItem(R.id.menu_tag).isChecked = true }
        if(tag3 != null && tag3.isVisible) {navigation.menu.findItem(R.id.menu_cal).isChecked = true }
        if(tag4 != null && tag4.isVisible) {navigation.menu.findItem(R.id.menu_location).isChecked = true }

    }


백 버튼 클릭 리스너를 재정의한 것 입니다.

if(supportFragmentManager.backStackEntryCount == 0) 은 백 스택을 조회하여 모두 비어있을 경우, 종료를 하는 코드입니다.
여기서 뒤로가기 버튼을 두번 눌러야지 종료만 되는 코드를 적용시켰습니다.
이 코드의 원리가 궁금하시면 역시 이 부분에 대해 다룬 이전 포스팅이 있으니 참고해주시길 바랍니다.

또한 방금 전에 만든 updateBottomMenu 를 여기서 호출해줍니다.

override fun onBackPressed() {
        if(supportFragmentManager.backStackEntryCount == 0) {
            val tempTime = System.currentTimeMillis()
            val intervalTime = tempTime - backPressedTime
            if (!(0 > intervalTime || FINISH_INTERVAL_TIME < intervalTime)) {
                finishAffinity()
                System.runFinalization()
                System.exit(0)
            } else {
                backPressedTime = tempTime
                Toast.makeText(this, "'뒤로' 버튼을 한 번 더 누르면 종료됩니다.", Toast.LENGTH_SHORT).show()
                return
            }
        }
        super.onBackPressed()
        val bnv = findViewById<View>(R.id.bottomNavigationView) as BottomNavigationView
        updateBottomMenu(bnv)
    }

이렇게만 하시면 프래그먼트 생애주기 관리도 되고 바텀 내비게이션 뷰와의 연동도 하나 빠짐없이 잘 되게 됩니다.


결론

이전에 백 스택 관리에 대해 다룬적이 있었는데 그 때에는 사실 학습과정 중이라 깊게는 다루지 못하고 또한 자료도 별로 없어서 자세히 알지 못했습니다.
이제와서 해결해보니 조금의 원리를 이해하고 간단한 메소드를 호출해여 해결할 수 있어서 이러한 부분들이 굉장히 편리하다고 생각됩니다.


향후과제


참고자료


Writer: Jae-Hwan Lee

댓글남기기