Android应用中多AsyncTask处理REST请求的技术咨询
解决Android ListView异步加载图片的AsyncTask常见问题与优化方案
嘿,我来帮你梳理下这个场景下的常见问题和解决方案——毕竟用AsyncTask给ListView加载图片,稍不注意就会踩一堆坑,比如图片错位、内存泄漏、重复请求这些,我给你一步步拆解:
一、先说说你大概率会遇到的坑
- ListView滑动时,因为Item复用,旧的AsyncTask还在跑,新的任务又启动,导致重复请求,浪费带宽和资源
- AsyncTask如果是Activity的非静态内部类,会持有Activity的强引用,一旦Activity销毁(比如用户切屏、退出),任务还在运行的话就会导致内存泄漏
- 图片加载完成后,对应的Item已经被复用去展示其他数据了,结果图片显示到了错误的Item上,也就是图片错位
- 大量AsyncTask同时运行,会把线程池占满,导致请求排队,界面卡顿
二、针对性的优化方案
1. 给ImageView打Tag,解决图片错位问题
在Adapter的getView()里,把图片的URL设为ImageView的Tag,等AsyncTask加载完成后,先检查当前ImageView的Tag和任务的URL是否一致,只有匹配时才设置图片:
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false); holder = new ViewHolder(); holder.imageView = convertView.findViewById(R.id.item_image); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } ItemModel item = getItem(position); // 给ImageView绑定图片URL作为Tag holder.imageView.setTag(item.getImageUrl()); // 先从缓存取,有缓存直接用 Bitmap cachedBitmap = ImageCache.getInstance().getBitmap(item.getImageUrl()); if (cachedBitmap != null) { holder.imageView.setImageBitmap(cachedBitmap); } else { // 启动异步加载任务 new ImageLoadTask(holder.imageView, item.getImageUrl()).execute(); } return convertView; } // 用静态ViewHolder,避免持有外部类引用 private static class ViewHolder { ImageView imageView; } private class ImageLoadTask extends AsyncTask<Void, Void, Bitmap> { // 用WeakReference持有ImageView,防止内存泄漏 private WeakReference<ImageView> imageViewRef; private String imageUrl; public ImageLoadTask(ImageView imageView, String imageUrl) { this.imageViewRef = new WeakReference<>(imageView); this.imageUrl = imageUrl; } @Override protected Bitmap doInBackground(Void... voids) { // 这里写你的REST请求逻辑,比如用HttpURLConnection或者OkHttp try { URL url = new URL(imageUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); InputStream is = conn.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(is); // 加载完存入缓存 ImageCache.getInstance().putBitmap(imageUrl, bitmap); return bitmap; } catch (Exception e) { e.printStackTrace(); return null; } } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); ImageView imageView = imageViewRef.get(); if (imageView != null && bitmap != null) { // 检查Tag是否匹配,避免图片错位 String currentTag = (String) imageView.getTag(); if (imageUrl.equals(currentTag)) { imageView.setImageBitmap(bitmap); } } } }
2. 避免AsyncTask导致的内存泄漏
- 不要把AsyncTask写成Activity的非静态内部类!非静态内部类会默认持有外部Activity的强引用,Activity销毁后任务还在跑的话,GC就没法回收Activity,导致内存泄漏。
- 用
WeakReference持有ImageView、Activity等组件,就像上面代码里对ImageView的处理。 - 在Activity销毁时,主动取消所有正在运行的AsyncTask:
// 先维护一个正在运行的任务列表 private List<AsyncTask> runningTasks = new ArrayList<>(); // 启动任务时加入列表 ImageLoadTask task = new ImageLoadTask(holder.imageView, item.getImageUrl()); runningTasks.add(task); task.execute(); // 在Activity的onDestroy方法里取消任务 @Override protected void onDestroy() { super.onDestroy(); for (AsyncTask task : runningTasks) { if (task.getStatus() == AsyncTask.Status.RUNNING) { task.cancel(true); } } runningTasks.clear(); }
另外,在doInBackground里要定期检查isCancelled()状态,一旦任务被取消,就及时停止操作:
@Override protected Bitmap doInBackground(Void... voids) { try { URL url = new URL(imageUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); // 检查任务是否被取消,是的话就断开连接返回null if (isCancelled()) { conn.disconnect(); return null; } InputStream is = conn.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(is); // 再次检查,避免白忙活 if (isCancelled()) { return null; } ImageCache.getInstance().putBitmap(imageUrl, bitmap); return bitmap; } catch (Exception e) { e.printStackTrace(); return null; } }
3. 优化图片缓存,减少重复请求
实现一个双层缓存(内存+磁盘),避免每次都从网络下载:
- 内存缓存用Android自带的
LruCache,基于LRU算法自动清理不常用的图片:
public class ImageCache { private static ImageCache instance; private LruCache<String, Bitmap> memoryCache; private ImageCache() { // 取应用可用内存的1/8作为缓存上限 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; memoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 返回图片大小,单位KB return bitmap.getByteCount() / 1024; } }; } // 单例模式,确保全局只有一个缓存实例 public static ImageCache getInstance() { if (instance == null) { synchronized (ImageCache.class) { if (instance == null) { instance = new ImageCache(); } } } return instance; } public void putBitmap(String key, Bitmap bitmap) { if (getBitmap(key) == null) { memoryCache.put(key, bitmap); } } public Bitmap getBitmap(String key) { return memoryCache.get(key); } }
- 磁盘缓存可以自己实现
DiskLruCache,这样即使应用重启,也能从本地磁盘加载图片,不用重新下载。
4. 更省心的替代方案:用成熟的图片加载库
其实AsyncTask在Android API 30已经被标记为过时了,官方更推荐用协程或者成熟的图片加载库,比如Glide、Picasso、Coil。这些库已经帮你处理了所有AsyncTask带来的问题,包括缓存、内存泄漏、图片错位、线程池优化等等,用起来超级简单:
比如用Glide的话,只需要几行代码:
// 先在build.gradle里加依赖 // implementation 'com.github.bumptech.glide:glide:4.16.0' // annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false); holder = new ViewHolder(); holder.imageView = convertView.findViewById(R.id.item_image); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } ItemModel item = getItem(position); // 一行代码搞定加载,还支持占位图、错误图 Glide.with(context) .load(item.getImageUrl()) .placeholder(R.drawable.loading_placeholder) .error(R.drawable.load_error) .into(holder.imageView); return convertView; }
这些库还支持图片压缩、圆角、高斯模糊等各种特效,性能也比自己写AsyncTask好很多,推荐直接用这个方案。
总结
如果一定要自己实现AsyncTask加载,就盯着内存泄漏、图片错位、重复请求这几个点优化;如果想省事儿又稳定,直接用Glide这类图片库就完事儿了,毕竟轮子已经造得很完美了~
内容的提问来源于stack exchange,提问作者Claudio P
相关产品推荐
相关产品推荐

