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

不使用Picasso/Glide,GridView/ListView加载400张图片性能优化求助

Hey there! Let's tackle this image loading slowdown issue you're facing with your GridView/ListView—totally get why you'd want to avoid third-party libs like Picasso or Glide, so let's break down what's going wrong and how to fix it.

The Root Cause

Both of your current approaches are hitting the same bottlenecks:

  • Synchronous network calls on the main thread: Whether you're converting all Bitmaps upfront or doing it in getView, you're blocking the UI thread while waiting for network responses. This doesn't just make loading slow—it risks ANRs (Application Not Responding) too.
  • No caching: Every time you load an image, you're making a fresh network request instead of reusing a previously downloaded copy. That's 400+ redundant calls to the server, which kills speed.

Practical Fixes (No Third-Party Libs Required)

Here are actionable steps to speed things up drastically:

1. Offload Network Work to Background Threads

Never do network operations on the main thread. Use AsyncTask, Coroutines (if you're using Kotlin), or a dedicated HandlerThread to fetch and process images in the background. Also, handle view recycling properly to avoid image misalignment:

  • Cancel pending tasks when a view is reused (so old requests don't overwrite new ones)
  • Use WeakReference for ImageViews to prevent memory leaks

2. Add Memory Caching with LruCache

Store frequently used Bitmaps in memory to avoid reloading them from disk or network. LruCache is perfect for this—it automatically evicts least recently used items when memory runs low:

private LruCache<String, Bitmap> memoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    // Allocate ~1/8 of your app's available memory for the cache
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    final int cacheSize = maxMemory / 8;

    memoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // Return the Bitmap's size in KB
            return bitmap.getByteCount() / 1024;
        }
    };
}

// Add bitmap to memory cache
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        memoryCache.put(key, bitmap);
    }
}

// Fetch bitmap from memory cache
public Bitmap getBitmapFromMemCache(String key) {
    return memoryCache.get(key);
}

3. Implement Disk Caching

Save downloaded Bitmaps to your app's cache directory so you don't have to re-download them. Use the image URL's hash as a filename to avoid conflicts:

// Load bitmap from disk cache
private Bitmap getBitmapFromDiskCache(String imageUrl) {
    String fileName = String.valueOf(imageUrl.hashCode());
    File cacheFile = new File(getApplicationContext().getCacheDir(), fileName);
    return cacheFile.exists() ? BitmapFactory.decodeFile(cacheFile.getAbsolutePath()) : null;
}

// Save bitmap to disk cache
private void saveBitmapToDiskCache(String imageUrl, Bitmap bitmap) {
    String fileName = String.valueOf(imageUrl.hashCode());
    File cacheFile = new File(getApplicationContext().getCacheDir(), fileName);
    try (FileOutputStream fos = new FileOutputStream(cacheFile)) {
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

4. Compress Bitmaps Before Loading

Don't load full-size images into memory—resize them to match your GridView item's dimensions. This cuts down memory usage and speeds up rendering:

private Bitmap decodeSampledBitmapFromStream(InputStream inputStream, int reqWidth, int reqHeight) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    // First pass: get image dimensions without loading the full bitmap
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(inputStream, null, options);

    // Calculate sample size to resize the image
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Second pass: load the resized bitmap
    options.inJustDecodeBounds = false;
    // Reopen the stream since the first decode consumed it
    try {
        URL url = new URL(imageUrl);
        inputStream = ((HttpURLConnection) url.openConnection()).getInputStream();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return BitmapFactory.decodeStream(inputStream, null, options);
}

private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        final int halfHeight = height / 2;
        final int halfWidth = width / 2;
        // Double the sample size until the resized image fits the target dimensions
        while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

5. Combine Everything in getView

Put it all together to load images efficiently:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
        convertView = LayoutInflater.from(context).inflate(R.layout.grid_item, parent, false);
        holder = new ViewHolder();
        holder.imageView = convertView.findViewById(R.id.item_image);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
        // Cancel pending tasks to avoid image misalignment
        if (holder.loadTask != null) {
            holder.loadTask.cancel(true);
        }
    }

    String imageUrl = imageUrls.get(position);
    // Check memory cache first
    Bitmap cachedBitmap = getBitmapFromMemCache(imageUrl);
    if (cachedBitmap != null) {
        holder.imageView.setImageBitmap(cachedBitmap);
    } else {
        holder.imageView.setImageResource(R.drawable.placeholder);
        // Start background task to load image
        holder.loadTask = new LoadImageTask(holder.imageView, imageUrl, itemWidth, itemHeight);
        holder.loadTask.execute();
    }
    return convertView;
}

private class LoadImageTask extends AsyncTask<Void, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewRef;
    private final String imageUrl;
    private final int reqWidth;
    private final int reqHeight;

    public LoadImageTask(ImageView imageView, String imageUrl, int reqWidth, int reqHeight) {
        imageViewRef = new WeakReference<>(imageView);
        this.imageUrl = imageUrl;
        this.reqWidth = reqWidth;
        this.reqHeight = reqHeight;
    }

    @Override
    protected Bitmap doInBackground(Void... voids) {
        if (isCancelled()) return null;
        // Check disk cache next
        Bitmap bitmap = getBitmapFromDiskCache(imageUrl);
        if (bitmap != null) {
            addBitmapToMemoryCache(imageUrl, bitmap);
            return bitmap;
        }
        // Fetch from network if no cache exists
        try {
            URL url = new URL(imageUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.connect();
            InputStream inputStream = connection.getInputStream();
            bitmap = decodeSampledBitmapFromStream(inputStream, reqWidth, reqHeight);
            if (bitmap != null) {
                saveBitmapToDiskCache(imageUrl, bitmap);
                addBitmapToMemoryCache(imageUrl, bitmap);
            }
            inputStream.close();
            connection.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        ImageView imageView = imageViewRef.get();
        if (imageView != null && bitmap != null && !isCancelled()) {
            imageView.setImageBitmap(bitmap);
        }
    }
}

static class ViewHolder {
    ImageView imageView;
    LoadImageTask loadTask;
}

Final Notes

Combining these optimizations will eliminate redundant network calls, keep your UI responsive, and drastically speed up image loading. The key is prioritizing cache layers (memory → disk → network) and never blocking the main thread.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:40:12