Parçaların gerçekten boş bir kurucuya ihtiyacı var mı?


258

Ben Fragmentbir yapıcı ile birden çok argüman alır. Uygulamam geliştirme sırasında iyi çalıştı, ancak üretimde kullanıcılarım bazen bu çökmeyi görüyor:

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

Bu hata mesajının önerdiği gibi boş bir kurucu yapabilirdim, ama o zamandan beri benim için mantıklı değil Fragment.

Bu çökmenin neden sadece ara sıra gerçekleştiğini merak ediyorum. Belki de ViewPageryanlış kullanıyorum ? Ben bütün Fragmentkendimi somutlaştırmak ve içindeki bir listeye kaydedin Activity. FragmentManagerİşlemleri kullanmıyorum , çünkü ViewPagergördüğüm örnekler gerektirmiyordu ve geliştirme sırasında her şey çalışıyor gibi görünüyordu.


22
android'in bazı sürümlerinde (en azından ICS), ayarlar -> geliştirici seçeneklerine gidebilir ve "Etkinlikleri tutma" seçeneğini etkinleştirebilirsiniz. Bunu yapmak, arg-no'lu olmayan bir kurucunun gerekli olduğu durumları test etmenin belirleyici bir yolunu verecektir.
Keith

Aynı sorunu yaşadım. Üye verilere (varsayılan olmayan bir ctor kullanarak) yerine paket veri atamak oldu. Uygulamamı öldürdüğümde programım çökmüyordu-sadece zamanlayıcı "yerden tasarruf etmek" için uygulamamı backburner'a koyduğunda oldu. Bunu keşfetme şeklim, Görev Yöneticisi'ne gidip bir sürü başka uygulama açıp uygulamamı hata ayıklamada yeniden açarak. Her seferinde çöktü. Paket bağımsız değişkenlerini kullanmak için Chris Jenkins yanıtını kullandığımda sorun çözüldü.
wizurd


5
Gelecekteki okuyucular için bir yan not: Fragmentalt sınıfınız herhangi bir kurucu bildirmezse, varsayılan olarak sizin için örtülü olarak boş bir genel kurucu yapılır (bu standart Java davranışıdır ). Sen do not zorunda açıkça ayrıca diğer kurucular (argümanlarla örn olanlar) ilan sürece boş bir yapıcı beyan ederim.
Tony Chan

En azından 14.1 sürümü için IntelliJ IDEA'nın, bir parçada varsayılan olmayan bir kurucuya sahip olmamanız gerektiği konusunda sizi uyaran bir uyarı verdiğinden bahsedeceğim.
RenniePet

Yanıtlar:


349

Evet onlar yapar.

Zaten kurucuyu geçersiz kılmamalısınız. newInstance()Statik bir yöntem tanımlanmış olmalı ve parametreleri bağımsız değişkenlerle (paket) geçirmelisiniz

Örneğin:

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

Ve elbette, argümanları şu şekilde kapmak:

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

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

O zaman fragman yöneticinizden şu şekilde örnek verirsiniz:

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

Bu şekilde, nesne durumu ayrılır ve yeniden eklenirse, bağımsız değişkenler aracılığıyla saklanabilir. Niyetlere bağlı paketler gibi.

Sebep - Ekstra okuma

Nedenini merak eden insanlar için neden açıklayacağımı düşündüm.

Kontrol ederseniz: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

Sınıfta yöntemi çağırır instantiate(..)yöntemi görürsünüz :FragmentnewInstance

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 () Anlatma sonrasında, erişimcinin neden olduğunu publicve bu sınıf yükleyicinin ona erişim izni verdiğini açıklar .

Sonuçta oldukça kötü bir yöntem, ama devletlerle FragmentMangeröldürmeye ve yeniden yaratmaya izin veriyor Fragments. (Android alt sistemi ile benzer şeyler yapar Activities).

Örnek Sınıf

Arama hakkında çok soru soruluyor newInstance. Bunu sınıf yöntemiyle karıştırmayın. Bu tüm sınıf örneği kullanımı göstermelidir.

/**
 * 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...
}

2
Etkinliği duraklatırsanız veya yok ederseniz. Böylece ana ekrana gidersiniz ve daha sonra etkinlik odadan tasarruf etmek için Android tarafından öldürülür. Parçaların durumu kaydedilir (argümanlar kullanılarak) ve sonra nesneye gc (normalde). Bu nedenle aktiviteye geri döndüğünüzde, fragmanlar kaydedilmiş durum, yeni Default () sonra onCreate vb. Kullanılarak yeniden oluşturulmaya çalışılmalıdır. .. Commonsguy daha iyi açıklayabilmelidir. Kısacası Bilmiyorsunuz! :)
Chris.Jenkins

1
@mahkie Gerçekten ALOT Nesneleri / Modelleri gerekiyorsa onları bir Veritabanı veya ContentProvider'dan eşzamansız olarak almalısınız.
Chris.Jenkins

1
@ Chris.Jenkins: Eğer net olmasaydım özür dilerim ... Demek istediğim, Etkinlikler'in aksine, Fragmanların yapıcıların veri aktarımı / paylaşımı için kullanılmaması gerektiğini açıkça ortaya koymamasıydı. Boşaltma / geri yükleme iyi olsa da, birkaç veri kopyasının tutulmasının bazen görüntüleme imhasının geri kazanabileceğinden daha fazla bellek alabileceğine inanıyorum. Bazı durumlarda, bu faaliyeti olmadığına dair bir koleksiyon tedavi seçeneğine sahip yararlı olabilir / a birim olarak fragmanlar hiç bir bütün ya da değil olarak imha edilecek - o zaman olabilir yapıcıları yoluyla veri geçmektedir. Şimdilik, bu konuda, sadece boş bir kurucu var.
kaay

3
Neden birkaç veri kopyası tutuyorsunuz? Paketler | Parcelable, gerçekte durumlar / parçalar / etkinlikler arasında olabileceği zaman bellek referansını iletir (aslında bazı garip durum sorunlarına neden olur), Parcelable gerçekten etkili bir şekilde "kopyalar" verileri süreçler ve tam yaşam döngüsü arasındadır. Örneğin, bir nesneyi aktivitenizden parçalarınıza geçirirseniz, geçen referansınız bir klon değil. Tek ek yükünüz ek parça nesneleridir.
Chris.Jenkins

1
@ Chris.Jenkins O zaman, Parcelable konusundaki bilgisizliğim buydu. Parcelable'ın kısa javadokunu ve Parsel'in "yeniden yapılandırılmış" kelimesini geçmeyen bir bölümünü okuduktan sonra, "Aktif Nesneler" kısmına ulaşmamıştım, bunun sadece düşük seviyeli daha optimize edilmiş ancak daha az çok yönlü Seri hale getirilebilir olduğu sonucuna vardım. Ben utanç ve mumble şapka don don "Hala parcelables paylaşmak olamaz ve parcelables yapmak bir rahatsız olabilir" :)
kaay

17

Bu soruda https://stackoverflow.com/a/16064418/1319061 adresindeki CommonsWare tarafından belirtildiği gibi , bu hata, bir Parçanın anonim bir alt sınıfı oluşturuyorsanız da oluşabilir, çünkü anonim sınıflar yapıcılara sahip olamaz.

Fragment :-) 'in anonim alt sınıflarını yapmayın


1
Veya bu yayında CommonsWare'in de belirttiği gibi, bu hatayı önlemek için bir iç Aktivite / Parça / Alıcıyı "statik" olarak bildirdiğinizden emin olun.
Tony Wickham

7

Evet, gördüğünüz gibi destek paketi parçaları da yok ediyor (yok edildiklerinde ve yeniden açıldıklarında). Sizin Fragmentbu çerçevede tarafından çağrılan ne olduğu gibi alt sınıfları bir kamu boş bir yapıcı gerekir.


Boş Parça Yapıcısı super () Yapıcısını çağırmalı ya da çağırmamalıdır? Bunu boş kamu kurucusunun zorunlu olduğunu düşündüğüm için soruyorum. super () 'i çağırmak boş kamu kurucusu için bir anlam ifade etmiyorsa
TNR

@TNR, tüm Fragment soyutlamalarının boş bir kurucuya sahip super()olması nedeniyle, üst sınıf boş genel kurucu kuralını ihlal ettiği için sonuç vermez . Yani hayır, kurucunun super()içinden geçmene gerek yok .
Chris.Jenkins

4
Aslında bir Fragment'ta boş bir kurucu tanımlamak zorunlu değildir. Her Java sınıfının zaten örtük bir varsayılan yapıcısı vardır. Alınan: docs.oracle.com/javase/tutorial/java/javaOO/constructors.html ~ "Derleyici, kurucuları olmayan herhangi bir sınıf için otomatik olarak bağımsız değişken, varsayılan bir kurucu sağlar."
IgorGanapolsky

-6

İşte benim basit çözümüm:

1 - Parçanızı tanımlayın

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

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

2 - Yeni parçanızı oluşturun ve parametreyi doldurun

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

3 - Keyfini çıkarın!

Açıkçası parametrelerin türünü ve sayısını değiştirebilirsiniz. Çabuk ve kolay.


5
Bu, parçanın sistem tarafından yeniden yüklenmesini ele almaz.
Vidia
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.