현재 '반성 식탁' 시리즈를 통해 프로젝트를 진행한 후기들을 작성 중이다. MVVM, Firebase, Coroutine, Github, 프로젝트에 있어 비중이 높은 파트들이었다. 다음으로 어떤 글을 작성할까 고민을 해봤는데, 자잘한 개발 요소들을 글로 다시 담아내봤자 덜 유익하지 않을까. 라는 생각을 해봤다. 그래서 프로젝트를 진행 중 궁금했던 사항들을 떠올려 보고, 지금 그 궁금증을 해결하는 시간을 가져보면 좋을 것 같았다. 첫 번째로는 View Binding과 관련된 궁금증이었다.
View Binding이 무엇인지부터 알아 보자. 우선 안드로이드 개발에서 findViewById()가 무엇인지 알아 볼 필요가 있다. 특정 View를 클릭했을 때 발생하는 이벤트 처리 코드를 작성한다고 가정해보자. 그렇다면
val myView1 : View = findViewById(R.id.myView1)
myView1.setOnClickListener{
}
이라는 코드를 작성하게 된다. 하지만 View를 두개만 더 추가해도.
val myView1 : View = findViewById(R.id.myView1)
myView1.setOnClickListener{
}
val myView2 : View = findViewById(R.id.myView2)
myView2.setOnClickListener{
}
val myView3 : View = findViewById(R.id.myView3)
myView3.setOnClickListener{
}
코드가 상당히 복잡해보인다. 사실 우리가 원하는 것은 myViewN 이라는 이름의 View 객체만 가져오는 것 아닌가. 그렇기 때문에 findViewById()라는 코드를 사용하지 않고도 특정 View를 가져올 수 있다면, 그렇게 할 것이다. 개발자는 동일한 동작을 수행하는 두 코드가 있다면 더 깔끔하고 가벼운 코드를 채택할테니 말이다.
myView1.setOnClickListener{
}
myView2.setOnClickListener{
}
myView3.setOnClickListener{
}
이렇게. 더욱 깔끔해졌다. 눈치를 챘을 수도, 아니면 처음부터 알고 있을지도 모르겠지만. View Binding을 사용하면 된다. 가는 날이 장날이다. 사용법도 한 번 살펴보자. View Binding의 사용 준비는 특별한 라이브러리를 추가하지 않아도 된다. build.gradle에 해당 코드만 추가하면 된다.
android {
...
viewBinding {
enabled = true
}
}
해당 코드를 추가하면, 각 XML layout 파일의 결합 클래스가 생성된다.
결합 클래스에는 root 및 ID가 있는 모든 뷰의 참조가 포함된다.
무턱대고 findViewById를 사용하지 않는 것이 아니다. ViewBinding을 사용 여부를 ture로 설정하면, id를 포함하고 있는 View들의 참조 (위에선 myView1, myView2, ..) 를 포함하는 Binding Class가 생성된다고 한다. 당연히 해당 바인딩 클래스 인스턴스의 뷰 참조들을 사용하는 것이다.
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityReviewBinding.inflate(layoutInflater)
setContentView(binding.root)
binding?.myView1.setOnClickListener{
}
binding?.myView2.setOnClickListener{
}
binding?.myView3.setOnClickListener{
}
}
}
가장 위에서 바인딩 클래스 변수를 late init해주고, onCreate 메서드 내에서 layoutInflater를 인자로 넘겨 바인딩 클래스 인스턴스를 만들어주는 것을 확인할 수 있다. binding.root는 해당 layout의 rootView(layoutView)를 참조하는데, 이를 해당 액티비티에 setContextView()에 전달함으로써 화면을 그리게 된다. 그리고 우리가 원하던 것처럼 findViewById() 메서드를 사용하지 않고도, 특정 View의 참조를 간단하게 사용할 수 있게 됐다. binding?. 이라는 바인딩 객체의 참조가 있긴하다만, kotlin의 스코프 함수를 쓰면 binding을 쓰지 않아도 되겠다.
View Binding에 대해 알아볼 때, View Binding을 적용 대신 findViewById()을 사용하면 속도가 더 느리다는 것을 보게 됐다. 궁금증은 여기서 시작된다. 코드가 간소화될 수록 대체로 성능이 느려지지 않은가? 라는 생각이 좀 들었다. 물론 배경지식이 부족한 초보자라 이런 생각을 했을 수도 있다. 하지만 프로그래밍 언어에서도 그렇다. 코드를 더욱 간편하게 작성할 수 있는 언어일 수록 성능은 "대체로" 낮아지지 않는가? 결과적으로 View Binding은 어떻게 성능을 향상시킬 수 있었는가에 대한 궁금증이다.
어느정도 찾아봤는데, 'ViewBinding이 상대적으로 빠른 이유'보다 눈에 띄게 발견되는 것은 'findViewById가 느린 이유'였다. 이는 findViewById의 동작 원리와 관련이 있다.
findViewById는 ViewGroup 밑에 있는 모든 뷰들을 전부 한 번씩 순회하며 id 값을 비교한다.
관찰 대상인 ViewGroup, 즉 layout이 복잡하면 복잡해질 수록 순회해야 할 View가 많아지므로 작업량이 많아진다고 한다. 그렇다면 ViewBinding의 속도가 더 빠른 이유는 지레 짐작이 된다. 아마도 클래스 생성 시에 모든 View를 참조하고 있는 Binding 클래스를 생성해놓는 것이 아닐까 싶다.
대충 예상한 것이 맞는 것 같다. View Binding은 layout 파일 각각을 컴파일 시 하나의 Binding 클래스로 "미리" 변환해두는 기능이다. 이제 View Binding을 사용함으로써, findViewById를 호출해 ViewGroup에서 View를 쥐잡 듯 찾아내지 않아도 된다. 그렇다면 이런 생각이 들 수도 있다.
View를 호출할 일이 없는 layout 파일도 Binding 클래스로 미리 변환해둔다면, 그것도 일종의 자원 낭비 아닌가?
View Binding은 그것까지 예상했나 보다. 다시 안드로이드 문서 - View Binding의 도입부로 돌아가면 이런 내용이 있다.
후.. 모를 때 읽어서 그냥 지나쳤던 부분이었다.
binding 클래스를 생성하는 동안 특정 레이아웃 파일을 바인딩 클래스로 변환하지 않고 싶다면
해당 레이아웃 파일의 최상단에 tools:viewBindingIgnore 속성을 true로 설정하면 된다.
View Binding에 대해 공부하면 대체로 두 가지 큰 장점이 있다는 것을 알 수 있다. 첫 번째는 앞서 언급한 '성능'이다. 두 번째는 'null-safe'이다. 앞서 줄기차게 본 findViewById()를 사용했던 경험을 떠올려 보자. 분명 인자에는 id 값이 들어가기 마련이다.
findViewById(R.id.XXX)
findViewById 메서드에 들어가는 인자 값을 넣어주기 위해 R.id. 까지 작성한 순간, id로 정의된 View의 id 리스트가 자동으로 뜨게 된다. 기억을 잘 떠올려 보면, 다른 layout 파일의 View id까지 뜬다. 더 큰 문제는, 실수로 내가 원하지 않는 다른 ViewGroup의 id를 넣더라도 빨간 줄이 뜨지 않는다. 즉 컴파일러는 이를 에러로 인식하지 않는다는 것이다. 실행시켜 보면 해당 view 변수에 null값이 할당된 것으로 확인된다. 이러한 동작 방식은 not-null-safe 하다. 즉, findViewById()가 파라미터로 받는 View id값은 현재 ViewGroup의 View id라는 조건이 없다.
그렇다면 ViewBinding은 당연히 이 문제를 해결했다는 소리이다. 아까, activity_main.xml 레이아웃 파일에 대한 바인딩 클래스로써 ActivityMainBinding을 사용했지 않던가? 해당 클래스 인스턴스를 변수 binding에 할당해서 'binding.XXX'와 같은 방식으로 사용했는데, 이를 통해 자연스레 activity_main.xml의 View만 접근할 수 있다는 것이다. 이는 findViewById() 메서드를 사용했을 때보다 null 위험성이 확연하게 줄어든 것이다.
더욱 간소화된 View 접근 방법, View Binding이 성능 면에서 왜 더 좋은지를 알게 됐다. 공부를 통해 findViewById()가 기본적으로 느린 동작 방식을 가지고 있다는 것을 알게 됐다. '코드가 간단할 수록 느려진다'라는 고정관념에서 출발한 궁금증을 해결했다. 더 깔끔한 코드도 작성하고, 성능도 향상하고, 이게 꿩 먹고 알 먹기 아닐까? 그래서 아는게 힘이라고 하는가 보다. 아무튼 여기서 이번 글을 마치겠다.