Glide 初探

1,032 阅读8分钟

Glide 是现在Android开发常用的一个图片加载工具,可以根据资源文件、网络URL请求图片,并设置到控件当中。并且有一套完整的缓存重用机制,可以很大程度上地节约内存。

传统的Android 图片请求

我们首先需要通过网络请求工具请求图片,以Stream的形式将图片存储于一个Bitmap对象中,然后再通过setBitmap(bitmap)将图片设置进去。

    Thread(Runnable {
        val bitmap = getBitmap(sampleWallPaper)//开启Http连接,加载Stream,转成Bitmap
        runOnUiThread {
            glide_out.setImageBitmap(bitmap)
        }
    }).start()

这样一来,就涉及到了在代码中分散的网络请求、网络连接等等,还涉及到生命周期对加载的影响。

Glide 的使用

不带任何配置的Glide图片请求很简单:

Glide.with(context)
     .load(url)
     .into(imageView)

Glide 的使用步骤中,有三个主要步骤,其中with是绑定生命周期,load是加载图片,into是设置图片。接下来我们会从这三个方面来进行分析。

With部分解析

With(Context context)方法

在Glide.java的定义中,With方法有如下几种重载方法,对象包括Context、Activity、Fragment等等,但是溯源到最后,都是调用的Fragment内部的fragment.getContext()或者是View.getContext()方法。

我们打开最基本的with(context)方法:

  @NonNull
  public static RequestManager with(@NonNull Context context) {
	    return getRetriever(context).get(context);
  }

这个静态方法传入了一个运行时的上下文对象Context,返回了一个RequestManager,而RequestManager的部分定义如下:

//RequestManager.java
public class RequestManager implements ComponentCallbacks2, LifecycleListener, ModelTypes<RequestBuilder<Drawable>> {
  // 省略
  protected final Glide glide;
  protected final Context context;

  @SuppressWarnings("WeakerAccess")
  @Synthetic
  final Lifecycle lifecycle;
  //省略 
 }

RequestManagerc持有着Glide、Context、LifeCycle的引用,前面二者我们比较熟悉,但是LifeCycle可能比较陌生:

public interface Lifecycle {
  void addListener(@NonNull LifecycleListener listener);
  void removeListener(@NonNull LifecycleListener listener);
}

其本身是一个接口,一共只有两个接口方法:1.添加接听者,2.移除监听者。而这个LifeCycleListener的定义就比较有意思了:

//package com.bumptech.glide.manager;
public interface LifecycleListener {
  void onStart();
  void onStop();
  void onDestroy();
}

这三个接口方法和Activity或者是Fragment中的三个生命周期方法同名。我们看看其中之一onStart()方法的调用处:

我们又回到了RequestManager.java中,显然,RequestManager通过这三个方法来控制生命周期和请求的停等关系

 @Override
  public synchronized void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }


  @Override
  public synchronized void onStop() {
    pauseRequests();
    targetTracker.onStop();
  }


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

其中有一个变量targetTracker,它本身也是实现了LifecycleListener。

private final TargetTracker targetTracker = new TargetTracker();

回到with方法:

  @NonNull
  public static RequestManager with(@NonNull Context context) {
	    return getRetriever(context).get(context);
  }

我们来看看getRetriever()方法,方法定义:

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

该CheckNull的意思是:我们不能把一个还未attach的View或者是一个Fragment中,调用getActivity还返回NULL的时候的Fragment进行调用(通常是Fragement的isAttached的状态之前或者是被Destroy之后)。

换句话来说,在Fragment中,我们只能在onAttach()和onDetach()状态之间调用。

在Glide.get方法主要功能是将我们之前构建的LifeCycle去创建一个RequestManager,但是构建RequestManager时,采用不同的context会有不同的构建方法和思路,这里不做展开,文章末尾有做解析与比较。下面的流程都是采用Activity作为Context进行的,而不是ApplicationContext。

  • ApplicationContext和普通的Activity.Context有什么区别? Context本意是“上下文”,就是当前的代码的一个运行环境。ApplicationContext就是整个Application下的一个Context,但是并没有ApplicationContext单独的一个类存在。通常情况下来说,和UI相关的Context相关操作都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。

详见:Context与ApplicationContext的区别

get方法中,根据Context的类型判断,做了分支处理。我们选择Activity分支进入:

@NonNull
  public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else if (activity instanceof FragmentActivity) {
      return get((FragmentActivity) activity);
    } else {
      assertNotDestroyed(activity);
      frameWaiter.registerSelf(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();//****
      return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));//****
    }
  }

一开始也是一个分支判断,不同的情况交由不同分支的重载函数进行处理。

  • 具体的处理方式可以看StackOverFlow上的这个问题:glide-image-loading-with-application-context 大意是:如果我们在Fragment上使用Glide,并且采用的是ApplicationContext,当我们的Fragment被移除后。Glide仍然会加载图片,甚至设置到ImageView当中,最后会被GC算法回收,就不会根据Fragment的生命周期来进行处理了。

注意加注释的这两行,这就是整个Glide与宿主生命周期绑定的关键,第一行获取了Activity的Fragment的FragmentManager。 在getRequestManagerFragment(Fragment:fragment)的实现方法中,这两行如下:

android.app.FragmentManager fm = fragment.getChildFragmentManager();
return fragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());

第二行的fragmentGet方法的方法体如下:

  private RequestManager fragmentGet(
  @NonNull Context context,
  @NonNull android.app.FragmentManager fm,
  @Nullable android.app.Fragment parentHint,
  boolean isParentVisible) {
	    RequestManagerFragment current = getRequestManagerFragment(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;
  }

在其中构建了一个RequestManagerFragment,这个RequestManagerFragment继承自Fragment就是我们日常使用的Fragment:

public class RequestManagerFragment extends Fragment {
	//省略
}

然后 通过builder模式创建RequestManager,并且将fragment的lifecycle传入,这样Fragment和RequestManager就建立了联系。RequestManagerFragment会持有一个ActivityFragmentLifecycle类型的,名为LifeCycle的变量,该变量可以回调RequestManager中的方法。当一个Fragment被贴在Activity上,这样一来,空白UI的Fragment中的三个生命周期的回调方法就能得到回调了:

With部分总结

接下来我们来总结一下Glide是如何与生命周期绑定的。首先明确三个基本问题:
1.Glide要如何感知绑定的Activity的生命周期?
2.Glide如何传递生命周期的钩子函数?
3.Glide绑定的流程是什么?

  • 对于第一个问题,答案是Glide构建了一个无UI的Fragment,并将它贴在需要监听的Activity上,通过Activity和Fragment的生命周期来进行感知。

  • 第二个问题:Glide的无UI的Fragment中,持有lifecycle的引用,ActivityFragmentLifecycle是Glide的类,内部包含了一个数据结构:

    	private final Set<LifecycleListener> lifecycleListeners = 
    Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>());
    

当我们在不同的生命周期的阶段会做不同的事情:

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

这里其实采用的是观察者模式,三个onXXX方法就是事件,一旦事件发生,那么就会通知所有已经订阅的消费者出来消费事件。lifecycleListeners在addListener方法被添加值:

  @Override
  public void addListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.add(listener);

    if (isDestroyed) {
      listener.onDestroy();
    } else if (isStarted) {
      listener.onStart();
    } else {
      listener.onStop();
    }
  }

而这个addListener正是在RequestManager中的构造函数中被调用的: Alt text

Alt text

图中的红色箭头表示RequestManager向LifeCycle注册自己;蓝色箭头表示向下传递事件,最后统一通知观察者来消费事件。
  • 对于第三个问题,绑定的流程,如图: ![Alt text](./Glide With流程.png)

附:Lifecycle的创建问题。

如果我们在get方法传入的Context是ApplicationContext,那么会像这样新建一个:new ApplicationLifecycle():

 factory.build(
     	 glide,
         new ApplicationLifecycle(),//新建一个ApplicationContext
         new EmptyRequestManagerTreeNode(),
         context.getApplicationContext());

如果我们传入的是其他的Context,那么则会直接调用current.GetGlideLifeCycle():

  factory.build(
          glide, 
          current.getGlideLifecycle(), 
          current.getRequestManagerTreeNode(), 
          context);

这种情况会返回current的LifeCycle,这个current是在getSupportRequestManagerFragment()中将空白UI的Fragment贴在宿主上之前,通过构造函数创建的:

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

 @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);//存入pendingSupportRequestManagerFragments
        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
      }
    }
    return current;
  }

两个并不是同一个LifeCycle,这和前文中提到的StackOverflow上的那篇回答【glide-image-loading-with-application-context】说的一样,ApplicationLifeCycle中只对添加、移除事件进行了处理:

class ApplicationLifecycle implements Lifecycle {
  @Override
  public void addListener(@NonNull LifecycleListener listener) {
    listener.onStart();
  }

  @Override
  public void removeListener(@NonNull LifecycleListener listener) {
    // Do nothing.
  }
}

由于Application的生命周期是App本身,所以自然而然地也就不需要处理那么多的onStart、onStop等等的方法了。一旦App退出或者被回收,那么自然这些对象也会被回收。 所以,传入不同的Context,实现的方式也有不同,在BackGroundThread执行或者是传入ApplicationContext是不会生成空白Fragment的。(前者的主要原因是如果在子线程执行,那么默认就是传入applicationContext)。使用ApplicationContext会使得Glide的生命周期和App一样长。

参考来源