안드로이드 개발에서 findViewById 메소드는 리소스 id를 통해서 레이아웃에 있는 뷰 객체들 중 일치하는 뷰를 가져오는 메소드입니다. 그 전에 setContentView와 같은 메소드로 xml에 있는 리소스들을 지정한 속성에 맞게 인스턴스를 생성하여 메모리에 로드하는 인플레이션 과정이 필요합니다.
하지만 findViewById 메소드를 통해 뷰 객체를 하나씩 가져오게 되면 보일러 플레이트 코드들을 많이 생성하게 되고, 성능상으로도 좋지 않기에 kotlin extension, Data Binding, View Binding과 같은 대체 방법들이 나타나고 있습니다. 대체 방법들은 다른 블로그에서도 많이 나와있기에 위 글은 findViewById가 어떠한 이유로 인해 성능이 안좋은지에 대해 작성하였습니다.
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
Activity에서 findViewById를 호출하면 내부적으로 Window 클래스의 findViewById를 호출하게 됩니다.
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
@Nullable
public final <T extends View> T findViewById(@IdRes int id) {
if (id == NO_ID) {
return null;
}
return findViewTraversal(id);
}
protected <T extends View> T findViewTraversal(@IdRes int id) {
if (id == mID) {
return (T) this;
}
return null;
}
View의 findViewById는 findViewTraversal 메소드를 호출합니다. findViewTraversal을 보면 해당 View의 id와 매개변수 id를 비교하여 맞으면 View 자체를 리턴하도록 되어있습니다. 하지만 우리가 확인하고자 하는 것은 레이아웃 중 id에 해당되는 뷰를 찾는 것입니다.
@Override
protected <T extends View> T findViewTraversal(@IdRes int id) {
if (id == mID) {
return (T) this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return (T) v;
}
}
}
return null;
}
DecorView는 setContentView에서 설정한 레이아웃의 루트 뷰이기에 ViewGroup에 해당되어 ViewGroup의 findViewTraversal 메소드를 호출하게 됩니다. 여기서부터가 findViewById의 원리가 제대로 나타나게 되는데, mChildren에 해당하는 자식 뷰들을 하나씩 탐색하여 id가 같은 뷰가 있으면 반환하고 종료하도록 로직이 구현되어 있습니다.
만일 mChildren 내에 View가 아닌 또 하나의 ViewGroup이 존재하면 해당 ViewGroup의 findViewById를 호출하고 findViewTraversal까지 다시 호출하게 됩니다. 결론적으로 mChildren 뷰들을 깊게 탐색 후 하나씩 id를 비교하는 로직으로 구현되어 트리구조의 깊이우선탐색(DFS)와 동일하게 수행된다고 볼 수 있습니다. 함수 이름도 Traversal(순회)라고 작성된 이유도 잘 반영된 걸로 보입니다. 😀
findViewById는 루트 뷰에서 트리순회를 거쳐서 탐색을 하기에 복잡한 뷰가 아니라면 크게 문제가 되지는 않지만 자식 뷰들이 많고 복잡할 수록 시간이 많이 소요된다는 점을 인지하고 사용하시면 좋을 것 같습니다. 그리고 ViewBinding, DataBinding에 대해서도 공부를 해보면 내부적으로 findViewById를 사용하여 뷰의 대한 참조를 미리 생성해 놓는다는 것을 알 수 있습니다. 이로 인해 Null Safety, Type Casting Safety와 같은 이점을 얻을 수 있고 뷰를 여러 번 접근을 하더라도 미리 생성해놓은 참조로 접근을 할 수 있다는 성능상의 이점도 존재할 수 있습니다. (이 부분은 findViewById를 사용하더라도 멤버변수로 저장하면 동일하지 않을까 싶습니다 :))