Czy fragmenty naprawdę potrzebują pustego konstruktora?

258

Mam Fragmentkonstruktora, który przyjmuje wiele argumentów. Moja aplikacja działała dobrze podczas programowania, ale podczas produkcji moi użytkownicy czasami widzą tę awarię:

android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment 
make sure class name exists, is public, and has an empty constructor that is public

Mógłbym stworzyć pustego konstruktora, jak sugeruje ten komunikat o błędzie, ale to nie ma dla mnie sensu, ponieważ musiałbym wtedy wywołać osobną metodę, aby zakończyć konfigurację Fragment.

Jestem ciekawy, dlaczego ta awaria zdarza się tylko czasami. Może używam ViewPagerniewłaściwie? Sam tworzę instancję wszystkich Fragmenti zapisuję je na liście w pliku Activity. Nie używam FragmentManagertransakcji, ponieważ ViewPagerprzykłady, które widziałem, nie wymagały tego i wszystko wydawało się działać podczas programowania.

stkent
źródło
22
w niektórych wersjach Androida (przynajmniej ICS) możesz przejść do ustawień -> opcje programistyczne i włączyć opcję „Nie zachowuj aktywności”. Dzięki temu uzyskasz deterministyczny sposób testowania przypadków, w których niezbędny jest konstruktor bez argumentu.
Keith,
Miałem ten sam problem. Zamiast tego przypisywałem dane pakietu zmiennym składowym (używając innego niż domyślny ctor). Mój program nie zawieszał się, kiedy zabiłem aplikację - działo się to tylko wtedy, gdy program planujący umieścił moją aplikację na palniku, aby „zaoszczędzić miejsce”. Odkryłem to, przechodząc do Task Mgr i otwierając mnóstwo innych aplikacji, a następnie ponownie otwierając moją aplikację podczas debugowania. Rozbijał się za każdym razem. Problem został rozwiązany, gdy użyłem odpowiedzi Chrisa Jenkinsa, aby użyć pakietu args.
wizurd
Być może zainteresuje Cię ten wątek: stackoverflow.com/questions/15519214/...
Stefan Haustein
5
Uwaga dodatkowa dla przyszłych czytelników: jeśli Fragmentpodklasa w ogóle nie deklaruje żadnych konstruktorów, domyślnie zostanie utworzony dla ciebie pusty konstruktor publiczny (jest to standardowe zachowanie Java ). Zdajesz nie trzeba jawnie deklarować pusty konstruktor chyba deklaruje innych konstruktorów (np te z argumentów).
Tony Chan,
Wspomnę tylko, że IntelliJ IDEA, przynajmniej dla wersji 14.1, zawiera ostrzeżenie ostrzegające o tym, że nie powinieneś mieć innego fragmentu niż domyślny konstruktor.
RenniePet,

Odpowiedzi:

349

Tak, robią.

Zresztą nie powinieneś tak naprawdę nadpisywać konstruktora. Powinieneś mieć newInstance()zdefiniowaną metodę statyczną i przekazać dowolne parametry za pomocą argumentów (pakiet)

Na przykład:

public static final MyFragment newInstance(int title, String message) {
    MyFragment f = new MyFragment();
    Bundle bdl = new Bundle(2);
    bdl.putInt(EXTRA_TITLE, title);
    bdl.putString(EXTRA_MESSAGE, message);
    f.setArguments(bdl);
    return f;
}

I oczywiście chwytanie argumentów w ten sposób:

@Override
public void onCreate(Bundle savedInstanceState) {
    title = getArguments().getInt(EXTRA_TITLE);
    message = getArguments().getString(EXTRA_MESSAGE);

    //...
    //etc
    //...
}

Następnie utworzyłbyś instancję z menedżera fragmentów w następujący sposób:

@Override
public void onCreate(Bundle savedInstanceState) {
    if (savedInstanceState == null){
        getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.content, MyFragment.newInstance(
                R.string.alert_title,
                "Oh no, an error occurred!")
            )
            .commit();
    }
}

W ten sposób po odłączeniu i ponownym podłączeniu stan obiektu można zapisać za pomocą argumentów. Podobnie jak pakiety dołączone do Intentów.

Powód - dodatkowe czytanie

Myślałem, że wyjaśnię, dlaczego ludzie zastanawiają się, dlaczego.

Jeśli zaznaczysz: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

Zobaczysz instantiate(..)metodę w Fragmentklasie wywołującą newInstancemetodę:

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
    try {
        Class<?> clazz = sClassMap.get(fname);
        if (clazz == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = context.getClassLoader().loadClass(fname);
            if (!Fragment.class.isAssignableFrom(clazz)) {
                throw new InstantiationException("Trying to instantiate a class " + fname
                        + " that is not a Fragment", new ClassCastException());
            }
            sClassMap.put(fname, clazz);
        }
        Fragment f = (Fragment) clazz.getConstructor().newInstance();
        if (args != null) {
            args.setClassLoader(f.getClass().getClassLoader());
            f.setArguments(args);
        }
        return f;
    } catch (ClassNotFoundException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (java.lang.InstantiationException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (IllegalAccessException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (NoSuchMethodException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": could not find Fragment constructor", e);
    } catch (InvocationTargetException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": calling Fragment constructor caused an exception", e);
    }
}

http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance () Wyjaśnia, dlaczego po utworzeniu instancji sprawdza, czy jest to akcesor publici czy moduł ładujący klasy umożliwia mu dostęp.

W sumie jest to dość paskudna metoda, ale pozwala FragmentMangerzabijać i odtwarzać Fragmentsze stanami. (Podsystem Android robi podobne rzeczy Activities).

Przykładowa klasa

Często pytano mnie o telefonowanie newInstance. Nie należy tego mylić z metodą klasową. Ten przykład całej klasy powinien pokazywać użycie.

/**
 * Created by chris on 21/11/2013
 */
public class StationInfoAccessibilityFragment extends BaseFragment implements JourneyProviderListener {

    public static final StationInfoAccessibilityFragment newInstance(String crsCode) {
        StationInfoAccessibilityFragment fragment = new StationInfoAccessibilityFragment();

        final Bundle args = new Bundle(1);
        args.putString(EXTRA_CRS_CODE, crsCode);
        fragment.setArguments(args);

        return fragment;
    }

    // Views
    LinearLayout mLinearLayout;

    /**
     * Layout Inflater
     */
    private LayoutInflater mInflater;
    /**
     * Station Crs Code
     */
    private String mCrsCode;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCrsCode = getArguments().getString(EXTRA_CRS_CODE);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mInflater = inflater;
        return inflater.inflate(R.layout.fragment_station_accessibility, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mLinearLayout = (LinearLayout)view.findViewBy(R.id.station_info_accessibility_linear);
        //Do stuff
    }

    @Override
    public void onResume() {
        super.onResume();
        getActivity().getSupportActionBar().setTitle(R.string.station_info_access_mobility_title);
    }

    // Other methods etc...
}
Chris.Jenkins
źródło
2
Jeśli wstrzymasz działanie lub je zniszczysz. Idziesz do ekranu głównego, a następnie aktywność zostaje zabita przez Androida, aby zaoszczędzić miejsce. Stan fragmentów zostanie zapisany (za pomocą argumentów), a następnie gc obiekt (normalnie). Tak więc po powrocie do działania fragmenty powinny zostać odtworzone przy użyciu stanu zapisanego, nowego Default (), a następnie onCreate itp. Również jeśli działanie próbuje oszczędzać zasoby (niski poziom pamięci) Może to spowodować usunięcie obiektów, które właśnie zostały wstrzymane .. Commonsguy powinien być w stanie lepiej wyjaśnić. Krótko mówiąc, nie wiesz! :)
Chris.Jenkins
1
@mahkie Naprawdę, jeśli potrzebujesz dużo obiektów / modeli, powinieneś pobrać je asynchronicznie z bazy danych lub dostawcy treści.
Chris.Jenkins
1
@ Chris.Jenkins Przepraszam, jeśli nie byłem jasny ... chodziło mi o to, że w przeciwieństwie do Aktywności, Fragmenty nie wyjaśniają jednoznacznie, że konstruktory nie mogą być używane do przekazywania / udostępniania danych. I chociaż wyrzucanie / przywracanie jest w porządku, uważam, że przechowywanie kilku kopii danych może czasami zajmować więcej pamięci niż odzyskanie zniszczonego widoku. W niektórych przypadkach może to być przydatne, aby mieć możliwość leczenia kolekcję Inne / Fragments jako jednostki, które mają być zniszczone w całości lub wcale - wtedy mógłby przekazać dane przez konstruktorów. Na razie, jeśli chodzi o tę kwestię, pusty konstruktor jest jedynym.
kaay
3
Dlaczego miałbyś przechowywać kilka kopii danych? Pakiety | Paczkowalne faktycznie przekazuje odwołanie do pamięci, gdy może między stanami / fragmentami / działaniami, (powoduje to naprawdę dziwne problemy ze stanem), Jedyny czas, w którym Parcelable faktycznie „duplikuje” dane, jest między procesami a pełnym cyklem życia. Np. Jeśli przekazujesz obiekt do swoich fragmentów ze swojej działalności, twoje odniesienie nie jest klonem. Twoim jedynym dodatkowym narzutem są dodatkowe fragmenty obiektów.
Chris.Jenkins
1
@ Chris.Jenkins Cóż, to była moja ignorancja w zakresie paczek. Po przeczytaniu krótkiego javadoc Parcelable i części Parcel niedaleko słowa „zrekonstruowany”, nie dotarłem do części „Active Objects”, stwierdzając, że była to tylko zoptymalizowana na niższym poziomie, ale mniej uniwersalna Serializowalna. Niniejszym zakładam czapkę wstydu i bełkoczę: „Nadal nie mogę dzielić rzeczy nie dostarczanych, a robienie przesyłek może być kłopotliwe” :)
kaay
17

Jak zauważył CommonsWare w tym pytaniu https://stackoverflow.com/a/16064418/1319061 , ten błąd może również wystąpić, jeśli tworzysz anonimową podklasę fragmentu, ponieważ anonimowe klasy nie mogą mieć konstruktorów.

Nie twórz anonimowych podklas Fragmentu :-)

Jesper B.
źródło
1
Lub, jak wspomniano w tym wpisie CommonsWare, upewnij się, że zadeklarowałeś wewnętrzną Aktywność / Fragment / Odbiornik jako „statyczny”, aby uniknąć tego błędu.
Tony Wickham,
7

Tak, jak widać, pakiet wsparcia tworzy również fragmenty (gdy zostaną zniszczone i ponownie otwarte). Twoje Fragmentpodklasy potrzebują publicznego pustego konstruktora, ponieważ tak nazywa się framework.

Sveinung Kval Bakken
źródło
Konstruktor pustych fragmentów powinien wywoływać konstruktor super () czy nie? Pytam o to, uważając, że pusty publiczny Konstruktor jest obowiązkowy. jeśli wywołanie super () nie ma sensu dla pustego konstruktora publicznego
TNR
@TNR, ponieważ wszystkie abstrakcje fragmentów mają pustego konstruktora, super()byłyby bezowocne, ponieważ klasa nadrzędna złamała regułę pustego konstruktora publicznego. Więc nie, nie musisz przechodzić super()do swojego konstruktora.
Chris.Jenkins
4
W rzeczywistości nie jest wymagane jawne zdefiniowanie pustego konstruktora we fragmencie. W każdym przypadku każda klasa Java ma domyślny domyślny konstruktor. Źródło : docs.oracle.com/javase/tutorial/java/javaOO/constructors.html ~ „Kompilator automatycznie zapewnia domyślny konstruktor bez argumentów dla dowolnej klasy bez konstruktorów.”
IgorGanapolsky
-6

Oto moje proste rozwiązanie:

1 - Zdefiniuj swój fragment

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

    public void setParameter(String parameter) {
        this.parameter = parameter;
    } 
}

2 - Utwórz nowy fragment i wypełnij parametr

    myfragment = new MyFragment();
    myfragment.setParameter("here the value of my parameter");

3 - Ciesz się!

Oczywiście możesz zmienić typ i liczbę parametrów. Szybko i łatwo.

Alecs
źródło
5
Nie obsługuje to jednak ponownego ładowania fragmentu przez system.
Vidia,