Glide source code learning - life cycle

Glide source code learning - life cycle

Advantages and disadvantages of Glide
advantage:
1. Diversified media loading
2. Lifecycle integration
3. Efficient cache strategy
4. Low memory overhead

Disadvantages:
The use method is complex
Because of its powerful function, there are many methods used, and the source code is complex and large

preface

The essence of Glide loading is also to call the system's network control HttpURLConnection to download the image resources, then convert it into Bitmap and then get the system setImageBitmap to load the network picture. The detailed steps are as follows:

     1,Open network sub thread request network HttpUrlConnection
     2 ,Render UI,Switch to main thread
     3 ,Convert stream to bitmap
     4 ,bitmap Set to image

Relevant codes:

public void getImage(View view) {
       String url = "https://img2.baidu.com/it/u=3542803101,6620711&fm=26&fmt=auto&gp=0.jpg";
//       Glide.with(this).load(url).into(mImage);
       //Load picture
       new Thread(new Runnable() {
           @Override
           public void run() {
               Bitmap bitmap = getImageBitmap(url);
               runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       mImage.setImageBitmap(bitmap);
                   }
               });
           }
       }).start();
   }


   private Bitmap getImageBitmap(String url){
       Bitmap bitmap = null;
       try {
           URL imgUrl = new URL(url);
           HttpURLConnection coon = (HttpURLConnection) imgUrl.openConnection();
           coon.connect();
           InputStream is =  coon.getInputStream();
           //Convert bitma engineering class into bitmap
           bitmap = BitmapFactory.decodeStream(is);
           is.close();
       } catch (MalformedURLException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       }
       return bitmap;
   }

1, Glide life cycle

1. View the life cycle of Activity and Fragment

Dynamically load a fragment in the activity and print all their lifecycles

//Dynamic loading fragment
FragmentManager fragmentManager =  getFragmentManager();
FragmentTransaction fragmentTransaction =  fragmentManager.beginTransaction();
fragmentTransaction.replace(android.R.id.content,new Fragment1());
fragmentTransaction.commit();

When the activity starts:

onAttcah: call after association with activity

When an activity is closed: go through the fragment lifecycle first, and then the activity lifecycle

Specific association process:

After testing, the fragment comments out the onCreateView create view method, which can still be called, which is equivalent to a transparent fragment, so that it can use its life cycle monitoring to do some operations

2. Read the source code related to the life cycle

After checking the source code, a fragment will also be created and bound to its life cycle in the method call of with in the source code of Glide, but only OnStart, onstop and ondestory are bound

//Glide source code
@NonNull
public RequestManager get(@NonNull FragmentActivity activity) {
  if (Util.isOnBackgroundThread()) {
    return get(activity.getApplicationContext());
  } else {
    assertNotDestroyed(activity);
    frameWaiter.registerSelf(activity);
    FragmentManager fm = activity.getSupportFragmentManager();
    return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
  }
}

@NonNull
private RequestManager supportFragmentGet(
    @NonNull Context context,
    @NonNull FragmentManager fm,
    @Nullable Fragment parentHint,
    boolean isParentVisible) {
  SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint);
  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);
    // This is a bit of hack, we're going to start the RequestManager, but not the
    // corresponding Lifecycle. It's safe to start the RequestManager, but starting the
    // Lifecycle might trigger memory leaks. See b/154405040
    if (isParentVisible) {
      requestManager.onStart();
    }
    current.setRequestManager(requestManager);
  }
  return requestManager;
}

SupportRequestManagerFragment is the fragment loaded into the current activity
There is a lifecycle method in SupportRequestManagerFragment, indicating that there is a lifecycle binding

@VisibleForTesting
@SuppressLint("ValidFragment")
public SupportRequestManagerFragment(@NonNull ActivityFragmentLifecycle lifecycle) {
  this.lifecycle = lifecycle;
}

Check the activityfragment lifecycle and find the following methods: Glide binds three fragment related methods

 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();
    }
  }
}

View in SupportRequestManagerFragment:

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

Implement several related methods in the RequestManager to do related operations

/**
* Lifecycle callback that registers for connectivity events (if the
* android.permission.ACCESS_NETWORK_STATE permission is present) and restarts failed or paused
* requests.
*/
@Override
public synchronized void onStart() {
  resumeRequests();
  targetTracker.onStart();
}


/**
* Lifecycle callback that unregisters for connectivity events (if the
* android.permission.ACCESS_NETWORK_STATE permission is present) and pauses in progress loads.
*/
@Override
public synchronized void onStop() {
  pauseRequests();
  targetTracker.onStop();
}


/**
* Lifecycle callback that cancels all in progress requests and clears and recycles resources for
* all completed requests.
*/
@Override
public synchronized void onDestroy() {
  targetTracker.onDestroy();
  for (Target<?> target : targetTracker.getAll()) {
    clear(target);
  }
  targetTracker.clear();
  requestTracker.clearRequests();
  lifecycle.removeListener(this);
  lifecycle.removeListener(connectivityMonitor);
  Util.removeCallbacksOnUiThread(addSelfToLifecycle);
  glide.unregisterRequestManager(this);
}

Summary:
In the onStart method, get the url link, calculate the picture width, use httpurlconnection to download the pictures and load them.

If the page is closed:
First, call onstop to suspend the loading of various resources
After calling onDestroy to release resources, remove various loading methods and clear the picture resources in memory. The next time the same picture is loaded, the picture will be read from the hard disk file. If not, the network download will be requested. Like the same link returning different pictures, the glide load is still the old picture, because the key stored in the hard disk address is the linked address, The address has not changed, so the picture read locally is still the same as before.
When an activity calls onDestroy to destroy, fragment calls the onDestroy method first.

Glide.with(this).load(url).into(mImage), the context in cannot pass the getApplicationContext() method, because the getApplicationContext() method will be retained throughout the application stage, so the fragment is bound not to the activity, but to the application.

Glide.with(this).load(url).into(mImage); This code cannot be executed in the sub thread, because it is found through the source code that if it is not running in the main thread, it will throw an exception directly, as follows:

public static void assertMainThread() {
  if (!isOnMainThread()) {
    throw new IllegalArgumentException("You must call this method on the main thread");
  }
}

2, Glide memory detection

This method will be called back in anchivity: when the memory is seriously insufficient, this method will be called back to clean up the memory

void onTrimMemory(@TrimMemoryLevel int level);

public void onTrimMemory(int level) {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level);
    mCalled = true;
    mFragments.dispatchTrimMemory(level);
}

Found the memory detection method in the FirstFrameAndAfterTrimMemoryWaiter class:

@Override
public void onTrimMemory(int level) {}

@Override
public void onLowMemory() {
  onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
}

The implementation method of this method will register the incoming activity


In the Glide class of Glide source code, two memory detection methods are called back: logic to deal with insufficient memory

//insufficient memory
@Override
public void onTrimMemory(int level) {
  trimMemory(level);
}

public void trimMemory(int level) {
  // Engine asserts this anyway when removing resources, fail faster and consistently
  Util.assertMainThread();
  // Request managers need to be trimmed before the caches and pools, in order for the latter to
  // have the most benefit.
  synchronized (managers) {
    for (RequestManager manager : managers) {
      manager.onTrimMemory(level);
    }
  }
  // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.
  memoryCache.trimMemory(level);
  bitmapPool.trimMemory(level);
  arrayPool.trimMemory(level);
}

//Serious memory shortage
@Override
public void onLowMemory() {
  clearMemory();
}

public void clearMemory() {
  // Engine asserts this anyway when removing resources, fail faster and consistently
  Util.assertMainThread();
  // memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
  memoryCache.clearMemory();
  bitmapPool.clearMemory();
  arrayPool.clearMemory();
}

3, Glide monitors network changes

Glide loads pictures. Even if the network is disconnected, pictures will be loaded on the re link. This is because glide has an internal network monitoring mechanism

In the RequestManager, there is an internal class for network monitoring. Do relevant processing. If the network is disconnected, re request. Click onConnectivityChanged to jump to broadcast registration and monitor the network

private class RequestManagerConnectivityListener
    implements ConnectivityMonitor.ConnectivityListener {
  @GuardedBy("RequestManager.this")
  private final RequestTracker requestTracker;




  RequestManagerConnectivityListener(@NonNull RequestTracker requestTracker) {
    this.requestTracker = requestTracker;
  }




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

public void restartRequests() {
  for (Request request : Util.getSnapshot(requests)) {
    if (!request.isComplete() && !request.isCleared()) {
      request.clear();
      if (!isPaused) {
        //Disconnection re request
        request.begin();
      } else {
        // Ensure the request will be restarted in onResume.
        pendingRequests.add(request);
      }
    }
  }
}

Then register the broadcast in the defaultconnectivity monitor and monitor the network

//Register broadcast
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);
        }
      }
    };

//isConnected view the current network status and return
@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) {
    // #1405 shows that this throws a SecurityException.
    // b/70869360 shows that this throws NullPointerException on APIs 22, 23, and 24.
    // b/70869360 also shows that this throws RuntimeException on API 24 and 25.
    if (Log.isLoggable(TAG, Log.WARN)) {
      Log.w(TAG, "Failed to determine connectivity status when connectivity changed", e);
    }
    // Default to true;
    return true;
  }
  return networkInfo != null && networkInfo.isConnected();
}

4, Glide cache fragment

There is always only one fragment in Glide, which is convenient for reuse and life cycle management
Each time a picture is loaded, with passes in the context, and then gets the bound fragment. At this time, take the fragment from the tag first. If the tag does not exist, go back to get the cached fragment. If the cached fragment does not exist, create the fragment and add it to the collection, and send a handler message to remove the previous fragment

final Map<FragmentManager, SupportRequestManagerFragment> pendingSupportRequestManagerFragments =
    new HashMap<>();

@NonNull
private SupportRequestManagerFragment getSupportRequestManagerFragment(
    @NonNull final FragmentManager fm, @Nullable Fragment parentHint) {
  SupportRequestManagerFragment current =
      (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
  if (current == null) {
    current = pendingSupportRequestManagerFragments.get(fm);
    if (current == null) {
      current = new SupportRequestManagerFragment();
      current.setParentFragmentHint(parentHint);
      pendingSupportRequestManagerFragments.put(fm, current);
      fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
      handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
    }
  }
  return current;
}

Each time a fragment is cached, the previous fragment is removed

@Override
public boolean handleMessage(Message message) {
  boolean handled = true;
  Object removed = null;
  Object key = null;
  switch (message.what) {
    case ID_REMOVE_FRAGMENT_MANAGER:
      android.app.FragmentManager fm = (android.app.FragmentManager) message.obj;
      key = fm;
      removed = pendingRequestManagerFragments.remove(fm);
      break;
    case ID_REMOVE_SUPPORT_FRAGMENT_MANAGER:
      FragmentManager supportFm = (FragmentManager) message.obj;
      key = supportFm;
      //Remove previous
      removed = pendingSupportRequestManagerFragments.remove(supportFm);
      break;
    default:
      handled = false;
      break;
  }
  if (handled && removed == null && Log.isLoggable(TAG, Log.WARN)) {
    Log.w(TAG, "Failed to remove expected request manager fragment, manager: " + key);
  }
  return handled;
}

summary

Keywords: Android

Added by reeferd on Sun, 02 Jan 2022 23:05:17 +0200