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

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

(0)

相关推荐

  • Nodejs异步回调的优雅处理方法

    前言 Nodejs最大的亮点就在于事件驱动, 非阻塞I/O 模型,这使得Nodejs具有很强的并发处理能力,非常适合编写网络应用.在Nodejs中大部分的I/O操作几乎都是异步的,也就是我们处理I/O的操作结果基本上都需要在回调函数中处理,比如下面的这个读取文件内容的函数: 复制代码 代码如下: fs.readFile('/etc/passwd', function (err, data) {   if (err) throw err;   console.log(data); }); 那,我们

  • C#中异步回调函数用法实例

    本文实例讲述了C#中异步回调函数用法.分享给大家供大家参考.具体如下: static void Main(string[] args) { Func<string,string> showMessage = ShowMessage; //设置了回调函数Completed,不能有返回值 IAsyncResult result = showMessage.BeginInvoke("测试异步委托",new AsyncCallback(Completed),null); //半段异

  • jquery Deferred 快速解决异步回调的问题

    jquery Deferred 快速解决异步回调的问题 function ok(name){ var dfd = new $.Deferred(); callback:func(){ return dfd.resolve( response ); } return dfd.promise(); } $.when(ok(1),ok(2)).then(function(resp1,resp2){}) //相关API 分成3类 1类:$.when(pro1,pro1) 将多个 promise 对象以a

  • python实现异步回调机制代码分享

    1 将下面代码拷贝到一个文件,命名为asyncore.py 复制代码 代码如下: import socketimport selectimport sys def ds_asyncore(addr,callback,timeout=5):    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)    s.connect(addr)    r,w,e = select.select([s],[],[],timeout)    if r:     

  • IP查询系统的异步回调案例

    话不多说,请看代码: package com.lxj.demo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; public class Http extends Thread{ // 下载结束的回调接口 public interface

  • jQuery的promise与deferred对象在异步回调中的作用

    一.前言 为了让前端们从回调的地狱中回到天堂, jQuery 也引入了 Promise 的概念. Promise 是一种令代码异步行为更加优雅的抽象,有了它,我们就可以像写同步代码一样去写异步代码. jQuery 从1.5版本开始实现了 CommonJS Promise/A 规范这一重量级方案,不过没有严格按照规范进行实现,有一些API上的差异. 好,让我们来看看他们的特性吧( 本文示例基于jquery 1.8版本以上 ). 二.示例 以前写动画时,我们通常是这么干的: $('.animateE

  • C# 委托的三种调用示例(同步调用 异步调用 异步回调)

    首先,通过代码定义一个委托和下面三个示例将要调用的方法: 复制代码 代码如下: public delegate int AddHandler(int a,int b);    public class 加法类    {        public static int Add(int a, int b)        {            Console.WriteLine("开始计算:" + a + "+" + b);            Thread.Sl

  • 使用Promise链式调用解决多个异步回调的问题

    介绍 所谓Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果. 缺少场景支撑,对于新手而言,很难理解Promise的意义. 在<你不知道的JavaScript中>有个场景介绍得很形象: 我走到快餐店的柜台,点了一个芝士汉堡.我交给收银员1.47美元.通过下订单并付款,我已经发出了一个对某个值(就是那个汉堡)的请求.我已经启 动了一次交易. 但是,通常我不能马上就得到这个汉堡.收银员会交给我某个东西来代替汉堡:一张带有 订单号的收据.订单号就是一个

  • JavaScript异步回调的Promise模式封装实例

    网页的交互越来越复杂,JavaScript 的异步操作也随之越来越多.如常见的 ajax 请求,需要在请求完成时响应操作,请求通常是异步的,请求的过程中用户还能进行其他的操作,不会对页面进行阻塞,这种异步的交互效果对用户来说是挺有友好的.但是对于开发者来说,要大量处理这种操作,就很不友好了.异步请求完成的操作必须预先定义在回调函数中,等到请求完成就必须调用这个函数.这种非线性的异步编程方式会让开发者很不适应,同时也带来了诸多的不便,增加了代码的耦合度和复杂性,代码的组织上也会很不优雅,大大降低了

  • 跨平台python异步回调机制实现和使用方法

    1 将下面代码拷贝到一个文件,命名为asyncore.py 复制代码 代码如下: import socketimport selectimport sys def ds_asyncore(addr,callback,timeout=5):    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)    s.connect(addr)    r,w,e = select.select([s],[],[],timeout)    if r:     

  • ajax异步回调函数中给外部变量赋值的问题探讨

    复制代码 代码如下: <!doctype html> <head> <meta charset="utf-8"> </head> <body> <script src="http://localhost/UIBMSPHPProj/public/js/jquery-1.8.2.min.js"></script> <script> $(function(){ var str

随机推荐