Android:文本变化时Retrofit API调用的性能优化方案
Hey there! Let's tackle your two main pain points—excessive API calls and slow list loading—plus dive into how Retrofit caching can help, along with other practical optimizations.
The biggest issue here is firing a request on every keystroke. We can fix this with two key techniques:
Debouncing
Add a small delay (like 300-500ms) before triggering the API call. If the user keeps typing within that window, we cancel the pending request and wait for them to stop inputting. This way, we only send one request per completed input sequence.
If you're using Kotlin Coroutines, you can implement this with Flow:
searchView.editText?.textChanges() .debounce(300L) // Wait 300ms after last keystroke .filter { it.length >= 3 } // Only trigger after 3+ characters .onEach { query -> // Trigger your Retrofit API call here } .launchIn(viewModelScope)
For RxJava users, use the debounce operator similarly:
RxTextView.textChanges(searchView) .debounce(300, TimeUnit.MILLISECONDS) .filter(text -> text.length() >= 3) .subscribe(query -> { // Execute Retrofit request });
Cancel In-Flight Requests
If a previous request is still loading when the user types a new query, cancel it immediately. This prevents outdated responses from overwriting fresh data and saves bandwidth.
With Coroutines, track the active request job:
private var searchJob: Job? = null fun performSearch(query: String) { searchJob?.cancel() // Cancel previous request searchJob = viewModelScope.launch { val response = apiService.search(query) // Handle response } }
With Retrofit's Call object:
private Call<List<Data>> currentCall; void performSearch(String query) { if (currentCall != null) currentCall.cancel(); currentCall = apiService.search(query); currentCall.enqueue(new Callback<List<Data>>() { // Handle response }); }
50k+ items loaded all at once will always be slow—let's fix this with pagination and RecyclerView optimizations.
Server-Side Pagination (Best Practice)
Work with your backend team to implement pagination. Instead of fetching all 50k items at once, request small chunks (e.g., 20-50 items per page) as the user scrolls.
Use Android's Paging 3 library to handle this seamlessly with Retrofit. Here's a quick outline:
- Define a Retrofit endpoint that accepts
pageandlimitparameters:
interface ApiService { @GET("search") suspend fun search( @Query("query") query: String, @Query("page") page: Int, @Query("limit") limit: Int = 20 ): Response<SearchResponse> }
- Create a
PagingSourceto fetch pages incrementally:
class SearchPagingSource( private val apiService: ApiService, private val query: String ) : PagingSource<Int, Data>() { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Data> { val currentPage = params.key ?: 1 return try { val response = apiService.search(query, currentPage, params.loadSize) val data = response.body()?.data ?: emptyList() LoadResult.Page( data = data, prevKey = if (currentPage == 1) null else currentPage - 1, nextKey = if (data.isEmpty()) null else currentPage + 1 ) } catch (e: Exception) { LoadResult.Error(e) } } }
- Connect this to your RecyclerView with a
PagerandPagingDataAdapter.
RecyclerView Performance Tweaks
Even with pagination, optimize your list to scroll smoothly:
- Use
DiffUtilto only update changed items instead of reloading the entire list - Set
recyclerView.setHasFixedSize(true)if your list's size doesn't change dynamically - Reuse ViewHolders efficiently (avoid creating new ones in
onBindViewHolder) - Offload heavy tasks like image loading to libraries like Glide/Picasso (with memory/disk caching)
- Enable
setItemViewCacheSizeto keep more views in memory for faster reuse
Retrofit itself doesn't handle caching—it relies on OkHttp under the hood. Here's how to set up caching to avoid re-requesting identical queries:
- Configure OkHttp Cache:
val cacheDir = File(context.cacheDir, "retrofit_cache") val cache = Cache(cacheDir, 100 * 1024 * 1024) // 100MB cache size
- Add a Cache Interceptor:
This interceptor sets cache rules for your requests. For example, cache GET requests for 1 minute:
val cacheInterceptor = Interceptor { chain -> val originalRequest = chain.request() val response = chain.proceed(originalRequest) // Cache GET requests for 1 minute val cacheControl = if (originalRequest.method == "GET") { CacheControl.Builder() .maxAge(1, TimeUnit.MINUTES) .build() } else { CacheControl.Builder().noCache().build() } response.newBuilder() .header("Cache-Control", cacheControl.toString()) .build() }
- Attach Cache and Interceptor to OkHttpClient:
val okHttpClient = OkHttpClient.Builder() .cache(cache) .addNetworkInterceptor(cacheInterceptor) .build()
- Use This Client in Retrofit:
val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build()
Note: Caching only works for GET requests, and make sure your backend doesn't return no-cache or no-store headers that override your settings.
- Local Database Caching: Use Room to store past search results. When the user searches again, first show cached data, then update with fresh server data if available.
- Trim Request Payloads: Work with your backend to return only the fields your list needs (e.g., skip large images or unused metadata) to reduce JSON size and parsing time.
- Optimize JSON Parsing: Use Moshi (instead of Gson) for faster Kotlin-friendly parsing, or enable Gson's optimizations like
@JsonClass(generateAdapter = true)for data classes.
内容的提问来源于stack exchange,提问作者Pratik Butani

