Android异步回调中的UI同步性问题

简介:

Android程序编码过程中,回调无处不在。从最常见的Activity生命周期回调开始,到BroadcastReceiver、Service以及Sqlite等。Activity、BroadcastReceiver和Service这些基本组件的回调路径和过程也就是通常意义上所谓的“生命周期”。同时,在处理具体的业务逻辑时,常常设计到不同线程之间的通信,如下载图片完成后通知 UI线程更新UI,凡此类场景,无论使用哪一种具体的线程间通信方式(Handler/Message、Handler/post、基于接口的回调、基于多对多的观察者模式如EventBus等),其本质上都是基于“回调”。在实际编码过程中,凡涉及到不同线程之间的通信,本质上更是属于“异步回调”。当需要在“异步回调”中修改UI时,此时需要特别注意UI同步性问题。

为了便于问题的阐述,在此先对“Android异步回调UI同步性问题”进行如下界定:当异步回调执行时(称之为“异步回调执行点”),当前UI界面上的元素与最初生成此异步回调的调用器开始执行时(称之为“异步回调生成点”)的UI元素已经存在不一致,不一致不仅包括UI元素可能的界面变化、可能的内容变化,也包括“异步回调执行点”和“异步回调生成点”时的UI元素中的某一特性的表征量(如某一具有表征当前UI元素的字段值)相关,即使UI元素界面和内容都尚未发生变化。

编码过程中,“Android异步回调UI同步性问题”经常存在,有时候稍不注意会产生一些看起来难以理解的bug,并由于异步特性的存在,此类bug还具有一定的随机性。有时候由于一些需求的复杂性,此类bug隐蔽性很强,也容易被忽略。至少到目前为止,在实际开发中,本人遇到此类问题已有数个。

纯文字的描述可能不太好理解,下面以一个很常用的Android-Universal-Image-Loader为例,简单举例一个潜在存在的“Android异步回调UI同步性问题”。

ListView Item View中有ImageView,通过Android-Universal-Image-Loader去加载显示,图片加载完成后需要做一些逻辑处理(如隐藏图片加载进度条等..),通常代码如下:

复制代码
 1 ImageLoader.getInstance().loadImage(imageUrl, new ImageLoadingListener() {
 2                                 
 3     @Override
 4     public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
 5         if (loadedImage != null) {
 6             imageView.setImageBitmap(loadedImage);
 7             // 其他业务逻辑处理..
 8         }
 9     }
10 
11     @Override
12     public void onLoadingStarted(String imageUri, View view) {
13         
14     }
15 
16     @Override
17     public void onLoadingCancelled(String arg0, View arg1) {
18         
19     }
20 
21     @Override
22     public void onLoadingFailed(String arg0, View arg1, FailReason arg2) {
23         
24     }
25 });
复制代码

初看上去,代码逻辑好像也没什么问题,网上大部分人也是这么写的。当较慢滑动ListView时,或在平时正常使用时,也没有什么问题。但是此处的代码逻辑真的严密吗?

ListView的getView复用特性,大家也都熟知。对于之前遇到的“图片错位/先显示之前的图片后再被正确的图片覆盖掉”,此类现象也都知道如何解决(在getView逻辑开始处理处将ImageView设置成最先的默认图片,其他UI元素类似处理),基本上也不会再有“图片错位/先显示之前的图片后再被正确的图片覆盖掉”这类现象了。实际上,当网速条件一般,且loadImage大致与上述代码所示,在ListView中快速滑动列表,几屏后,不出意外,会发现“图片错位/先显示之前的图片后再被正确的图片覆盖掉”此问题依然存在。

此时问题出现的原因不在于getView本身,因为getView逻辑开始时已经将ImageView重置为默认图片,而在于“Android异步回调UI同步性问题”。由于ViewHolder的不断复用,网速一般时快速滑动几屏后,onLoadingComplete的异步回调执行时与当前UI元素已经存在不一致,简单点理解,ImageView被复用了ImageView position 0,ImageView position 11, ImageView position 21,此时滑动停止,onLoadingComplete的异步回调执行时ImageView已经是最后一次的ImageView position 21,而onLoadingComplete的异步回调可能被执行数次(ImageView position 0,ImageView position 11, ImageView position 21,且顺序还取决于异步中的具体处理和网络环境等),于是问题发生了。

解决方案:
抓住”UI元素中的某一特性的表征量“,在异步回调中通过比较“异步回调生成点”和“异步回调执行点”此特征变量的值直接作出逻辑上的处理。

复制代码
 1 public class HardRefSimpleImageLoadingListener implements ImageLoadingListener {
 2 
 3     public int identifier;
 4 
 5     public HardRefSimpleImageLoadingListener() {
 6     }
 7 
 8     public HardRefSimpleImageLoadingListener(int identifier) {
 9         this.identifier = identifier;
10     }
11 
12     @Override
13     public void onLoadingCancelled(String arg0, View arg1) {
14 
15     }
16 
17     @Override
18     public void onLoadingComplete(String arg0, View arg1, Bitmap arg2) {
19 
20     }
21 
22     @Override
23     public void onLoadingFailed(String arg0, View arg1, FailReason arg2) {
24 
25     }
26 
27     @Override
28     public void onLoadingStarted(String arg0, View view) {
29     
30     }
31 }
32 
33 ImageLoader.getInstance().loadImage(imageUrl, new HardRefSimpleImageLoadingListener(did) {
34     @Override
35     public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
36         if (loadedImage != null) {
37             if (identifier != did) {
38                 return;
39             }
40             imageView.setImageBitmap(loadedImage);
41             // 其他业务逻辑处理..
42         }
43     }
44 });
复制代码

总之,凡此类“Android异步回调UI同步性问题”,最好都通过比较“异步回调生成点”和“异步回调执行点”特征变量的值去针对性的做逻辑处理,以免出现不必要的Bug,是非常必要且有效的手段。

 

---------------------------------------------------------------------------------
笔者水平有限,若有错漏,欢迎指正,如果转载以及CV操作,请务必注明出处,谢谢!
分类: Android

本文转自Windstep博客园博客,原文链接:http://www.cnblogs.com/lwbqqyumidi/p/4110377.html,如需转载请自行联系原作者
目录
相关文章
|
16天前
|
消息中间件 安全 数据处理
Android为什么不能在子线程更新UI
Android为什么不能在子线程更新UI
23 0
|
1月前
|
Java 数据库 Android开发
Android异步之旅:探索AsyncTask
Android异步之旅:探索AsyncTask
22 0
|
3月前
|
Android开发 开发者
Android UI设计: 请解释Activity的Theme是什么,如何更改应用程序的主题?
Android UI设计: 请解释Activity的Theme是什么,如何更改应用程序的主题?
41 1
|
3月前
|
开发工具 Android开发 开发者
Android UI设计: 解释Android的Nine-Patch图像是什么,它用于什么目的?
Android UI设计: 解释Android的Nine-Patch图像是什么,它用于什么目的?
31 4
|
5天前
|
编解码 Android开发 UED
安卓UI/UX设计原则:打造引人入胜的用户体验
【4月更文挑战第13天】本文探讨了安卓UI/UX设计的关键原则,包括一致性、简洁性、反馈、清晰性、效率和适应性。一致性要求视觉和行为保持一致,利用系统UI;简洁性减少用户行动,简化导航;反馈需即时且明确;清晰性强调表达清晰,布局有序;效率关注性能优化和任务简化;适应性涉及多设备适配和用户多样性。遵循这些原则,可创建出色应用,提供无缝用户体验。设计应持续迭代,适应技术发展和用户需求。
|
11天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
1月前
|
Android开发 开发者
Android异步之旅:探索IntentService
Android异步之旅:探索IntentService
19 0
|
1月前
|
消息中间件 数据库 Android开发
Android异步之旅:探索HandlerThread
Android异步之旅:探索HandlerThread
21 0
|
1月前
|
Android开发 对象存储
OSS对象储存android开发进行下载到本地文件时异步操作失效
android vivo80使用官方示例代码进行文件下载,但是使用oss.asyncGetObject(get, new OSSCompletedCallback<GetObjectRequest, GetObjectResult>()时onSuccess和onFailure不执行
|
1月前
|
XML API Android开发
【Android 从入门到出门】第三章:使用Hilt处理Jetpack Compose UI状态
【Android 从入门到出门】第三章:使用Hilt处理Jetpack Compose UI状态
26 4