Analysis of Synchronization of UI in Asynchronous Callback of Android

  • 2021-07-18 08:59:09
  • OfStack

Callbacks are ubiquitous in the coding of Android programs. Start with the most common Activity lifecycle callbacks, through BroadcastReceiver, Service, Sqlite, and so on. The callback paths and processes for the basic components of Activity, BroadcastReceiver, and Service are commonly referred to as the "lifecycle." At the same time, when dealing with specific business logic, communication between different threads is often designed, such as notifying UI thread to update UI after downloading pictures. In such scenarios, no matter which specific inter-thread communication mode is used (Handler/Message, Handler/post, interface-based callback, many-to-many observer mode such as EventBus, etc.), it is essentially based on "callback". In the actual coding process, when it comes to the communication between different threads, it is essentially "asynchronous callback". When you need to modify UI in an "asynchronous callback", you need to pay special attention to UI synchronization.

In order to facilitate the exposition of the problem, Here, "Android asynchronous callback UI synchronization problem" is defined as follows: When an asynchronous callback executes (called an "asynchronous callback execution point"), The element on the current UI interface is different from the UI element when the caller that originally generated this asynchronous callback started execution (called "asynchronous callback generation point"). No 1 will not only include the possible interface changes and possible content changes of UI elements, but also include the characterization quantity of a certain 1 characteristic in UI elements when "asynchronous callback execution point" and "asynchronous callback generation point" (such as a certain 1 has a field value representing the current UI element), even if the interface and content of UI elements have not changed.

In the coding process, "Android asynchronous callback UI synchronization problem" often exists. Sometimes, if you don't pay attention to it, you will produce some bug that seem difficult to understand. Because of the asynchronous characteristics, this kind of bug also has a certain randomness. Sometimes, due to the complexity of some requirements, such bug is very concealed and easily overlooked. At least so far, I have encountered several such problems in actual development.

The plain text description may not be easy to understand. Take a commonly used Android-Universal-Image-Loader as an example, and give a simple example of a potential "Android asynchronous callback UI synchronization problem".

There is ImageView in ListView Item View, which is loaded and displayed through Android-Universal-Image-Loader. After the picture is loaded, it needs to do some logical processing (such as hiding the picture loading progress bar, etc.). The usual code is as follows:


ImageLoader.getInstance().loadImage(imageUrl, new ImageLoadingListener() {
        
 @Override
 public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
  if (loadedImage != null) {
   imageView.setImageBitmap(loadedImage);
   //  Other business logic processing ..
  }
 }

 @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) {
  
 }
});

At first glance, there seems to be nothing wrong with the code logic, which is what most people on the Internet write. There is no problem when sliding ListView slowly or when it is used normally. But is the code logic here really tight?

The getView multiplexing feature of ListView is well known to all. For the previous encounter of "picture dislocation/displaying the previous picture first and then overwriting it with the correct picture", We all know how to solve this kind of phenomenon (ImageView is set as the first default picture at the beginning of getView logic processing, and other UI elements are processed similarly), and basically there will be no such phenomenon as "picture misplacement/showing the previous picture first and then being covered by the correct picture". In fact, when the network speed condition is equal to 1, and loadImage is roughly as shown in the above code, the list is quickly slid in ListView, and after a few screens, there is no accident, and the problem of "picture dislocation/showing the previous picture first and then being covered by the correct picture" still exists.

The cause of the problem at this point is not getView itself, because getView logic has reset ImageView to the default picture at the beginning, but "Android asynchronous callback UI synchronization problem". Due to the continuous reuse of ViewHolder, After sliding a few screens quickly at the network speed of 1, When the asynchronous callback of onLoadingComplete is executed, it is different from the current UI element. To understand simply, ImageView is multiplexed with ImageView position 0, ImageView position 11 and ImageView position 21, and the sliding stops at this time. When the asynchronous callback of onLoadingComplete is executed, ImageView is already the last ImageView position 21, and the asynchronous callback of onLoadingComplete may be executed several times (ImageView position 0, ImageView position 11, ImageView position,

Solution:
Grasping the "characterization quantity of a 1 characteristic in UI element", this paper directly makes logical treatment by comparing the value of the characteristic variable "asynchronous callback generation point" and "asynchronous callback execution point" in asynchronous callback.


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);
   //  Other business logic processing ..
  }
 }
});


In a word, every kind of "Android asynchronous callback UI synchronization problem", it is best to do logical treatment by comparing the value of characteristic variables of "asynchronous callback generation point" and "asynchronous callback execution point", so as to avoid unnecessary Bug, which is a very necessary and effective means.

Original address: http://www.cnblogs.com/lwbqqyumidi/p/4110377. html


Related articles: