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去加载显示,图片加载完成后需要做一些逻辑处理(如隐藏图片加载进度条等..),通常代码如下:
ImageLoader.getInstance().loadImage(imageUrl, new ImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if (loadedImage != null) {
imageView.setImageBitmap(loadedImage);
// 其他业务逻辑处理..
}
}
@Override
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingCancelled(String arg0, View arg1) {
}
@Override
public void onLoadingFailed(String arg0, View arg1, FailReason arg2) {
}
});
初看上去,代码逻辑好像也没什么问题,网上大部分人也是这么写的。当较慢滑动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元素中的某一特性的表征量“,在异步回调中通过比较“异步回调生成点”和“异步回调执行点”此特征变量的值直接作出逻辑上的处理。
public class HardRefSimpleImageLoadingListener implements ImageLoadingListener {
public int identifier;
public HardRefSimpleImageLoadingListener() {
}
public HardRefSimpleImageLoadingListener(int identifier) {
this.identifier = identifier;
}
@Override
public void onLoadingCancelled(String arg0, View arg1) {
}
@Override
public void onLoadingComplete(String arg0, View arg1, Bitmap arg2) {
}
@Override
public void onLoadingFailed(String arg0, View arg1, FailReason arg2) {
}
@Override
public void onLoadingStarted(String arg0, View view) {
}
}
ImageLoader.getInstance().loadImage(imageUrl, new HardRefSimpleImageLoadingListener(did) {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if (loadedImage != null) {
if (identifier != did) {
return;
}
imageView.setImageBitmap(loadedImage);
// 其他业务逻辑处理..
}
}
});
总之,凡此类“Android异步回调UI同步性问题”,最好都通过比较“异步回调生成点”和“异步回调执行点”特征变量的值去针对性的做逻辑处理,以免出现不必要的Bug,是非常必要且有效的手段。
Android异步回调中的UI同步性问题的更多相关文章
- 使用domain模块捕获异步回调中的异常
和其他服务器端语言相比,貌似node.js 对于异常捕捉确实非常困难. 首先你会想到try/catch ,但是在使用过程中我们会发现并没有真正将错误控制在try/catch 语句中. 为什么? 答案是 ...
- node.js 使用domain模块捕获异步回调中的异常
和其他服务器端语言相比,貌似node.js 对于异常捕捉确实非常困难. 首先你会想到try/catch ,但是在使用过程中我们会发现并没有真正将错误控制在try/catch 语句中. 为什么? 答案是 ...
- js即时函数在异步回调中的运用
在编程中我们会接触到循环和异步编程的情况,这时异步回调执行逻辑就会出现问题.我们用setTimeout来模拟异步的: for(var i=0;i<3;i++){ setTimeout(funct ...
- Android 子线程中进行UI操作遇到的小问题
今天在学习<第一行Android代码>第9章-子线程进行UI操作时遇到了一些问题. 代码是这样的: ... import java.util.logging.Handler; ... pu ...
- 老问题:Android子线程中更新UI的3种方法
在Android项目中经常有碰到这样的问题,在子线程中完成耗时操作之后要更新UI,下面就自己经历的一些项目总结一下更新的方法: 方法一:用Handler 1.主线程中定义Handler: Handle ...
- Android子线程中更新UI的4种方法
方法一:用Handler 1.主线程中定义Handler: Handler mHandler = new Handler() { @Override public void handleMessage ...
- 在异步回调中调用MessageBox.Show
public static void Test() { ThreadStart aThreadStart = delegate() { ); MessageBox.Show("Good!&q ...
- Android 在子线程中更新UI的几种方法
第一种: new Handler(context.getMainLooper()).post(new Runnable() { @Override public void run() { // 在这里 ...
- Android Binder机制中的异步回调
“Binder通信是同步而不是异步的”,但是在实际使用时,是设计成客户端同步而服务端异步. 看看Framwork层的各service类java源码便会知道,在客户端调用服务端的各种方法时,通常会传递一 ...
随机推荐
- FIR.im Weekly - 劳动节我们也没有停下来
五一到五四的节假日对勤劳的开发者们似乎是零存在,各种干货好资源也并未因假期的到来而减少,所以这周的 Weekly 依然多产. Swift 样式指南:2015年4月更新 这是 @开发技术前线 收录的由 ...
- C# WebBrowser 获得选中部分的html源码
Winform程序 2.0的. 需要引用Microsoft.mshtml. private void Form1_Load(object sender, EventArgs e) { webBrows ...
- iOS-应用上架
其实挺讨厌苹果这一套上架机制,审核周期长,而且网速又慢,但又没办法,只能上架到AppStore,无奈. 上架前需要确保应用图标齐全,并在Info.plist中的Icon files添加了png图片,不 ...
- css3使用box-sizing布局
css3增添了盒模型box-sizing,属性值有下面三个: content-box:默认值,让元素维持W3C的标准盒模型.元素的宽度/高度(width/height)(所占空间)等于元素边框宽度(b ...
- Java environment variables and their functionality
Explanations of Functionalities: 1. PATH env variable It is used to search the command directory whe ...
- asp.net的简易的参数化查询
protected void btnInsert_Click(object sender, EventArgs e) { string sql = "insert into contactg ...
- WampServer64提示You don't have permission to access
由于配置了php后,这里的“Deny from all”已经拒绝了一切连接.把该行改成“allow from all”,修改后的代码如下,问题解决. <Directory /> O ...
- 移动开发框架剖析(一) Hammer专业的手势控制
一直在写jQuery的源码教程,都没时间研究别的框架了.Hammer是我项目御用的一个手势库,早期1.x版本的swipe事件的响应不灵敏的问题而改过源码,2.x就已经更正过来,而且源码的结构也整个翻新 ...
- Web Workers 的基本信息
问题:JavaScript 并行性 要将有趣的应用(例如从侧重服务器端的实施)移植到客户端 JavaScript,存在很多制约瓶颈.其中包括浏览器兼容性.静态类型.可访问性和性能.幸运的是,随着浏览器 ...
- demo
NGUI demo:http://112.124.104.173/killer/demo/demo.html 网络游戏架构(服务器放在阿里云,有时连接不上可能是服务器没有开) 只是测试框架,美术都是本 ...