Android 앱 메모리 부족 문제-모든 것을 시도했지만 여전히 손실


87

개발중인 앱의 메모리 누수를 파악하기 위해 4 일 동안 모든 노력을 다했지만 오래 전에는 이해가되지 않았습니다.

내가 개발중인 앱은 사회적 성격을 띠고 있으므로 활동 프로필 (P)을 생각하고 데이터와 함께 활동을 나열합니다 (예 : 배지 (B)). 프로필에서 배지 목록, 다른 프로필, 다른 목록 등으로 이동할 수 있습니다.

따라서 P1-> B1-> P2-> B2-> P3-> B3 등과 같은 흐름을 상상해보십시오. 일관성을 위해 동일한 사용자의 프로필과 배지를로드하고 있으므로 각 P 페이지는 동일합니다. 각 B 페이지.

문제의 일반적인 요지는 각 페이지의 크기에 따라 조금씩 탐색 한 후 임의의 위치 (비트 맵, 문자열 등)에서 메모리 부족 예외가 발생합니다. 일관성이없는 것 같습니다.

내가 기억이 부족한 이유를 알아 내기 위해 상상할 수있는 모든 일을했지만 아무것도 생각하지 못했습니다. 내가 이해하지 못하는 것은 로딩시 메모리가 부족하고 대신 충돌하는 경우 Android가 P1, B1 등을 죽이지 않는 이유입니다. onCreate () 및 onRestoreInstanceState ()를 통해 다시 돌아 오면 이러한 초기 활동이 죽고 부활 할 것으로 예상합니다.

이것은 말할 것도없고-내가 P1-> B1-> 뒤로-> B1-> 뒤로-> B1을해도 여전히 충돌이 발생합니다. 이것은 일종의 메모리 누수를 나타내지 만 hprof를 덤프하고 MAT 및 JProfiler를 사용한 후에도 정확히 찾을 수 없습니다.

웹에서 이미지로드를 비활성화하고 (그리고이를 보완하고 테스트를 공정하게 만들기 위해로드 된 테스트 데이터를 늘 렸습니다) 이미지 캐시가 SoftReferences를 사용하는지 확인했습니다. Android는 실제로 가지고있는 몇 가지 SoftReference를 해제하려고하지만 메모리가 부족해지기 직전입니다.

배지 페이지는 웹에서 데이터를 가져 와서 BaseAdapter의 EntityData 배열로로드하고이를 ListView에 공급합니다 (실제로 CommonsWare의 우수한 MergeAdapter를 사용 하고 있지만이 배지 활동에서는 어쨌든 어댑터가 1 개뿐입니다. 이 사실을 어느 쪽이든 언급하고 싶었습니다).

나는 코드를 살펴본 결과 누출되는 것을 찾을 수 없었습니다. 내가 찾을 수있는 모든 것을 지우고 무효화했고 심지어 System.gc () 왼쪽과 오른쪽까지도 앱이 충돌합니다.

스택에있는 비활성 활동이 왜 거두어지지 않는지 여전히 이해가 안되며 그 사실을 알아 내고 싶습니다.

이 시점에서 나는 도움이 될 수있는 힌트, 조언, 해결책을 찾고 있습니다.

감사합니다.


그렇다면 지난 20 초 동안 15 개의 활동을 열었다면 (사용자가 매우 빠르게 진행 중입니다) 그게 문제일까요? 활동이 표시된 후 활동을 지우려면 어떤 코드 줄을 추가해야합니까? 나는 얻을 outOfMemory오류입니다. 감사!
Ruchir Baronia

답변:


109

스택에있는 비활성 활동이 왜 거두어지지 않는지 여전히 이해가 안되며 그 사실을 알아 내고 싶습니다.

이것은 일이 작동하는 방식이 아닙니다. 활동 수명주기에 영향을 미치는 유일한 메모리 관리는 모든 프로세스 의 전역 메모리입니다. Android는 메모리가 부족하여 일부를 되찾기 위해 백그라운드 프로세스를 종료해야한다고 결정합니다.

애플리케이션이 포 그라운드에 앉아 더 많은 활동을 시작하는 경우 백그라운드로 이동하지 않으므로 시스템이 프로세스를 종료하기 전에 항상 로컬 프로세스 메모리 제한에 도달합니다. (그리고 프로세스를 종료하면 현재 포 그라운드에있는 모든 활동을 포함하여 모든 활동을 호스팅하는 프로세스 가 종료됩니다.)

그래서 당신의 기본적인 문제는 당신이 너무 많은 활동을 동시에 실행하게하고 / 또는 각 활동이 너무 많은 자원을 보유하고 있다는 것입니다.

잠재적으로 무거운 활동을 임의의 수에 의존하지 않도록 탐색을 다시 디자인하면됩니다. onStop ()에서 상당한 양의 작업을 수행하지 않는 한 (예 : setContentView ()를 호출하여 활동의 뷰 계층 구조를 지우고 다른 변수의 변수를 지우는 등) 메모리가 부족하게됩니다.

이 임의의 활동 스택을 메모리를보다 엄격하게 관리하는 단일 활동으로 대체하기 위해 새로운 Fragment API를 사용하는 것을 고려할 수 있습니다. 예를 들어 프래그먼트의 백 스택 기능을 사용하는 경우 프래그먼트가 백 스택으로 이동하여 더 이상 표시되지 않으면 onDestroyView () 메서드가 호출되어 뷰 계층 구조를 완전히 제거하여 공간을 크게 줄입니다.

이제 뒤로 누르고, 활동으로 이동하고, 뒤로 누르고, 다른 활동으로 이동하는 등의 흐름에서 충돌이 발생하고 딥 스택이없는 한 누수가 있습니다. 이 블로그 게시물은 누출을 디버그하는 방법을 설명합니다. http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html


47
OP의 변호에서 이것은 문서가 제안하는 것이 아닙니다. 인용 developer.android.com/guide/topics/fundamentals/... "(활동 정지), 그것은 더 이상 표시 사용자에게없는 그것은 메모리가 다른 곳에서 필요로하는 시스템에 의해 살해 될 수있다." "활동이 일시 중지되거나 중지되면 시스템은 완료를 요청하여 메모리에서 삭제할 수 있습니다 (finish () 메서드 호출)." "(onDestroy ()가 호출 됨) 시스템이이 인스턴스를 일시적으로 파괴하기 때문입니다. 공간을 절약하기위한 활동입니다. "
CommonsWare 2011 년

42
같은 페이지에서 "그러나 시스템이 메모리를 복구하기 위해 활동을 파괴하는 경우". 당신이 나타내는 것은 Android 가 메모리를 회수하기 위해 활동을 파괴 하지 않고 그렇게 하기 위해 프로세스를 종료한다는 것입니다. 그렇다면이 페이지는 Android가 메모리를 회수하기 위해 활동을 파괴 할 것이라고 반복적으로 제안하기 때문에 심각한 재 작성이 필요합니다 . 또한 인용 된 많은 구절이 ActivityJavaDocs 에도 존재한다는 점에 유의하십시오 .
CommonsWare 2011 년

6
나는 이것을 여기에 넣었다고 생각했지만 이것에 대한 문서 업데이트 요청을 게시했습니다 : code.google.com/p/android/issues/detail?id=21552
Justin Breitfeller

15
2013 년이되었고 문서는 잘못된 점을 더 명확하게 제시했습니다. developer.android.com/training/basics/activity-lifecycle/… , "활동이 중지되면 시스템이 복구해야하는 경우 인스턴스를 파괴 할 수 있습니다. 시스템 메모리. 극단적 인 경우 시스템은 단순히 앱 프로세스를 종료 할 수 있습니다. "
kaay

2
글쎄, 쓰레기. 나는 항상 (문서 때문에) 시스템이 프로세스에서 중지 된 활동을 관리하고 필요에 따라 일련 화 및 역 직렬화 (파괴 및 생성) 할 것이라고 생각했습니다.
dcow

21

몇 가지 팁 :

  1. 활동 컨텍스트가 누출되지 않았는지 확인하십시오.

  2. 비트 맵에 대한 참조를 유지하지 않도록하십시오. Activity # onStop의 모든 ImageView를 다음과 같이 정리하십시오.

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  3. 더 이상 필요하지 않으면 비트 맵을 재활용하십시오.

  4. memory-lru와 같은 메모리 캐시를 사용하는 경우 많은 메모리를 사용하지 않는지 확인하십시오.

  5. 이미지는 많은 메모리를 차지할뿐만 아니라 메모리에 다른 데이터를 너무 많이 보관하지 않도록하십시오. 앱에 무한 목록이있는 경우 쉽게 발생할 수 있습니다. DataBase에서 데이터를 캐시 해보십시오.

  6. Android 4.2에는 하드웨어 가속에 버그 (stackoverflow # 13754876) 가 있으므로 hardwareAccelerated=true매니페스트에서 사용하면 메모리 누수가 발생합니다. GLES20DisplayList-(2) 단계를 수행하고 다른 사람이이 비트 맵을 참조하지 않더라도 참조를 유지하십시오. 여기에 다음이 필요합니다.

    a) api 16/17에 대한 하드웨어 가속 비활성화
    또는
    b) 비트 맵을 보유한 뷰 분리

  7. Android 3 이상 android:largeHeap="true"에서는 AndroidManifest. 그러나 그것은 당신의 기억 문제를 해결하지 않고 단지 연기합니다.

  8. 무한 탐색과 같이 필요한 경우 Fragments를 선택해야합니다. 따라서 하나의 활동이 있으며 조각간에 전환됩니다. 이렇게하면 4 번과 같은 일부 메모리 문제도 해결할 수 있습니다.

  9. 메모리 분석기를 사용하여 메모리 누수의 원인을 찾으십시오.
    다음은 Google I / O 2011의 아주 좋은 동영상 입니다. Android 앱용 메모리 관리
    비트 맵을 다루는 경우 반드시 읽어야합니다. 비트 맵을 효율적으로 표시


그렇다면 지난 20 초 동안 15 개의 활동을 열었다면 (사용자가 매우 빠르게 진행 중입니다) 그게 문제일까요? 활동이 표시된 후 활동을 지우려면 어떤 코드 줄을 추가해야합니까? 나는 얻을 outOfMemory오류입니다. 감사!
Ruchir Baronia

4

비트 맵은 Android에서 메모리 오류의 원인이되는 경우가 많으므로 다시 확인하는 것이 좋습니다.


그렇다면 지난 20 초 동안 15 개의 활동을 열었다면 (사용자가 매우 빠르게 진행 중입니다) 그게 문제일까요? 활동이 표시된 후 활동을 지우려면 어떤 코드 줄을 추가해야합니까? 나는 얻을 outOfMemory오류입니다. 감사!
Ruchir Baronia

2

각 활동에 대한 참고 자료를 보유하고 있습니까? AFAIK 이것은 Android가 스택에서 활동을 삭제하지 못하게하는 이유입니다.

다른 장치에서도이 오류를 재현 할 수 있습니까? ROM 및 / 또는 하드웨어 제조업체에 따라 일부 Android 장치에서 이상한 동작을 경험했습니다.


최대 힙이 16MB로 설정된 CM7을 실행하는 Droid에서이를 재현 할 수 있었는데, 이는 에뮬레이터에서 테스트하는 것과 동일한 값입니다.
Artem Russakovskii 2011 년

당신은 무언가에있을 수 있습니다. 두 번째 활동이 시작되면 첫 번째 활동이 onPause-> onStop 또는 onPause를 수행합니까? 모든 on ******* 수명주기 호출을 인쇄 중이고 onStop없이 onPause-> onCreate가 표시되기 때문입니다. 그리고 크래시 덤프 중 하나는 실제로 죽이는 3 가지 활동에 대해 onPause = true 또는 onStop = false와 같은 것을 말했습니다.
Artem Russakovskii 2011 년

OnStop은 활동이 화면을 벗어날 때 호출되어야하지만 시스템에서 조기에 회수 할 경우 호출되지 않을 수 있습니다.
dten 2011 년

다시 클릭하면 onCreate 및 onRestoreInstanceState가 호출되지 않기 때문에 회수되지 않습니다.
Artem Russakovskii 2011 년

라이프 사이클에 따르면 결코 호출되지 않을 수도 있습니다. 공식 개발자 블로그에 대한 링크가있는 내 답변을 확인하십시오. 비트 맵을 전달하는 방식보다 더 많을 것입니다
dten

2

나는 문제가 여기에 언급 된 많은 요소의 조합이 문제를 일으키는 것이라고 생각합니다. @Tim이 말했듯이 활동 또는 해당 활동의 요소에 대한 (정적) 참조로 인해 GC가 활동을 건너 뛸 수 있습니다. 이 패싯을 설명하는 기사는 다음과 같습니다 . 나는 가능성이있는 문제가 활동을 "가시적 프로세스"상태 이상으로 유지하는 것에서 비롯된 것이라고 생각합니다. 이는 활동 및 관련 리소스가 절대 회수되지 않는다는 것을 거의 보장 할 것입니다.

나는 서비스와 함께 한동안 반대 문제를 겪었다. 그래서 나는 이런 생각을하게되었다. 당신의 활동을 프로세스 우선 순위 목록에서 높게 유지하여 시스템 GC의 영향을받지 않게하는 무언가가있다. 참조 (@Tim) 또는 루프 (@Alvaro). 루프는 끝이 없거나 오래 실행되는 항목 일 필요가 없으며 재귀 메서드 나 계단식 루프 (또는 해당 라인을 따라 실행되는 항목)처럼 많이 실행되는 항목 일뿐입니다.

편집 : 내가 이것을 이해하면 onPause 및 onStop은 Android에서 필요에 따라 자동으로 호출됩니다. 호스팅 프로세스가 중지되기 전에 필요한 작업 (변수 저장, 상태 수동 저장 등)을 처리 할 수 ​​있도록이 메서드는 주로 재정의 할 수 있습니다. 그러나 onStop (onDestroy와 함께) 모든 경우에 호출되는 것은 아니라는 점에 유의하십시오 . 또한 호스팅 프로세스가 "Forground"또는 "Visible"상태 인 Activity, Service 등도 호스팅하는 경우 OS는 프로세스 / 스레드를 중지하지 않을 수도 있습니다. 예 : 활동과 서비스가 모두 동일한 프로세스에서 luanched되고 서비스가 반환합니다.START_STICKY 에서onStartCommand()프로세스는 자동으로 최소한 가시 상태를 취합니다. 이것이 핵심 일 수 있습니다. 활동에 대한 새 proc을 선언하고 변경 사항이 있는지 확인하십시오. 이 줄을 매니페스트의 활동 선언에 android:process=":proc2"다음과 같이 추가해보십시오. 활동이 다른 프로세스와 프로세스를 공유하는 경우 테스트를 다시 실행하십시오. 여기에 생각은 당신이 당신의 활동을 정리하고있는 한 경우이다 확인에 문제가 활동 아니라고 후 뭔가 다른 문제와 그에 대한 사냥꾼의 시간입니다.

또한 어디에서 보았는지 기억할 수 없지만 (Android 문서에서 본 경우에도) PendingIntentActivity 참조 에 대한 내용이 있으면 Activity가 이러한 방식으로 동작 할 수 있다는 것을 기억합니다 .

다음onStartCommand()프로세스 비 킬링 프론트에 대한 몇 가지 통찰력이 있는 페이지에 대한 링크입니다 .


제공 한 링크 (감사합니다)를 기반으로 onStop이 호출되면 활동이 종료 될 수 있지만 제 경우에는 onStop 실행을 방해하는 것이 있다고 생각합니다. 나는 확실히 이유를 조사 할 것이다. 그러나 그것은 또한 onPause 만 사용하는 활동도 종료 될 수 있다고 말하며 내 경우에는 발생하지 않습니다 (onPause가 호출되지만 onStop은 아님).
Artem Russakovskii 2011 년

1

그래서 제가 생각할 수있는 유일한 것은 컨텍스트를 직접 또는 간접적으로 참조하는 정적 변수가있는 경우입니다. 응용 프로그램의 일부에 대한 참조도 마찬가지입니다. 나는 당신이 이미 그것을 시도했다고 확신하지만 가비지 수집기가 그것을 얻는 지 확인하기 위해 onDestroy ()에서 모든 정적 변수를 nulling 해보십시오.


1

내가 찾은 메모리 누수의 가장 큰 원인은 컨텍스트에 대한 전역 적, 높은 수준 또는 오랜 참조로 인해 발생했습니다. "컨텍스트"를 변수에 저장해두면 예측할 수없는 메모리 누수가 발생할 수 있습니다.


그래, 나는 이것을 여러 번 듣고 읽었지만 그것을 고정하는 데 어려움을 겪었다. 그것들을 추적하는 방법을 확실히 알아낼 수만 있다면.
Artem Russakovskii 2011 년

1
제 경우에는 컨텍스트와 동일하게 설정된 클래스 수준의 모든 변수로 변환되었습니다 (예 : Class1.variable = getContext ();). 일반적으로 내 앱의 모든 "context"사용을 "getContext"또는 이와 유사한 새로운 호출로 대체하면 가장 큰 메모리 문제가 해결되었습니다. 그러나 내 것은 불안정하고 불규칙하며 귀하의 경우처럼 예측할 수 없으므로 아마도 다른 것입니다.
D2TheC 2011 년

1

컨텍스트가 필요한 모든 것에 getApplicationContext ()를 전달해보십시오. 활동에 대한 참조를 보유하고 가비지 수집되는 것을 방지하는 전역 변수가있을 수 있습니다.


1

필자의 경우 메모리 문제에 실제로 도움 이 된 것 중 하나는 결국 Bitmaps에 대해 inPurgeable을 true로 설정했습니다. BitmapFactory의 inPurgeable 옵션을 사용하지 않는 이유는 무엇입니까?를 참조하십시오 . 자세한 내용은 답변의 토론을 참조하십시오.

Dianne Hackborn의 답변과 우리의 후속 토론 (또한 감사합니다, CommonsWare)은 제가 혼란 스러웠던 특정 사항을 명확히하는 데 도움이되었으므로 감사합니다.


나는 이것이 오래된 주제라는 것을 알고 있지만 많은 검색 에서이 스레드를 찾는 것 같습니다. 여기에서 많이 배운 훌륭한 튜토리얼을 링크하고 싶습니다 : developer.android.com/training/displaying-bitmaps/index.html
Codeversed

0

나는 당신과 같은 문제에 직면했습니다. 인스턴트 메시징 앱에서 작업 중이 었는데 동일한 연락처에 대해 ChatActivity에서 ProfileActivity를 시작할 수 있으며 그 반대의 경우도 마찬가지입니다. 다른 활동을 시작하려는 의도에 문자열을 추가하기 만하면 시작 활동의 클래스 유형 정보와 사용자 ID를 가져옵니다. 예를 들어 ProfileActivity는 ChatActivity를 시작한 다음 ChatActivity.onCreate에서 호출자 클래스 유형 'ProfileActivity'와 사용자 ID를 표시하고 활동을 시작할 경우 사용자의 'ProfileActivity'인지 여부를 확인합니다. . 그렇다면 'finish ()'를 호출하고 새 프로필을 만드는 대신 이전 ProfileActivity로 돌아갑니다. 메모리 누수는 또 다른 문제입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.