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,是非常必要且有效的手段。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM