看完這篇,你確定你的Glide不會發生記憶體洩漏嗎?
hello:大家好我是 小小小小小鹿,一枚菜雞Android程式猿。最近正在閱讀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 /* Callback */);
}
它的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) {
//如果在子執行緒,使用Application級別的RequestManager
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 activity = findActivity(view.getContext());
//如果activity為空則直接使用
if (activity == null) {
return get(view.getContext().getApplicationContext());
}
//查詢到view所屬的fragment,則使用fragment 查詢不到則使用activity。
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);
}
通過activity查詢當前View所屬的fragment
private android.app.Fragment findFragment(@NonNull View target, @NonNull Activity activity) {
//是以View作為key Fragment作為value的ArrayMap
tempViewToFragment.clear();
//將所有的fragment 包含activity下的fragment和fragment中的子Fragment
//通過遞迴的方式全部新增到 tempViewToFragment
findAllFragmentsWithViews(activity.getFragmentManager(), tempViewToFragment);
android.app.Fragment result = null;
View activityRoot = activity.findViewById(android.R.id.content);
View current = target;
//不斷對比直到當前的view為contentView 則停止查詢fragment
while (!current.equals(activityRoot)) {
result = tempViewToFragment.get(current);
//查詢到了對應的fragment,退出當前迴圈
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) {
//獲取當前FragmentManager 下的SupportRequestManagerFragment 在getSupportRequestManagerFragment內部,如果沒有對應的fragment,會為其新增。
SupportRequestManagerFragment current =
getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
//當前SupportRequestManagerFragment 沒有RequestManager 則建立一個RequestManager與其生命週期繫結。
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;
//建立一個網咯變化的監聽 網路監聽是Glide預設實現的,我們也可以通過指定factory實現其它的一些業務邏輯。
//當網路連線上後會將所有請求失敗的重新嘗試。
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());
//將當前RequestManager新增到Glide方便統一進行管理
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() {
//通知所有target呼叫onDestory
targetTracker.onDestroy();
//通知每一個target 進行清除
for (Target<?> target : targetTracker.getAll()) {
clear(target);
}
//清除集合
targetTracker.clear();
//清除當前requestManager管理的request
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) {
//清除對應的request,並對request進行重置,放進物件重用池。
boolean isOwnedByUs = untrack(target);
//如果當前的target不歸自己管理,會遍歷所有的requestManager查詢到合適的requestManager進行處理。
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 the Target doesn't have a request, it's already been cleared.
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, /*isSafeToRecycle=*/ true);
}
private boolean clearRemoveAndMaybeRecycle(@Nullable Request request, boolean isSafeToRecycle) {
if (request == null) {
return true;
}
//如果能夠從集合中移除成功,那麼這個request歸屬當前的RequestManager管理
boolean isOwnedByUs = requests.remove(request);
// Avoid short circuiting.
isOwnedByUs = pendingRequests.remove(request) || isOwnedByUs;
if (isOwnedByUs) {
//執行request.clear 對request狀態進行轉變,和做相應的通知
request.clear();
if (isSafeToRecycle) {
//這個是不會進行記憶體洩漏的關鍵,將request對應的引用回撥置空,切斷引用關係。
//對應程式碼可以參考SingleRequest
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);
//特定路徑 休眠300s 模擬網路載入緩慢
if(assetPath.contains("DuiMianDeNvHaiKanGuoLai--RenXianQi.mp3")){
SystemClock.sleep(10*30*1000);//休眠300s
}
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載入和在子執行緒進行圖片載入,需要謹慎使用,除非你明確他們的使用場景與自身的業務契合。