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

개발중인 앱의 메모리 누수를 파악하기 위해 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 () 왼쪽과 오른쪽까지도 앱이 충돌합니다.

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

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

감사합니다.



답변

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

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

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

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

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

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

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


답변

몇 가지 팁 :

  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 앱용 메모리 관리
    비트 맵을 다루는 경우 반드시 읽어야합니다. 비트 맵을 효율적으로 표시


답변

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


답변

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

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


답변

나는 문제가 여기에 언급 된 많은 요소의 조합이 문제를 일으키는 것이라고 생각합니다. @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()프로세스 비 킬링 프론트에 대한 몇 가지 통찰력이 있는 페이지에 대한 링크입니다 .


답변

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


답변

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