정답보다 해답을

[Android] LayoutInflater, 알고 쓰자 - 생명주기와 View 생성 관리 본문

Android

[Android] LayoutInflater, 알고 쓰자 - 생명주기와 View 생성 관리

DOLMENG. 2024. 11. 13. 01:49

목차 📑

  1. LayoutInflater란 무엇인가
  2. 동작 방식과 기본 개념
  3. 생명주기에 따른 Inflate 패턴
  4. RecyclerView의 View 재활용과 Inflate
  5. inflate() 메서드와 파라미터 이해
  6. ViewBinding과 함께 사용하기
  7. 안티패턴과 주의사항
  8. 주요 사용 팁

1. LayoutInflater란 무엇인가 💡

LayoutInflater는 XML 레이아웃 파일을 실제 View 객체로 변환하는 안드로이드의 시스템 서비스입니다.

// XML 레이아웃
<LinearLayout>
    <TextView android:id="@+id/title"/>
</LinearLayout>

// View 객체로 변환
val view = layoutInflater.inflate(R.layout.my_layout, container, false)

2. 동작 방식과 기본 개념 ⚙️

2.1 기본 동작 과정

  1. XML 파싱
  2. View 객체 생성
  3. 속성 적용
  4. 부모에 추가 (attachToRoot 값에 따라)

2.2 LayoutInflater 얻기

// Activity에서
val inflater = layoutInflater

// Context에서
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

// 정적 메서드로
val inflater = LayoutInflater.from(context)

3. 생명주기에 따른 Inflate 패턴 🎯

3.1 Activity의 생명주기와 Inflate

class MainActivity : AppCompatActivity() {
    private val binding by lazy { 
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        // View 사용 가능
    }
}

Activity 특징:

  • onCreate에서 한 번만 inflate
  • Activity 종료 시 자동 정리
  • Context 직접 사용 가능
  • Window에 직접 설정

3.2 Fragment의 생명주기와 Inflate

class MyFragment : Fragment() {
    private var _binding: FragmentBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null  // 메모리 정리 필수
    }
}

Fragment 특징:

  • onCreateView에서 매번 새로 inflate
  • onDestroyView에서 명시적 정리
  • Configuration changes 대응
  • View 재생성 가능성

4. RecyclerView의 View 재활용과 Inflate 📋

4.1 View 재활용 매커니즘

class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            ItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        // View 재사용
    }
}

RecyclerView 특징:

  • View 필요시에만 inflate
  • View 재활용으로 메모리 효율화
  • parent.context로 안전한 Context 사용

5. inflate() 메서드와 파라미터 이해 📝

fun inflate(
    @LayoutRes resource: Int,   // 레이아웃 리소스 ID
    root: ViewGroup?,           // 부모 ViewGroup
    attachToRoot: Boolean       // 부모에 즉시 추가 여부
): View

파라미터 의미:

  • resource: inflate할 레이아웃 리소스
  • root: 생성될 View의 부모
  • attachToRoot: 부모에 즉시 추가 여부

6. ViewBinding과 함께 사용하기 🔄

6.1 ViewBinding 설정

android {
    buildFeatures {
        viewBinding = true
    }
}

6.2 컴포넌트별 ViewBinding

// Activity
val binding = ActivityMainBinding.inflate(layoutInflater)

// Fragment
val binding = FragmentMainBinding.inflate(inflater, container, false)

// RecyclerView
val binding = ItemBinding.inflate(
    LayoutInflater.from(parent.context),
    parent,
    false
)

7. 안티패턴과 주의사항 ⚠️

7.1 피해야 할 패턴

// Activity Context 직접 참조
val inflater = requireActivity().layoutInflater  // 위험!
/*
문제점:
Fragment의 생명주기와 Activity의 생명주기가 다름
Fragment가 detach된 상태에서 Activity가 재생성되면 크래시 발생 가능
Configuration changes(화면 회전 등) 발생 시 메모리 누수 위험
Fragment가 여러 Activity에서 재사용될 때 문제 발생

*/

// null root 사용
inflater.inflate(R.layout.item, null)  // LayoutParams 미설정
/*
문제점:
XML에 정의된 레이아웃 파라미터(layout_width, layout_height 등)가 무시됨
부모 ViewGroup의 레이아웃 특성이 적용되지 않음
예상치 못한 레이아웃 동작 발생
RecyclerView나 ViewPager 등에서 크기 측정 오류 발생 가능
*/

// Fragment에서 잘못된 attachToRoot
inflater.inflate(R.layout.fragment, container, true)  // 잘못된 사용

/*
문제점:
Fragment의 View가 container에 직접 추가되어 Fragment 매니저의 관리를 받지 못함
Fragment 생명주기 이벤트가 정상적으로 동작하지 않음
Fragment 트랜잭션(add, replace 등) 동작 시 문제 발생
여러 개의 Fragment가 동시에 추가되어 화면이 겹치는 문제 발생 가능
*/

7.2 올바른 사용법

// Fragment에서
override fun onCreateView(...): View {
    return FragmentBinding.inflate(inflater, container, false).root
}

// RecyclerView에서
override fun onCreateViewHolder(...): ViewHolder {
    return ViewHolder(
        ItemBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
    )
}

8. 주요 사용 팁 💡

  1. Fragment에서는 전달받은 inflater 사용하기
  2. RecyclerView에서는 parent.context 활용하기
  3. attachToRoot 값 상황에 맞게 사용하기
  4. ViewBinding으로 type-safe하게 접근하기
  5. 적절한 생명주기에서 정리하기