当前位置: 首页 > 编程笔记 >

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

潘弘壮
2023-03-14
本文向大家介绍Android异步回调中的UI同步性问题分析,包括了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,是非常必要且有效的手段。

 原文地址:http://www.cnblogs.com/lwbqqyumidi/p/4110377.html

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • 我正在尝试创建一个回调函数: 首先,我创建了一个函数接口,用于定义回调函数的约定 我创建了一个类,该类将定义一个方法来调用我的回调(我通过使用lambda表达式传递了接口的实现作为对此方法的引用) 下面是我的代码: 但当我运行这段代码时,我得到了这样的结果。 有人能告诉我为什么我会有这个例外吗?

  • 这是我的build.gradle文件 而且,我的文件也如下所示:

  • 问题内容: 如何快速进行异步回调?我正在为我的应用程序编写一个小框架,因为它应该同时在iOS和OS X上运行。因此,我将非特定于设备的主要代码放入该框架中,该框架还处理对我的在线api的请求。很显然,我也希望应用程序的GUI以及ViewController在api请求完成后立即做出反应。在Objective- C中,我通过将包含必须在id变量中调用的函数以及函数本身的视图保存在选择器变量中的视图来

  • 几周前刚开始学Node.js....我不明白为什么“products”数组包含null而不是所需的对象.... 在第13行,当我对对象进行控制台日志记录时,我得到了所需的对象,但我不明白当我在map函数完成它的执行后在第40行对它们进行控制台日志记录时,它们为什么是空的.... 如果数组长度是2(这意味着推入成功),为什么里面存储的对象仍然是空的,而不是我想要存储的对象? 控制台输出 订单模式

  • 问题内容: 我有一个在另一个类中启动异步任务,然后应该等待结果。 问题是方法将在方法运行完成后立即完成,对吗? 这意味着,通常,在启动异步任务后,它将立即关闭,并且不再在那里接收结果。 如何使以上代码段正常工作?开始任务后,我已经尝试放入(任意持续时间)。似乎可以正常工作。 但这绝对不是一个干净的解决方案。也许甚至有一些严重的问题。 有更好的解决方案吗? 问题答案: 使用标准类代替,从回调启动异步

  • 同步调用异步方法最安全的方法是什么?

  • 本文向大家介绍jquery Deferred 快速解决异步回调的问题,包括了jquery Deferred 快速解决异步回调的问题的使用技巧和注意事项,需要的朋友参考一下 jquery Deferred 快速解决异步回调的问题 //相关API 分成3类 1类:$.when(pro1,pro1) 将多个 promise 对象以and的关系 合并为1个 2类:promise 激发为 解决 deferr

  • 本文向大家介绍分析JS单线程异步io回调的特性,包括了分析JS单线程异步io回调的特性的使用技巧和注意事项,需要的朋友参考一下 我们最开始接触javascript应该大部分是从html中的js脚本开始,但是这种看似简单的语言稀里糊涂的用了好几年,也没有搞清楚它的一些原理机制,有没有躺枪! 起码javascript在操作dom的时候用了各种事件回调,比如按钮,链接的点击,鼠标经过,获取焦点等等. 在