2015-02-12 3 views
20

ПРОБЛЕМА:Android Фрагменты на Backstack занимают слишком много памяти

У меня есть Android-приложение, которое позволяет пользователю просматривать в профиле пользователя ViewProfileFragment. Внутри ViewProfileFragment пользователь может нажать на изображение, которое приведет его к StoryViewFragment, где появятся фотографии различных пользователей. Можно щелкнуть по фотографии профиля пользователя, которая перенесет их в другой экземпляр ViewProfileFragment с профилем нового пользователя. Если пользователь повторно нажимает на профили пользователя, кликает изображение, которое переносит их в галерею, затем нажимает на другой профиль, который фрагменты складываются в память, быстро вызывая страшный OutOfMemoryError. Вот диаграмма, описывающая то, что я описываю:

Пользователь нажимает на профиль Боба. Профиль пользователя Bob's UserA нажимает на ImageA, беря его в галерею фотографий различных пользователей (включая Боба). UserA щелкает по профилю Си затем на одном из ее образов - процесс повторяется, и т.д., и т.д.

UserA -> ViewProfileFragment 
     StoryViewFragment -> ViewProfileFragment 
           StoryViewFragment -> ViewProfileFragment 

Так как вы можете видеть из обычного потока есть много примеров ViewProfileFragment и StoryViewFragment накапливался в backstack ,

Применимое КОД

Я загрузки их в виде фрагментов со следующей логикой:

//from MainActivity 
fm = getSupportFragmentManager(); 
ft = fm.beginTransaction(); 
ft.replace(R.id.activity_main_content_fragment, fragment, title); 
ft.addToBackStack(title); 

, что я попытался

1) Я специально с помощью FragmentTransactionreplace так что метод будет запущен, когда принимает replace место. Внутри Я пытаюсь высвободить столько ресурсов, сколько могу (например, очистка данных в адаптерах ListView, «обнуление» переменных и т. Д.), Так что, когда фрагмент не является активным фрагментом и нажимается на заднюю часть, будет освобождается память. Но мои усилия по освобождению ресурсов - это лишь частичный успех. Согласно MAT, у меня все еще много памяти, которая потребляется GalleryFragment и ViewProfileFragment.

2) Я также удалил вызов addToBackStack(), но, очевидно, это плохой пользовательский интерфейс, потому что он не может вернуться назад (приложение просто закрывается, когда пользователь нажимает кнопку «Назад»).

3) Я использовал MAT для поиска всех объектов, которые занимают много места, и я использовал их разными способами в методах (и onResume), чтобы высвободить ресурсы, но они все еще значительны в размере.

enter image description here

4) Я также написал цикл в оба фрагмента , который устанавливает все мои ImageViews к нулю, используя следующую логику:

for (int i=shell.getHeaderViewCount(); i<shell.getCount(); i++) { 

    View h = shell.getChildAt(i); 
    ImageView v = (ImageView) h.findViewById(R.id.galleryImage); 
     if (v != null) { 
      v.setImageBitmap(null); 
     } 
    } 

myListViewAdapter.clear() 

ВОПРОСЫ

1) Могу ли я игнорировать способ, позволяющий Фрагменту оставаться на заднем ходу, но также освобождать его ресурсы, чтобы цикл .replace (фрагмент) не съедал все o В моей памяти?

2) Каковы «лучшие практики», когда ожидается, что много фрагментов можно загрузить в заднюю часть? Как разработчик правильно справляется с этим сценарием? (Или логика в моем заявлении по своей сути ошибочна, и я просто делаю это неправильно?)

Любая помощь в мозговом штурме решения этого будет очень признательна.

+0

Вы используете метод onPause фрагмента? Http: //developer.android.com/reference/android/app/Fragment.html#onPause() Я думаю, вы должны освободить любые ресурсы растрового изображения, связанные с фрагментом в onPause метод фрагмента. И для повышения скорости, особенно если ваши изображения загружаются из Интернета, реализуйте кеш изображения. Вы можете использовать [Универсальный загрузчик изображений] (https://github.com/nostra13/Android-Universal-Image-Loader) или реализовать свой собственный. –

+0

Да ' .onPause()' попадает, когда выдается '.replace (new_fragment)'. Я не думаю, что это мои проблемы, поскольку я вызываю 'listAdapter.clear()' в методе 'onPause()', удаляя любые данные изображения. Они загружаются с использованием Pico. –

+0

AFAIK, 'listAdapter.clear()' не освобождает ресурсы растрового изображения, связанные со всем фрагментом ... По крайней мере, все они. Он удаляет только все элементы из списка. –

ответ

5

Оказывается, что фрагменты используют один и тот же жизненный цикл, как и их родительская активность. Согласно Fragment documentation:

Фрагмент всегда должен быть встроен в деятельности и жизненный цикл фрагмента напрямую зависит от жизненного цикла хозяина активность в. Например, если операция приостановлена, то есть все фрагменты в ней и , когда действие уничтожено, так же как и все фрагменты. Однако в то время как выполняется действие (оно находится в состоянии возобновленного жизненного цикла), вы можете самостоятельно управлять каждым фрагментом.

Таким образом, шаг, который вы предприняли для очистки некоторых ресурсов в onPause() фрагмента, не будет срабатывать, если родительская активность не приостановится. Если у вас есть несколько фрагментов, которые загружаются родительской активностью, то, скорее всего, вы используете какой-то механизм для переключения, который является активным.

Возможно, вы сможете решить проблему, не полагаясь на , но переопределяя setUserVisibleHint на фрагменте.Это дает вам хорошее место, чтобы определить, где выполнить настройку ресурсов или очистить ресурсы, когда фрагмент входит и выходит из вида (например, когда у вас есть PagerAdapter, который переключается с FragmentA на FragmentB).

public class MyFragment extends Fragment { 
    @Override 
    public void setUserVisibleHint(boolean isVisibleToUser) { 
    super.setUserVisibleHint(isVisibleToUser); 
    if (isVisibleToUser) { 
     //you are visible to user now - so set whatever you need 
     initResources(); 
    } 
    else { 
    //you are no longer visible to the user so cleanup whatever you need 
    cleanupResources(); 
    } 
    } 
} 

Как уже упоминалось вы штабелирования элементы вверх на backstack так ожидается, что будет по крайней мере, немного след памяти, но вы можете свести к минимуму след путем очистки ресурсов, когда фрагмент из с рассмотренной выше методикой.

Другое предложение состоит в том, чтобы получить действительно хорошее понимание вывода инструмента анализатора памяти (MAT) и анализа памяти в целом. Here is a good starting point. На самом деле очень легко протекать в памяти Android, поэтому, на мой взгляд, необходимо понять эту концепцию и как память может уйти от вас. Возможно, что ваши проблемы связаны с тем, что вы не освобождаете ресурсы, когда фрагмент выходит из поля зрения , а также утечки памяти, поэтому, если вы пройдете маршрут использования setUserVisibleHint, чтобы инициировать очистку ваших ресурсов, и вы все равно видите большой объем используемой памяти, тогда утечка памяти может быть преступником, поэтому убедитесь, что они оба вычеркнуты.

+0

Тот факт, что я забыл, что 'onPause' попадает только тогда, когда вызывается' onPause' родителя, - это то, что изначально сработало. Итак, теперь мои методы очистки ударяются, когда фрагмент выходит из поля зрения так же, как вы предлагали. Благодаря! –

+0

setUserVisibleHint не вызывается системой, поэтому полагаться на это было бы нецелесообразно – RunLoop

+0

Метод Fragments 'onPause() DO вызывается во время транзакции FragmentManager.replace. – duggulous

17

Трудно видеть всю картину (даже если вы показали нам много информации), без конкретного доступа к исходному коду, что, я уверен, было бы непрактичным, если не невозможным.

Это, как говорится, есть несколько вещей, которые следует иметь в виду при работе с Фрагментами. Сначала часть отказа от ответственности.

Когда были представлены фрагменты, они звучали как лучшая идея всех времен. Возможность отображать несколько видов деятельности в то же время, вид. Это был пункт продажи.

Итак, весь мир медленно начал использовать Фрагменты. Это был новый ребенок на блоке. Все использовали Фрагменты. Если вы не использовали Фрагменты, скорее всего, «вы делали это неправильно».

Несколько лет назад и приложения позже, тренд (к счастью) возвращается к большей активности, меньше фрагмента. Это обеспечивается новыми API-интерфейсами (возможность перехода между действиями, без которых пользователь действительно замечает, как это видно в API переходах и т. Д.).

Итак, в резюме: Ненавижу фрагменты. Я считаю, что это одна из худших реализаций Android всех времен, которая только завоевала популярность из-за отсутствия Transition Framework (как она существует сегодня) между действиями. Жизненный цикл фрагмента - это, во всяком случае, шар случайных обратных вызовов, которые никогда не гарантируются, когда вы их ожидаете.

(Хорошо, я немного преувеличиваю, но спросите любого разработчика Android, если у него возникли проблемы с фрагментами в какой-то момент, и ответ будет громким да).

При всем том, что говорят, Фрагменты работают. И поэтому ваше решение должно работать.

Итак, давайте начнем смотреть, кто и где может хранить эти жесткие ссылки.

примечание: Я просто собираюсь рассказать о том, как отлаживать это, но вряд ли я дам прямое решение. Используйте его как ссылку.

ЧТО ПРОИСХОДИТ?: Вы добавляете фрагменты в Backstack. Стойка содержит твердый Ссылка на Фрагмент, не слабая или мягкая. (source)

Теперь, кто хранит сток-подвал? FragmentManager и ... как вы уже догадались, он использует также живую ссылку (source).

И, наконец, каждое действие содержит твердую ссылку на FragmentManager.

Вкратце: пока ваша деятельность не умрет, все ссылки на ее фрагменты будут существовать в памяти. Независимо от операций добавления/удаления, которые выполнялись на уровне Fragment Manager/backstack.

ЧТО ВЫ МОЖЕТЕ СДЕЛАТЬ? Несколько вещей приходят мне на ум.

  1. Попробуйте использовать простой образ загрузчика/кэш-LIB, как Picasso, если что-нибудь, чтобы убедиться, что изображения не просочилась. Вы можете позже удалить его, если хотите использовать свою собственную реализацию. При всех его недостатках Пикассо действительно прост в использовании и пришел к состоянию, где он имеет дело с памятью «правильный путь».

  2. После того, как вы удалили проблему «Я могу протекать растровые изображения» из картинки (не каламбур!), Пришло время пересмотреть жизненный цикл фрагмента. Когда вы кладете фрагмент в backstack, он не уничтожается, но ... у вас есть шанс очистить ресурсы: вызывается Fragment#onDestroyView(). И здесь вы хотите убедиться, что фрагмент аннулирует любые ресурсы.

Вы не упоминаете, если ваши фрагменты используют setRetainInstance(true), будьте осторожны с этим, потому что они не разрушаются/воссозданы, когда активность уничтожаются/воссоздано (например, поворот), и все виды могут быть утечки если не правильно обрабатываться.

  1. Наконец, это сложнее диагностировать, может быть, вы захотите вернуться к своей архитектуре. Вы запускаете один и тот же фрагмент (viewprofile) несколько раз, вы можете рассмотреть вместо этого, повторно использовать один и тот же экземпляр и загрузить в него «нового пользователя». Backstack можно обрабатывать, отслеживая список пользователей в том порядке, в котором они загружены, поэтому вы можете перехватить onBackPressed и переместить «вниз» в стек, но всегда загружать новые/старые данные по мере того, как пользователь переходит. То же самое касается и вашего StoryViewFragment.

В целом, это все предложения, которые пришли из моего опыта, но очень сложно помочь вам, если мы не сможем увидеть более подробно.

Надеемся, это будет отправной точкой.

Удачи.

+0

Моя проблема, похоже, больше связана с утечкой памяти, с которой мне сложно отслеживать время. В идеальном мире каждый фрагмент не должен занимать очень много памяти, когда «onPause» попадает (если правильно закодировано), поэтому потребуется много времени, прежде чем возникнут какие-либо проблемы с памятью. В моем приложении есть только одно место, где это проблема (никакие другие фрагменты не отображают это поведение). Я использую MAT, чтобы сузить место утечки. В ближайшее время я уточню свой вопрос. –

+0

Прохладный. Удачи :) –

+1

@ MartínMarconcini хорошо, это очень полезный и тщательный ответ. +1 Не обращал внимания на Transition Framework – Drew

Смежные вопросы