SoFunction
Updated on 2025-04-07

Solution to handle asynchronous callbacks at the end of Activity/Fragment

IllegalArgumentException for headaches

During Android development, operations related to UI can only be executed in the main thread, otherwise the following exception will be thrown:

$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Of course, this is basic common sense and is not the focus of this article's discussion, but all subsequent discussions revolve around this basic common sense. When developing Android applications, if all the code is executed on the main thread, ANR is easily present, and Android has prohibited network requests in the main thread after 4.0, so it needs to deal with multithreads to a greater or lesser extent. Whether using the currently popular OkHttp (Retrofit), or using outdated Volley or Android-Async-Http, they all support asynchronous requests. The request process for these asynchronous requests is generally as follows:

The main thread initiates the request

->Network framework opens worker thread for network request

->The worker thread gets the request result

-> Return the request result to the main thread through Handler

->The main thread updates the UI and completes a network request

This process seems normal, but it actually implies a crisis. The following crash is one example.

: View=$DecorView{24e9c19a ..... R......D 0,0-1026,348} not attached to window manager
at (:403)
at (:322)
at (:84)
at (:368)
at (:351)
at (Unknown Source)
at (Unknown Source)
at (Unknown Source)
at $(Unknown Source)
at (:102)
at (:135)
at (:5539)
at (Native Method)
at (:372)
at $(:960)
at (:755)

How did this crash happen? The reason is very simple. The worker thread executes a network request. If the request is executed for too long (due to network delay, etc.), the Activity or Fragment no longer exists (destroyed), and the thread does not know about this. After the request data result comes back, the data is thrown to the main thread through the Handler. In the asynchronous callback, the data is generally updated or the progress bar is updated, but the page no longer exists, so it is gg. . .

Current solutions

Some people may ask, how could framework designers not consider this issue? In fact, they did consider this issue. Currently there are two solutions:

  1. Cancel the request at the end of the activity
  2. During asynchronous callback,()Method to determine whether the activity has been destroyed

The essence of these two solutions is the same, which is to judge whether the activity is destroyed. If it is destroyed, either cancel the callback or not execute the callback.

Cancel the request at the end of the activity

Taking Volley as an example, in the RequestQueue class, a tag is provided to cancel related network requests.

/**
 * Cancels all requests in this queue with the given tag. Tag must be non-null
 * and equality is by identity.
 */
public void cancelAll(final Object tag) {
 if (tag == null) {
 throw new IllegalArgumentException("Cannot cancelAll with a null tag");
 }
 cancelAll(new RequestFilter() {
 @Override
 public boolean apply(Request<?> request) {
  return () == tag;
 }
 });
}

This tag is used when the main thread calls the network request.(Object)Passed in. Tags can be of any type, and generally two types can be used:

  • Context
  • PATH

Context

Bring the Context into the request as a tag. When the object holding the Context is destroyed, the request thread can be notified to cancel. A typical use scenario is in ActivityonDestroy()Method call(this)to cancel all requests related to this Context. As for Volley, it will determine whether the request is cancelled before the request is issued and after the data comes back.

However, as an Android developer, you should know that it is dangerous to hold a thread referenced by a Context. If a thread dies, the Context reference will be held by the thread all the time, resulting in the Context not being released, which is easy to cause memory leakage. If the instance of the Context is an Activity, then the result is disastrous.

Is there a solution? have. Threads hold Context through weak references. When the system needs GC in insufficient memory, objects holding weak references will be recycled first. However, there are still hidden dangers in doing so, and there is a risk of memory leakage.

PATH

Since the problem of holding a Context is serious, we can uniquely identify a request based on the request path. The object that initiates the request needs to maintain a list, record the currently issued request path, and delete the request from the list through the path when the request comes back. When the activity ends but the request has not been issued or returned, all requests in the list bound to this Activity will be cancelled.

It looks like the solution is good, but how is it executed? Each activity needs to maintain a list of requests issued by the current page, and cancel the requests in the list when the activity ends. Facing a dozens or hundreds of activities in one application, such implementation is undoubtedly a pain.

Is there a solution? have. With a good design, this problem can be avoided. You can use a singleton path management class to manage all requests. All requests issued need to be registered in this management class. The request can be bound to the class name (Class) of the currently issued page, so that when the page is destroyed, all requests associated with the page are cancelled.

()

In the process of asynchronous requests, we noticed the last two steps:

-> Return the request result to the main thread through Handler

->The main thread updates the UI and completes a network request

During these last two steps of execution, we can add judgment. If the page is destroyed, then return directly without notifying the main thread to update the UI, which can perfectly solve the problem.

A similar example is as follows:

(new <JSONObject>() {
 @Override
 public void onSuccess(JSONObject object) {
 if(isFinishing()){
  return;
 }
 do what you want...
 }

 @Override
 public void onFail(int code, String msg) {
 if(isFinishing()){
  return;
 }
 do what you want...
 }
});

Although the problem has been solved, troubles have come again. If there is only one person developing, it is okay. You need to always remember that when each network callback is executed, you need to make a judgment in advance (). However, an App is often completed by multiple developers. If a developer does not make a judgment according to the regulations, then the App may have the above hidden dangers, and it is not realistic to modify so many network callbacks on the original network basic framework.

Is there a better solution? Please see below.

Asynchronous callback framework based on Lifeful interface

Lifeful interface design

We define Lifeful, an interface that does not depend on Context or PATH.

/**
  * Created by xingli on 9/21/16.
  *
  * An interface that determines whether the life cycle has ended.
  */
public interface Lifeful {
 /**
  * Determine whether a component's life cycle has reached the end.  It is generally used to determine whether the activity or Fragment life cycle has ended during asynchronous callbacks.
  *
  * @return
  */
 boolean isAlive();
}

In fact, we only need to let the class with a life cycle (usually Activity or Fragment) implement this interface, and then use this interface to determine whether the implementation class still exists, so we can decouple from the Context.

Next, define an interface generator, wrap the implementation class of the Lifeful interface through weak references, and return the required relevant information.

/**
  * Created by xingli on 9/22/16.
  *
  * Lifecycle specific object generator.
  */
public interface LifefulGenerator&lt;Callback&gt; {

 /**
  * @return Returns the callback interface.
  */
 Callback getCallback();

 /**
  * Get weak references bound to the life cycle, generally Context, using a WeakReference wrapper.
  *
  * @return Returns a weak reference bound to the lifecycle.
  */
 WeakReference&lt;Lifeful&gt; getLifefulWeakReference();

 /**
  * Whether the incoming reference is Null.
  *
  * @return true if {@link Lifeful} is null.
  */
 boolean isLifefulNull();
}

Provide a default implementation of this interface:

/**
  * Created by xingli on 9/22/16.
  *
  * Default lifecycle management wrapper generator.
  */

public class DefaultLifefulGenerator&lt;Callback&gt; implements LifefulGenerator&lt;Callback&gt; {

 private WeakReference&lt;Lifeful&gt; mLifefulWeakReference;
 private boolean mLifefulIsNull;
 private Callback mCallback;

 public DefaultLifefulGenerator(Callback callback, Lifeful lifeful) {
 mCallback = callback;
 mLifefulWeakReference = new WeakReference&lt;&gt;(lifeful);
 mLifefulIsNull = lifeful == null;
 }

 @Override
 public Callback getCallback() {
 return mCallback;
 }

 public WeakReference&lt;Lifeful&gt; getLifefulWeakReference() {
 return mLifefulWeakReference;
 }

 @Override
 public boolean isLifefulNull() {
 return mLifefulIsNull;
 }
}

Then use a static method to determine whether the life cycle of the object is:

/**
  * Created by xingli on 9/22/16.
  *
  * Lifecycle related help classes.
  */

public class LifefulUtils {
 private static final String TAG = ();

 public static boolean shouldGoHome(WeakReference&lt;Lifeful&gt; lifefulWeakReference, boolean objectIsNull) {
 if (lifefulWeakReference == null) {
  (TAG, "Go home, lifefulWeakReference == null");
  return true;
 }
 Lifeful lifeful = ();
 /**
   * If the passed Lifeful is not null, but the weak reference is null, the object is recycled.
   */
 if (null == lifeful &amp;&amp; !objectIsNull) {
  (TAG, "Go home, null == lifeful &amp;&amp; !objectIsNull");
  return true;
 }
 /**
   * End of the life cycle of the object
   */
 if (null != lifeful &amp;&amp; !()) {
  (TAG, "Go home, null != lifeful &amp;&amp; !()");
  return true;
 }
 return false;
 }

 public static &lt;T&gt; boolean shouldGoHome(LifefulGenerator&lt;T&gt; lifefulGenerator) {
 if (null == lifefulGenerator) {
  (TAG, "Go home, null == lifefulGenerator");
  return true;
 } if (null == ()) {
  (TAG, "Go home, null == ()");
  return true;
 }
 return shouldGoHome((), ());
 }
}

Runnable with life cycle

Specifically for the asynchronous classes that deal with threads, only Runnable (Thread is also a subclass), so you only need to process Runnable. We can use the Wrapper wrapper pattern to determine whether the object still exists through the Lifeful interface before processing the real Runnable class, and if it does not exist, it will be returned directly. For Runnable:

/**
  * Created by xingli on 9/21/16.
  *
  * Async thread callback class related to cycles.
  */
public class LifefulRunnable implements Runnable {

 private LifefulGenerator&lt;Runnable&gt; mLifefulGenerator;

 public LifefulRunnable(Runnable runnable, Lifeful lifeful) {
  mLifefulGenerator = new DefaultLifefulGenerator&lt;&gt;(runnable, lifeful);
 }

 @Override
 public void run() {
  if ((mLifefulGenerator)) {
   return;
  }
  ().run();
 }
}

Lifeful implementation class

Finally, let’s talk about the implementation classes of Lifeful class, mainly including Activity and Fragment.

public class BaseActivity extends Activity implements Lifeful {
 
 @Override
 public boolean isAlive() {
  return activityIsAlive();
 }

 public boolean activityIsAlive() {
 if (currentActivity == null) return false;
  if (.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
   return !(() || ());
  } else {
   return !();
  }
 }
}
public class BaseFragment extends Fragment implements Lifeful {
 
 @Override
 public boolean isAlive() {
  return activityIsAlive();
 }
 
 public boolean activityIsAlive() {
 Activity currentActivity = getActivity();
 if (currentActivity == null) return false;
  if (.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
   return !(() || ());
  } else {
   return !();
  }
 }
}

In addition to these two classes, if other classes have life cycles or contain references to life cycles, they can also implement Lifeful interfaces (such as View, which can be used throughonAttachedToWindow()andonDetachedToWindow() ) 。

Asynchronous calls containing life cycles

Calling is also very convenient for places where asynchronous needs to be used.

// ThreadCore is a ThreadPoolExecutor encapsulation class for thread scheduling, which is also used for switching between the main thread and the worker thread().postOnMainLooper(new LifefulRunnable(new Runnable() {
 @Override
 public void run() {
  // Implement real logic. }
}, this));

Summarize

This article mainly focuses on the process of decoupling the corresponding asynchronous thread processing method when objects with life cycles in Android have been destroyed. By defining the Lifeful interface, a method is implemented that does not rely on Context or other objects that are prone to memory leakage, but can be bound to the life cycle of the object. OK, the above is the entire content of this article. I hope the content of this article will be of some help to all Android developers. If you have any questions, you can leave a message to communicate.