RecyclerView滚动卡顿求助:Retrofit分页加载大JSON数据场景
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
ResponseBodyand 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
@SerializedNameto only map fields your app actually needs, and enable Gson’s exclusion policies to skip unused data. This reduces parsing time and memory usage.
Then addGsonBuilder() .excludeFieldsWithoutExposeAnnotation() .create()@Exposeonly 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
isLoadingandisLastPageflags 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 = falseonce 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, useDiffUtilto 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
findViewByIdinonBindViewHolder). - 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
- Cache all view references in your ViewHolder (don’t call
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.,
Intinstead ofInteger) 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
WeakReferencefor 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

