Android非同期コールバックにおけるUI同期性の問題

10174 ワード

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同期性の問題」はしばしば存在し、少し注意しないと理解しにくいバグが発生し、非同期特性の存在によりランダム性がある場合がある.いくつかのニーズの複雑さのため、このようなバグは隠蔽性が強く、無視されやすい場合があります.少なくともこれまで、実際の開発では、本人がこのような問題に遭遇したのはいくつかあった.
純粋な文字の説明はよく理解できないかもしれませんが、以下では、一般的に使用されている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同期性の問題」は、不要なバグが発生しないように、「非同期コールバック生成点」と「非同期コールバック実行点」の特徴変数の値を比較して論理的に処理することが望ましい.