当前位置:网站首页>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 ?
ViewRootImplWhen drawing the first frame , It will be used layer by layer view OfdispatchAttachedToWindowMethod , In this method, theonAttachedToWindowMethod- Jiangzi View Add to parent ViewGroup in , And father ViewGroup Member variables of
mAttachInfo( It's defined in View in ) Isn't empty ( staydispatchAttachedToWindowMethod ,dispatchDetachedFromWindowEmpty in method ),view OfdispatchAttachedToWindowWill be called , And then callonAttachedToWindowMethod
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
边栏推荐
- 【PaddlePaddle】 PaddleDetection 人脸识别 自定义数据集
- Let more young people from Hong Kong and Macao know about Nansha's characteristic cultural and creative products! "Nansha kylin" officially appeared
- Eliminate the writing of 'if () else{}'
- LeetCode 6109. 知道秘密的人数
- Sophon CE Community Edition is online, and free get is a lightweight, easy-to-use, efficient and intelligent data analysis tool
- About Estimation with Cross-Validation
- How can cluster deployment solve the needs of massive video access and large concurrency?
- Is it safe to open an account and register stocks for stock speculation? Is there any risk? Is it reliable?
- ViewPager + RecyclerView的内存泄漏
- Image classification, just look at me!
猜你喜欢

Star ring technology data security management platform defender heavy release

Tencent music launched its new product "quyimai", which provides music commercial copyright authorization

pytorch yolov5 训练自定义数据

Isprs2022 / Cloud Detection: Cloud Detection with Boundary nets Boundary Networks Based Cloud Detection

vulnhub之darkhole_2

Sophon KG升级3.1:打破数据间壁垒,解放企业生产力

华夏基金:基金行业数字化转型实践成果分享

Sophon CE社区版上线,免费Get轻量易用、高效智能的数据分析工具

ViewPager + RecyclerView的内存泄漏

分享:中兴 远航 30 pro root 解锁BL magisk ZTE 7532N 8040N 9041N 刷机 刷面具原厂刷机包 root方法下载
随机推荐
Star Ring Technology launched transwarp Navier, a data element circulation platform, to help enterprises achieve secure data circulation and collaboration under privacy protection
Access the database and use redis as the cache of MySQL (a combination of redis and MySQL)
JDBC reads a large amount of data, resulting in memory overflow
[TestLink] testlink1.9.18 solutions to common problems
数值计算方法 Chapter8. 常微分方程的数值解
写作写作写作写作
登录连接 CDB 和 PDB
Isprs2022 / Cloud Detection: Cloud Detection with Boundary nets Boundary Networks Based Cloud Detection
Gimp 2.10 tutorial "suggestions collection"
Numerical calculation method chapter8 Numerical solutions of ordinary differential equations
Failed to virtualize table with JMeter
苹果手机炒股安全吗?打新债是骗局吗?
记录Pytorch中的eval()和no_grad()
兄弟组件进行传值(显示有先后顺序)
吳恩達團隊2022機器學習課程,來啦
如何获取飞机穿过雷达两端的坐标
Sophon kg upgrade 3.1: break down barriers between data and liberate enterprise productivity
小林coding的内存管理章节
Is it safe to open an account and register stocks for stock speculation? Is there any risk? Is it reliable?
第十一届中国云计算标准和应用大会 | 华云数据成为全国信标委云计算标准工作组云迁移专题组副组长单位副组长单位