메모리가 부족한데요?

Updated:

메모리가 부족하다!!!!!!!!!

얼마전 회사에서 개발을 하다가 앱이 백그라운드에서 있다가 다시 포어그라운드로 올 때

앱이 재시작 되는 경우 (메모리 할당이 해제된) 에서

singleton 객체들이 제대로 초기화 안된 경우를 경험했다.

앱의 시작 flow 를 제대로 거치지 않고 메모리 해제가 되었던 부분만 다시 Loading 하다 보니

singleton 객체들이 메모리 해제 이후 정상적인 프로세스를 거치지 못한 것 이었다……

그래서 여러가지로 찾아 보다가 onTrimMemory 를 찾았다.

앱 메모리 관리 를 보면 앱의 메모리 상태가 크게 4가지 경우를 거칠 수 있다고 되어있다.

  • 백그라운드로 이동한 경우
  • 앱이 실행되는 동안 장치의 메모리가 부족한 경우
  • 앱이 LRU 목록에 있고 시스템 메모리가 부족한경우

  • 기타 경우

로 나뉠 수 있다.

onTrimMemory 에서 로그를 찍게하고 Fill Ram Memory을 이용하여

디바이스의 메모리를 강제로 채우게 하다보니

각각의 상태를 경험 할 수 있었다.

그중 앱이 가진 memory 가 해제 되는 경우는

ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE, ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW, ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL

의 경우 와 추가적으로

ComponentCallbacks2.TRIM_MEMORY_COMPLETE

의 경우에서는 드물게

앱에 할당된 메모리가 release 되는 현상을 경험했다.

그렇기에 해당 경우를 앱을 사용하지 않는 경우로 잡아두고

해당 경우를 경험 할 때

앱을 강제로 죽이도록 로직을 짜두었다.

    override fun onTrimMemory(level: Int) {
        if (level == TRIM_MEMORY_RUNNING_CRITICAL || level == TRIM_MEMORY_RUNNING_LOW
            || level == TRIM_MEMORY_RUNNING_MODERATE || level == TRIM_MEMORY_COMPLETE
        ) {
            finishAffinity()
            exitProcess(0)
        } else {
            super.onTrimMemory(level)
        }
    }

하지만 앱이 포어그라운드에 있을 때에도 onTrimMemory 가 호출 되게 되었고

메모리가 작은 디바이스에서는 정상적인 앱 사용 경우에도 앱이 죽어 버리는 현상을 겪었다.

따라서 앱이 foreground 인지 여부를 판단하는 것을 lifecycleHandler 를 이용하여 구현 하였고

class lifecycleHandler(
    application: Application
) {
    private var runningActivityCount = 0
	  private val lock = Any()

    init {
        application.registerActivityLifecycleCallbacks(
            object : Application.ActivityLifecycleCallbacks {

                override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}

                override fun onActivityStarted(activity: Activity) {
                    synchronized(lock) {
                        ++runningActivityCount
                    }
                }

                override fun onActivityResumed(activity: Activity) {
                }

                override fun onActivityPaused(activity: Activity) {}

                override fun onActivityStopped(activity: Activity) {
										synchronized(lock) {
                        --runningActivityCount
                    }
                }

                override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}

                override fun onActivityDestroyed(activity: Activity) {}

            }
        )
    }

    fun isApplicationForeGround(): Boolean {
        return runningActivityCount > 0
    }
} 

background 상태에서만 앱이 죽도록 설정 하도록 하여

해당 이슈를 마무리하였다.

    override fun onTrimMemory(level: Int) {
        if (!isApplicationForeground()) {
            if (level == TRIM_MEMORY_RUNNING_CRITICAL || level == TRIM_MEMORY_RUNNING_LOW
                || level == TRIM_MEMORY_RUNNING_MODERATE || level == TRIM_MEMORY_COMPLETE
            ) {
                finishAffinity()
                exitProcess(0)
            } else {
                super.onTrimMemory(level)
            }
        }
    }

해당 처리 방법은 내가 짜서 만든 코드지만 그리 좋지 못한 방법이라고 생각한다.

최선보다 차악을 선택한 기분이랄까

singleton 객체의 메모리가 왜 해제되었을 때 정상적인 초기화가 되지 못하는지

앱을 죽이는 게 최선인지 등등 수 많은 생각을 하게 만들었으며

언젠가는 해당 코드들을 모두 걷어내고 좀 더 깔끔하고

앱이 메모리 해제를 당해도 백그라운드에서 제대로 동작하는

그런 좋은 코드를 짜고 싶다. (+ 글쓰는 솜씨도 )