RecyclerView滚动卡顿?如何异步实现图片缩放加载?
解决RecyclerView图片缩放导致的滚动卡顿问题
兄弟,你找对问题根源了!现在滚动卡顿完全是因为图片缩放操作在主线程同步执行,把UI渲染的线程给阻塞了。要解决这个问题,核心就是把图片加载、缩放这些耗时逻辑丢到后台线程去做,等处理完再切回主线程更新ImageView。下面给你两个靠谱的方案,按需选择:
方案一:自己实现异步处理(适合不想加第三方库的场景)
如果不想引入额外依赖,可以用线程池或者协程来做异步操作。这里分别给Kotlin和Java的实现示例:
Kotlin 协程版本
协程是现在Android推荐的异步处理方式,代码简洁还能避免内存泄漏:
// 把缩放逻辑放到IO线程执行的挂起函数 private suspend fun scaleImageAsync(imageId: Int, scaleFactor: Float): Drawable? { return withContext(Dispatchers.IO) { val image = context.resources.getDrawable(imageId, context.theme) if (image == null || image !is BitmapDrawable) { return@withContext image } val originalBitmap = image.bitmap val targetWidth = (image.intrinsicWidth * scaleFactor).roundToInt() val targetHeight = (image.intrinsicHeight * scaleFactor).roundToInt() val scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, false) BitmapDrawable(context.resources, scaledBitmap) } } // 在onBindViewHolder里启动协程 override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = galleryList[position] holder.title.text = item.image_title holder.img.scaleType = ImageView.ScaleType.CENTER_CROP // 绑定ViewHolder的生命周期,防止内存泄漏 holder.lifecycleScope.launch { val scaledDrawable = scaleImageAsync(item.image_ID, ScaleFactor) scaledDrawable?.let { holder.img.setImageDrawable(it) } } }
小贴士:如果你的ViewHolder没有
lifecycleScope,可以在Adapter里创建一个全局的CoroutineScope,记得在Adapter销毁时取消所有协程哦。
Java 线程池版本
如果还在用Java,用线程池+Handler也能搞定:
// 初始化线程池(控制并发数,避免线程过多) private ExecutorService imageExecutor = Executors.newFixedThreadPool(4); private Handler mainHandler = new Handler(Looper.getMainLooper()); // 异步执行缩放逻辑 private void scaleImageAsync(int imageId, float scaleFactor, ImageView targetView) { imageExecutor.execute(() -> { Drawable originalDrawable = context.getResources().getDrawable(imageId); if (originalDrawable == null || !(originalDrawable instanceof BitmapDrawable)) { mainHandler.post(() -> targetView.setImageDrawable(originalDrawable)); return; } Bitmap originalBitmap = ((BitmapDrawable) originalDrawable).getBitmap(); int targetWidth = Math.round(originalDrawable.getIntrinsicWidth() * scaleFactor); int targetHeight = Math.round(originalDrawable.getIntrinsicHeight() * scaleFactor); Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, false); Drawable scaledDrawable = new BitmapDrawable(context.getResources(), scaledBitmap); // 切回主线程更新UI mainHandler.post(() -> targetView.setImageDrawable(scaledDrawable)); }); } // 在onBindViewHolder里调用 @Override public void onBindViewHolder(ViewHolder viewHolder, int i) { viewHolder.title.setText(galleryList.get(i).getImage_title()); viewHolder.img.setScaleType(ImageView.ScaleType.CENTER_CROP); // 丢给后台线程处理 scaleImageAsync(galleryList.get(i).getImage_ID(), ScaleFactor, viewHolder.img); } // 记得在Adapter销毁时关闭线程池,避免内存泄漏 @Override public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { super.onDetachedFromRecyclerView(recyclerView); imageExecutor.shutdown(); }
方案二:用成熟的图片加载库(强烈推荐)
自己写异步逻辑要处理的细节太多了——比如图片缓存、重复加载拦截、内存管理、线程复用,稍不留神就会踩坑。不如直接用现成的图片加载库,比如Glide或者Picasso,它们把这些问题都帮我们封装好了,用起来省心还高效。
Glide 示例
- 先在build.gradle里加依赖:
dependencies { implementation 'com.github.bumptech.glide:glide:4.16.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' }
- 然后在
onBindViewHolder里一行代码搞定:
@Override public void onBindViewHolder(ViewHolder viewHolder, int i) { viewHolder.title.setText(galleryList.get(i).getImage_title()); viewHolder.img.setScaleType(ImageView.ScaleType.CENTER_CROP); // Glide自动处理异步加载、缩放、缓存 Glide.with(context) .load(galleryList.get(i).getImage_ID()) .centerCrop() // 对应你设置的ScaleType .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) // 如果需要按比例缩放,可以自定义尺寸或者用transform .into(viewHolder.img); }
划重点:Glide不仅会在后台线程处理图片,还会自动缓存处理后的图片,下次滚动到相同item时直接复用,既解决了卡顿,还能避免OOM问题,简直是RecyclerView图片加载的标配!
额外提醒
不管用哪种方案,都要注意:
- 取消未完成的任务:当RecyclerView的item被回收时,要取消正在进行的异步操作,防止更新已经被回收的ImageView,导致内存泄漏。
- 缓存优化:自定义实现时可以加个LruCache来缓存缩放后的图片,避免重复做相同的缩放操作;用库的话就不用管了,库已经帮你搞定了。
内容的提问来源于stack exchange,提问作者Rookie
相关产品推荐
相关产品推荐

