ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 화면 회전하면 Fragment가 두 번 호출되는 문제
    카테고리 없음 2020. 11. 11. 21:06

    상황:

    Activity에 Fragment를 붙여놓고 Activity가 생성되면 자동으로 Fragment가 생성되도록 해놨다. 무슨 말인지 이해가 안 되면 조금 내려서 나오는 첫 번째 코드를 참고하자.

     

    문제:

    화면을 회전시키면 액티비티가 Destroy되었다가 다시 생성된다. 액티비티, 화면, 회전, 생명주기(activity, screen, rotation, lifecycle)의 키워드로 검색하면 많이 나올테니 자세한 설명은 생략한다.

    여기까지는 좋은데, 한 번 회전시킬 때마다 Fragment가 두 번씩 생성되는 문제가 생겼다.

     

    아래와 같은 코드로 로그를 찍어 확인해보았다.

     

    액티비티 코드:

    class MainActivity : AppCompatActivity() {
        private val TAG = "ActivityLog"
    
        override fun onCreate(savedInstanceState: Bundle?) {
            Log.d(TAG, "onCreate()")
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            // 프래그먼트 생성
            val exFragment = ExFragment.newInstance()
            // 프래그먼트 붙임
            supportFragmentManager.beginTransaction()
                .replace(R.id.csl_fragment, exFragment)
                .commit()
        }
    
        override fun onDestroy() {
            Log.d(TAG, "onDestroy()")
            super.onDestroy()
        }
    }

    프래그먼트 코드:

    class ExFragment : Fragment() {
        private val TAG = "FragmentLog"
    
        override fun onCreate(savedInstanceState: Bundle?) {
            Log.d(TAG, "onCreate()")
            super.onCreate(savedInstanceState)
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            return inflater.inflate(R.layout.fragment_ex, container, false)
        }
    
        companion object {
            @JvmStatic
            fun newInstance() = ExFragment()
        }
    }

    로그 화면:

    첫 생성 때는 Fragment도 정상적으로 한 번만 생성되나, 화면 회전 이후에는 두 번씩 생성되는 것을 볼 수 있다.

     

    필자가 만드는 앱의 경우에는 두 번씩 생성해도 기능에는 문제가 없었다. 그렇지만 나중에 사소한 문제가 눈덩이처럼 불어나서 뒤통수를 칠 것이 염려되어, 구글링을 해보았다.

     

    마침 인터넷 세상에는 이와 같은 문제를 이미 겪은 사람이 있어 해결 방법이 나와있었다. 아래는 해결 방법이 나와있는 링크이다.

    stackoverflow.com/questions/48806201/why-is-oncreateview-in-fragment-called-twice-after-device-rotation-in-android/48807683

     

    채택된 답변을 대충 요약하자면, 이미 첫 번째 생성할 때 프래그먼트가 하나 있는 상태인데, 화면을 회전해서 액티비티가 다시 onCreate되면 기존에 만든 Fragment와 새로 생성되는 Fragment 두 개가 생긴다는 뜻이다... (완벽하게 무슨 일이 일어나는지는 잘 모르겠다.)

    아마 이미 만들어 둔 Fragment가 있는데, Activity의 onCreate()가 다시 호출되면서 아래 코드 부분이 다시 실행되어 프래그먼트를 또 만들어 붙이려 하기 때문에 일어나는 일인 것 같다.

    // 프래그먼트 생성
    val exFragment = ExFragment.newInstance()
    // 프래그먼트 붙임
    supportFragmentManager.beginTransaction()
            .replace(R.id.csl_fragment, exFragment)
            .commit()

    추측이 맞나 확인해보기 위해 Fragment의 onDestroy와 onDetach도 로그에 찍어보았다.

     

    갈색 상자 부분이 맨 처음에 만들어진 Fragment이고, 회전 후 Activity가 다시 onCreate될 때, 파란색 상자로 표시된 Fragment가 생성되고, 새로 생긴 Fragment로 replace되어서 기존의 Fragment가 떨어져 나가고 소멸되었다고 하면 대충 이해가 되기도 한다. (어디까지나 추측이라 확실히 이렇다고는 말을 못 하겠다.)

     

    추측이 맞다면 그냥 둬도 크게 문제가 되지는 않겠지만, 사소하게 앱의 성능을 갉아먹는 요인이 될 것 같으므로 해결하는 것이 좋아보인다.

     

    해결 방법은 위 링크의 채택된 답변을 그대로 복붙해서 사용하면 된다. (채택된 답변은 질문이 끝나고 바로 나오는 답변으로, 추천 수 밑에 초록색 체크 표시가 되어있다.)

    채택답변: stackoverflow.com/questions/48806201/why-is-oncreateview-in-fragment-called-twice-after-device-rotation-in-android/48808138#48808138

     

    답변의 코드에 생략된 부분이 있어 초보자에게는 혼란의 여지가 있고, 더 간결히 정리할 수 있는 것 같아 조금 다듬어보았다.

    class MainActivity : AppCompatActivity() {
            // 첫 번째 코드와 동일. 생략
    
            // 프래그먼트 생성
            val exFragment: ExFragment =
                supportFragmentManager.findFragmentById(R.id.csl_fragment) as ExFragment?
                    ?: ExFragment.newInstance()
            // 첫 번째 코드와 동일. 생략
        }
        // 첫 번째 코드와 동일. 생략
    }

    supportFragmentManager.findFragmentById 부분에서 기존에 만들어 둔 프래그먼트가 있으면 가져오고, 없으면 null을 반환하는 대신 Fragment를 새로 생성한다. (답변 링크에서는 findFragmentByTag를 사용했는데, 필자의 경우는 findFragmentById를 사용했다. 둘 다 기능은 비슷하다. 무엇을 사용할지는 fragment를 replace 또는 add 할 때 tag를 이용했는지, R.id.~~~의 형태로 id를 이용했는지에 따라 달라진다.)

     

    아래는 정상 작동하는 로그이다. 화면 회전 한 번에 Fragment가 한 번만 onCreate된다.

     

    끝!

    댓글

Designed by Tistory.