Apakah fragmen benar-benar membutuhkan konstruktor kosong?

258

Saya punya Fragmentdengan konstruktor yang membutuhkan banyak argumen. Aplikasi saya berfungsi dengan baik selama pengembangan, tetapi dalam produksi, pengguna saya terkadang melihat kerusakan ini:

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

Saya bisa membuat konstruktor kosong seperti pesan kesalahan ini menyarankan, tapi itu tidak masuk akal bagi saya sejak itu saya harus memanggil metode terpisah untuk menyelesaikan pengaturan Fragment.

Saya ingin tahu mengapa kecelakaan ini hanya terjadi sesekali. Mungkin saya menggunakan yang ViewPagersalah? Saya instantiate semua Fragments sendiri dan menyimpannya dalam daftar di dalam Activity. Saya tidak menggunakan FragmentManagertransaksi, karena ViewPagercontoh yang saya lihat tidak memerlukannya dan semuanya tampak berfungsi selama pengembangan.

stkent
sumber
22
di beberapa versi android (setidaknya ICS), Anda dapat pergi ke pengaturan -> opsi pengembang dan mengaktifkan "Don't keep activities". Melakukan ini akan memberi Anda cara deterministik menguji kasus-kasus di mana konstruktor no-arg diperlukan.
Keith
Saya punya masalah yang sama. Saya menetapkan data bundel sebagai gantinya untuk variabel anggota (menggunakan ctor non-default). Program saya tidak mogok ketika saya membunuh aplikasi-itu hanya terjadi ketika penjadwal menempatkan aplikasi saya di backburner untuk "menghemat ruang". Cara saya menemukan ini adalah dengan membuka Task Mgr dan membuka banyak aplikasi lain, kemudian membuka kembali aplikasi saya dalam debug. Itu jatuh setiap waktu. Masalah ini teratasi ketika saya menggunakan jawaban Chris Jenkins untuk menggunakan bundle args.
wizurd
Anda mungkin tertarik dengan utas ini: stackoverflow.com/questions/15519214/…
Stefan Haustein
5
Catatan tambahan untuk pembaca masa depan: jika Fragmentsubkelas Anda tidak mendeklarasikan konstruktor sama sekali, maka secara default konstruktor publik kosong secara implisit akan dibuat untuk Anda (ini adalah perilaku Java standar ). Anda tidak harus secara eksplisit mendeklarasikan konstruktor kosong kecuali jika Anda juga mendeklarasikan konstruktor lain (mis. Yang memiliki argumen).
Tony Chan
Saya hanya akan menyebutkan bahwa IntelliJ IDEA, setidaknya untuk versi 14.1, memberikan peringatan yang mengingatkan Anda pada fakta bahwa Anda seharusnya tidak memiliki konstruktor non-default dalam sebuah fragmen.
RenniePet

Jawaban:

349

Ya mereka melakukanya.

Anda seharusnya tidak benar-benar mengabaikan konstruktor. Anda harus memiliki newInstance()metode statis yang ditentukan dan meneruskan parameter apa pun melalui argumen (bundel)

Sebagai contoh:

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;
}

Dan tentu saja meraih argumen seperti ini:

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

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

Maka Anda akan instantiate dari manajer fragmen Anda seperti:

@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();
    }
}

Dengan cara ini jika terlepas dan dilampirkan kembali keadaan objek dapat disimpan melalui argumen. Sama seperti bundel yang melekat pada Intents.

Alasan - Bacaan ekstra

Saya pikir saya akan menjelaskan mengapa orang bertanya-tanya mengapa.

Jika Anda memeriksa: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

Anda akan melihat instantiate(..)metode di Fragmentkelas memanggil newInstancemetode:

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 () Menjelaskan alasannya, saat instantiasi memeriksa apakah accessornya publicdan bahwa loader kelas memungkinkan akses ke sana.

Ini adalah metode yang cukup jahat, tetapi memungkinkan FragmentMangeruntuk membunuh dan membuat ulang Fragmentsdengan status. (Subsistem Android melakukan hal serupa dengan Activities).

Kelas Contoh

Saya banyak ditanya tentang menelepon newInstance. Jangan bingung ini dengan metode kelas. Contoh seluruh kelas ini harus menunjukkan penggunaannya.

/**
 * 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
sumber
2
Jika Anda menghentikan aktivitas atau menghancurkannya. Jadi Anda pergi ke layar beranda dan aktivitas tersebut kemudian dibunuh oleh Android untuk menghemat ruang. Keadaan fragmen akan disimpan (menggunakan args) kemudian gc objek (biasanya). Jadi, ketika kembali ke aktivitas, fragmen-fragmen tersebut harus dibuat ulang dengan menggunakan status tersimpan, Default baru () lalu onCreate dll. Juga jika aktivitas tersebut mencoba menghemat sumber daya (ponsel dengan mem rendah) Mungkin ia dapat menghapus objek yang baru saja dijeda .. Commonsguy harus bisa menjelaskan lebih baik. Singkatnya, Anda tidak tahu! :)
Chris.Jenkins
1
@mahkie Benar-benar jika Anda membutuhkan BANYAK Objek / Model Anda harus mengambilnya secara tidak sinkron dari Database atau ContentProvider.
Chris. Jenkins
1
@ Chris.Jenkins Maaf jika saya tidak jelas ... maksud saya adalah bahwa, tidak seperti Aktivitas, Fragmen tidak menjelaskan bahwa konstruktor tidak boleh digunakan untuk meneruskan / berbagi data. Dan sementara dumping / restore baik-baik saja, saya percaya bahwa memegang beberapa salinan data kadang-kadang dapat mengambil lebih banyak memori daripada yang dapat diperoleh kembali dari kerusakan. Dalam beberapa kasus, mungkin berguna untuk memiliki opsi untuk memperlakukan kumpulan Kegiatan / Fragmen sebagai satu unit, untuk dihancurkan secara keseluruhan atau tidak sama sekali - maka kita dapat meneruskan data melalui konstruktor. Untuk saat ini, mengenai masalah ini, konstruktor kosong adalah satu-satunya yang dimiliki.
kaay
3
Mengapa Anda menyimpan beberapa salinan data? Bundel | Parcelable benar-benar melewati referensi memori ketika itu bisa antara negara / fragmen / kegiatan, (itu menyebabkan beberapa masalah negara aneh sebenarnya), Satu-satunya waktu Parcelable sebenarnya secara efektif "duplikat" data adalah antara proses dan siklus hidup penuh. Misalnya, jika Anda meneruskan objek ke fragmen dari aktivitas Anda, referensi yang lewat bukan klon. Satu-satunya overhead ekstra nyata Anda adalah objek fragmen tambahan.
Chris.Jenkins
1
@ Chris. Jenkins Nah, itu, kemudian, adalah ketidaktahuan saya tentang Parcelable. Setelah membaca javadoc pendek Parcelable, dan bagian dari Parcel tidak jauh dari kata "direkonstruksi", saya belum mencapai bagian "Objek Aktif", menyimpulkan bahwa itu hanya level rendah yang lebih dioptimalkan tetapi kurang fleksibel Serializable. Saya dengan ini tidak mengenakan topi rasa malu dan bergumam "Masih tidak bisa berbagi yang tidak cocok dan membuat paket bisa mengganggu" :)
kaay
17

Seperti dicatat oleh CommonsWare dalam pertanyaan ini https://stackoverflow.com/a/16064418/1319061 , kesalahan ini juga dapat terjadi jika Anda membuat subkelas anonim dari Fragmen, karena kelas anonim tidak dapat memiliki konstruktor.

Jangan membuat subclass anonim Fragmen :-)

JesperB
sumber
1
Atau, seperti CommonsWare yang disebutkan dalam posting itu, pastikan Anda mendeklarasikan Aktivitas / Fragmen / Penerima dalam sebagai "statis" untuk menghindari kesalahan ini.
Tony Wickham
7

Ya, seperti yang Anda lihat paket dukungan juga instantiates fragmen (ketika mereka dihancurkan dan dibuka kembali). FragmentSubclass Anda memerlukan konstruktor kosong publik karena ini adalah apa yang disebut oleh framework.

Sveinung Kval Bakken
sumber
Empty Fragment Constructor harus memanggil super () Konstruktor atau tidak? Saya bertanya ini karena saya menemukan bahwa Konstruktor publik yang kosong adalah wajib. jika memanggil super () tidak masuk akal untuk konstruktor publik yang kosong
TNR
@TNR karena semua abstraksi Fragmen memiliki konstruktor kosong super()akan sia-sia, karena kelas induk telah melanggar aturan konstruktor publik kosong. Jadi tidak, Anda tidak perlu melewati super()bagian dalam konstruktor Anda.
Chris.Jenkins
4
Sebenarnya itu bukan persyaratan untuk secara eksplisit mendefinisikan konstruktor kosong di Fragmen. Setiap kelas Java tetap memiliki konstruktor default implisit. Diambil dari: docs.oracle.com/javase/tutorial/java/javaOO/constructors.html ~ "Compiler secara otomatis menyediakan no-argumen, konstruktor default untuk setiap kelas tanpa konstruktor."
IgorGanapolsky
-6

Inilah solusi sederhana saya:

1 - Tentukan fragmen Anda

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

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

2 - Buat fragmen baru Anda dan isi parameter

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

3 - Nikmati saja!

Jelas Anda dapat mengubah jenis dan jumlah parameter. Cepat dan mudah.

Alecs
sumber
5
Ini tidak menangani pemuatan ulang fragmen oleh sistem.
Vidia