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