You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RecyclerView滚动卡顿求助:Retrofit分页加载大JSON数据场景

Fixing RecyclerView Scrolling Lag with Retrofit Pagination

Hey there, let's work through that annoying scrolling lag you're hitting with your RecyclerView and Retrofit pagination. I’ve debugged this exact issue more times than I can count, so here are the most actionable fixes to get your scroll smooth as butter:

1. Optimize JSON Parsing for Large Datasets

Large JSON payloads can choke the main thread if parsed inefficiently. Here’s how to fix that:

  • Use streaming parsing instead of loading the entire JSON into memory: Instead of letting Retrofit automatically convert the response to a list, fetch the raw ResponseBody and parse it incrementally. For example with Gson:
    val responseBody = response.body()
    responseBody?.charStream()?.use { reader ->
        val gson = Gson()
        val type = object : TypeToken<List<YourModel>>() {}.type
        val items = gson.fromJson<List<YourModel>>(reader)
        // Submit items to adapter on main thread
    }
    
  • Trim unnecessary fields: Use @SerializedName to only map fields your app actually needs, and enable Gson’s exclusion policies to skip unused data. This reduces parsing time and memory usage.
    GsonBuilder()
        .excludeFieldsWithoutExposeAnnotation()
        .create()
    
    Then add @Expose only to fields you want to parse in your model class.

2. Fix Pagination Threading & Request Throttling

Even if Retrofit runs requests in the background, messy pagination logic can still block the main thread or flood your app with duplicate requests:

  • Add loading state guards: Never let your scroll listener trigger multiple concurrent requests. Add isLoading and isLastPage flags to your adapter/view model:

    private var isLoading = false
    private var isLastPage = false
    
    // In your EndlessScrollListener
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        val visibleItemCount = layoutManager.childCount
        val totalItemCount = layoutManager.itemCount
        val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
    
        if (!isLoading && !isLastPage) {
            if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
                && firstVisibleItemPosition >= 0) {
                isLoading = true
                // Trigger your Retrofit pagination request here
            }
        }
    }
    

    Remember to set isLoading = false once the request completes (success or failure).

  • Offload all parsing/processing to background threads: Even if Retrofit handles the network call in the background, heavy post-processing (like mapping models) should stay off the main thread. Use Kotlin coroutines or RxJava to handle this:

    viewModelScope.launch(Dispatchers.IO) {
        val response = apiService.getPaginatedData(page)
        if (response.isSuccessful) {
            val processedItems = response.body()?.map { it.toUiModel() } ?: emptyList()
            withContext(Dispatchers.Main) {
                adapter.addItems(processedItems)
                isLoading = false
            }
        } else {
            withContext(Dispatchers.Main) {
                isLoading = false
                // Handle error
            }
        }
    }
    

3. Optimize RecyclerView’s Core Performance

Most scrolling lag comes from RecyclerView itself not being optimized. Here are the must-do tweaks:

  • Enable fixed size: If your RecyclerView’s dimensions don’t change dynamically, add this:

    recyclerView.setHasFixedSize(true)
    

    This skips unnecessary layout measurements during scroll.

  • Use DiffUtil for efficient updates: Never call notifyDataSetChanged()—it forces the entire RecyclerView to redraw. Instead, use DiffUtil to only update changed items when adding new pagination data:

    fun addItems(newItems: List<YourUiModel>) {
        val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
            override fun getOldListSize() = currentItems.size
            override fun getNewListSize() = currentItems.size + newItems.size
            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                val oldItem = currentItems[oldItemPosition]
                val newItem = if (newItemPosition < currentItems.size) currentItems[newItemPosition] else newItems[newItemPosition - currentItems.size]
                return oldItem.id == newItem.id
            }
            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                val oldItem = currentItems[oldItemPosition]
                val newItem = if (newItemPosition < currentItems.size) currentItems[newItemPosition] else newItems[newItemPosition - currentItems.size]
                return oldItem == newItem
            }
        })
        currentItems.addAll(newItems)
        diffResult.dispatchUpdatesTo(this)
    }
    
  • Optimize ViewHolder & Bindings:

    • Cache all view references in your ViewHolder (don’t call findViewById in onBindViewHolder).
    • Avoid any heavy operations in onBindViewHolder—like image loading should be handled by libraries like Glide/Picasso, which automatically load images in the background and cache them:
      override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
          val item = currentItems[position]
          Glide.with(holder.itemView.context)
              .load(item.imageUrl)
              .placeholder(R.drawable.placeholder)
              .into(holder.imageView)
          holder.title.text = item.title
      }
      
    • Disable item animators if you don’t need them:
      recyclerView.itemAnimator = null
      

4. Reduce Memory Pressure

Frequent garbage collection (GC) pauses are a common cause of scroll lag. Here’s how to minimize them:

  • Simplify your data models: Use primitive types (e.g., Int instead of Integer) where possible, and remove any redundant fields or nested objects that aren’t used in the UI.
  • Tweak image caching: Configure Glide/Picasso to limit memory cache size so it doesn’t hog too much RAM:
    GlideBuilder()
        .setMemoryCache(LruResourceCache(10 * 1024 * 1024)) // 10MB cache
        .build()
    
  • Avoid memory leaks: Ensure your adapters, listeners, and view models don’t hold strong references to activities/fragments longer than needed. Use WeakReference for listeners if necessary.

5. Debug the Root Cause

If you’re still stuck, use Android Studio’s Profiler to pinpoint the lag:

  • Open the CPU Profiler to see if main thread is blocked by parsing, layout, or draw operations.
  • Use the Memory Profiler to check for memory leaks or excessive memory usage from your data models or images.
  • Enable StrictMode to catch accidental main thread operations:
    StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
        .detectDiskReads()
        .detectDiskWrites()
        .detectNetwork()
        .penaltyLog()
        .build())
    

内容的提问来源于stack exchange,提问作者Muhammad Noamany

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:05:45