3
本文首发于 vivo互联网技术 微信公众号 
链接:https://mp.weixin.qq.com/s/uTv44vJFFJI_l6b5YKSXYQ
作者:连凌能

Android App中图片的展示是很基本也很重要的一个功能,在Android平台上有很多的图片加载解决方案,但是官方认可的是Glide。Android App的页面是有生命周期的,Glide比较好的一个功能就是具有生命周期管理功能,能够根据页面和APP的生命周期来管理图片的加载和停止,也开放接口供用户在内存紧张时手动进行内存管理。本文重点是生命周期源码的分析,不会从简单的使用着手。

一、综述

这是Glide源码分析的第二篇文章,第一篇是《Glide缓存流程》,从资源的获取流程对源码进行分析。本篇会聚焦于生命周期模块的原理。开始之前先思考下面这几个问题:

  • Glide怎么实现页面生命周期?
  • Glide为什么对Fragment做缓存?
  • Glide如何监听网络变化?
  • Glide如何监测内存?

二、Glide生命周期传递

先来看with函数的执行, 会构造glide单例,而 

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

构造完成RequestManagerRetriever通过get返回一个 RequestManager, 如果不在主线程,默认会传入 getApplicationContext,也就是不进行生命周期管理:

  • 在getRequestManagerFragment中先查看当前Activity中有没有FRAGMENT_TAG这个标签对应的Fragment,如果有就直接返回
  • 如果没有,会判断pendingRequestManagerFragments中有没有,如果有就返回
  • 如果没有,就会重写new一个,然后放入到pendingRequestManagerFragments中,然后添加到当前Activity,再给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;
       ...
   }
   ...
 }

这里面需要注意一个问题,就是如果with()函数中传进来的不是Activity,而是Fragment,那么也会去创建一个没有界面的RequestManagerFragment,而它的父Fragment就是传进来的Fragment。

上面为什么需要pendingRequestManagerFragments先进行缓存呢?这个放到下面第二个问题中说明。先接着往下看生命周期的传递。

RequestManagerFragment是一个很重要的类,Glide就是通过它作为生命周期的分发入口,RequestManagerFragment的默认构造函数会实例化一个ActivityFragmentLifecycle,在每个生命周期onStart/onStop/onDestroy中会调用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();
  }
     
  ...
}

RequestManagerFragment里面有一个实例RequestManager,在前面的fragmentGet,RequestManagerFragment拿到以后会尝试获取它的RequestManager,第一次获取肯定是没有,就会重新构造一个, 通过RequestManagerRetriever构造时传入的RequestManagerFactory工厂类实例化一个RequestManager, 把RequestManagerFragment中的ActivityFragmentLifecycle传进去:


// 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) {
      return new RequestManager(glide, lifecycle, requestManagerTreeNode, context);
    }
  };

很明显生命周期的关键就在ActivityFragmentLifecycle, 在RequestManagerFragment中相应生命周期中会回调它,那么猜测它肯定是在里面维护了一个观察者列表,相应事件发生的时候进行通知, 看下它的源码:


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

所以RequestManagerFragment把这个传给RequestManager后,肯定会注册观察者,看一下RequestManager的相关代码,在构造函数里面lifecycle.addListener(this);,把自己注册为观察者:


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

在看下RequestManager对应的生命周期里面, 在这里面分别启动,停止和销毁请求:

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

三、Glide为什么对Fragment做缓存?

再贴一次RequestManagerRetriever中获取Fragment的代码,前面留了一个疑问,为什么这里会需要一个pendingRequestManagerFragments对Fragment进行缓存。

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

我们看一个情况:

Glide.with(Context).load(ImageUrl1).into(imageview1); // task1
Glide.with(Context).load(ImageUrl2).into(imageview2); // task2

Android开发应该都知道主线程有一个Handler机制,会往消息队列中放消息,通过Looper按顺序取出来执行。那么主线程中的执行顺序和消息队列中的执行顺序关系是什么?看个栗子:


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!============");
 }

在start中打印的顺序和它里面的Handler中的信息哪个先打印?start中handler的信息和Next函数中的信息打印顺序是怎样的?看下打印结果:

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!============

Handler中的顺序会在主线程之后,Handler中的消息执行顺序就是队列先进先出。

上面执行到task1的时候,在下面这两行代码,add操作会往消息队列放一个消息,这里标记为msg1:

fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();

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

那么如果不把task1中构造的RequestManagerFragment放到pendingRequestManagerFragments中,那么在执行task2的时候也会再重新构造一个RequestManagerFragment,并且往主线程中放一个消息msg2,这个时候就会出现重复add的情况。

所以在前面new 出来一个RequestManagerFragment,随后就把它放到pendingRequestManagerFragments中,那么task2再进来的时候从缓存中能取到,就不会再重新new和add了。

那么下一个问题来了,为什么会出现下面这行代码,add后又需要马上发一个消息remove掉?在前面阻止掉task2重复new和add的操作后,就把这个缓存删掉,可以避免内存泄漏和内存压力:

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

四、Glide如何监听网络变化

从上面页面生命周期的分析部分知道,对于任务的控制都是通过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);
    ...
  }

所以也是把它注册为ActivityFragmentLifecycle的观察者,ConnectivityMonitor通过ConnectivityMonitorFactory进行构造,提供了默认实现类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;
    return hasPermission
        ? new DefaultConnectivityMonitor(context, listener) : new NullConnectivityMonitor();
  }
}

接着就往下看DefaultConnectivityMonitor, 在onStart中registerReceiver监听手机网络状态变化的广播,然后在connectivityReceiver中调用isConnect进行网络状态确认,根据网络状态是否变化,如果有变化就回调监听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 void register() {
    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;
    }
    return networkInfo != null && networkInfo.isConnected();
  }
 
  @Override
  public void onStart() {
    register();
  }
 
  @Override
  public void onStop() {
    unregister();
  }
 
  @Override
  public void onDestroy() {
    // Do nothing.
  }
}

ConnectivityMonitor.ConnectivityListener是在RequestManager中传入,有网络重新连接后重启请求:

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

五、Glide如何监测内存

在Glide构造的时候会调用registerComponentCallbacks进行全局注册, 系统在内存紧张的时候回调onTrimMemory,然后根据系统内存紧张级别进行memoryCache/bitmapPool/arrayPool的回收:

// Glide.java
  public static Glide get(@NonNull Context context) {
    if (glide == null) {
      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context);
        }
      }
    }
 
    return glide;
  }
 
  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);
  }

六、总结

再回顾前面的四个问题,我相信聪明的你已经有了答案,文章的各小节标题就是根据问题来进行分析的,这么就不再赘述了,要不有凑字数的嫌疑。Glide的源码是比较庞大而且高质量的,所以一两篇文章是说不清楚的,后面对于Glide的源码分析还会有后续的文章,欢迎关注。

更多内容敬请关注 vivo 互联网技术 微信公众号

注:转载文章请先与微信号:labs2020 联系。


vivo互联网技术
3.3k 声望10.2k 粉丝

引用和评论

0 条评论