안드로이드 수명 주기와 메모리 누수

2023. 5. 1. 22:05Mobile/Android

작성자알 수 없는 사용자

728x90
반응형

안녕하세요. 기깔나는 사람들에서 안드로이드를 맡고 있는 마플입니다.
이번 시간에는 안드로이드 수명주기와 메모리 누수(memory leak)를 방지를 위해 할당 해제해야 할 것들에 대해 알아보도록 할게요.


안드로이드에서 Activity와 Fragment의 인스턴스는 수명 주기 안에서 서로 다른 상태를 통해 전환이 됩니다. 
Activity와 Fragment 클래스에서 활동이 상태 변화를 알아차릴 수 있는 콜백을 제공합니다.

각 콜백을 통해 수명 주기에 맞는 일을 하면 되죠.

 

Activty 수명 주기

 

Fragment 수명 주기


onStop()

onStop 메서드에서는 앱에서 백그라운드에 있는 동안 필요하지 않는 리소스를 해제해야 해요.

만약 CPU를 많이 소모하는 종료 작업(ex: 데이터베이스에 저장)을 넣을 시기를 못 찾았으면 onStop에서 하면 좋습니다. 

onStop() 메서드에서는 앱이 사용자에게 보이지 않는 동안 앱은 필요하지 않은 리소스를 해제하거나 조정해야 합니다. 예를 들어 앱은 애니메이션을 일시중지하거나, 세밀한 위치 업데이트에서 대략적인 위치 업데이트로 전환할 수 있습니다. onPause() 대신 onStop()을 사용하면 사용자가 멀티 윈도우 모드에서 활동을 보고 있더라도 UI 관련 작업이 계속 진행됩니다.
또한 onStop()을 사용하여 CPU를 비교적 많이 소모하는 종료 작업을 실행해야 합니다. 예를 들어 정보를 데이터베이스에 저장할 적절한 시기를 찾지 못했다면 onStop() 상태일 때 저장할 수 있습니다. 다음 예시는 초안 내용을 영구 저장소에 저장하는 onStop()을 구현한 것입니다.

onDestroy()

생명 주기에 따라 마지막에 안 쓰이는 Activity나 Fragment는 onDestroy 메서드에서 자원 할당을 해제 해줘야 해요.
만약 View Model을 쓰는 경우는 다시 생성을 하지 않을 경우 ViewModel의 onCleard 매서드를 호출하여 데이터를 정리해야 해요. 만약 자원 할당 해제를 안 할 경우 메모리 누수(memory leak)이 일어 날 수 있어요.

활동에 소멸되는 이유를 결정하는 로직을 입력하는 대신 ViewModel 객체를 사용하여 활동의 관련 뷰 데이터를 포함해야 합니다. 활동이 구성 변경으로 인해 다시 생성될 경우 ViewModel은 그대로 보존되어 다음 활동 인스턴스에 전달되므로 추가 작업이 필요하지 않습니다. 활동이 다시 생성되지 않을 경우 ViewModel은 onCleared() 메서드를 호출하여 활동이 소멸되기 전에 모든 데이터를 정리해야 합니다.
이와 같은 두 가지 시나리오는 isFinishing() 메서드로 구분할 수 있습니다.
활동이 종료되는 경우 onDestroy()는 활동이 수신하는 마지막 수명 주기 콜백이 됩니다. 구성 변경으로 인해 onDestroy()가 호출되는 경우 시스템이 즉시 새 활동 인스턴스를 생성한 다음, 새로운 구성에서 그 새로운 인스턴스에 관해 onCreate()를 호출합니다.
onDestroy() 콜백은 이전의 콜백에서 아직 해제되지 않은 모든 리소스(예: onStop())를 해제해야 합니다.

 

하지만 onDestory()는 100% 호출 된다는 보장이 없습니다. onStart/onStop, onResume/onPause에 자원 등록/해제 하는 것이 확실한 방법이라네요. 

onDestory는 100%호출 된다는 보장이 없는 콜백입니다. 간혹 호출되지 않고 앱이 종료될 수도 있습니다. 예를 들면, 안드로이드 시스템이 메모리가 부족하거나 하는 등의 이유로 앱을 킬할 수도 있기 때문입니다. 따라서 콜백같은 경우는 onStart/onStop, onResume/onPause 등을 통해서 등록/해제를 하는 것이 확실한 방법입니다.

개발자의 판단에 따라 자원할당/해제는 이루어져야 겠군요.

그러면 안드로이드 개발을 할 때 메모리 누수를 방지하기 위해서 무엇을 해야 할까요?

1. 메모리 누수 찾는 라이브러리 활용

메모리 누수를 감지하는 오픈소스 라이브러리인 LeakCanary가 있어요.
아래의 디펜던시를 Gradle에 넣어서 sync를 해주면, 디버그 모드일 때 작동을 하게 되요.

dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
}

LeakCanary는 5가지 객체들에 대해 메모리 누수를 감지해줘요.

파괴되서 GC가 회수를 해야하는데 회수가 안 될 경우를 감지하는 것이죠.

  • destroyed Activity 인스턴스
  • destroyed Fragment 인스턴스
  • destroyed fragment View 인스턴스
  • cleared ViewModel 인스턴스
  • destroyed Service 인스턴스

LeakCanary는 메모리 누수를 4 단계를 걸처 감지해요.

  1. Detecting retained objects. (retained 객체 감지)
  2. Dumping the heap. (힙 덤프)
  3. Analyzing the heap. (힙 분석)
  4. Categorizing leaks. (누수 분류)

메모리 누수 분석

메모리 누수를 감지한 것을 토대로 개발자가 수정을 해야겠죠?

2. 메모리 누수가 일어날 수 있는 케이스 대응

개발자가 메모리 누수가 자주 일어날 수 있는 케이스들을 알고 해당 케이스들을 미리 대비하면 좋겠죠?

그러면 어떤 케이스들이 있을까요?

알아봅시다!

1. Broadcast Receivers

Broadcast Recevier 객체를 Activity에 등록하여 사용할 경우 문제가 발생 할 수도 있어요.

Activity가 등록 해제를 안 하고 종료 될 경우, Broadcast Recevier가 Activity를 가리키고 있어서 문제가 발생하게 되요.

=> onStart()에 Broadcast Recevier를 등록하고, onStop()에는 등록해제 하도록 꼭! 기억하자.

2. Static Activity or View Reference

당연한 이야기 지만 Activity, View, Context는 static으로 선언하면 안 되요! GC가 해당 객체를 수거해가지 못 해요.

3. Singleton Class Reference

Singleton 클래스에 Context가 필요한 경우, Application Context를 던져 줄 것!

만약 Activitiy를 던져 주면, Activity가 Destroy 이후에도 레퍼런스가 존재하여 메모리 누수가 일어나요.

4. Inner Class Reference

내부 클래스를 쓸 때는 주의 해야 해요. 잘 못 된 사용은 메모리 누수로 직결되요.

주의 할 점은 

  • 내부 클래스의 정적 변수를 쓰지 않는다. 
  • 익명 클래스 시 static으로 설정 한다.
    • 바깥 클래스의 묵시적 참조를 보유하지 않도록 하기 위함
  • View/Activity는 weakReference를 사용한다.
    • GC에 의해 수거되기 위함

개발자 판단 하에 내부 클래스를 적절하게 쓰는 것이 중요하겠죠?

5. Anonymous Class Reference

익명 클래스 또한 내부 클래스와 똑같은 요령으로 처리하면 되요.

6. AsyncTask Reference

비동기 작업을 위해 클래스를 만들었으면, 주의 해야할 사항이 있어요.

  • Activity 생명 주기 내에서 작동이 멈추도록 해야 한다.
    • onDestroy에서라도 해당 객체가 있으면 멈추도록 한다.
  • Activity 내에서 해당 클래스를 참조하면 안 된다.
    • 만약 하게 되면, Static으로 클래스를 설정을 해야 한다.
      • 부모 클래스의 암시적 참조 보유하지 않기 위함이다.
  • 내부의 View/Activity는 weakReference를 사용한다.

7. Handler Reference

Handler를 사용해야하는 경우 주의 해야할 사항이 있어요.

  • Activity 내부에서 해당 클래스를 참조하지 않는다.
    • 만약 하게 되면, Static으로 클래스를 설정을 해야 한다.
      • 메인 쓰레드에서 인스턴스화 할때 Looper의 메시지 큐와 연관이 되기 때문에, 메시지 큐에 해당 참조를 보유하고 있다.
  • 내부의 Activity는 weakReference를 사용한다.

8. Threads Reference

Threads는 사용해야할 경우 주의할 점은 

  • Non-static으로 인한 암묵적 참조 허용
    • Static을 사용하는 것이 좋겠죠?
  • Activity 생명 주기 내에서 작동이 멈추도록 해야 한다.
    • onDestroy에서라도 해당 객체가 있으면 멈추도록 한다.

9. TimerTask Reference

Timer Task 사용 시 주의할 점은

  • Activity 생명 주기 내에서 작동이 멈추도록 해야 한다.
    • onDestroy에서라도 해당 객체가 있으면 멈추도록 한다.

10. webview 

자주 사용되는 webView에서도 메모리 누수가 발생할 수가 있어요.

사용시 주의 해야할 점은

  • intance 생성을 통해 처리해야 한다.
  • 완전히 연관 리로스를 clear해라.
    • freeMemory는 deprecated되었으니, loadUrl("about:blank")로 대체 권장
  @Override
    protected void onDestroy() {
        super.onDestroy();
        mWebView.removeJavascriptInterface("bridge");

        mWebView.loadUrl("about:blank");

        jsInterface = null;
        mJSCallback = null;

        mWebviewlayout.destroyDrawingCache();
        mWebviewlayout.removeAllViews();
        mWebviewlayout.removeAllViewsInLayout();
        mWebviewlayout.setVisibility(View.GONE);
        mWebviewlayout = null;

        mWebView.clearHistory();
        mWebView.destroyDrawingCache();
        mWebView.setWebViewClient(null);
        mWebView.setWebChromeClient(null);
        mWebView.removeAllViews();
        mWebView.clearCache(true);
        mWebView.freeMemory();
        mWebView.removeAllViewsInLayout();
        mWebView.setVisibility(View.GONE);
        mWebView.destroy();
        mWebView = null;
        ...

11. view binding 

Fragment에서 사용되는 View Binding는 메모리 누수를 막기 위해서 onDestoryView()에서 null를 통해 할당 해제해야 해요.

    private ResultProfileBinding binding;

    @Override
    public View onCreateView (LayoutInflater inflater,
                              ViewGroup container,
                              Bundle savedInstanceState) {
        binding = ResultProfileBinding.inflate(inflater, container, false);
        View view = binding.getRoot();
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }

12. app data

최후에는 apk별 임시 저장소인 data>data>{app package}의 내용을 지우라네요. 
해당 폴더의 내용이 싹다 지워지니 개발자의 판단하에 진행을 하면 되요.

 13. RecyclerView

recyclerView에 adapter를 할당해서 쓰이는데, 파괴 되는 과정에서 adapter가 recyclerView보다 생명 주기가 더 긴 경우가 발생하게 됩니다. 그 때는 recyclerView에게 빈 객체를 넣어 주는 것이 좋습니다.

mRecyclerView.setAdapter(new RecyclerView.Adapter() {
            @NonNull
            @Override
            public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
                return null;
            }

            @Override
            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

            }

            @Override
            public int getItemCount() {
                return 0;
            }
        });

이로써 안드로이드에서 Activity와 Fragment의 수명 주기에 대해 살짝 보았고, 주기 중 일부에서 자원 할당 해제가 필요하다는 것을 알아 내는 시간이였어요.

그리고 메모리 누수를 막기 위해서 기억해야하는 케이스들에대해 좀 다뤄봤어요. 소개한 것보다 더 많은 케이스가 있을 수 있으니 더 찾아보는 것도 좋은 공부가 되겠어요. 


https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko 

 

활동 수명 주기에 관한 이해  |  Android 개발자  |  Android Developers

활동은 사용자가 전화 걸기, 사진 찍기, 이메일 보내기 또는 지도 보기와 같은 작업을 하기 위해 상호작용할 수 있는 화면을 제공하는 애플리케이션 구성요소입니다. 각 활동에는 사용자 인터페

developer.android.com

https://developer.android.com/guide/fragments/lifecycle

 

Fragment lifecycle  |  Android Developers

Fragment lifecycle Stay organized with collections Save and categorize content based on your preferences. Each Fragment instance has its own lifecycle. When a user navigates and interacts with your app, your fragments transition through various states in t

developer.android.com

https://enumclass.tistory.com/9

 

android memory leak 처리

Inner Class 누수 Activity 내부에 아래와 같이 Inner Class를 정의 할 경우 잠재적으로 누수의 대상이 된다. ... public class MainActivity extends Activity { private static Innserclass inner; private String mStr; @Override protected

enumclass.tistory.com

https://www.masterqna.com/android/97395/%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0%EB%A5%BC-%EC%A2%85%EB%A3%8C%EC%8B%9C%ED%82%AC%EB%95%8C-%ED%95%B4%EC%A0%9C%ED%95%98%EB%A0%A4%EB%A9%B4-onstop-null%EB%A1%9C-%ED%95%B4%EC%A4%98%EC%95%BC-%ED%95%98%EB%82%98%EC%9A%94-%EC%95%84%EB%8B%88%EB%A9%B4-ondestroy-%ED%95%B4%EC%A4%98%EC%95%BC-%ED%95%98%EB%82%98%EC%9A%94

 

안드로이드 - 액티비티를 종료시킬때, 메모리 해제하려면, onStop()에서 변수를 null로 초기화 해줘

안드로이드 라이프사이클은 상당히 복잡한 토픽입니다. 경험이 상당히 많은 개발자들도 늘 문서를 들여다 보고 테스트를 해봐야 할 정도록 헷갈리는 것 중의 하나입니다. onDestory는 100%호출 된

www.masterqna.com

https://leveloper.tistory.com/197

 

[Android] LeakCanary로 메모리릭 잡기

LeakCanary란? LeakCanary란 Square사에서 만든 오픈소스 라이브러리로써, 메모리릭을 감지하여 OOM(Out of Memory) 에러를 줄일 수 있도록 도와준다. 메모리릭(Memory leak)이란? 메모리릭이란 애플리케이션에

leveloper.tistory.com

https://square.github.io/leakcanary/

 

LeakCanary

🤔 Documentation issue? Report or edit LeakCanary 🐤 LeakCanary is a memory leak detection library for Android. LeakCanary’s knowledge of the internals of the Android Framework gives it a unique ability to narrow down the cause of each leak, helping

square.github.io

https://medium.com/android-news/9-ways-to-avoid-memory-leaks-in-android-b6d81648e35e

 

9 ways to avoid memory leaks in Android

I have been an android developer for quite some time now. And I realised that most of that time, I tend to spend on adding new features to…

medium.com

https://developer.android.com/topic/libraries/view-binding?hl=ko 

 

뷰 결합  |  Android 개발자  |  Android Developers

뷰 결합 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 뷰 결합 기능을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있습니다. 모듈에서 사용 설정

developer.android.com

 

728x90
반응형