最近正在阅读Glide源码,今天我们要研究的部分是Glide RequestManager 生命周期管理。 本来这个也是这篇文章应该是Glide生命周期管理。但是在源码阅读中我发现原来我以前的项目对于Glide的使用存在着一些内存泄漏的可能,因此临时决定更改了文章的名字,希望能够引起大家的重视。
这个是我们的主界面样式
通过最下面的一排选项卡,控制主界面的一级fragment ,一级Fragment下面又有若干的子Fragment,fragment又包含一些其它的View。以RecyclerView举例,在对应的Adapter创建的时候会传递Context对象。加载的时候
Glide.with(context).load("path").into(imagerView) 这样做会存在内存泄漏的可能。
下面正式分析内因为Glide使用不当造成内存泄漏的原理。
Glide生命周期
作为一个android开发者,说到生命周期,最先想到的应该是activity的生命周期了吧。activity的生命周期是android系统开发者给我们设定的一些模板方法,我们只需要在对应的方法中实现对应的业务逻辑即可。那么Glide的生命周期是怎么来的呢?
Glide生命生命周期主要分为两个:
- activity/fragment 生命周期方法调用,影响到整个页面所有请求。
- 网络状态变化引起整个requestManager 所管理的所有请求发生改变。
页面管理
Glide#with方法返回的是一个RequestManager对象,而RequestManager的获取实际上都调用了RequestManagerRetriever#get来获取RequestManager对象的。
RequestManagerRetriever用于创建新的 RequestManager 或从Activity和Fragment中检索现有的。
RequestManagerRetriever的构建
public RequestManagerRetriever(@Nullable RequestManagerFactory factory) {
this.factory = factory != null ? factory : DEFAULT_FACTORY;
handler = new Handler(Looper.getMainLooper(), this );
}
它的factory由Glide传递过来,如果我们不进行配置默认为空。就是使用DEFAULT_FACTORY进行创建
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);
}
};
RequestManagerRetriever获取对应的RequestManager
RequestManagerRetriever#get传递的参数有下面几类。
- Context 会尝试将其转换成对应的activity否则获取的是Application 级别的RequestManager
- Activity/fragment RequestManagerRetriever会尝试通过他们的FragmentManager获取一个不可见的子fragment,如果没有获取成功则新建一个。并添加到activity/fragment中。
- View 当传递一个View进来的时候,会先获取对应的activity。如果获取不到则直接使用Application级别的RequestManager,如果获取到了activity,会查看当前View是否在某一个activity中,如果在使用fragment获取对应的ReauestManager 如果不在则使用Activity的RequestManager。
需要特别注意的是:不论传递什么参数,在子线程进行图片加载都会统一使用Application级别的RequestManager。
这里以RequestManagerRetriever#get(View view)来说明其流程
@NonNull
public RequestManager get(@NonNull View view) {
if (Util.isOnBackgroundThread()) {
return get(view.getContext().getApplicationContext());
}
Preconditions.checkNotNull(view);
Preconditions.checkNotNull(view.getContext(),
"Unable to obtain a request manager for a view without a Context");
Activity activity = findActivity(view.getContext());
if (activity == null) {
return get(view.getContext().getApplicationContext());
}
?
if (activity instanceof FragmentActivity) {
Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);
return fragment != null ? get(fragment) : get(activity);
}
?
android.app.Fragment fragment = findFragment(view, activity);
if (fragment == null) {
return get(activity);
}
return get(fragment);
}
通过activity查找当前View所属的fragment
private android.app.Fragment findFragment(@NonNull View target, @NonNull Activity activity) {
tempViewToFragment.clear();
findAllFragmentsWithViews(activity.getFragmentManager(), tempViewToFragment);
?
android.app.Fragment result = null;
?
View activityRoot = activity.findViewById(android.R.id.content);
View current = target;
while (!current.equals(activityRoot)) {
result = tempViewToFragment.get(current);
if (result != null) {
break;
}
if (current.getParent() instanceof View) {
current = (View) current.getParent();
} else {
break;
}
}
tempViewToFragment.clear();
return result;
}
通过fragment获取RequestManager
RequestManagerRetriever#get(Fragment fragment) 会调用supportFragmentGet来获取RequestManager。
@NonNull
private RequestManager supportFragmentGet(
@NonNull Context context,
@NonNull FragmentManager fm,
@Nullable Fragment parentHint,
boolean isParentVisible) {
SupportRequestManagerFragment current =
getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
RequestManager的构建过程
RequestManager有两个构造方法,但是最终都会执行下面这个。
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);
?
defaultRequestListeners =
new CopyOnWriteArrayList<>(glide.getGlideContext().getDefaultRequestListeners());
setRequestOptions(glide.getGlideContext().getDefaultRequestOptions());
glide.registerRequestManager(this);
}
网络监听
在构造方法中,创建connectivityMonitor的时候,将requestTracker传递给了RequestManagerConnectivityListener。他的实现如下:
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();
}
}
}
}
RequestManager#onDestory
@Override
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);
}
target的清除过程
RequestManger的clear(Target<?> target)会内用untrackOrDelegate
private void untrackOrDelegate(@NonNull Target<?> target) {
boolean isOwnedByUs = untrack(target);
if (!isOwnedByUs && !glide.removeFromManagers(target) && target.getRequest() != null) {
Request request = target.getRequest();
target.setRequest(null);
request.clear();
}
}
request清除过程
synchronized boolean untrack(@NonNull Target<?> target) {
Request request = target.getRequest();
if (request == null) {
return true;
}
?
if (requestTracker.clearRemoveAndRecycle(request)) {
targetTracker.untrack(target);
target.setRequest(null);
return true;
} else {
return false;
}
}
?
public boolean clearRemoveAndRecycle(@Nullable Request request) {
return clearRemoveAndMaybeRecycle(request, true);
}
?
private boolean clearRemoveAndMaybeRecycle(@Nullable Request request, boolean isSafeToRecycle) {
if (request == null) {
return true;
}
boolean isOwnedByUs = requests.remove(request);
isOwnedByUs = pendingRequests.remove(request) || isOwnedByUs;
if (isOwnedByUs) {
request.clear();
if (isSafeToRecycle) {
request.recycle();
}
}
return isOwnedByUs;
}
Glide真的不会发生内存泄漏吗?
前面我们梳理了Glide的生命周期,知道在生命相关的activity/Fragment销毁的时候会暂停和回收相关的请求,并且切断网络请求回调的引用。那么Glide是不是真的能够完全避免内存内泄漏呢?
这个直接给出我的结论:正常情况下使用Glide不会造成内Activity、Fragment、View内存泄漏。但是如果Glide使用不当是可能造成内存泄漏的。比如在Fragment使用Glide#with传递activity对象。 原因是Fragment结束的时候,Activity几倍RequestManager并没有接收到相应的生命周期方法。
实验证明:
改造我们在Glide数据输入输出编写的加载音频封面的ModelLoader,当遇到特定该音频的时候线程休眠300秒
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
try {
Log.d(TAG,"loadData assetPath "+assetPath);
AssetFileDescriptor fileDescriptor = assetManager.openFd(assetPath);
if(assetPath.contains("DuiMianDeNvHaiKanGuoLai--RenXianQi.mp3")){
SystemClock.sleep(10*30*1000);
}
mediaMetadataRetriever.setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(),fileDescriptor.getDeclaredLength());
byte[] bytes = mediaMetadataRetriever.getEmbeddedPicture();
if(bytes == null){
callback.onLoadFailed(new FileNotFoundException("the file not pic"));
return;
}
ByteBuffer buf = ByteBuffer.wrap(bytes);
Log.d(TAG,"loadData assetPath "+assetPath +" success");
callback.onDataReady(buf);
} catch (IOException e) {
e.printStackTrace();
callback.onLoadFailed(e);
}
}
在Activity中添加一个Fragment,当页面创建成功后,使用Glide#with传递activity/context对象,并在activity中移除该Fragment。
将fragment的根View与fragment强制关联。方便利用LeakCanary进行内存泄漏检测。
public static class MyTestFragment extends Fragment {
ImageView imageView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_glide_source_test,container,false);
imageView = root.findViewById(R.id.imageView);
root.setTag(this);
Log.d(TAG,"onCreateView finish");
return root;
}
?
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
?
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Glide.with(getActivity()).load(Uri.parse("file:///android_asset/DuiMianDeNvHaiKanGuoLai--RenXianQi.mp3")).diskCacheStrategy(DiskCacheStrategy.NONE).into(imageView);
Log.d(TAG,"onActivityCreated load ");
imageView.postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG,"onActivityCreated remove ");
getActivity().getSupportFragmentManager().beginTransaction().remove(MyTestFragment.this).commit();
}
},300);
?
}
?
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}
实验结果:
可以看这里因为Fragment被View持有导致了Fragment内存泄漏。这个也就反应了当Glide使用不当,会导致View的内存泄漏。 解决:传递正确的参数给with。或者调用ViewTarget#clearOnDetach。我没有使用过clearOnDetach 根据Glide注释,这个是一组实验性api,后续可能会被移除。
小结Glide使用注意事项
Glide#with方法在参数使用优先级
fragment > view > activity > application
其中view和activity 在明确知道当前使用的页面是activity优先传递activity 因为view会通过多次循环遍历查找fragment、activity。正确的使用Glide可以避免因为Glide造成内存泄漏。
Glide RequestOptions 可以分为三个级别:
- 应用级 可以进行全局配置
- 页面级别 activty/fragment 可以为每一个特殊的页面进行定制化处理,作用于RequestManager
- 单个请求 作用于RequestBuilder 为每一个请求构建请求配置项
Glide如何保证图片的加载不会出现错乱
ViewTarget#setRequest会调用View的setTag 将request请求对象放在View中。在请求的时候会通过ViewTarget#getRequest,如果返回的与前一个请求一致则使用原来的请求,否则清除原来的请求。
对于使用application加载和在子线程进行图片加载,需要谨慎使用,除非你明确他们的使用场景与自身的业务契合。
|