当前位置:网站首页>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
边栏推荐
- [utiliser Electron pour développer le Bureau sur youkirin devrait]
- 星环科技数据安全管理平台 Defensor重磅发布
- Simulate the hundred prisoner problem
- How to solve the error "press any to exit" when deploying multiple easycvr on one server?
- 让更多港澳青年了解南沙特色文创产品!“南沙麒麟”正式亮相
- The easycvr platform reports an error "ID cannot be empty" through the interface editing channel. What is the reason?
- Image classification, just look at me!
- 《力扣刷题计划》复制带随机指针的链表
- Use of print function in MATLAB
- 兄弟组件进行传值(显示有先后顺序)
猜你喜欢

模拟百囚徒问题

Huaxia Fund: sharing of practical achievements of digital transformation in the fund industry

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

Star ring technology data security management platform defender heavy release

《2022中国信创生态市场研究及选型评估报告》发布 华云数据入选信创IT基础设施主流厂商!

To solve the stubborn problem of Lake + warehouse hybrid architecture, xinghuan Technology launched an independent and controllable cloud native Lake warehouse integrated platform

Wu Enda team 2022 machine learning course, coming

吴恩达团队2022机器学习课程,来啦

第十一届中国云计算标准和应用大会 | 华云数据成为全国信标委云计算标准工作组云迁移专题组副组长单位副组长单位

About Estimation with Cross-Validation
随机推荐
消除`if()else{ }`写法
MATLAB中print函数使用
EasyCVR平台通过接口编辑通道出现报错“ID不能为空”,是什么原因?
LeetCode笔记:Weekly Contest 300
About Estimation with Cross-Validation
记录Pytorch中的eval()和no_grad()
从XML架构生成类
ClickHouse(03)ClickHouse怎么安装和部署
南京大学:新时代数字化人才培养方案探讨
含重复元素取不重复子集[如何取子集?如何去重?]
吳恩達團隊2022機器學習課程,來啦
Nanjing University: Discussion on the training program of digital talents in the new era
生词生词生词生词[2]
Use JMeter to record scripts and debug
从类生成XML架构
Is it safe to open an account and register stocks for stock speculation? Is there any risk? Is it reliable?
破解湖+仓混合架构顽疾,星环科技推出自主可控云原生湖仓一体平台
GIMP 2.10教程「建议收藏」
[use electron to develop desktop on youqilin]
Notes on common management commands of openshift