Glide source code analysis Two

A, Glide basic use

Glide official document address

1.1、 Example

Glide.with(imageView.context)
    .load(roundRectUrl)
    .apply(RequestOptions().priority(Priority.HIGH))
    .apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.DATA))
    .apply(RequestOptions.centerCropTransform())
    .apply(
        RequestOptions.bitmapTransform(
            RoundedCornersTransformation(
                radius, 0, RoundedCornersTransformation.CornerType.ALL
            )
        )
    )
    .into(imageView)
复制代码

GlideDemo GitHub Address

1.2, custom AppGlideModule

After Glide4.0, GlideModule recommend using annotations (@GlideModule) realized in the form

@GlideModule
class GlideConfigModule : AppGlideModule() {

    override fun applyOptions(context: Context, builder: GlideBuilder) {
        //设置内存大小
        builder.setMemoryCache(LruResourceCache(1024 * 1024 * 100)) // 100M

        //设置图片缓存大小
        builder.setBitmapPool(LruBitmapPool(1024 * 1024 * 50))

        /**
         * 设置磁盘缓存大小
         */
        // 内部缓存目录  data/data/packageName/DiskCacheName
        builder.setDiskCache(
            InternalCacheDiskCacheFactory(context, "GlideDemo", 1024 * 1024 * 100)
        )
        // 外部磁盘SD卡
        /*builder.setDiskCache(
            ExternalPreferredCacheDiskCacheFactory(context, "GlideDemo", 1024 * 1024 * 10)
        )*/
    }

    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        // 替换网络请求组件,使用OkHttp
        val builder = OkHttpClient.Builder()
        builder.addInterceptor(ProgressInterceptor)
        val okHttpClient = builder.build()

        registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(okHttpClient))
    }

    /**
     * 禁用清单解析
     */
    override fun isManifestParsingEnabled(): Boolean {
        return false
    }
}
复制代码

Second, Preface

2.1 A good image loading framework necessary functions:

Before reading Glide source code, we should consider a problem, let us assume themselves to achieve load a picture frame, which functions will need?

 1, network components, download pictures OkHttp HttpURLConnection
 2, the request queue, the thread pool priority processing, termination request
 3, cache: memory cache, local file cache, the cache server, etc.
 4, memory resource recovery mechanisms: FIFO, or life cycle
 5, more basic component is the transcoding decoding various image resource

2.2 Android contrast image loading frame

  Suppose you go to the interview, the interviewer asks you what the picture frame using, you said you use Glide. The interviewer does not want you to say Glide, but you choose causes and reasons Glide image loading frame. The advantages and disadvantages of the frame to load it with other images.

  The entire article predecessors, not in a separate writing another: Android picture frame loaded Fresco, Glide, Picasso comparative analysis

Three, Glide flow analysis

3.1 flow chart

This is a flow chart from the Internet to find:


  Your heart will think: so simple? ?
  I answer you, of course, is not so simple, but it really is the most important bottom of the architecture, all the images load frame are the same, the difference lies in the extension of a variety of small details on this basis, such as much-level cache, the cache location ( the application is cached in memory, or cache in shared memory systems anonymous), life-cycle management.

3.2 Glide trilogy use source code analysis

Here it is necessary to read my previous article a: Glide source code analysis One

3.3 class diagram

OK, when you read 3.2, Glide look at the class diagram you will be quite clear, this figure is what I find from the Internet:



Then there is some of the details of the

Fourth, Life Cycle Management

Life cycle management is in: The first step Glide.with () , the specific logic created in RequestManagerRetriever.get () is implemented:

4.1 RequestManagerRetriever.get():

public RequestManager get(@NonNull Context context) {
  、、、
  if (Util.isOnMainThread() && !(context instanceof Application)) {
    if (context instanceof FragmentActivity) {
      return get((FragmentActivity) context);
    } else if (context instanceof Activity) {
      return get((Activity) context);
    } else if (context instanceof ContextWrapper) {
      return get(((ContextWrapper) context).getBaseContext());
    }
  }
  return getApplicationManager(context);
}
复制代码

  get () method overload a lot of parameters corresponding to the parameters and Glide.with () method, respectively Context, Activity, FragmentActivity, Fragment, android.support.v4.app.Fragment, View.
parameter is very important, very important parameters parameter is very important! ! ! Next, an explanation:
  . 1, RequestManagerRetriever.get (Activity): GET parameter Activity () method

  public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();  // FragmentManager直接来自Activity
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }
复制代码

  2, RequestManagerRetriever.get (Fragment): parameter to get Fragment () method

  public RequestManager get(@NonNull Fragment fragment) {
    if (Util.isOnBackgroundThread()) {
      return get(fragment.getActivity().getApplicationContext());
    } else {
      FragmentManager fm = fragment.getChildFragmentManager();  // FragmentManager来自Fragment
      return supportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());
    }
  }
复制代码

  3, RequestManagerRetriever.get (View): View of the parameters get () method

  public RequestManager get(@NonNull View view) {
    ...
    Activity activity = findActivity(view.getContext());
    if (activity == null) {
      return get(view.getContext().getApplicationContext());
    }

    // Support Fragments.
    // Although the user might have non-support Fragments attached to FragmentActivity, searching
    // for non-support Fragments is so expensive pre O and that should be rare enough that we
    // prefer to just fall back to the Activity directly.
    if (activity instanceof FragmentActivity) {
      Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);
      return fragment != null ? get(fragment) : get(activity);
    }

    // Standard Fragments.
    android.app.Fragment fragment = findFragment(view, activity);
    if (fragment == null) {
      return get(activity);
    }
    return get(fragment);
  }
复制代码

By View to find it in the Fragment, if in the View Fragment in, go directly to parameters Fragment of get (Fragment) method; if Activity, then go directly to the parameters for the Activity of get (Activity) method

All overloaded get () method, eventually only three cases:
  1), getApplicationManager ()
  2), fragmentGet ()
  3), supportFragmentGet ()

  • 1, getApplicationManager () parameter ApplicationContext or when in a non-main thread run, will execute the getApplicationManager () Method:
private RequestManager getApplicationManager(@NonNull Context context) {
  // Either an application context or we are on a background thread.
  if (applicationManager == null) {
    synchronized (this) {
      if (applicationManager == null) {
        // Normally pause/resume is taken care of by the fragment we add to the fragment or
        // activity. However, in this case since the manager attached to the application will not
        // receive lifecycle events, we must force the manager to start resumed using
        // ApplicationLifecycle.

        // TODO(b/27524013): Factor out this Glide.get() call.
        Glide glide = Glide.get(context.getApplicationContext());
        applicationManager =
            factory.build(
                glide,
                new ApplicationLifecycle(),
                new EmptyRequestManagerTreeNode(),
                context.getApplicationContext());
      }
    }
  }
  return applicationManager;
}
复制代码

Look at the method of annotation, Request (ie, buffer) is to follow the life cycle of Application

  • 2 fragmentGet (FragmentManager)
  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) {
      Glide glide = Glide.get(context);
      // build RequestManager时,将RequestManagerFragment的生命周期(current.getGlideLifecycle())传入
      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) {
    // 通过FragmentManager寻找是否已经添加过RequestManagerFragment,避免重复添加
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); 
    if (current == null) {
      current = pendingRequestManagerFragments.get(fm);
      if (current == null) {
        current = new RequestManagerFragment();  // 新建一个空布局的Fragment
        current.setParentFragmentHint(parentHint);
        if (isParentVisible) {
          current.getGlideLifecycle().onStart();
        }
        // 在原有的界面上添加一个空布局的Fragment,用于生命周期管理
        pendingRequestManagerFragments.put(fm, current); 
        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
      }
    }
    return current;
  }
复制代码

Lifecycle management request Glide is achieved by adding an empty layout Fragment to the page, it says that argument is very important to everyone here doubts.
   1, when the parameter Activity, Fragment empty layout directly added to the Activity, in this case follows the life cycle of Request the Activity;
   2, when the parameter is Fragment, Fragment empty layout directly added to the Fragment, at this time the Request follow the life cycle of the father Fragment;
   3, a single parameter for the View, then go look for the View by View is still in Activity in both cases were to go on Fragment;

4.2 RequestManager

RequestManager(
    Glide glide,
    Lifecycle lifecycle,
    RequestManagerTreeNode treeNode,
    RequestTracker requestTracker,
    ConnectivityMonitorFactory factory,
    Context context) {
  ···
  // If we are 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()) {
    mainHandler.post(addSelfToLifecycle);
  } else {
    lifecycle.addListener(this);
  }
  ···
}

private final Runnable addSelfToLifecycle = new Runnable() {
  @Override
  public void run() {
    lifecycle.addListener(RequestManager.this);
  }
};
复制代码

1, mainHandler.post (addSelfToLifecycle);
  the current thread is a background process, or application level is the main thread, the main thread through mainHandler cut back, because too refreshed UI, the main thread safe
2, lifecycle.addListener (the this);
   direct monitoring Previous resulting layout is empty fragment of the life cycle

Five priority Priority

5.1 Priority Class

public enum Priority {
  IMMEDIATE,
  HIGH,
  NORMAL,
  LOW,
}
复制代码

5.2 DecodeJob

  When the previous analysis found Glide execution flow, DecodeJob is the real work of the class that implements the Runnable interface, but it also implements Comparable <DecodeJob <? >> Interface.
When Engine instantiation DecodeJob, priority will be property of the incoming DecodeJob in particular the method used in the comparison of its implementation:

/**
 * @return  a negative integer, zero, or a positive integer as  
 * this object is less than, equal to, or greater than the specified        
 **/
@Override
public int compareTo(@NonNull DecodeJob<?> other) {
  int result = getPriority() - other.getPriority();
  if (result == 0) {
    result = order - other.order;
  }
  return result;
}
复制代码

ThreadPoolExecutor:
  the Execute ()
PriorityBlockingQueue:   // thread pool task queue, select high-priority task execution priority
  the offer ()
  siftUpComparable ()
while achieving Runnable and Comparable <DecodeJob <>> interface thread pool automatically priority automatically? Sort.

Sixth, the cache

6.1 LRU caching algorithm

When Glide initialization, specify a default caching algorithms in GlideBuilder.build () method

Glide build(@NonNull Context context) {
  ···
  if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
  }
  ···
}
复制代码

  Use the LRU (Least Recently Used) algorithm , the core is the least recently used. In the internal algorithm maintains a list of LinkHashMap (doubly linked list) , when put through the data to determine whether the memory is full, if full, then the least recently used data to weed out, so as to achieve the status of memory is not full .


  The more important point is that when we pass get () when the data acquisition method, the acquired data will go to the end of the queue from the queue, thus well positioned to meet our LruCache of algorithm design.
LinkedHashMap.get ():

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMapEntry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMapEntry<K,V> p =
            (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}
复制代码

6.2 Glide caching logic

Key caching logic class Engine

6.2.1 Cache Key

  • Enginr.load():
public synchronized <R> LoadStatus load(...) {
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
  ...
}
复制代码

model: Url, Uri, FilePath, image source address

Here is a prerequisite knowledge must know:
  DiskCacheStrategy.NONE nothing cache
  DiskCacheStrategy.DATA      only cache the original full-resolution images.
  DiskCacheStrategy.RESOURCE    caching-only final image , that is, after lowering the resolution (or converted)
  DiskCacheStrategy.ALL all cached version of the image
  DiskCacheStrategy.AUTOMATIC let Glide to choose which one to use caching strategy based on intelligent image resource (the default option )

width, height: width containing high energy derived from the cache key, the same picture, not the cache assumptions set by the original picture, different width and height of the different cache images.
Advantages: load faster, less processing process picture width and height.
Cons: memory is more costly

another Android image loading frame Picasso, only cache a full-size picture, the advantage is small memory, the disadvantage is shown again when the explicit need to re-adjust the size.

6.2 cache reads logic

Enginr.load():

public synchronized <R> LoadStatus load() {
 ... 
 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);

EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
 cb.onResourceReady(active, DataSource.MEMORY_CACHE);
 if (VERBOSE_IS_LOGGABLE) {
   logWithTimeAndKey("Loaded resource from active resources", startTime, key);
 }
 return null;
}

EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
 cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
 if (VERBOSE_IS_LOGGABLE) {
   logWithTimeAndKey("Loaded resource from cache", startTime, key);
 }
 return null;
}

EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
 current.addCallback(cb, callbackExecutor);
 if (VERBOSE_IS_LOGGABLE) {
   logWithTimeAndKey("Added to existing load", startTime, key);
 }
 return new LoadStatus(cb, current);
}

// 生成网络请求逻辑
...
}

@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
 if (!isMemoryCacheable) {
   return null;
 }
 EngineResource<?> active = activeResources.get(key);
 if (active != null) {
   active.acquire();  // 引用计数器加1
 }
 return active;
}

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
 if (!isMemoryCacheable) {
   return null;
 }

 EngineResource<?> cached = getEngineResourceFromCache(key);  // 将缓存数据从缓存队列中移出
 if (cached != null) {
   cached.acquire();  // 引用计数器加1
   activeResources.activate(key, cached);  // 将数据放入正在活动的缓存列表中
 }
 return cached;
}
复制代码

EngineResource.acquire():

synchronized void acquire() {
 if (isRecycled) {
   throw new IllegalStateException("Cannot acquire a recycled resource");
 }
 ++acquired; // 资源被引用的计数器
}
复制代码

6.3 cache generation

After the picture is in fact the data is loaded, into the cache list. The final callback method in the Engine of onEngineJobComplete () method.

public synchronized void onEngineJobComplete(
   EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
 // A null resource indicates that the load failed, usually due to an exception.
 if (resource != null) {
   resource.setResourceListener(key, this);

   if (resource.isCacheable()) { // 判断该资源是否需要缓存
     activeResources.activate(key, resource);   // 缓存
   }
 }
 jobs.removeIfCurrent(key, engineJob);
}
复制代码

6.4 cache removal

Engine.onResourceReleased():

public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
 activeResources.deactivate(cacheKey); // 从活动列表移除
 if (resource.isCacheable()) {
   cache.put(cacheKey, resource);  // 放入缓存列表
 } else {
   resourceRecycler.recycle(resource);
 }
}
复制代码

Remove cached specific call flow:
  Here you have to wander in front of a life-cycle management, RequestManager achieve a LifecycleListener, receiving or Activity Fragment life cycle, where the main analysis onDestory () method:
RequestManager.onDestory ():

public synchronized 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);
}
复制代码

RequestTracker.clearRequests() -> SingleRequest.clear() -> Engine.release() -> EngineResource.release()

void release() {
  // To avoid deadlock, always acquire the listener lock before our lock so that the locking
  // scheme is consistent (Engine -> EngineResource). Violating this order leads to deadlock
  // (b/123646037).
  synchronized (listener) {
    synchronized (this) {
      if (acquired <= 0) {
        throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
      }
      if (--acquired == 0) { // 引用数值标准acquire减1
        listener.onResourceReleased(key, this);
      }
    }
  }
}
复制代码

Refer to the following excellent articles:

  Glide source analysis
  Glide source code analysis process mind mapping


© wearing a shirt programmer

Guess you like

Origin blog.csdn.net/weixin_33735077/article/details/91368657