Android ListView滚动时重复加载Firebase图片问题求助
Hey there, let's break down why this is happening and how to fix it quickly!
Why You're Seeing This Problem
The root cause is ListView's view recycling mechanism: when an item scrolls off the screen, its convertView gets reused for new items coming into view. If you're loading images directly from Firebase Storage every time getView() is called (without any caching), you're forcing a fresh network request each time the item reappears—this causes the lag you're seeing, especially if you have multiple items.
The Best Solutions (From Simplest to More Custom)
1. Use a Third-Party Image Loading Library (Recommended)
Libraries like Glide or Picasso handle async loading, memory/disk caching, and image resizing out of the box—they're designed exactly for this scenario. Let's use Glide as an example:
First, make sure you have Glide added to your build.gradle (Module level):
dependencies { implementation 'com.github.bumptech.glide:glide:4.16.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' }
Then update your ListView Adapter's getView() method:
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; // ViewHolder pattern to avoid repeated findViewById calls if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.your_custom_item_layout, parent, false); holder = new ViewHolder(); holder.gameImage = convertView.findViewById(R.id.item_image_view); holder.gameTitle = convertView.findViewById(R.id.item_title_text); // Initialize your buttons here too convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // Get your game data for this position Game currentGame = mGameList.get(position); // Load image with Glide—handles caching and async automatically Glide.with(mContext) .load(currentGame.getFirebaseImageUrl()) // Your Firebase Storage image URL .placeholder(R.drawable.loading_placeholder) // Show while loading .error(R.drawable.error_placeholder) // Show if load fails .centerCrop() // Adjust scaling as needed .into(holder.gameImage); // Set other content holder.gameTitle.setText(currentGame.getTitle()); // Add button click listeners here (make sure to use position correctly!) return convertView; } // Static ViewHolder class to hold references private static class ViewHolder { ImageView gameImage; TextView gameTitle; // Add your buttons here }
Why this works:
- Glide caches images in memory and on disk, so returning items load from cache instead of Firebase.
- It loads images on a background thread, so your main UI thread (and ListView scroll) stays smooth.
- It automatically resizes images to match your ImageView's dimensions, reducing memory usage.
2. Implement Custom Caching (If You Avoid Third-Party Libs)
If you want to build it yourself, you'll need two things: memory caching (for fast access) and disk caching (for persistence between app sessions), plus async image loading.
Step 1: Add a Memory Cache (LruCache)
private LruCache<String, Bitmap> mMemoryCache; // Initialize in your Adapter's constructor public GameListAdapter(Context context, List<Game> gameList) { mContext = context; mGameList = gameList; // Set up LruCache (use 1/8th of available memory) final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); final int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // Return size in KB return bitmap.getByteCount() / 1024; } }; } // Helper methods to add/get from cache public void addBitmapToCache(String key, Bitmap bitmap) { if (getBitmapFromCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getBitmapFromCache(String key) { return mMemoryCache.get(key); }
Step 2: Async Image Loading with Firebase
Use an AsyncTask (or Coroutines for newer Kotlin code) to load images in the background, check cache first:
private class LoadImageTask extends AsyncTask<String, Void, Bitmap> { private ImageView mImageView; private String mImageUrl; public LoadImageTask(ImageView imageView, String imageUrl) { mImageView = imageView; mImageUrl = imageUrl; } @Override protected Bitmap doInBackground(String... params) { // Check memory cache first Bitmap cachedBitmap = getBitmapFromCache(mImageUrl); if (cachedBitmap != null) { return cachedBitmap; } // If not cached, download from Firebase try { InputStream inputStream = FirebaseStorage.getInstance().getReferenceFromUrl(mImageUrl).getStream().getStream(); Bitmap bitmap = BitmapFactory.decodeStream(inputStream); // Add to cache addBitmapToCache(mImageUrl, bitmap); return bitmap; } catch (IOException e) { e.printStackTrace(); return null; } } @Override protected void onPostExecute(Bitmap bitmap) { if (bitmap != null && mImageView.getTag().equals(mImageUrl)) { // Only set if the ImageView is still bound to this URL (avoids wrong images due to recycling) mImageView.setImageBitmap(bitmap); } } }
Then in getView():
// Set tag to track which URL this ImageView is supposed to load holder.gameImage.setTag(currentGame.getFirebaseImageUrl()); // Check cache first Bitmap cached = getBitmapFromCache(currentGame.getFirebaseImageUrl()); if (cached != null) { holder.gameImage.setImageBitmap(cached); } else { // Start async load new LoadImageTask(holder.gameImage, currentGame.getFirebaseImageUrl()).execute(); }
3. Extra ListView Optimizations
- Fixed Item Height: Set a fixed
android:layout_heightfor your item layout (instead ofwrap_content). This helps ListView calculate scroll positions faster. - Disable Caching: Add these attributes to your ListView in XML:
android:scrollingCache="false" android:animationCache="false" - Pause Loading on Scroll: Add a scroll listener to pause image loading while scrolling, then resume when stopped:
mListView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) { // Pause Glide requests (if using Glide) Glide.with(mContext).pauseRequests(); } else { Glide.with(mContext).resumeRequests(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // No action needed here for this optimization } });
Final Notes
The third-party library approach is almost always better—it saves you time handling edge cases like memory leaks, cache eviction, and network errors. Glide also supports Firebase Storage directly, so it's a perfect match for your setup.
内容的提问来源于stack exchange,提问作者Dylan Kane

