In this paper, starting from vivo Internet technology WeChat public links: mp.weixin.qq.com/s/uTv44vJFF… Author: Lian Lingneng

Image display in Android App is a very basic and important function, there are many image loading solutions on the Android platform, but the official recognition is Glide. Android App pages have a life cycle, Glide is a better function is to have a life cycle management function, according to the page and App life cycle to manage the loading and stopping of pictures, but also open interface for users to manually memory management when memory is tight. This article focuses on the analysis of the lifecycle source code and does not begin with simple usage.

Were reviewed,

This is the second article of Glide source analysis, the first article is “Glide cache process”, from the source of the process for source analysis. This article will focus on the principles of the lifecycle module. Here are some questions to consider before you start:

  • How does Glide implement the page life cycle?

  • Why does Glide cache fragments?

  • How does Glide listen for network changes?

  • How does Glide monitor memory?


Second, Glide life cycle transmission

Let’s start with the execution of the WITH function, which constructs glide singleton, while

RequestManagerRetriever is constructed in initializeGlide.

// Glide.java
public static RequestManager with(@NonNull Activity activity) {
   return getRetriever(activity).get(activity);
}
 
  @NonNull
  private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    // Context could be null for other reasons (ie the user passes in null), but in practice it will
    // only occur due to errors with the Fragment lifecycle.
    Preconditions.checkNotNull(
        context,
        "You cannot start a load on a not yet attached View or a Fragment where getActivity() "
            + "returns null (which usually occurs when getActivity() is called before the Fragment "
            + "is attached or after the Fragment is destroyed).");
    return Glide.get(context).getRequestManagerRetriever();
  }
 
  @NonNull
  public static Glide get(@NonNull Context context) {
    if (glide == null) {
      synchronized (Glide.class) {
        if(glide == null) { checkAndInitializeGlide(context); }}}return glide;
  }
 
  private static void checkAndInitializeGlide(@NonNull Context context) {
    // In the thread running initGlide(), one or more classes may call Glide.get(context).
    // Without this check, those calls could trigger infinite recursion.
    if (isInitializing) {
      throw new IllegalStateException("You cannot call Glide.get() in registerComponents(),"
          + " use the provided Glide instance instead");
    }
    isInitializing = true;
    initializeGlide(context);
    isInitializing = false;
  }Copy the code


Get returns a RequestManager. If you are not in the main thread, getApplicationContext is passed in by default, so there is no lifecycle management involved:

  • In getRequestManagerFragment to view the current Activity is FRAGMENT_TAG this tag corresponding Fragment, if there is returned directly

  • If not, will determine whether pendingRequestManagerFragments is there, if there is returned

  • If not, will rewrite the new one, and then into the pendingRequestManagerFragments, then add to the current Activity, and then send a remove message to a Handler

// RequestManagerRetriever.java
 @NonNull
 public RequestManager get(@NonNull Activity activity) {
   if (Util.isOnBackgroundThread()) {
     return get(activity.getApplicationContext());
   } else {
     assertNotDestroyed(activity);
     android.app.FragmentManager fm = activity.getFragmentManager();
     return fragmentGet(
         activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
   }
 }
 
 private RequestManager fragmentGet(@NonNull Context context,
     @NonNull android.app.FragmentManager fm,
     @Nullable android.app.Fragment parentHint,
     boolean isParentVisible) {
   RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
   RequestManager requestManager = current.getRequestManager();
   if (requestManager == null) {
     // TODO(b/27524013): Factor out this Glide.get() call.
     Glide glide = Glide.get(context);
     requestManager =
         factory.build(
             glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
     current.setRequestManager(requestManager);
   }
   return requestManager;
 }
 
 private RequestManagerFragment getRequestManagerFragment(
     @NonNull final android.app.FragmentManager fm,
     @Nullable android.app.Fragment parentHint,
     boolean isParentVisible) {
   RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
   if (current == null) {
     current = pendingRequestManagerFragments.get(fm);
     if (current == null) {
       current = new RequestManagerFragment();
       current.setParentFragmentHint(parentHint);
       if(isParentVisible) { current.getGlideLifecycle().onStart(); } pendingRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
 }
 
 public boolean handleMessage(Message message) {
   ...
   switch (message.what) {
     case ID_REMOVE_FRAGMENT_MANAGER:
       android.app.FragmentManager fm = (android.app.FragmentManager) message.obj;
       key = fm;
       removed = pendingRequestManagerFragments.remove(fm);
       break; . }... }Copy the code


The with() function will create a RequestManagerFragment with no interface, and its parent Fragment will be the passed Fragment.

Why need pendingRequestManagerFragments cache first above? This is illustrated in the second question below. Let’s move on to lifecycle transitions.

RequestManagerFragment is a very important class, Glide through it as the life cycle of distributing entrance, RequestManagerFragment default constructor will instantiate a ActivityFragmentLifecycle, In each life cycle onStart/onStop/onDestroy is called ActivityFragmentLifecycle:

// RequestManagerFragment.java
public class RequestManagerFragment extends Fragment {
  private static final String TAG = "RMFragment";
  private final ActivityFragmentLifecycle lifecycle;
  @Nullable private RequestManager requestManager;
     
  public RequestManagerFragment() {
    this(new ActivityFragmentLifecycle());
  }
     
  RequestManagerFragment(@NonNull ActivityFragmentLifecycle lifecycle) {
    this.lifecycle = lifecycle;
  }
     
  @Override
  public void onStart() {
    super.onStart();
    lifecycle.onStart();
  }
 
  @Override
  public void onStop() {
    super.onStop();
    lifecycle.onStop();
  }
 
  @Override
  public void onDestroy() { super.onDestroy(); lifecycle.onDestroy(); unregisterFragmentWithRoot(); }... }Copy the code


There is an instance RequestManager RequestManagerFragment, in front of the fragmentGet, RequestManagerFragment attain RequestManager will try to get it, Add a new RequestManager from the RequestManagerFactory factory factory class passed in as part of the Retriever construction. Hand in the the ActivityFragmentLifecycle RequestManagerFragment:

// RequestManagerRetriever.java
public interface RequestManagerFactory {
    @NonNull
    RequestManager build(
        @NonNull Glide glide,
        @NonNull Lifecycle lifecycle,
        @NonNull RequestManagerTreeNode requestManagerTreeNode,
        @NonNull Context context);
  }
 
  private static final RequestManagerFactory DEFAULT_FACTORY = new RequestManagerFactory() {
    @NonNull
    @Override
    public RequestManager build(@NonNull Glide glide, @NonNull Lifecycle lifecycle,
        @NonNull RequestManagerTreeNode requestManagerTreeNode, @NonNull Context context) {
      returnnew RequestManager(glide, lifecycle, requestManagerTreeNode, context); }};Copy the code


Obviously the key to the life cycle in ActivityFragmentLifecycle, in RequestManagerFragment lifecycle callback it accordingly, so guess it must be inside maintains a list of observers, corresponding to notice when the incident happened, look at its source code:

// ActivityFragmentLifecycle.java
class ActivityFragmentLifecycle implements Lifecycle {
  private final Set<LifecycleListener> lifecycleListeners =
      Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>());
  private boolean isStarted;
  private boolean isDestroyed;
 
  @Override
  public void addListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.add(listener);
 
    if (isDestroyed) {
      listener.onDestroy();
    } else if (isStarted) {
      listener.onStart();
    } else {
      listener.onStop();
    }
  }
 
  @Override
  public void removeListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.remove(listener);
  }
 
  void onStart() {
    isStarted = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStart();
    }
  }
 
  void onStop() {
    isStarted = false;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStop();
    }
  }
 
  void onDestroy() {
    isDestroyed = true;
    for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onDestroy(); }}}Copy the code


Lifecycle. AddListener (this); Lifecycle.addListener(this); lifecycle.addListener(this); Register yourself as an observer:

// RequestManager.java
public class RequestManager implements LifecycleListener,
    ModelTypes<RequestBuilder<Drawable>> {
  ...
  protected final Glide glide;
  protected final Context context;
  @Synthetic final Lifecycle lifecycle;
  private final RequestTracker requestTracker;
  private final RequestManagerTreeNode treeNode;
  private final TargetTracker targetTracker = new TargetTracker();
  private final Runnable addSelfToLifecycle = new Runnable() {
    @Override
    public void run() { lifecycle.addListener(RequestManager.this); }}; private final Handler mainHandler = new Handler(Looper.getMainLooper()); private final ConnectivityMonitor connectivityMonitor; private RequestOptions requestOptions; public RequestManager( @NonNull Glide glide, @NonNull Lifecycle lifecycle, @NonNull RequestManagerTreeNode treeNode, @NonNull Context context) { this( glide, lifecycle, treeNode, new RequestTracker(), glide.getConnectivityMonitorFactory(), context); } // Our usage is safe here. @SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
  RequestManager(
      Glide glide,
      Lifecycle lifecycle,
      RequestManagerTreeNode treeNode,
      RequestTracker requestTracker,
      ConnectivityMonitorFactory factory,
      Context context) {
    this.glide = glide;
    this.lifecycle = lifecycle;
    this.treeNode = treeNode;
    this.requestTracker = requestTracker;
    this.context = context;
 
    connectivityMonitor =
        factory.build(
            context.getApplicationContext(),
            new RequestManagerConnectivityListener(requestTracker));
 
    if (Util.isOnBackgroundThread()) {
      mainHandler.post(addSelfToLifecycle);
    } else {
      lifecycle.addListener(this);
    }
    lifecycle.addListener(connectivityMonitor);
 
    setRequestOptions(glide.getGlideContext().getDefaultRequestOptions());
 
    glide.registerRequestManager(this);
  }Copy the code


Take a look at the lifecycle of the RequestManager to start, stop, and destroy requests:

// RequestManager
@Override
  public void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }
 
  @Override
  public void onStop() {
    pauseRequests();
    targetTracker.onStop();
  }
 
  @Override
  public void onDestroy() {
    targetTracker.onDestroy();
    for(Target<? > target : targetTracker.getAll()) { clear(target); } targetTracker.clear(); requestTracker.clearRequests(); lifecycle.removeListener(this); lifecycle.removeListener(connectivityMonitor); mainHandler.removeCallbacks(addSelfToLifecycle); glide.unregisterRequestManager(this); }Copy the code


Why does Glide cache fragments?

Stick again RequestManagerRetriever from fragments of code, the front left a question that why here would need a pendingRequestManagerFragments to cache fragments.

// RequestManagerRetriever.java
  /**
   * Pending adds for RequestManagerFragments.
   */
  @SuppressWarnings("deprecation")
  @VisibleForTesting
  final Map<android.app.FragmentManager, RequestManagerFragment> pendingRequestManagerFragments = new HashMap<>();
 
private RequestManagerFragment getRequestManagerFragment(
      @NonNull final android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    if (current == null) {
      current = pendingRequestManagerFragments.get(fm);
      if (current == null) {
        current = new RequestManagerFragment();
        current.setParentFragmentHint(parentHint);
        if(isParentVisible) { current.getGlideLifecycle().onStart(); } pendingRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
  }Copy the code


Let’s look at a situation:

Glide.with(Context).load(ImageUrl1).into(imageview1); // task1
Glide.with(Context).load(ImageUrl2).into(imageview2); // task2Copy the code


Android developers probably know that the main thread has a Handler mechanism, which puts messages into a message queue and executes them through Looper. So what is the relationship between the execution order in the main thread and the execution order in the message queue? Take a look:

private void start() {
     mHandler = new Handler(getMainLooper());
 
     VLog.i("HandlerRunT"."=========Begin! = = = = = = = = = = = =");
     mHandler.post(new Runnable() {
         @Override
         public void run() {
             VLog.i("HandlerRunT"."=========First! = = = = = = = = = = = ="); }}); VLog.i("HandlerRunT"."=========Middle! = = = = = = = = = = = =");
     mHandler.sendMessage(Message.obtain(mHandler, new Runnable() {
         @Override
         public void run() {
             VLog.i("HandlerRunT"."=========Second! = = = = = = = = = = = ="); }})); VLog.i("HandlerRunT"."=========End! = = = = = = = = = = = =");
     Next();
 }
 
 private void Next() {
     VLog.i("HandlerRunT"."=========Next Begin! = = = = = = = = = = = =");
     mHandler.post(new Runnable() {
         @Override
         public void run() {
             VLog.i("HandlerRunT"."=========Next First! = = = = = = = = = = = ="); }}); VLog.i("HandlerRunT"."=========Next Middle! = = = = = = = = = = = =");
     mHandler.sendMessage(Message.obtain(mHandler, new Runnable() {
         @Override
         public void run() {
             VLog.i("HandlerRunT"."=========Next Second! = = = = = = = = = = = ="); }})); VLog.i("HandlerRunT"."=========Next End! = = = = = = = = = = = =");
 }Copy the code


Which is printed first, the order printed in start or the information in its Handler? What is the print order of the handler information in start and the information in the Next function? Take a look at the print result:

HandlerRunT: =========Begin! ============ HandlerRunT: =========Middle! ============ HandlerRunT: =========End! ============ HandlerRunT: =========Next Begin! ============ HandlerRunT: =========Next Middle! ============ HandlerRunT: =========Next End! ============ HandlerRunT: =========First! ============ HandlerRunT: =========Second! ============ HandlerRunT: =========Next First! ============ HandlerRunT: =========Next Second! = = = = = = = = = = = =Copy the code


The order in the Handler is after the main thread, and the order in which messages are executed in the Handler is queue first in, first out.

When task1 is executed above, in the following two lines, the add operation puts a message to the message queue, marked msg1:

fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();Copy the code



// FragmentManager.java
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if(! allowStateLoss) { checkStateLoss(); } synchronized (this) {if (mDestroyed || mHost == null) {
                if (allowStateLoss) {
                    // This FragmentManager isn't attached, so drop the entire transaction. return; } throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { mPendingActions = new ArrayList<>(); } mPendingActions.add(action); scheduleCommit(); } } private void scheduleCommit() { synchronized (this) { boolean postponeReady = mPostponedTransactions ! = null && ! mPostponedTransactions.isEmpty(); boolean pendingReady = mPendingActions ! = null && mPendingActions.size() == 1; if (postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); }}}Copy the code


So if you don’t put the structure in the task1 RequestManagerFragment pendingRequestManagerFragments, Task2 reconstructs a RequestManagerFragment and adds a message msg2 to the main thread.

So in front of the new out a RequestManagerFragment, then put it in the pendingRequestManagerFragments, then task2 came in again from the cache can take that will not be back to new again and add.

So the next question is, why does the following code need to send a message remove immediately after the add? After preventing task2 from repeating new and add operations, delete the cache to avoid memory leaks and stress:

// RequestManagerRetriever.java
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();Copy the code


How does Glide monitor network changes

ConnectivityMonitor (RequestManager) ConnectivityMonitor (RequestManager) ConnectivityMonitor

// RequestManager.java public class RequestManager implements LifecycleListener, ModelTypes<RequestBuilder<Drawable>> { ... protected final Glide glide; protected final Context context; @Synthetic final Lifecycle lifecycle; private final RequestTracker requestTracker; private final RequestManagerTreeNode treeNode; private final TargetTracker targetTracker = new TargetTracker(); private final Handler mainHandler = new Handler(Looper.getMainLooper()); private final ConnectivityMonitor connectivityMonitor; . RequestManager( Glide glide, Lifecycle lifecycle, RequestManagerTreeNode treeNode, RequestTracker requestTracker, ConnectivityMonitorFactory factory, Context context) { this.glide = glide; this.lifecycle = lifecycle; this.treeNode = treeNode; this.requestTracker = requestTracker; this.context = context; connectivityMonitor = factory.build( context.getApplicationContext(), new RequestManagerConnectivityListener(requestTracker));if (Util.isOnBackgroundThread()) {
      mainHandler.post(addSelfToLifecycle);
    } else{ lifecycle.addListener(this); } lifecycle.addListener(connectivityMonitor); . }Copy the code


So is it registered as ActivityFragmentLifecycle observer, ConnectivityMonitor through ConnectivityMonitorFactory structure, provides a default implementation class DefaultConnectivityMonitorFactory:

// DefaultConnectivityMonitorFactory.java
public class DefaultConnectivityMonitorFactory implements ConnectivityMonitorFactory {
  private static final String TAG = "ConnectivityMonitor";
  private static final String NETWORK_PERMISSION = "android.permission.ACCESS_NETWORK_STATE";
 
  @NonNull
  @Override
  public ConnectivityMonitor build(
      @NonNull Context context,
      @NonNull ConnectivityMonitor.ConnectivityListener listener) {
    int permissionResult = ContextCompat.checkSelfPermission(context, NETWORK_PERMISSION);
    boolean hasPermission = permissionResult == PackageManager.PERMISSION_GRANTED;
    returnhasPermission ? new DefaultConnectivityMonitor(context, listener) : new NullConnectivityMonitor(); }}Copy the code


Then look down DefaultConnectivityMonitor in onStart registerReceiver listening radio mobile phone network status changes, and then call in connectivityReceiver isConnect network status confirmation, According to whether the network status changes, if have change the callback to monitor ConnectivityMonitor ConnectivityListener:

final class DefaultConnectivityMonitor implements ConnectivityMonitor {
  private static final String TAG = "ConnectivityMonitor";
  private final Context context;
  @SuppressWarnings("WeakerAccess") @Synthetic final ConnectivityListener listener;
 
  @SuppressWarnings("WeakerAccess") @Synthetic boolean isConnected;
  private boolean isRegistered;
 
  private final BroadcastReceiver connectivityReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(@NonNull Context context, Intent intent) {
      boolean wasConnected = isConnected;
      isConnected = isConnected(context);
      if(wasConnected ! = isConnected) {if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "connectivity changed, isConnected: "+ isConnected); } listener.onConnectivityChanged(isConnected); }}}; DefaultConnectivityMonitor(@NonNull Context context, @NonNull ConnectivityListener listener) { this.context = context.getApplicationContext(); this.listener = listener; } private voidregister() {
    if (isRegistered) {
      return;
    }
 
    // Initialize isConnected.
    isConnected = isConnected(context);
    try {
      // See # 1405
      context.registerReceiver(connectivityReceiver,
          new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
      isRegistered = true;
    } catch (SecurityException e) {
      // See #1417, registering the receiver can throw SecurityException.
      if (Log.isLoggable(TAG, Log.WARN)) {
        Log.w(TAG, "Failed to register", e);
      }
    }
  }
 
  private void unregister() {
    if(! isRegistered) {return;
    }
 
    context.unregisterReceiver(connectivityReceiver);
    isRegistered = false;
  }
 
  @SuppressWarnings("WeakerAccess")
  @Synthetic
  // Permissions are checked in the factory instead.
  @SuppressLint("MissingPermission")
  boolean isConnected(@NonNull Context context) {
    ConnectivityManager connectivityManager =
        Preconditions.checkNotNull(
            (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
    NetworkInfo networkInfo;
    try {
      networkInfo = connectivityManager.getActiveNetworkInfo();
    } catch (RuntimeException e) {
      if (Log.isLoggable(TAG, Log.WARN)) {
        Log.w(TAG, "Failed to determine connectivity status when connectivity changed", e);
      }
      // Default to true;
      return true;
    }
    returnnetworkInfo ! = null && networkInfo.isConnected(); } @Override public voidonStart() {
    register();
  }
 
  @Override
  public void onStop() {
    unregister();
  }
 
  @Override
  public void onDestroy() {
    // Do nothing.
  }
}Copy the code


In RequestManager ConnectivityMonitor ConnectivityListener is passed, with the network connection again after reboot request:

// RequestManager.java
  private static class RequestManagerConnectivityListener implements ConnectivityMonitor
      .ConnectivityListener {
    private final RequestTracker requestTracker;
 
    RequestManagerConnectivityListener(@NonNull RequestTracker requestTracker) {
      this.requestTracker = requestTracker;
    }
 
    @Override
    public void onConnectivityChanged(boolean isConnected) {
      if(isConnected) { requestTracker.restartRequests(); }}}Copy the code


How does Glide monitor memory

At the time of Glide structure registerComponentCallbacks will call for a global registration, Nervous system in memory callback onTrimMemory, then according to the system memory tension level memoryCache/bitmapPool/arrayPool recycling:

// Glide.java
  public static Glide get(@NonNull Context context) {
    if (glide == null) {
      synchronized (Glide.class) {
        if(glide == null) { checkAndInitializeGlide(context); }}}returnglide; } private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) { Context applicationContext = context.getApplicationContext(); . applicationContext.registerComponentCallbacks(glide); Glide.glide = glide; } @Override public void onTrimMemory(int level) { trimMemory(level); } public void trimMemory(int level) { Util.assertMainThread(); memoryCache.trimMemory(level); bitmapPool.trimMemory(level); arrayPool.trimMemory(level); }Copy the code


Six, summarized

Review the front of the four questions, I believe that you have a smart answer, the title of each section of the article is based on the problem to analyze, so no longer repeat, or have the suspicion of the word count. Glide’s source code is relatively large and high quality, so one or two articles is not clear, behind the source analysis of Glide will have a follow-up article, welcome to pay attention to.


For more content, please pay attention to vivo Internet technology wechat public account

Note: To reprint the article, please contact our wechat account: LABs2020.