Search code examples
androidkotlinandroid-recyclerviewandroid-databinding

Data Binding inflation is very slow


I'm currently using DataBinding with a RecyclerView, and I'm getting pretty severe lag when a list first loads. However, after I scroll past a page and it stops creating new viewholders, everything is fine.

During creation, the only thing I'm doing is inflating a layout using DataBindingUtils, so I'm wondering if there are parts that can be improved.

Attached below are relevant code snippets. I'm currently using a library, FastAdapter, so the signatures will not match exactly

When creating a viewholder, I do the following:


override fun createView(ctx: Context, parent: ViewGroup?): View {
    val start = System.nanoTime()
    val binding: ViewDataBinding = DataBindingUtil.inflate(
        LayoutInflater.from(ctx),
        layoutRes, parent,
        false,
        null
    )
    L.d { "Create view ${(System.nanoTime() - start) / 1000000}" }
    return binding.root
}

It appears that for my more complex layout, viewholders take around 30-60 ms each, resulting in notable lag.

Binding and unbinding are like so:

final override fun bindView(holder: ViewHolder, payloads: MutableList<Any>) {
    super.bindView(holder, payloads)
    val binding = DataBindingUtil.getBinding<Binding>(holder.itemView) ?: return
    binding.bindView(holder, payloads)
    binding.executePendingBindings()
}

open fun Binding.bindView(holder: ViewHolder, payloads: MutableList<Any>) {
    setVariable(BR.model, data)
}

final override fun unbindView(holder: ViewHolder) {
    super.unbindView(holder)
    val binding = DataBindingUtil.getBinding<Binding>(holder.itemView) ?: return
    binding.unbindView(holder)
    binding.unbind()
}

open fun Binding.unbindView(holder: ViewHolder) {}

final override fun getViewHolder(v: View): ViewHolder = ViewHolder(v, layoutRes)

Basically, I usually have a single data model that I set, and I implement unbinding myself per viewholder. There doesn't seem to be a problem there.

Reading other articles, it appears that most developers have the same viewholder creation method as I do. Is DataUtilBinding not meant to be done upon creation, or is there something I'm missing?

As an addition, here is my relevant layout xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="model"
            type="github.fragment.ShortRepoRowItem" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?android:windowBackground"
        android:foreground="?selectableItemBackground"
        android:paddingStart="@dimen/kau_activity_horizontal_margin"
        android:paddingTop="@dimen/kau_padding_small"
        android:paddingEnd="@dimen/kau_activity_horizontal_margin"
        android:paddingBottom="@dimen/kau_padding_small"
        tools:context=".activity.MainActivity">

        <TextView
            android:id="@+id/repo_name"
            style="@style/TextAppearance.AppCompat.Medium"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@{model.name}"
            android:textColor="?android:textColorPrimary"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="@tools:sample/full_names" />

        <TextView
            android:id="@+id/repo_desc"
            style="@style/TextAppearance.AppCompat.Caption"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:maxLines="2"
            android:text="@{model.description}"
            android:textColor="?android:textColorSecondary"
            app:goneFlag="@{model.description}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/repo_name"
            tools:text="@tools:sample/lorem/random" />

        <com.google.android.material.chip.Chip
            android:id="@+id/repo_stars"
            style="@style/RepoChips"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:chipIcon="@drawable/ic_star_border"
            app:compactNumberText="@{model.stargazers.totalCount}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/repo_desc"
            app:layout_constraintWidth_percent="0.12"
            tools:text="123" />

        <com.google.android.material.chip.Chip
            android:id="@+id/repo_forks"
            style="@style/RepoChips"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:chipIcon="@drawable/ic_fork"
            app:compactNumberText="@{model.forkCount}"
            app:layout_constraintStart_toEndOf="@id/repo_stars"
            app:layout_constraintTop_toBottomOf="@id/repo_desc"
            app:layout_constraintWidth_percent="0.12"
            tools:text="123" />

        <com.google.android.material.chip.Chip
            android:id="@+id/repo_issues"
            style="@style/RepoChips"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:chipIcon="@drawable/ic_issues"
            app:compactNumberText="@{model.issues.totalCount}"
            app:layout_constraintStart_toEndOf="@id/repo_forks"
            app:layout_constraintTop_toBottomOf="@id/repo_desc"
            app:layout_constraintWidth_percent="0.12"
            tools:text="1.5k" />

        <com.google.android.material.chip.Chip
            android:id="@+id/repo_prs"
            style="@style/RepoChips"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:chipIcon="@drawable/ic_pull_requests"
            app:compactNumberText="@{model.pullRequests.totalCount}"
            app:layout_constraintStart_toEndOf="@id/repo_issues"
            app:layout_constraintTop_toBottomOf="@id/repo_desc"
            app:layout_constraintWidth_percent="0.12"
            tools:text="123" />

        <com.google.android.material.chip.Chip
            android:id="@+id/repo_language"
            style="@style/RepoChips"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@{model.primaryLanguage.name}"
            app:chipIcon="@drawable/ic_language"
            app:languageColor="@{model.primaryLanguage.color}"
            app:layout_constraintEnd_toStartOf="@id/repo_date"
            app:layout_constraintStart_toEndOf="@id/repo_prs"
            app:layout_constraintTop_toBottomOf="@id/repo_desc"
            app:layout_constraintWidth_percent="0.25"
            tools:text="JavaScript" />

        <com.google.android.material.chip.Chip
            android:id="@+id/repo_date"
            style="@style/RepoChips"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:chipIcon="@drawable/ic_time"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/repo_language"
            app:layout_constraintTop_toBottomOf="@id/repo_desc"
            app:relativeDateText="@{model.pushedAt}"
            app:textStartPadding="4dp"
            tools:text="@tools:sample/date/mmddyy" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

I've tried doing the same thing with two linear layouts instead of ConstraintLayout, but it doesn't seem to make much of a difference.


Code snapshot: https://github.com/AllanWang/GitDroid/tree/f802c991580d70470b422186fc43f46b9cfe2465


Solution

  • I did some more measurements and found out that the culprit was actually the layout inflation (ie 60ms) instead of just the binding (ie 3ms). To verify, inflate the view as is without DataBindingUtil, then call bind on the view.

    From there, the problem was also the use of MaterialChips. After switching 6 material chips to textviews, inflation time went down 3 folds to around 10-30ms.