본문 바로가기
Android/DataBinding

[Android] executePendingBindings() 꼭 써야 할까?

by 태크민 2025. 1. 8.

DataBinding을 사용하는 코드를 보면, executePendingBindings()을 사용하는 코드가 많이 보인다.

정확한 사용 이유에 대해 알고 쓰지 못한 점을 반성하며, 어떤 역할을 하고 있는지 한번 알아보자.


executePendingBindings()

executePendingBindings()는 DataBinding에서 제공하는 메서드로, 예약(Pending) 중인 바인딩 작업을 즉시 실행하는 역할을 한다.

 

Databinding은 변수나 Observable이 변경되면 그 변경사항을 화면에 반영하기 위해 바인딩을 예약하는데, 이 바인딩 작업은 다음 프레임에 적용되도록 예약된다. (즉시 적용 x)

하지만, executePendingBinding()을 사용하면 다음 프레임이 반영이 아닌, 예약된 바인딩을 즉시 실행 한다는 것이다.

 

프레임이란?

안드로이드에서 프레임(Frame)은 화면의 한 장면(Scene)을 의미하며, 화면이 초당 몇 번 갱신되는지를 나타낸다. 일반적으로 프레임은 애플리케이션의 UI를 업데이트하거나 애니메이션을 렌더링할 때 관련된다.

 

안드로이드의 프레임 처리 개념

  • 안드로이드는 화면 갱신을 VSYNC 신호에 맞춰 처리한다..
    • VSYNC: 하드웨어 디스플레이가 화면을 새로 고침하는 신호로, 60Hz 디스플레이에서는 16.67ms마다 한 번씩 발생.
    • 앱은 이 신호에 맞춰 UI 갱신 작업(그리기, 측정, 레이아웃, 렌더링)을 수행한다.
  • 데이터 바인딩은 데이터 변경 작업을 효율적으로 관리하기 위해 즉각 처리하지 않고, 다음 VSYNC 신호에 맞춰 작업을 처리하도록 예약한다.
    • 즉, 현재 프레임에서는 데이터 변경이 반영되지 않고, 다음 프레임에서 UI와 동기화된다.
      (다음 VSYNC 주기(약 16.67ms 이후)에 실행된다는 의미)
    • 다시말해, 다음 VSYNC 신호가 발생하면, 데이터 바인딩 작업이 실행되고 UI가 갱신되어 변경된 데이터가 반영된다는 의미이다.

 

 

내부 구조

실제 내부 코드는 아래 코드와 같이 되어 있으며, 주석을 해석해보면,

"pending된 바인딩을 계산하고, 변경된 변수에 대한 바인딩된 표현식을 가진 View를 업데이트한다" 

라고 되어 있다.

    /**
     * Evaluates the pending bindings, updating any Views that have expressions bound to
     * modified variables. This <b>must</b> be run on the UI thread.
     */
    public void executePendingBindings() {
        if (mContainingBinding == null) {
            executeBindingsInternal();
        } else {
            mContainingBinding.executePendingBindings();
        }
    }

만약 현재 객체가 다른 바인딩 객체를 포함하고 있다면, 그 포함된 바인딩 객체에서 exectePendingBindings()를 호출하도록 위임한다. 그게 아니라면, 내부적으로 executeBindingsInternal() 메서드를 호출하여 바인딩을 처리한다.

  • mContainingBinding: 이 변수는 현재 객체가 다른 바인딩 객체를 포함하는지 여부를 나타낸다. 만약 포함된 바인딩이 있다면, 그 객체에서 executePendingBindings()를 호출하여 해당 바인딩을 처리한다.
  • executeBindingsInternal(): 포함된 바인딩이 없다면, 현재 객체에서 바인딩을 직접 처리하는 내부 메서드를 호출한다.

 

아래는 executeBindingsInternal()함수 내부구조이다.

private void executeBindingsInternal() {
    if (mIsExecutingPendingBindings) {
        requestRebind();
        return;
    }
    if (!hasPendingBindings()) {
        return;
    }
    mIsExecutingPendingBindings = true;
    mRebindHalted = false;
    if (mRebindCallbacks != null) {
        mRebindCallbacks.notifyCallbacks(this, REBIND, null);

        // The onRebindListeners will change mPendingHalted
        if (mRebindHalted) {
            mRebindCallbacks.notifyCallbacks(this, HALTED, null);
        }
    }
    if (!mRebindHalted) {
        executeBindings();
        if (mRebindCallbacks != null) {
            mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
        }
    }
    mIsExecutingPendingBindings = false;
}

 

이 메서드는 실제로 바인딩을 실행하는 내부 로직을 처리한다. 주로 데이터 바인딩이 필요한 상황에서 호출되며, 여러 조건에 따라 바인딩을 실행하거나 중단할 수 있다.

executeBindingsInternal()는 아래 순서에 따라 동작한다.

  • 중복 실행 방지
    • mIsExecutingPendingBindings가 true일 경우, 이미 바인딩 작업이 실행 중이라는 의미이다. 이 경우 requestRebind()를 호출하여 바인딩 요청을 다시 하도록 한다.
  • 바인딩 상태 확인
    • hasPendingBindings() 메서드를 통해 현재 바인딩이 대기 중인 상태인지 확인한다. 대기 중인 바인딩이 없다면 아무 작업도 수행하지 않는다.
  • 재바인딩 상태 처리
    • mIsExecutingPendingBindings를 true로 설정하여 바인딩 실행 중임을 표시한다.
    • notifyCallbacks()를 호출하여 REBIND (바인딩 시도) 상태를 알리고, onRebindListeners가 mRebindHalted 값을 변경할 수 있게 한다.
    • 만일 바인딩 시도 중, mRebindHalted가 true로 설정되면, 바인딩이 중단되었다고 판단하여 HALTED (바인딩 중단) 상태를 알린다.
  • 바인딩 실행
    • mRebindHalted가 false일 경우, executeBindings()를 호출하여 바인딩을 실제로 실행한다.
    • 바인딩 후에는 REBIND 상태에서 REBOUND (바인딩 성공) 상태로 변경된 것을 알린다.
  • 실행 종료:
    • 마지막으로, mIsExecutingPendingBindings를 false로 설정하여 바인딩 실행이 종료됨을 알린다.

 

 

예시를 통해 실제 동작을 이해 해보자.

1. RecyClerView

다음 RecyclerView의 Adapter 예시로, executePendingBindings() 사용 유무에 따라 어떻게 달라지는지 살펴보자.

override fun onBindViewHolder(binding: MyBindingClass, position: Int, viewType: Int) {
        //Your binding code
        binding.executePendingBindings()
    }

 

executePendingBindings() 사용 x


위 이미지를 살펴보면, 새로운 리스트를 가져올 때 화면이 깜빡이는 현상을 볼 수 있다.

이러한 현상이 나타나는 이유는 데이터 바인딩이 예약 (Pending) 상태남아있기 때문이다.

executePendingBinding()을 사용하지 않으면 다음 프레임에 예약(Pending)된 데이터 바인딩이 적용된다.

그렇기 때문에, 잠시 이전 데이터나 빈화면이 표시가 되는 것이며, 깜빡임 현상이 발생하는 것이다.

executePendingBindings() 사용 o

 

반면, executePendingBindings()를 사용하면, 깜빡임 없이 바로 화면에 데이터를 표시하는 것을 볼 수 있다.

executePendingBindings()은 예약(Pending)상태인 바인딩을 즉시 처리하고, 다음 프레임에서 바인딩 된 View를 화면에 반영한다. 그 결과로 UI의 갱신이 매끄럽게 이루어지고 깜빡임 현상이 사라지는 것이다.

 

또한, RecyclerView는 onBindViewHolder가 완료된 후 설정한 데이터에 따라 행 크기와 높이를 측정하는데, 

executePendingBindings()를 부르지 않으면 새 콘텐츠가 차지하는 공간을 즉시 고려할 수 없다.

따라서, 바로 데이터를 업데이트하도록 강제하면 잘못된 행 높이 크기 측정으로 부터 안전하게 보호할 수 있다.

 

2. EditText

이번엔 다른 예시를 살펴보자.

EditText가 있고 EditText에 대한 Visibility를 아래와 같이 데이터 바인딩 시켜줬다고 가정하자.

android:visibility="@{editTextVisible ? View.VISIBLE : View.GONE}"

그리고, 특정 버튼을 누르면 EditText와 포커스를 주고 키보드가 올라오도록 작성했다.

btnOpen.setOnClickListener {
    editTextVisible = true
    editText.requestFocus()
    showKeyboard()
}

 

그냥 보면 EditText가 보여지고 포커스가 생기고, 키보드가 올라올 것 처럼 보이지만

실제로는 동작하지 않았다.

 

원인은 editText.requestFocus()가 불린 순간 EditText는 아직 VISIBLE 상태가 아니였던 것이었다.

따라서, GONE인 EditText에 포커스를 줄 수 없었던 것이다.

위에서 언급한바와 같이 이는 데이터바인딩의 변경사항 스케줄링 때문인데, 실제로 다음 프레임에 반영되기 때문에 UI가 곧바로 적용되지 않았던 것이다.

결국 아래와 같이 executePendingBindings()를 호출하면 즉각적으로 변경사항이 적용되어, EditText가 즉시 VISIBLE 상태가 되고 requestFocus는 성공한다.

btnOpen.setOnClickListener {
   editTextVisible = true
   executePendingBindings()
   editText.requestFocus()
   showKeyboard()
}

 

근데, 도대체 다음 프레임에 표시 된다는게 무슨말이야?

필자는 해당 내용에 대하여 직관적으로 이해가 안되었기 때문에, EditText 예시를 가지고 프레임과 연관지어 다시 설명해보겠다.

아래 내용을 보고 이해가 되길바란다.

 

코드 실행 흐름

1. 버튼 클릭이 발생하면 btnOpen.setOnClickListener 내의 코드가 실행된다.

2. 첫 번째로 editTextVisible = true가 실행되어 데이터 바인딩에서 editTextVisible 값이 변경된다.

  • 이때 화면에 직접적으로 바로 반영되지 않고, 다음 프레임에 반영된다.. 왜냐면 UI 업데이트는 다음 프레임에 예약되기 때문이다.

3. 그 후 editText.requestFocus()가 실행된다.

  • 이 부분은 즉시 반영된다. (하지만 UI상 EditText가 표시되지 않았기 때문에 동작하지 않는다.)

4. showKeyboard()가 호출되어 키보드가 화면에 표시된다.

  • 이 작업 역시 즉시 반영된다. (하지만 UI상으로 EditText가 표시되지 않았기 때문에 동작하지 않는다.)

정리해보면,

  1. 현재 프레임에서 editTextVisible = true 값이 변경된. 이 값의 변화는 즉시 화면에 반영되지 않고, 다음 프레임에서 반영된다.
  2. requestFocus()와 showKeyboard()는 현재 프레임에서 바로 실행

예를 들어 60Hz 화면에서:

  • 버튼을 클릭한 현재 프레임 (0ms ~ 16.67ms) 에서 editTextVisible = true 값이 변경된다.
  • requestFocus()와 showKeyboard()는 즉시 실행된다. 하지만 현재 프레임에서 EditText는 표시가 되지 않았기 때문에 ,동작이 무시된다.
  • 화면 갱신 주기(다음 프레임)는 16.67ms 후 발생하며, 이때 editTextVisible의 변경이 반영되어 UI에 EditText가 표시된다.

 


참고 자료

https://stackoverflow.com/questions/52996894/android-why-when-to-use-executependingbindings

 

Android Why/ when to use ExecutePendingBindings

Lately I've been using data bindings and I came across the executePendingBindings method. The documentation doesn't say much about it, so I don't understand how it works or when to use it. Here is an

stackoverflow.com

https://stackoverflow.com/questions/58678913/when-to-use-executependingbindings-and-when-its-not-required

 

When to use executePendingBindings() and when its not required?

I am looking for a practical example for both the cases that what to use when? I have seen similar threads but they only tell this "when binding must be executed immediately" but there is no real t...

stackoverflow.com

https://medium.com/androiddevelopers/android-data-binding-recyclerview-db7c40d9f0e4

 

Android Data Binding: RecyclerView

Reduce, Reuse, Rebind

medium.com

https://developer.android.com/topic/libraries/data-binding/generated-binding#immediate_binding

 

생성된 바인딩 클래스  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 생성된 바인딩 클래스 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 데이터 결합 라이브러리는 사용

developer.android.com

https://velog.io/@cmplxn/Databinding-RxJava-Thread

 

Databinding 반영 시점, RxJava Thread

꼭 이번주에 있었던 문제는 아니지만, 최근 개발하다가 삽질한 이력을 남겨본다.View들의 Visibility를 데이터 바인딩을 이용해 변경하고 있었다. 위처럼 uiVisible를 할당하면 Activity나 Fragment에서 uiVi

velog.io