如何在为ImageView设置Bitmap后立即获取对应Bitmap对象
问题背景
- 需求:从指定URL获取图片,将图片转为字符串存储到SharedPreferences中,不采用直接存储图片文件的方案
- 现有问题:参考实现的异步图片加载功能可以将图片设置到ImageView,但无法在图片设置到控件的第一时间获取到最新Bitmap,即便调用
invalidate()方法刷新控件,取到的仍然是控件上之前的旧图片
原有实现代码如下:
class DownloadImageFromInternet(var imageView: ImageView) : AsyncTask<String, Void, Bitmap?>() { override fun doInBackground(vararg urls: String): Bitmap? { val imageURL = urls[0] var image: Bitmap? = null try { val `in` = URL(imageURL).openStream() image = BitmapFactory.decodeStream(`in`) } catch (e: Exception) { Log.e("Error Message", e.message.toString()) e.printStackTrace() } return image } override fun onPostExecute(result: Bitmap?) { imageView.setImageBitmap(result) imageView.invalidate() } }
解决方案
核心误区说明
你拿不到最新Bitmap的根本原因是思路错了:完全不需要从ImageView控件反向提取Bitmap。invalidate()的作用只是给View打上「需要重绘」的标记,实际的视图重绘是排队等主线程消息队列调度的,不会在你调用方法的瞬间同步执行。你刚调用setImageBitmap就立刻去读取控件上的图片内容,新的Bitmap还没完成和控件的绑定,自然只能拿到旧值。
实际上onPostExecute的入参result就是刚下载完成、即将设置给ImageView的最新Bitmap对象,在这个回调里直接使用这个对象,就是能拿到新图的最早时机,不存在任何时序问题。
推荐实现
直接在onPostExecute回调中先处理Bitmap的转码、存储逻辑,再将Bitmap设置给ImageView即可,不需要等控件刷新,也不需要事后从控件反向取图。
注意SharedPreferences的单键值对存在长度限制,转码存储前建议先对Bitmap做压缩,避免存储失败。
修正后的完整代码如下:
class DownloadImageFromInternet( private val imageView: ImageView ) : AsyncTask<String, Void, Bitmap?>() { override fun doInBackground(vararg urls: String): Bitmap? { val imageURL = urls[0] return try { val inputStream = URL(imageURL).openStream() BitmapFactory.decodeStream(inputStream) } catch (e: Exception) { Log.e("ImageLoadError", e.message.toString()) e.printStackTrace() null } } override fun onPostExecute(result: Bitmap?) { result ?: return // 第一时间处理最新Bitmap:转Base64字符串存入SharedPreferences val byteArrayOutputStream = ByteArrayOutputStream() // 按需调整压缩格式和质量,避免字符串过长存储失败 result.compress(Bitmap.CompressFormat.JPEG, 70, byteArrayOutputStream) val imageBase64Str = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT) imageView.context.getSharedPreferences("app_cache", Context.MODE_PRIVATE) .edit() .putString("cached_avatar", imageBase64Str) .apply() // 处理完存储逻辑后再把图片设置给ImageView imageView.setImageBitmap(result) } }
非必要不推荐的方案:从ImageView直接取图
如果有特殊场景必须从ImageView控件本身获取最新Bitmap,可以通过ViewTreeObserver添加预绘制监听,在控件完成内容绑定、即将执行绘制的时候取图,代码示例:
override fun onPostExecute(result: Bitmap?) { result ?: return imageView.setImageBitmap(result) imageView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { // 此时可以拿到控件绑定的最新Bitmap val latestBitmap = (imageView.drawable as? BitmapDrawable)?.bitmap latestBitmap?.let { // 在这里做转码、存储逻辑 } // 移除监听避免重复触发 imageView.viewTreeObserver.removeOnPreDrawListener(this) return true } }) }
该方案依赖系统视图绘制的调度时序,相比直接使用
onPostExecute返回的Bitmap对象性能更差,也存在不同系统版本的兼容风险,非特殊场景不要使用。
内容的提问来源于stack exchange,提问作者Arijit Paul
相关产品推荐
相关产品推荐

