Deep understanding of Glide life cycle management

Keywords: Operation & Maintenance Fragment Android network Java

Author: His Aunt and Aunt
Links: https://www.jianshu.com/p/317b2d6bde1b

This is the first in a Glide Source Parsing series where you can learn:

  • 1. How Glide binds Activity, Fragment life cycles.
  • 2. How Glide monitors memory and network changes.
  • 3. How Glide handles the life cycle of requests.

1.0 Life Cycle Related UML Class Diagram

2.0 Lifecycle Binding

Glide lifecycle binding starts with the entry singleton class Glide and works through multiple overload methods with().

public static RequestManager with(Fragment fragment)  
public static RequestManager with(FragmentActivity activity)  
public static RequestManager with(Activity activity)  
public static RequestManager with(Context context)

Take the Activity parameter as an example:

public static RequestManager with(Activity activity) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(activity);
}

RequestManagerRetriever is a singleton class that can be understood as a factory class that creates RequestManager by receiving different parameters through the get method.

public RequestManager get(Activity activity) {
    if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        return get(activity.getApplicationContext());
    } else {
        assertNotDestroyed(activity);
        android.app.FragmentManager fm = activity.getFragmentManager();
        return fragmentGet(activity, fm);
    }
}
public RequestManager get(android.app.Fragment fragment) {
    if (fragment.getActivity() == null) {
        throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
    }
    if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
        return get(fragment.getActivity().getApplicationContext());
    } else {
        android.app.FragmentManager fm = fragment.getChildFragmentManager();
        return fragmentGet(fragment.getActivity(), fm);
    }
}

If it's a with operation on a child thread, Glide uses the ApplicationContext by default, which translates into not managing the life cycle of the request, taking the FragmentManager through Activity, and passing on the task of creating the RequestManager.Finally, you come to the fragmentGet method, noting that the subtle difference is that the parameter passed by Activity is the FragmentManager of Activity, and the parameter passed by Fragment is the ChildFragmentManager, which is not one thing.

RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
    //Get the RequestManager fragment and the RequestManager bound to it
    RequestManagerFragment current = getRequestManagerFragment(fm);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
    //If the Get RequestManager Fragment is not already bound to RequestManager, create RequestManager and bind to RequestManager Fragment
        requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

2.0.1 Create RequestManagerFragment

This method creates a fragment and creates and binds a RequestManager to see how getRequestManager Fragment gets the RequestManager Fragment.

RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {
    //Attempt to find previously created Request Manager Fragment based on id
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    if (current == null) {
        //If not found, look in temporary storage
        current = pendingRequestManagerFragments.get(fm);
        if (current == null) {
            //If it is still not found, create a new RequestManagerFragment and add it to the temporary storage.
            //Then open the transaction binding fragments and use handler to send a message to remove the temporary stored fragments.
            current = new RequestManagerFragment();
            pendingRequestManagerFragments.put(fm, current);
            fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
            handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
        }
    }
    return current;
}

Here's the question, why do you need to use a collection such as pendingRequestManager Fragments to store fragments when they arrive, and then immediately send a message through handler to remove them?This is actually related to the Lower mechanism of the main thread and the Fragment transaction mechanism (click here to see the Fragment transaction process analysis).We know that the main thread in android is a closed loop, sending messages to MessageQueue through Handler, then getting messages through Looper polling and handing them to Handler (click here to see Activity Start Process Analysis).Here is a common scenario:

Glide.with(this).load(url_1).into(mImageView_1);
Glide.with(this).load(url_2).into(mImageView_2);

This code loads two pictures through Glide and is set on two ImageView s. When the code block above is executed, the Message of the code group to which it belongs has just been removed from the MessageQueue and is being processed. Let's assume the Message is m1 and there are no other messages in the MessageQueue.This is the case:

When the code executes the getRequestManager Fragment method, it binds the fragment to the activity by opening the transaction with the following source code (interesting click here to view the Fragment Transaction Flow Analysis), which is in Fragment ManagerImpl.java:

public void enqueueAction(Runnable action, boolean allowStateLoss) {
    if (!allowStateLoss) {
        checkStateLoss();
    }
    synchronized (this) {
        if (mDestroyed || mHost == null) {
            throw new IllegalStateException("Activity has been destroyed");
        }
        if (mPendingActions == null) {
            mPendingActions = new ArrayList<Runnable>();
        }
        mPendingActions.add(action);
        if (mPendingActions.size() == 1) {
            mHost.getHandler().removeCallbacks(mExecCommit);
            mHost.getHandler().post(mExecCommit);
        }
    }
}

The mHost here is actually created by activity and holds references to activity and mMainHandler. As you can see from the code above, the operation of binding fragments is ultimately handled by sending a message through the handler of the main thread. Let's assume this message is m2.Then handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); the message sent by this code is m3.Then when Glide.with(this).load(url_1).in(mImageView_1); this code executes here, the message queue changes:

However, the message m2 will not be processed immediately because M1 still has code to execute, that is, the fragment will not be bound immediately, and M1 will continue to execute down to the second code Glide.with(this).load(url_2).in(mImageView_2);When this code goes to getRequestManagerFragment s, if we do not temporarily store fragments in pendingRequestManagerFragments at m1, since m2 has not yet been processed, then

RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);

It is obviously unreasonable that this fragment cannot be found, which will result in the re-creation of a new, duplicate fragment and the opening of a transaction binding, because Glide needs to guarantee the uniqueness of the rootFragment, which is the top-level RequestManager Fragment created by the fragment-dependent or non-fragment-dependent activity.
Next, look at what the RequestManager Fragment construction method does.

public RequestManagerFragment() {
    this(new ActivityFragmentLifecycle());
}

Create an ActivityFragmentLifecycle directly, which is actually a lifecycle callback management class that implements the Lifecycle interface.All Lifecycle Listeners are added to a collection, and when the RequestManager Fragment lifecycle method triggers, the ActivityFragment Lifecycle corresponding lifecycle method is called, which then iterates through all Lifecycle Listener lifecycle methods. For example, in the onStart lifecycle method, RequestManager Fragment:

public void onStart() {
    super.onStart(); 
    lifecycle.onStart();
}

Then in ActivityFragmentLifecycle:

void onStart() {
    isStarted = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
        lifecycleListener.onStart();
    }
}

2.0.2 rootRequestManagerFragment

On the UML diagram above, you can see that RequestManager fragment also has a member variable of root RequestManagerFragment, and Glide attempts to instantiate the rootRequestManager fragment whenever a RequestManager fragment is created, which is the RequestManager fragment created by the top-level Activity with the relevant code:

public void onAttach(Activity activity) {
    super.onAttach(activity);
    rootRequestManagerFragment = RequestManagerRetriever.get()
            .getRequestManagerFragment(getActivity().getFragmentManager());
    if (rootRequestManagerFragment != this) {
        rootRequestManagerFragment.addChildRequestManagerFragment(this);
    }
}

@Override
public void onDetach() {
    super.onDetach();
    if (rootRequestManagerFragment != null) {
        rootRequestManagerFragment.removeChildRequestManagerFragment(this);
        rootRequestManagerFragment = null;
    }
}

You can see that, regardless of how the current RequestManager Fragment is created, a RequestManager Fragment is initialized at OnAttach by getting the Fragment Manager of the currently bound Activity. This RequestManager Fragment may be its own and may have been initialized, for example, by using ActivityActivity) is initialized, so it's obvious

RequestManagerRetriever.get().getRequestManagerFragment(getActivity().getFragmentManager());

This code gets itself, and if created in the form of a Fragment Fragment, the rootRequestManager Fragment will point to the current fragment bound to the RequestManager Fragment of the Activity, and if the Activity is not bound, the transaction will open to bind a RequestManager Fragment.And if you are not a rootRequestManager Fragment, you will save yourself to a collection of rootRequestManager Fragments:

private void addChildRequestManagerFragment(RequestManagerFragment child) {
    childRequestManagerFragments.add(child);
}

In short, Glide creates a RequestManager Fragment for an Activity as a rootFragment and saves all of the RequestManager Fragments created under that Activity, if any.

2.0.3 RequestManagerTreeNode

When RequestManager Fragment is initialized, it also initializes RequestManagerTreeNode, which, as the name implies, is used to hold request tree nodes, such as an activity in the form of Viewpager + Fragment, where the Fragment is a ViewPager +In the form of a Fragment, how do you know which LifeCycle of the RequestManager Fragment binding should be called if one of the RequestManager Fragment lifecycle methods goes away?Ideally, the lifecycle of the Request Manager Fragment that binds all its children to the RequestManager Fragment should be invoked. For example, in the following scenario, there are two Fragments in the Activity and two Fragments in each. In all Fragments, pictures are loaded with(this), and after previous analysis we canTo know, there are six RMFs (RMFs, RequestManager Fragments) stored in the ROOT RMF:

When the F1 RMF life cycle responds, because RequestManager Fragments are non-interface, it can be understood that the F1 life cycle responds.We hope that RequestManager Fragments bound to F11 and F12 will also respond immediately.But F2 and the RequestManager Fragments underneath it should not respond to life cycle events. We know that any RequestManager Fragments can get these six RMFs through the rootRequestManager Fragments, and then get their corresponding RequestManagers. So how can we determine the F11 RMF and F12 RMF s?This is what RequestManagerTreeNode does, the non-static internal class FragmentRequestManagerTreeNode in RequestManager Fragment implements RequestManagerTreeNode:

private class FragmentRequestManagerTreeNode implements RequestManagerTreeNode {
    @Override
    public Set<RequestManager> getDescendants() {
        Set<RequestManagerFragment> descendantFragments = getDescendantRequestManagerFragments();
        HashSet<RequestManager> descendants =
            new HashSet<RequestManager>(descendantFragments.size());
        for (RequestManagerFragment fragment : descendantFragments) {
            if (fragment.getRequestManager() != null) {
                descendants.add(fragment.getRequestManager());
            }
        }
        return descendants;
    }
}

What this class does is simple. Call the method getDescendantRequestManagerFragments of the external class RequestManagerFragments to get all descendant Fragments.Then take out its RequestManager and assemble it back, where descendants, in the previous example, refer to F11 RMF and F12 RMF, and see how getDescendantRequestManager Fragments got F11 and F12:

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public Set<RequestManagerFragment> getDescendantRequestManagerFragments() {
    //If you are a rootFragment, return directly to childRequestManagerFragments
    if (rootRequestManagerFragment == this) {
        return Collections.unmodifiableSet(childRequestManagerFragments);
    } else if (rootRequestManagerFragment == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
        // Pre JB MR1 doesn't allow us to get the parent fragment so we can't introspect hierarchy, so just
        // return an empty set.
        return Collections.emptySet();
    } else {
        HashSet<RequestManagerFragment> descendants = new HashSet<RequestManagerFragment>();
        for (RequestManagerFragment fragment
                //Walk through the RMF in the rootFragment and get its parentFragment to find its descendants.
                : rootRequestManagerFragment.getDescendantRequestManagerFragments()) {
            if (isDescendant(fragment.getParentFragment())) {
                descendants.add(fragment);
            }
        }
        return Collections.unmodifiableSet(descendants);
    }
}

See how the isDescendant method determines this:

private boolean isDescendant(Fragment fragment) {
    Fragment root = this.getParentFragment();
    while (fragment.getParentFragment() != null) {
        if (fragment.getParentFragment() == root) {
            return true;
        }
        fragment = fragment.getParentFragment();
    }
    return false;
}

In the example above, when traversing to F11 RMF, the parameter is passed to F11, root is F1, F11 gets parent, also F1, returns true, and F12 RMF returns true similarly.
When traversing to F21 RMF, the parameter passes in F21, the root is still F1, and F21 can't take Parent any more, returning false.
In short, the RequestManager TreeNode is used to get the RequestManager bound by all the child Fragments bound to the RequestManager Fragment of that RequestManager Fragment

2.0.4 RequestManager

You've been saying RequestManager Fragment above, so go back to the FragmentGet method and paste it again to avoid the upside-down problem:

RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
    //Get the RequestManager fragment and the RequestManager bound to it
    RequestManagerFragment current = getRequestManagerFragment(fm);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
    //If the Get RequestManager Fragment is not already bound to RequestManager, create RequestManager and bind to RequestManager Fragment
        requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

From the UML diagram above, you can see that RequestManager is a very core class and also implements Lifecycle Listener to handle the life cycle of requests.When creating RequestManager, the code above passes three parameters, context, LifeCycle and RequestManagerTreeNode, created by the initialization RequestManager Fragment analyzed earlier.Look directly at the constructor for RequestManager:

public RequestManager(Context context, Lifecycle lifecycle, RequestManagerTreeNode treeNode) {
    this(context, lifecycle, treeNode, new RequestTracker(), new ConnectivityMonitorFactory());
}

Another constructor was called and two new parameters, RequestTracker and ConnectivityMonitorFactory, were added.

RequestManager(Context context, final Lifecycle lifecycle, RequestManagerTreeNode treeNode,
        RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
    this.context = context.getApplicationContext();
    this.lifecycle = lifecycle;
    this.treeNode = treeNode;
    this.requestTracker = requestTracker;
    this.glide = Glide.get(context);
    this.optionsApplier = new OptionsApplier();

    ConnectivityMonitor connectivityMonitor = factory.build(context,
            new RequestManagerConnectivityListener(requestTracker));

    // If we're the application level request manager, we may be created on a background thread. In that case we
    // cannot risk synchronously pausing or resuming requests, so we hack around the issue by delaying adding
    // ourselves as a lifecycle listener by posting to the main thread. This should be entirely safe.
    if (Util.isOnBackgroundThread()) {
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                lifecycle.addListener(RequestManager.this);
            }
        });
    } else {
        lifecycle.addListener(this);
    }
    lifecycle.addListener(connectivityMonitor);
}

RequestTracker is the true handler of all Request operations, and all Request pause cancel operations are performed by RequestTracker, such as RequestManager suspend Request implementation:

public void pauseRequests() {
    Util.assertMainThread();
    requestTracker.pauseRequests();
}

2.0.5 Network Status Monitoring

Next to the implementation details of the request lifecycle, let's take a look at what ConnectivityMonitorFactory produces for the time being.

public class ConnectivityMonitorFactory {
    public ConnectivityMonitor build(Context context,     ConnectivityMonitor.ConnectivityListener listener) {
        final int res = context.checkCallingOrSelfPermission("android.permission.ACCESS_NETWORK_STATE");
        final boolean hasPermission = res == PackageManager.PERMISSION_GRANTED;
        if (hasPermission) {
            return new DefaultConnectivityMonitor(context, listener);
        } else {
            return new NullConnectivityMonitor();
            }
    }
}

It's simple to receive a ConnectivityListener to create a corresponding network monitor based on whether or not you have permission to monitor the state of the network.
DefaultConnectivityMonitor is also simpler in that it internally defines a broadcast receiver and also implements lifeCycleListener.In the above RequestManager construction method, a RequestManager ConnectivityListener is created:

private static class RequestManagerConnectivityListener implements ConnectivityMonitor.ConnectivityListener {
    private final RequestTracker requestTracker;

    public RequestManagerConnectivityListener(RequestTracker requestTracker) {
        this.requestTracker = requestTracker;
    }

    @Override
    public void onConnectivityChanged(boolean isConnected) {
        if (isConnected) {
            requestTracker.restartRequests();
        }
    }
}

This listener is simple, restart request upon receiving a network state connection.The DefaultConnectivityMonitor is then created from the factory and added to the lifecycle.Here's how Glide monitors the state of the network to restart the request. The general steps are as follows:
In the corresponding life cycle method, the lifecycle method is called, which calls the corresponding life cycle method implemented by DefaultConnectivityMonitor to register and unregister the broadcast recipient of the network state. After receiving the broadcast, the onConnectivityChanged method of the previously passed parameter ConnectivityListener is called back to process the Request.

2.0.6 Memory Status Monitoring

There is also an instance of the entry class Glide in RequestManager, which is obtained directly from the construction method to handle changes in the memory state. It is easy to see the flow, for example, onTrimMemory.
When onTrimMemory of RequestManager Fragment is called, the corresponding method of its bound RequetManager is called to handle it:

@Override
public void onTrimMemory(int level) {
    // If an activity is re-created, onTrimMemory may be called before a manager is ever set.
    // See #329.
    if (requestManager != null) {
        requestManager.onTrimMemory(level);
    }
}

RequestManager then calls the trimMemory of the Glide entry class to free up more memory:

public void onTrimMemory(int level) {
    glide.trimMemory(level);
}

2.0.7 Life Cycle Callback Process Summary

In the RequestManager construction method, it also adds itself to LifeCycle so that the whole process is smooth:

Carefully, you can see that although the parameter RequestManagerTreeNode was passed in the construction of RequestManager, there is no call to RequestManager for all descendants of RMF in this callback process. Glide does not call by default, but this does not mean that these RequestManagers will not be called. In fact, when the current RMF life cycle is called, it means thatThe descendant Fragment life cycle will also be called, and the descendant Fragment process will continue to run once, so what exactly is the use of RequestManagerTreeNode? The answer is no use, no use at all, if you just use Glide.Of course, RequestManager exposes interfaces for developers to use:

public void resumeRequestsRecursive() {
    Util.assertMainThread();
    resumeRequests();
    for (RequestManager requestManager : treeNode.getDescendants()) {
        requestManager.resumeRequests();
    }
}

Calling this method will process requests from all descendants at the same time.

**Recommended reading: [Byte Jump Android Interview True Question Analysis for calendar year 2017-2020 (1.82 million downloads, ongoing updates)
](https://www.jianshu.com/p/7f9ade51232e)**
The Latest High Frequency Interview Question Analysis for Android Factory in 2020 Daquan (BAT TMD JD Millimeter)

Posted by Karlos2394 on Sun, 05 Apr 2020 12:16:58 -0700