当前位置:网站首页>Memory leak of viewpager + recyclerview
Memory leak of viewpager + recyclerview
2022-07-05 18:21:00 【Cattle in the yard】
author : What would you like to eat today
Reprinted address :https://juejin.cn/post/7113395533460275236
Basic information
There was a question about... When optimizing memory leakage on the project RecyclerView Memory leak , The page structure is shown in the figure :
LeakCanary The captured reference chain is as follows
┬───
│ GC Root: Thread object
│
├─ java.lang.Thread instance
│ Thread name: 'main'
│ ↓ Thread.threadLocals
│ ~~~~~~~~~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap instance
│ ↓ ThreadLocal$ThreadLocalMap.table
│ ~~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap$Entry[] array
│ ↓ ThreadLocal$ThreadLocalMap$Entry[4]
│ ~~~
├─ java.lang.ThreadLocal$ThreadLocalMap$Entry instance
│ ↓ ThreadLocal$ThreadLocalMap$Entry.value
│ ~~~~~
├─ androidx.recyclerview.widget.GapWorker instance
│ ↓ GapWorker.mRecyclerViews
│ ~~~~~~~~~~~~~~
├─ java.util.ArrayList instance
│ ↓ ArrayList[0]
│ ~~~
╰→ androidx.recyclerview.widget.RecyclerView instance
Identify problems
From the reference chain, we can see that the key point is GapWorker, First look at this GapWorker
RecyclerView stay android 21 And above versions will use GapWorker
Implement the preload mechanism , stay Recyclerview Of onAttachedToWindow
Method to try to instantiate it , And pass GapWorker Of add
Methods will Recyclerview Add yourself to GapWoker
Member variables of mRecyclerViews
Go to the list , stay onDetachedFromWindow
Would call GapWorker
Of remove
Method removes its reference to itself ,GapWoker
The instance is saved in its class static member variable sGapWorker
(ThreadLocal) in , Make sure that the main thread has only one instance
RecyclerView
@Override
protected void onAttachedToWindow() {
......
if (ALLOW_THREAD_GAP_WORK) {
// from ThreadLocal In order to get GapWorker example , by null Then create a
mGapWorker = GapWorker.sGapWorker.get();
if (mGapWorker == null) {
mGapWorker = new GapWorker();
Display display = ViewCompat.getDisplay(this);
float refreshRate = 60.0f;
if (!isInEditMode() && display != null) {
float displayRefreshRate = display.getRefreshRate();
if (displayRefreshRate >= 30.0f) {
refreshRate = displayRefreshRate;
}
}
mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate);
// Will create the GapWorker Instance set to ThreadLocal In the middle
GapWorker.sGapWorker.set(mGapWorker);
}
// Add your own reference
mGapWorker.add(this);
}
}
@Override
protected void onDetachedFromWindow() {
......
if (ALLOW_THREAD_GAP_WORK && mGapWorker != null) {
// A reference to the exception itself
mGapWorker.remove(this);
mGapWorker = null;
}
}
final class GapWorker implements Runnable {
......
static final ThreadLocal<GapWorker> sGapWorker = new ThreadLocal<>();
ArrayList<RecyclerView> mRecyclerViews = new ArrayList<>();
public void add(RecyclerView recyclerView) {
if (RecyclerView.DEBUG && mRecyclerViews.contains(recyclerView)) {
throw new IllegalStateException("RecyclerView already present in worker list!");
}
mRecyclerViews.add(recyclerView);
}
public void remove(RecyclerView recyclerView) {
boolean removeSuccess = mRecyclerViews.remove(recyclerView);
if (RecyclerView.DEBUG && !removeSuccess) {
throw new IllegalStateException("RecyclerView removal failed!");
}
}
......
GapWoker
The instance is created in the main thread ThreadLocalMap
Will be followed by a key by sGapWorker
,value For this instance Entry preservation ,GapWoker
Will not actively call sGapWorker
(ThreadLocal) Of remove
Method Entry from ThreadLocalMap
Remove , That is to say, the main thread corresponds to ThreadLocalMap
Will always hold this Entry, So this is Recyclerview
Memory leaks create conditions : as long as GapWorker.add
and GapWorker.remove
There are no paired calls , It will lead to Recyclerview
Has been GapWorker
Members of mRecyclerViews
Hold strong citation , Form a reference chain :
Thread->ThreadLocalMap->Entry(sGapWorker,GapWoker example )->mRecyclerViews->Recyclerview->Context
The next step is to find out where the problem occurred , Find... Through breakpoints Recyclerview
Of onAttachedToWindow
Method is executed twice ,onDetachedFromWindow
Method is executed only once , And that leads to this GapWorker
Of mRecyclerViews
Still keep a pair of Recyclerview
References to , So find out why onAttachedToWindow
One more execution is the answer to the question , So, usually, in the layout View Of onAttachedToWindow
When will it be called ?
ViewRootImpl
When drawing the first frame , It will be used layer by layer view OfdispatchAttachedToWindow
Method , In this method, theonAttachedToWindow
Method- Jiangzi View Add to parent ViewGroup in , And father ViewGroup Member variables of
mAttachInfo
( It's defined in View in ) Isn't empty ( staydispatchAttachedToWindow
Method ,dispatchDetachedFromWindow
Empty in method ),view OfdispatchAttachedToWindow
Will be called , And then callonAttachedToWindow
Method
From the structure of the page ,Recyclerview Belong to Fragment Of View, and Fragment Cling to ViewPager On , be Fragment The instantiation of is by ViewPager control , stay ViewPager Of onMeasure Method to load the current page Fragment
ViewPager
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
......
mInLayout = true;
//1 Instantiate the current page Fragment
populate();
mInLayout = false;
......
}
void populate() {
populate(mCurItem);
}
void populate(int newCurrentItem) {
......
if (curItem == null && N > 0) {
//2 It will call adapter Of instantiateItem Method instantiation fragment
// And will call FragmentManager.beginTransaction() Start transaction , take fragment Of attach,add And so on
curItem = addNewItem(mCurItem, curIndex);
}
......
//3 The previously generated transaction will be executed here , take fragment Of view Add to ViewPager in
mAdapter.finishUpdate(this);
......
}
}
In the third point of the code FragmentManager
Executing a transaction will Fragment Of view Add to ViewPager in , This is what I mentioned above onAttachedToWindow
The second case where the method is called .( here ViewPager Already in the drawing process ,mAttachInfo
Not empty )
Look at the project Fragment load view Code for , as follows :
In the project Fragment
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_list, container, true /** The problem */)
// What needs to be noted here is LayoutInflator.infalte Of attachToRoot by true when , What is returned is the incoming root Parameters , It's just container
// Here container the truth is that ViewPager, Therefore, it is necessary to pass findViewById find R.layout.fragment_list The root of the view return
val list = view.findViewById<RecyclerView>(R.id.list)
return list
}
inflate
Methodical attachToRoot
Parameter passed true, Led to LayoutInflater
Would call root.addView()
take view Add to root( That is to say ViewPager) In the middle
LayoutInflater
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
......
if (root != null && attachToRoot) {
root.addView(temp, params);
}
......
}
ViewGroup
public void addView(View child, LayoutParams params) {
addView(child, -1, params);
}
public void addView(View child, int index, LayoutParams params) {
......
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
......
//ViewPager Already in measure In the process ,mAttachInfo Not empty , this case Will enter
AttachInfo ai = mAttachInfo;
if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
......
//child by fragment In view
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
}
......
}
Sort out the process :ViewPager stay onMeasure
Load in Framgent,Fragment Of onCreateView
Load in view when attachToWindow
Pass on true Triggered view For the first time onAttachedToWindow
, stay Fragment After loading ,ViewPager There is no judgment view The father of View Whether for themselves , And through the FragmentManager
Once again will be view added , This triggers view The second time onAttachedToWindow
, thus Recyclerview
Two calls mGapWorker.add(this)
Add yourself to GapWoker
Of mRecyclerViews
In the middle , stay Activity Exit time ,onDetachedFromWindow
Called once , be mRecyclerViews
There is still a pair left Recyclerview
A strong reference to , This leads to memory leaks .
Solution : take true Change it to false solve the problem , Sometimes minor mistakes can waste a lot of your time
reflection
ThreadLocalMap Of Entry about Key Isn't it a weak reference ? Why does it cause memory leaks ?
From the definition of weak reference , If an object is only referenced by a weak reference , Then the object will be gc Recycling . But from GapWorker
You can see the source code of ,sGapWorker
yes static final
Decorated class static members ,sGapWorker
For what it points to ThreadLocal
Instances are strong references , And that leads to this ThreadLocalMap
Corresponding Entry Of Key It won't be gc Recycling , that ThreadLocal
Medium get
and set
Yes key by null Of Entry The removed assistive mechanism will not take effect , So in addition to actively removing Entry outside , Only after the main thread exits GapWorker
To be recycled , But it is no longer meaningful for the main thread to exit the recycle .
In that case, why Entry Of Key Also use weak references ?
hypothesis key Strong references are used , Imagine this scenario , We used the thread pool to create multiple threads , And these threads call during task execution sGapWorker
Of set
Method , These threads are cached after execution , So these threads ThreadLocalMap
Corresponding Entry Medium Key Would be right sGapWorker
Point to the ThreadLocal
Instances hold strong references , As a result, the instance cannot be recycled and a memory leak occurs , that key Using weak references can avoid this problem .
In that case, why Entry Of Value Why not use weak references ?
class Test{
static final ThreadLocal<GapWorker> sGapWorker = new ThreadLocal<>();
void A(){
sGapWorker.set(new GapWorker());
}
void B(){
GapWorker gp = sGapWorker.get()
}
}
hypothesis value It uses weak references , Imagine this scenario , First call Test
Methods A, Then it happened gc, because value Point to the GapWorker
The only object is value A weak reference to it , Then it will be recycled , At some point after that, the method was called B, Then the obtained value will be null. It can be seen that in this case value The saved values are quite unstable , Can be recycled at any time .
But because of value Strong references are used ,value The referenced object still has the possibility of memory leakage ,ThreadLocal Of set and get Methods will also be used for these key by null Of Entry Scavenging , However, there is uncertainty about the timing of such recovery , To avoid value Memory leak , We need to take the initiative to call at the appropriate time ThreadLocal
Of remove
Method to remove value References to
边栏推荐
- Introduction to the development function of Hanlin Youshang system of Hansheng Youpin app
- Thoroughly understand why network i/o is blocked?
- Image classification, just look at me!
- 使用Jmeter虚拟化table失败
- How to solve the error "press any to exit" when deploying multiple easycvr on one server?
- Multithreading (I) processes and threads
- 图像分类,看我就够啦!
- Sophon CE社区版上线,免费Get轻量易用、高效智能的数据分析工具
- 让更多港澳青年了解南沙特色文创产品!“南沙麒麟”正式亮相
- Sophon Base 3.1 推出MLOps功能,为企业AI能力运营插上翅膀
猜你喜欢
《力扣刷题计划》复制带随机指针的链表
The 11th China cloud computing standards and Applications Conference | cloud computing national standards and white paper series release, and Huayun data fully participated in the preparation
华夏基金:基金行业数字化转型实践成果分享
Whether to take a duplicate subset with duplicate elements [how to take a subset? How to remove duplicates?]
U-Net: Convolutional Networks for Biomedical Images Segmentation
Sophon CE Community Edition is online, and free get is a lightweight, easy-to-use, efficient and intelligent data analysis tool
记一次使用Windbg分析内存“泄漏”的案例
LeetCode 6111. 螺旋矩阵 IV
ISPRS2022/雲檢測:Cloud detection with boundary nets基於邊界網的雲檢測
JVM第三话 -- JVM性能调优实战和高频面试题记录
随机推荐
[utiliser Electron pour développer le Bureau sur youkirin devrait]
[performance test] full link voltage test
【PaddleClas】常用命令
隐私计算助力数据的安全流通与共享
金太阳开户安全吗?万一免5开户能办理吗?
让更多港澳青年了解南沙特色文创产品!“南沙麒麟”正式亮相
开户注册挖财安全吗?有没有风险的?靠谱吗?
pytorch yolov5 训练自定义数据
[QNX hypervisor 2.2 user manual]6.3.2 configuring VM
Sophon CE Community Edition is online, and free get is a lightweight, easy-to-use, efficient and intelligent data analysis tool
Vulnhub's darkhole_ two
Isprs2022 / Cloud Detection: Cloud Detection with Boundary nets Boundary Networks Based Cloud Detection
Introduction to the development function of Hanlin Youshang system of Hansheng Youpin app
Eliminate the writing of 'if () else{}'
Check namespaces and classes
Generate XML schema from class
写作写作写作写作
GIMP 2.10教程「建议收藏」
Introduction to VC programming on "suggestions collection"
How to solve the error "press any to exit" when deploying multiple easycvr on one server?