Napisałem sztuczną aktywność, która przełącza się między dwoma fragmentami. Kiedy przechodzisz z Fragmentu A do Fragmentu B, FragmentA zostaje dodany do tylnego stosu. Jednak kiedy wracam do Fragmentu A (naciskając wstecz), tworzony jest całkowicie nowy Fragment A, a stan, w którym był, zostaje utracony. Mam wrażenie, że szukam tego samego, co to pytanie, ale dołączyłem pełny przykład kodu, aby pomóc wykorzenić problem:
public class FooActivity extends Activity {
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentA());
transaction.commit();
}
public void nextFragment() {
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentB());
transaction.addToBackStack(null);
transaction.commit();
}
public static class FragmentA extends Fragment {
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View main = inflater.inflate(R.layout.main, container, false);
main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((FooActivity) getActivity()).nextFragment();
}
});
return main;
}
@Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save some state!
}
}
public static class FragmentB extends Fragment {
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.b, container, false);
}
}
}
Po dodaniu niektórych komunikatów dziennika:
07-05 14:28:59.722 D/OMG ( 1260): FooActivity.onCreate
07-05 14:28:59.742 D/OMG ( 1260): FragmentA.onCreateView
07-05 14:28:59.742 D/OMG ( 1260): FooActivity.onResume
<Tap Button on FragmentA>
07-05 14:29:12.842 D/OMG ( 1260): FooActivity.nextFragment
07-05 14:29:12.852 D/OMG ( 1260): FragmentB.onCreateView
<Tap 'Back'>
07-05 14:29:16.792 D/OMG ( 1260): FragmentA.onCreateView
Nigdy nie wywołuje FragmentA.onSaveInstanceState i tworzy nowy FragmentA po uderzeniu z powrotem. Jeśli jednak jestem na FragmentA i zablokuję ekran, zostanie wywołany FragmentA.onSaveInstanceState. To dziwne ... czy mylę się, spodziewając się, że fragment dodany do tylnego stosu nie będzie wymagał ponownego tworzenia? Oto, co mówią dokumenty :
Natomiast jeśli wywołasz addToBackStack () podczas usuwania fragmentu, to fragment zostanie zatrzymany i zostanie wznowiony, jeśli użytkownik nawiguje wstecz.
ListView
. Wygląda na to, że zbyt dużo przeskakiwania do kółka, aby dołączyć nasłuchiwanie przewijania i zaktualizować zmienną instancji.Odpowiedzi:
Jeśli wrócisz do fragmentu z tylnego stosu, nie utworzy on ponownie fragmentu, ale ponownie użyje tego samego wystąpienia i rozpocznie się
onCreateView()
w cyklu życia fragmentu, zobacz Cykl życia fragmentu .Więc jeśli chcesz przechowywać stan, powinieneś używać zmiennych instancji i nie polegać na nich
onSaveInstanceState()
.źródło
W porównaniu do Apple
UINavigationController
iUIViewController
Google nie radzi sobie dobrze w architekturze oprogramowania Android. Dokument na temat AndroidaFragment
niewiele pomaga.Po wprowadzeniu FragmentB z FragmentA istniejące wystąpienie FragmentA nie zostanie zniszczone. Po naciśnięciu przycisku Back w FragmentB i powrocie do FragmentA nie tworzymy nowej instancji FragmentA. Istniejące wystąpienia FragmentA
onCreateView()
wywołana .Kluczową sprawą jest to, że nie powinniśmy ponownie zawyżać widoku w FragmentA
onCreateView()
, ponieważ używamy istniejącej instancji FragmentA. Musimy zapisać i ponownie użyć rootView.Poniższy kod działa dobrze. Nie tylko zachowuje stan fragmentów, ale także zmniejsza obciążenie pamięci RAM i procesora (ponieważ nadmuchujemy układ tylko w razie potrzeby). Nie mogę uwierzyć, że przykładowy kod i dokument Google nigdy o tym nie wspominają, ale zawsze zawyżają układ .
Wersja 1 (nie używaj wersji 1. Użyj wersji 2)
------ Aktualizacja 3 maja 2005 r .: -------
Jak wspomniano w komentarzach, czasami
_rootView.getParent()
jest pustyonCreateView
, co powoduje awarię. Wersja 2 usuwa _rootView w onDestroyView (), zgodnie z sugestią dell116. Przetestowano na Androidzie 4.0.3, 4.4.4, 5.1.0.Wersja 2
OSTRZEŻENIE!!!
To jest HACK! Chociaż używam go w mojej aplikacji, musisz uważnie przetestować i przeczytać komentarze.
źródło
Myślę, że istnieje alternatywny sposób na osiągnięcie tego, czego szukasz. Nie mówię, że to kompletne rozwiązanie, ale w moim przypadku spełniło to zadanie.
To, co zrobiłem, to zamiast zastąpić fragment, który właśnie dodałem fragment docelowy. Więc zasadniczo użyjesz
add()
metody zamiast tegoreplace()
.Co jeszcze zrobiłem. Ukrywam mój aktualny fragment, a także dodaję go do stosu.
W związku z tym nakłada nowy fragment na bieżący fragment bez niszczenia jego widoku. (Sprawdź, czy jego
onDestroyView()
metoda nie jest wywoływana. Plus dodanie go dobackstate
daje mi przewagę w postaci wznowienia fragmentu.Oto kod:
AFAIK System tylko dzwoni
onCreateView()
wtedy, gdy widok jest zniszczony lub nie został utworzony. Ale tutaj zapisaliśmy widok, nie usuwając go z pamięci. Więc nie utworzy nowego widoku.A kiedy wrócisz z Destination Fragment, wyskoczy ostatni
FragmentTransaction
usuwany górny fragment, co spowoduje, że najwyższy widok (SourceFragment) pojawi się na ekranie.KOMENTARZ: Jak powiedziałem, nie jest to rozwiązanie kompletne, ponieważ nie usuwa widoku fragmentu Źródła i przez to zajmuje więcej pamięci niż zwykle. Ale mimo wszystko, służ celowi. Ponadto używamy zupełnie innego mechanizmu ukrywania widoku zamiast go zastępować, co jest nietradycyjne.
Więc tak naprawdę nie chodzi o to, jak utrzymujesz stan, ale o to, jak utrzymujesz widok.
źródło
Activity A{Fragment A --> Fragment B}
kiedy ponownie uruchamiam aplikację po naciśnięciu przycisku home,onResume()
wywoływane są oba fragmenty i tym samym rozpoczynają swoje odpytywanie. Jak mogę to kontrolować?Proponuję bardzo proste rozwiązanie.
Weź zmienną referencyjną widoku i ustaw widok w OnCreateView. Sprawdź, czy widok już istnieje w tej zmiennej, a następnie zwróć ten sam widok.
źródło
if (_rootView.getParent() != null) { ((ViewGroup)_rootView.getParent()).removeView(_rootView); }
jest właściwe, aby wyczyścić pamięć?null
dofragmentView
zmiennej wonDestroy()
metodzie.onDestroyView()
. To czyszczenie nie dotyczy naszej zmiennej widoku kopii zapasowej (tutajfragmentView
) i spowoduje wyciek pamięci, gdy fragment zostanie z powrotem ułożony w stos / zniszczony. To samo odniesienie można znaleźć w [Typowe przyczyny wycieków pamięci] ( square.github.io/leakcanary/fundamentals/… ) we wprowadzeniu do LeakCanery.Natknąłem się na ten problem we fragmencie zawierającym mapę, która ma zbyt wiele szczegółów konfiguracji, aby zapisać / przeładować. Moim rozwiązaniem było po prostu utrzymanie tego fragmentu aktywnego przez cały czas (podobnie jak wspomniał @kaushal).
Załóżmy, że masz aktualny fragment A i chcesz wyświetlić fragment B. Podsumowując konsekwencje:
Dlatego, jeśli chcesz zachować oba fragmenty „zapisane”, po prostu przełącz je za pomocą funkcji hide () / show ().
Zalety : łatwa i prosta metoda utrzymania wielu fragmentów działających
Wady : zużywasz dużo więcej pamięci, aby wszystkie działały. Może napotkać problemy, np. Wyświetlając wiele dużych bitmap
źródło
onSaveInstanceState()
jest wywoływana tylko w przypadku zmiany konfiguracji.Ponieważ zmiana z jednego fragmentu na inny nie ma zmiany konfiguracji, więc nie ma wywołania
onSaveInstanceState()
. Który stan nie jest zbawiony? Czy możesz określić?Jeśli wpiszesz jakiś tekst w EditText, zostanie on automatycznie zapisany. Każdy element interfejsu użytkownika bez identyfikatora to element, którego stan widoku nie zostanie zapisany.
źródło
onSaveInstanceState()
jest również wywoływana, gdy system niszczy Aktywność, ponieważ brakuje mu zasobów.Tutaj, ponieważ
onSaveInstanceState
in fragment nie wywołuje po dodaniu fragmentu do backstacka. Cykl życia fragmentu w stosie po przywróceniu początkuonCreateView
i końca,onDestroyView
gdyonSaveInstanceState
jest wywoływany międzyonDestroyView
ionDestroy
. Moje rozwiązanie polega na utworzeniu zmiennej instancji i init wonCreate
. Przykładowy kod:I sprawdź to
onActivityCreated
:źródło
źródło
Mój problem był podobny, ale pokonałem mnie, nie utrzymując fragmentu przy życiu. Załóżmy, że masz aktywność, która ma 2 fragmenty - F1 i F2. F1 jest początkowo uruchamiany i powiedzmy, że zawiera pewne informacje o użytkowniku, a następnie pod pewnymi warunkami pojawia się F2, prosząc użytkownika o uzupełnienie dodatkowego atrybutu - jego numeru telefonu. Następnie chcesz, aby ten numer telefonu wrócił do F1 i dokończył rejestrację, ale zdajesz sobie sprawę, że wszystkie poprzednie informacje o użytkowniku zostały utracone i nie masz ich poprzednich danych. Fragment jest odtwarzany od zera i nawet jeśli zapisałeś te informacje w
onSaveInstanceState
paczce, wraca do wartości nullonActivityCreated
.Rozwiązanie: Zapisz wymagane informacje jako zmienną instancji w działaniu wywołującym. Następnie przekaż tę zmienną instancji do swojego fragmentu.
A więc idąc za moim przykładem: zanim wyświetlę F2, zapisuję dane użytkownika w zmiennej instancji za pomocą wywołania zwrotnego. Następnie uruchamiam F2, użytkownik wpisuje numer telefonu i naciska Zapisz. Używam innego wywołania zwrotnego w działaniu, zbieram te informacje i zastępuję mój fragment F1, tym razem ma on pakiet danych, których mogę użyć.
Więcej informacji na temat callbacków można znaleźć tutaj: https://developer.android.com/training/basics/fragments/communicating.html
źródło
źródło
Zastąp fragment przy użyciu następującego kodu:
Aktywność onBackPressed () to:
źródło
źródło
Idealne rozwiązanie, które znajduje stary fragment w stosie i ładuje go, jeśli istnieje w stosie.
użyj go jak
źródło