前言
今天刷到一篇内容和观点都挺新颖的文章《小题大做 | Handler内存泄露全面分析》,看到后面如何解决内存泄漏时有一段内容引起我的思考,
“比如Glide使用的时候传的上下文不要用Activity而改用Application的上下文。”
印象中这个说法中在很多其他文章都提到过,矛头都指向了“万恶的”Glide.with(context),我想Glide这个量级的开源项目了,会犯这种低级错误吗,它到底拿这个context去干了什么?
开扒源码
就拿Glide的Master代码来分析,快进到最危险的Glide.with(Activity activity)方法,传入参数activity,
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
getRetreiver方法是一个获取RequestManager的获取器,
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
…
return Glide.get(context).getRequestManagerRetriever();
}
传给Glide.get,初始化一个Glide的单例,
public static Glide get(@NonNull Context context) {
if (glide == null) {
GeneratedAppGlideModule annotationGeneratedModule =
getAnnotationGeneratedGlideModules(context.getApplicationContext());
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context, annotationGeneratedModule);
}
}
}
return glide;
}
其中getAnnotationGeneratedGlideModules传入的是applicationContext,这里不存在activity泄漏,继续看getAnnotationGeneratedGlideModules->initializeGlide,
private final GlideContext glideContext;
…
private static void initializeGlide(
@NonNull Context context,
@NonNull GlideBuilder builder,
@Nullable GeneratedAppGlideModule annotationGeneratedModule) {
Context applicationContext = context.getApplicationContext();
...
可以看到第一行就是获取applicationContext,后面的代码都只使用了applicationContet,这个context被保存到GlideContext里,glideContext是单例Glide的成员,所以这里也排除了activity泄漏的可能。
回到开头,难道是RequestManagerRetriver.get(Activity)方法有问题?
public RequestManager get(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else 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
// Only unwrap a ContextWrapper if the baseContext has a non-null application context.
// Context#createPackageContext may return a Context without an Application instance,
// in which case a ContextWrapper may be used to attach one.
&& ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
跟进get((Activity) context)
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));
}
}
第一个情况在后台线程getApplication没问题,其他分支就是通过Activity获取fragmentManager,Glide通过空白Fragment监听Activity生命周期来调度请求的方案,这里就不做细究了,我们只关心activity最后流向了哪里可能会导致泄漏的。
跟进所有分支,发现获取fragmentManager后,activity并没有被存储起来,除了有的会走到Glide的单例构造方法,但传入的也是applicationContext,例如:
private RequestManager getApplicationManager(@NonNull Context context) {
// Either an application context or we're on a background thread.
if (applicationManager == null) {
synchronized (this) {
...
Glide glide = Glide.get(context.getApplicationContext());
applicationManager =
factory.build(
glide,
new ApplicationLifecycle(),
new EmptyRequestManagerTreeNode(),
context.getApplicationContext());
}
}
}
return applicationManager;
}
得出结论
Glide拿context主要就两个事情:
- 用applicationContext构造glideContext,获取上下文信息;
- 获取FragmentManager监听activity生命周期
所以不管你在Glide.with(context)传入的是什么context,理论上都不会造成这个context的泄漏,除了FragmentManager可能存在bug,或是RequestManagerFragment出了bug导致内存问题,Glide本身的设计上是不存在大问题的。毕竟是被这么多人使用久经考验的工具库。
当然也许旧版本的Glide可能存在泄漏的问题今天就不去考究了,至少目前最新release的版本,大家可以放心的给Glide传入activity!
在RePlugin插件里使用Glide的一些问题
在RePlugin框架的插件中使用Glide,传入哪个context还是要注意一下的。
Glide的placeholder在加载的时候,如果传入的是资源id,会去上文提到的glideContext里的context去加载drawable,
private Drawable loadDrawable(@DrawableRes int resourceId) {
Theme theme =
requestOptions.getTheme() != null ? requestOptions.getTheme() : context.getTheme();
return DrawableDecoderCompat.getDrawable(glideContext, resourceId, theme);
}
因为Glide是单例构造,如果在插件中第一次使用传入的context不是插件的context的话,会导致后续加载drawble的时候出现异常。