Library 기준

androidx.fragment:fragment-ktx:1.5.7

androidx.recyclerview:recyclerview:1.3.0

Fragment Lifecycle

Fragment.java 파일과 공식 문서 를 보면 알 수 있듯이, Fragment는 mLifecycleRegistrymViewLifecycleOwner 두 개의 Lifecycle를 가지고 있다. Fragment 생명주기를 View lifecycle 과 Fragment 자체 lifecycle로 분리해서 바라보면, callback 함수명에서 알 수 있듯이, onDestoryView() 호출로 Fragment의 View는 사라지고, Fragment는 onDetach() 호출 전과 onDestoryView() 호출 후에 onDestory() 가 호출되어 사라진다.

fragmentLifecycle.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//Fragment.java

private void initLifecycle() {
    mLifecycleRegistry = new LifecycleRegistry (this);
    // ...
}

// ... 
@MainThread
@NonNull
public LifecycleOwner getViewLifecycleOwner() {
    // ...
    return mViewLifecycleOwner;
}

// ... 
@Override
@NonNull
public Lifecycle getLifecycle() {
    return mLifecycleRegistry;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void performStart () { // 예시 performStart() 함수, mLifecycleRegistry, mViewLifecycleOwner을 모두 이용하는 모습
    mChildFragmentManager.noteStateNotSaved();
    mChildFragmentManager.execPendingActions(true);
    mState = STARTED;
    mCalled = false;
    onStart();
    if (!mCalled) {
        throw new SuperNotCalledException ("Fragment " + this
                + " did not call through to super.onStart()");
    }
    mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
    if (mView != null) {
        mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START);
    }
    mChildFragmentManager.dispatchStart();
}

RecyclerView 내부 구조

fragment_structure.png

RecyclerView.java 내부를 보면, AdaptermObservable을 가지고 있고, Observer들은 RecyclerView를 참조한다. 또한 RecyclerViewAdapter를 참조하기에, AdatperRecyclerView양방향 참조, cycle이 생긴다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// RecyclerView.java

public class RecyclerView extends ViewGroup implements ScrollingView,
NestedScrollingChild2, NestedScrollingChild3 {
    //...
    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
    //...
    Adapter mAdapter;
    //...
}

private class RecyclerViewDataObserver extends AdapterDataObserver {
    // ...
}

public abstract static class Adapter<VH extends ViewHolder> {
    //...
    private final AdapterDataObservable mObservable = new AdapterDataObservable();
    //...
}

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
    public boolean hasObservers() { return !mObservers.isEmpty(); }
    //...
}

Memory Leak 발생 가능성

  • BottomNaviagtion을 사용하는 상황
  • BottomNavView 에 A,B,C 3개의 Fragment가 menu로 등록된 상태
  • 현재 A_Fragment가 Resume인 상황에서, B_Fragment로 전환시에
    • A_Fragment는 onDestoryView()가 호출
    • RecyclerView가 사라질때, mOberver 참조로 인해 Memory Leak 발생

View or Data Binding 를 사용할때도 유사하게 Memory Leak 이 발생 할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class A_Fragment : Fragment() {

    private lateinit var mockAdapter: MockAdapter
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        mockAdapter = MockAdapter()
        
        val recyclerView: RecyclerView = view.findViewById(R.id.recycler_view)
        recyclerView.layoutManager = LinearLayoutManager(this.context)
        recyclerView.adapter = mockAdapter
    }
}

LeakCanary 을 통해 확인한 Memory Leak 상태

memoryLeak.png

해결 방법

AdatperRecyclerView양방향 참조onDestoryView()시에 해제

  1. mockAdapter 를 nullable 하게 설정
  2. onDestoryView() 호출시 mockAdapter = null
  3. B_Fragment에서 다시 A_Fragment 로 전환시, Fragment는 onCreateView() 부터 생명주기 다시 시작
  4. onCreateView() 호출시에 mockAdapter를 MockAdapeter() 로 초기화
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A_Fragment : Fragment() {

    private var mockAdapter: MockAdapter? = null

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        mockAdapter = MockAdapter()
        //...
    }
    
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        val recyclerView: RecyclerView = view.findViewById(R.id.recycler_view)
        recyclerView.layoutManager = LinearLayoutManager(this.context)
        recyclerView.adapter = mockAdapter
    }

    override fun onDestroyView() {
        super.onDestroyView()
        mockAdapter = null
    }
}

Reference