ПОСЛЕДНЯЯ ИНФОРМАЦИЯ:
Я сузил свою проблему до проблемы с fragmentManager, сохраняющим экземпляры старых фрагментов, и мой просмотрщик не синхронизирован с моим FragmentManager. См. Эту проблему ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Я до сих пор не знаю, как это решить. Какие-либо предложения...
Я пытался отладить это в течение долгого времени, и любая помощь будет принята с благодарностью. Я использую FragmentPagerAdapter, который принимает список таких фрагментов:
List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName()));
...
new PagerAdapter(getSupportFragmentManager(), fragments);
Реализация стандартная. Я использую ActionBarSherlock и библиотеку вычислимости v4 для фрагментов.
Моя проблема в том, что после выхода из приложения и открытия нескольких других приложений и возврата фрагменты теряют свою ссылку обратно на FragmentActivity (т.е. getActivity() == null
). Я не могу понять, почему это происходит. Я пытался установить вручную, setRetainInstance(true);
но это не помогает. Я полагал, что это происходит, когда моя FragmentActivity разрушается, однако это все равно происходит, если я открываю приложение до того, как получаю сообщение журнала. Есть идеи?
@Override
protected void onDestroy(){
Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
super.onDestroy();
}
Адаптер:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
@Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
@Override
public int getCount() {
return this.fragments.size();
}
}
Один из моих фрагментов удален, но я сказал, что все раздели и все еще не работает ...
public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
handler = new Handler();
setHasOptionsMenu(true);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
context = activity;
if(context== null){
Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
}else{
Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
}
}
@Override
public void onActivityCreated(Bundle savedState) {
super.onActivityCreated(savedState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment,container, false);
return v;
}
@Override
public void onResume(){
super.onResume();
}
private void callService(){
// do not call another service is already running
if(startLoad || !canSet) return;
// set flag
startLoad = true;
canSet = false;
// show the bottom spinner
addFooter();
Intent intent = new Intent(context, MyService.class);
intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
context.startService(intent);
}
private ResultReceiver resultReceiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, final Bundle resultData) {
boolean isSet = false;
if(resultData!=null)
if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
removeFooter();
startLoad = false;
isSet = true;
}
}
switch(resultCode){
case MyService.STATUS_FINISHED:
stopSpinning();
break;
case SyncService.STATUS_RUNNING:
break;
case SyncService.STATUS_ERROR:
break;
}
}
};
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.activity, menu);
}
@Override
public void onPause(){
super.onPause();
}
public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;
if(away){
// startLoad can now be set again
canSet = true;
}
if(loadMore)
}
public void onScrollStateChanged(AbsListView arg0, int state) {
switch(state){
case OnScrollListener.SCROLL_STATE_FLING:
adapter.setLoad(false);
lastState = OnScrollListener.SCROLL_STATE_FLING;
break;
case OnScrollListener.SCROLL_STATE_IDLE:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_IDLE;
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
break;
}
}
@Override
public void onDetach(){
super.onDetach();
if(this.adapter!=null)
this.adapter.clearContext();
Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}
public void update(final int id, String name) {
if(name!=null){
getActivity().getSupportActionBar().setTitle(name);
}
}
}
Метод обновления вызывается, когда пользователь взаимодействует с другим фрагментом, и getActivity возвращает значение null. Вот метод, который вызывает другой фрагмент ...
((MyFragment) pagerAdapter.getItem(1)).update(id, name);
Я считаю, что когда приложение уничтожается, а затем создается снова, вместо того, чтобы просто запускать приложение до фрагмента по умолчанию, приложение запускается, а затем viewpager переходит на последнюю известную страницу. Это кажется странным, разве приложение не должно просто загружаться в фрагмент по умолчанию?
источник
Ответы:
Вы столкнулись с проблемой, потому что вы создаете экземпляры и храните ссылки на свои фрагменты вне
PagerAdapter.getItem
, и пытаетесь использовать эти ссылки независимо от ViewPager. Как говорит Сераф, у вас есть гарантии, что фрагмент был создан / добавлен в ViewPager в определенное время - это следует рассматривать как деталь реализации. ViewPager выполняет отложенную загрузку своих страниц; по умолчанию загружается только текущая страница, а также страницы слева и справа.Если вы поместите свое приложение в фоновый режим, фрагменты, которые были добавлены в диспетчер фрагментов, сохраняются автоматически. Даже если ваше приложение убито, эта информация восстанавливается при перезапуске приложения.
Теперь представьте, что вы просмотрели несколько страниц, фрагменты A, B и C. Вы знаете, что они были добавлены в диспетчер фрагментов. Поскольку вы используете,
FragmentPagerAdapter
а неFragmentStatePagerAdapter
, эти фрагменты все равно будут добавляться (но потенциально отсоединяться) при прокрутке к другим страницам.Учтите, что затем вы выполняете фоновое изображение для своего приложения, а затем его убивают. Когда вы вернетесь, Android вспомнит, что раньше у вас были фрагменты A, B и C в диспетчере фрагментов, поэтому он воссоздает их для вас, а затем добавляет их. Однако те, которые теперь добавлены в диспетчер фрагментов, НЕ являются теми, которые есть в вашем списке фрагментов в вашей деятельности.
FragmentPagerAdapter не будет пытаться вызвать,
getPosition
если уже есть фрагмент, добавленный для этой конкретной позиции страницы. Фактически, поскольку фрагмент, воссозданный Android, никогда не будет удален, у вас нет никакой надежды заменить его вызовомgetPosition
. Получить указание на него также довольно сложно, потому что он был добавлен с тегом, который вам неизвестен. Это сделано специально; вам не рекомендуется возиться с фрагментами, которыми управляет пейджер представления. Вы должны выполнять все свои действия внутри фрагмента, взаимодействовать с действием и запрашивать переход на определенную страницу, если это необходимо.Теперь вернемся к вашей проблеме с отсутствующим действием. Вызов
pagerAdapter.getItem(1)).update(id, name)
после того, как все это произошло, возвращает вам фрагмент в вашем списке, который еще не был добавлен в диспетчер фрагментов , поэтому у него не будет ссылки на Activity. Я бы посоветовал вашему методу обновления изменить некоторую общую структуру данных (возможно, управляемую действием), а затем, когда вы переходите на определенную страницу, она может рисовать себя на основе этих обновленных данных.источник
instantiateItem
и вы действительно должны делать это вonCreate
своей деятельности. см. подробности здесь: stackoverflow.com/questions/14035090/…Я нашел простое решение, которое сработало для меня.
Сделайте так, чтобы ваш адаптер фрагмента расширял FragmentStatePagerAdapter вместо FragmentPagerAdapter и переопределил метод onSave для возврата значения null
Это не позволяет Android воссоздавать фрагмент
Через день я нашел другое, лучшее решение.
Вызовите
setRetainInstance(true)
все свои фрагменты и сохраните где-нибудь ссылки на них. Я сделал это в статической переменной в своей деятельности, потому что она объявлена как singleTask, и фрагменты могут оставаться неизменными все время.Таким образом, Android не воссоздает фрагменты, а использует те же экземпляры.
источник
setRetainInstance(true)
сFragmentPagerAdapter
. Все работает хорошо. Но когда я поворачиваю устройство, в адаптере остаются фрагменты, но фрагменты не отображаются. Также не вызываются методы жизненного цикла фрагментов. Кто-нибудь может помочь?Я решил эту проблему, получив доступ к моим фрагментам напрямую через FragmentManager, а не через FragmentPagerAdapter, как это. Сначала мне нужно выяснить тег фрагмента, автоматически созданного FragmentPagerAdapter ...
Затем я просто получаю ссылку на этот фрагмент и делаю то, что мне нужно, вот так ...
Внутри моих фрагментов я установил
setRetainInstance(false);
так, чтобы я мог вручную добавлять значения в пакет savedInstanceState.а затем в OnCreate я беру этот ключ и при необходимости восстанавливаю состояние фрагмента. Простое решение, которое было трудно (по крайней мере, для меня) понять.
источник
FragmentByTag
в ViewPager.Глобальное рабочее протестированное решение.
getSupportFragmentManager()
несколько раз сохраняет пустую ссылку, и пейджер просмотра не создает новую, поскольку находит ссылку на тот же фрагмент. Таким образом, это использованиеgetChildFragmentManager()
решает проблему простым способом.Не делайте этого:
new PagerAdapter(getSupportFragmentManager(), fragments);
Сделай это:
new PagerAdapter(getChildFragmentManager() , fragments);
источник
FragmentStatePagerAdapter(activity!!.supportFragmentManager)
в болееFragmentStatePagerAdapter(childFragmentManager)
Не пытайтесь взаимодействовать между фрагментами в ViewPager. Вы не можете гарантировать, что другой фрагмент прикреплен или даже существует. Вместо того, чтобы менять заголовок панели действий из фрагмента, вы можете сделать это из своей активности. Используйте для этого стандартный шаблон интерфейса:
источник
Вы можете удалить фрагменты при уничтожении окна просмотра, в моем случае я удалил их на
onDestroyView()
своем фрагменте:источник
ViewPager
тоже на базеchildFragmentManager
адаптера (неfragmentManager
). Также работает другой вариант: не использоватьonDestroyView
, а удалить дочерние фрагменты передViewPager
созданием адаптера.После нескольких часов поиска аналогичной проблемы я думаю, что есть другое решение. По крайней мере, это сработало для меня, и мне нужно изменить только пару строк.
Это проблема, с которой я столкнулся, у меня есть активность с пейджером просмотра, который использует FragmentStatePagerAdapter с двумя фрагментами. Все работает нормально, пока я не заставлю действие быть уничтоженным (параметры разработчика) или пока я не поверну экран. Я сохраняю ссылку на два фрагмента после их создания внутри метода getItem.
В этот момент действие будет создано снова, и на этом этапе все работает нормально, но я потерял ссылку на мои фрагменты, поскольку getItem больше не вызывается.
Вот как я исправил эту проблему внутри FragmentStatePagerAdapter:
Вы не получите вызов getItem снова, если адаптер уже имеет внутреннюю ссылку на него, и вам не следует это менять. Вместо этого вы можете получить используемый фрагмент, посмотрев на этот другой метод instantiateItem (), который будет вызываться для каждого из ваших фрагментов.
Надеюсь, это кому-нибудь поможет.
источник
Поскольку FragmentManager позаботится о восстановлении ваших фрагментов, как только будет вызван метод onResume (), у меня есть вызов фрагмента для действия и добавление себя в список. В моем случае я храню все это в своей реализации PagerAdapter. Каждый фрагмент знает свою позицию, потому что он добавляется к аргументам фрагмента при создании. Теперь, когда мне нужно манипулировать фрагментом по определенному индексу, все, что мне нужно сделать, это использовать список из моего адаптера.
Ниже приведен пример адаптера для настраиваемого ViewPager, который будет увеличивать фрагмент при перемещении в фокус и уменьшать его при перемещении из фокуса. Помимо классов адаптера и фрагмента, у меня есть все, что вам нужно, это чтобы родительская активность могла ссылаться на переменную адаптера, и все настроено.
адаптер
Фрагмент
источник
Мое решение: я установил почти все View как
static
. Теперь мое приложение отлично работает. Возможность вызывать статические методы отовсюду - это, возможно, не лучший стиль, но зачем играть с кодом, который не работает? Я прочитал много вопросов и их ответы здесь, на SO, и ни одно решение не принесло успеха (для меня).Я знаю, что это может привести к утечке памяти и потере кучи, и мой код не будет соответствовать другим проектам, но я не боюсь этого - я тестировал приложение на разных устройствах и в разных условиях, никаких проблем, Android Платформа, похоже, справится с этим. Пользовательский интерфейс обновляется каждую секунду, и даже на устройстве S2 ICS (4.0.3) приложение способно обрабатывать тысячи гео-маркеров.
источник
Я столкнулся с той же проблемой, но мой ViewPager находился внутри TopFragment, который создал и установил адаптер с помощью
setAdapter(new FragmentPagerAdapter(getChildFragmentManager()))
.Я исправил эту проблему, переопределив
onAttachFragment(Fragment childFragment)
в TopFragment следующим образом:Как уже известно (см. Ответы выше), когда childFragmentManager воссоздает себя, он также создает фрагменты, которые были внутри viewPager.
Важная часть состоит в том, что после этого он вызывает onAttachFragment, и теперь у нас есть ссылка на новый воссозданный фрагмент!
Надеюсь, это поможет любому, кто получит этот старый Q, как я :)
источник
Решил проблему, сохранив фрагменты в SparceArray:
источник
Просто чтобы вы знали ...
В дополнение к списку проблем, связанных с этими классами, есть довольно интересная ошибка, которой стоит поделиться.
Я использую ViewPager для навигации по дереву элементов (выберите элемент, и пейджер представит анимацию прокрутки вправо, появится следующая ветвь, перейдите назад, а ViewPager прокручивает в противоположном направлении, чтобы вернуться к предыдущему узлу) .
Проблема возникает, когда я выталкиваю и выталкиваю фрагменты с конца FragmentStatePagerAdapter. Достаточно умен, чтобы заметить, что элементы меняются, и достаточно умен, чтобы создавать и заменять фрагмент при изменении элемента. Но недостаточно умен, чтобы отбросить состояние фрагмента, или недостаточно умен, чтобы обрезать внутренне сохраненные состояния фрагмента при изменении размера адаптера. Итак, когда вы вставляете элемент и вставляете новый в конец, фрагмент для нового элемента получает сохраненное состояние фрагмента для старого элемента, что вызвало абсолютный хаос в моем коде. Мои фрагменты несут данные, для получения которых из Интернета может потребоваться много работы, поэтому не сохранять состояние действительно не вариант.
У меня нет чистого обходного пути. Я использовал что-то вроде этого:
Неидеальное решение, потому что новый экземпляр фрагмента по-прежнему получает пакет savedState, но, по крайней мере, он не содержит устаревших данных.
источник
Поскольку люди не склонны читать комментарии, вот ответ, который в основном дублирует то, что я написал здесь :
Основная причина проблемы заключается в том, что система Android не вызывает
getItem
фактически отображаемых фрагментов, аinstantiateItem
. Этот метод сначала пытается найти и повторно использовать экземпляр фрагмента для данной вкладки вFragmentManager
. Вызывается только в случае сбоя этого поиска (что происходит только при первомFragmentManager
создании)getItem
. По очевидным причинам не следует воссоздавать фрагменты (которые могут быть тяжелыми), например, каждый раз, когда пользователь поворачивает свое устройство.Чтобы решить эту проблему, вместо того, чтобы создавать фрагменты с помощью
Fragment.instantiate
в своей деятельности, вы должны сделать это,pagerAdapter.instantiateItem
и все эти вызовы должны быть окруженыstartUpdate/finishUpdate
вызовами методов, которые запускают / фиксируют транзакцию фрагмента соответственно.getItem
должно быть местом, где действительно создаются фрагменты с использованием соответствующих конструкторов.источник