क्या टुकड़ों को वास्तव में एक खाली निर्माणकर्ता की आवश्यकता है?


258

मेरे पास एक Fragmentकंस्ट्रक्टर है जो कई तर्क देता है। मेरे ऐप ने विकास के दौरान ठीक काम किया, लेकिन उत्पादन में मेरे उपयोगकर्ता कभी-कभी इस दुर्घटना को देखते हैं:

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

मैं एक खाली कंस्ट्रक्टर बना सकता हूं क्योंकि यह त्रुटि संदेश बताता है, लेकिन इससे मुझे कोई मतलब नहीं है क्योंकि मुझे सेटिंग सेट करने के लिए एक अलग विधि को कॉल करना होगा Fragment

मैं उत्सुक हूं कि यह दुर्घटना कभी-कभार ही क्यों होती है। शायद मैं ViewPagerगलत तरीके से उपयोग कर रहा हूं ? मैं Fragmentअपने आप को सभी s को तुरंत बताता हूं और उन्हें अंदर की सूची में सहेजता हूं Activity। मैं FragmentManagerलेन-देन का उपयोग नहीं करता हूं , क्योंकि ViewPagerमैंने जो उदाहरण देखे हैं, उन्हें इसकी आवश्यकता नहीं थी और सब कुछ विकास के दौरान काम कर रहा था।


22
Android के कुछ संस्करणों में (कम से कम ICS), आप सेटिंग में जा सकते हैं -> डेवलपर विकल्प और "गतिविधियाँ न रखें" को सक्षम करें। ऐसा करने से आपको उन मामलों का परीक्षण करने का एक निर्धारित तरीका मिलेगा जहां एक गैर-आर्गन निर्माता आवश्यक नहीं है।
कीथ

मेरा भी यही मुद्दा था। मैं सदस्य चर के बजाय बंडल डेटा असाइन कर रहा था (एक गैर-डिफ़ॉल्ट ctor का उपयोग करके)। मेरा प्रोग्राम क्रैश नहीं हो रहा था जब मैंने ऐप को मार दिया था-यह केवल तब हो रहा था जब शेड्यूलर ने मेरे ऐप को "स्पेस" के लिए बैकबर्नर पर रखा था। जिस तरह से मुझे पता चला कि यह टास्क मेगर पर जा रहा है और एक टन अन्य एप्लिकेशन खोल रहा है, फिर डिबग में अपने ऐप को फिर से खोल रहा है। यह हर बार दुर्घटनाग्रस्त हो गया। इस मुद्दे को हल किया गया था जब मैंने बंडल के उपयोग के लिए क्रिस जेनकिंस के उत्तर का उपयोग किया था।
११:

आप इस सूत्र में रुचि ले सकते हैं: stackoverflow.com/questions/15519214/…
स्टीफन हस्टीन

5
भविष्य के पाठकों के लिए एक साइड नोट: यदि आपका Fragmentउपवर्ग किसी भी कंस्ट्रक्टर को घोषित नहीं करता है, तो डिफ़ॉल्ट रूप से एक खाली सार्वजनिक कंस्ट्रक्टर आपके लिए स्पष्ट रूप से बनाया जाएगा (यह मानक जावा व्यवहार है )। आपको स्पष्ट रूप से एक खाली कंस्ट्रक्टर घोषित करने की आवश्यकता नहीं है, जब तक कि आपने अन्य कंस्ट्रक्टर (जैसे तर्क वाले) को भी घोषित नहीं किया है।
टोनी चैन

मैं बस उल्लेख करता हूं कि IntelliJ IDEA, कम से कम 14.1 संस्करण के लिए, आपको इस तथ्य के लिए चेतावनी देता है कि आपके पास एक खंड में एक गैर-डिफ़ॉल्ट निर्माता नहीं होना चाहिए।
रेनीपेट

जवाबों:


349

हाँ, वो करते हैं।

आप वास्तव में वैसे भी निर्माता को ओवरराइड नहीं करना चाहिए। आपके पास एक newInstance()स्थिर विधि होनी चाहिए और किसी भी पैरामीटर को तर्कों (बंडल) के माध्यम से पास करना चाहिए

उदाहरण के लिए:

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

और निश्चित रूप से इस तरह से आर्गल्स को हथियाना:

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

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

तब आप अपने खंड प्रबंधक से तुरंत इन्कार करेंगे:

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

इस तरह से यदि अलग किया गया और ऑब्जेक्ट स्टेट को फिर से अटैच किया गया तो उसे तर्कों के माध्यम से संग्रहीत किया जा सकता है। बहुत कुछ बंडलों की तरह है जो इरादों से जुड़ा हुआ है।

कारण - अतिरिक्त पढ़ना

मैंने सोचा कि मैं समझाऊंगा कि लोग सोच क्यों रहे हैं।

यदि आप जांच करते हैं: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

आप देखेंगे instantiate(..)में विधि Fragmentवर्ग कॉल newInstanceविधि:

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 () बताती है कि, इंस्टेंटेशन पर यह क्यों जाँचता है कि एक्सेसर है publicऔर यह कि क्लास लोडर इसे एक्सेस कर रहा है।

यह सब में एक बहुत बुरा तरीका है, लेकिन यह राज्यों FragmentMangerको मारने और फिर Fragmentsसे बनाने की अनुमति देता है। (एंड्रॉइड सबसिस्टम इसी तरह की चीजों को करता है Activities)।

उदाहरण वर्ग

मुझे कॉल करने के बारे में बहुत कुछ पूछा जाता है newInstance। इसे क्लास विधि से भ्रमित न करें। इस पूरे वर्ग उदाहरण का उपयोग दिखाना चाहिए।

/**
 * 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
यदि आप गतिविधि को रोकते हैं या इसे नष्ट करते हैं। तो आप होम स्क्रीन पर जाते हैं और फिर गतिविधि को कमरे को बचाने के लिए एंड्रॉइड द्वारा मार दिया जाता है। टुकड़े अवस्था में सहेजे जाएंगे (आर्ग का उपयोग करते हुए) तब ऑब्जेक्ट (सामान्य रूप से) को जीसी। तो गतिविधि पर लौटने पर खंडों को सहेजे गए राज्य, नए डिफ़ॉल्ट () का उपयोग करके फिर से बनाने की कोशिश की जानी चाहिए, फिर चालू करें ... साथ ही अगर गतिविधि संसाधनों (कम मेम फोन) को बचाने की कोशिश कर रही है, तो यह उन वस्तुओं को हटा सकता है जिन्हें अभी रोका गया था .. कॉमन्सुगी को बेहतर तरीके से समझाने में सक्षम होना चाहिए। शॉर्ट में आप नहीं जानते! :)
क्रिस। जेनकिंस

1
@mahkie वास्तव में यदि आपको ऑब्जेक्ट्स / मॉडल की बहुत आवश्यकता है, तो आपको उन्हें डेटाबेस या ContentProvider से एसिंक्रोनस रूप से हड़पना चाहिए।
क्रिस। जेनकिंस

1
@ Chris.Jenkins क्षमा करें यदि मैं स्पष्ट नहीं था ... मेरी बात यह थी कि, गतिविधियों के विपरीत, फ्रेग्मेंट यह स्पष्ट नहीं करते हैं कि डेटा को पारित / साझा करने के लिए निर्माणकर्ताओं का उपयोग नहीं किया जाना चाहिए। और डंपिंग / रिस्टोरिंग ठीक है, मेरा मानना ​​है कि डेटा की कई प्रतियों को धारण करने से कभी-कभी अधिक मेमोरी लग सकती है, जबकि विनाश नष्ट हो सकता है। कुछ मामलों में, एक इकाई के रूप में गतिविधियों / फ़्रैगमेंट के संग्रह का इलाज करने का विकल्प होना उपयोगी हो सकता है, एक पूरे के रूप में नष्ट होने के लिए या बिल्कुल भी नहीं - फिर हम निर्माणकर्ताओं के माध्यम से डेटा पारित कर सकते हैं। अभी के लिए, इस मुद्दे के बारे में, एक खाली कंस्ट्रक्टर केवल एक ही है।
काए

3
आप डेटा की कई प्रतियां क्यों धारण करेंगे? बंडलों | पार्सलबल वास्तव में मेमोरी संदर्भ पास करता है जब वह राज्यों / टुकड़ों / गतिविधियों के बीच हो सकता है, (यह वास्तव में कुछ अजीब राज्य मुद्दों का कारण बनता है), केवल समय पार्सल वास्तव में प्रभावी रूप से "डुप्लिकेट" डेटा प्रक्रियाओं और पूर्ण जीवनचक्र के बीच है। उदाहरण के लिए, यदि आप अपनी गतिविधि से अपने अंशों के लिए एक वस्तु पारित करते हैं, तो आपके पास का संदर्भ क्लोन नहीं है। आपका एकमात्र वास्तविक अतिरिक्त ओवरहेड अतिरिक्त टुकड़ा ऑब्जेक्ट है।
क्रिस। जेनकिंस

1
@ क्रिस। जेनकिंस वेल, उस समय, मेरी पार्सल की अज्ञानता थी। Parcelable के लघु javadoc को पढ़ने के बाद, और Parcel का एक हिस्सा "पुनर्निमित" शब्द से बहुत दूर नहीं था, मैं "सक्रिय ऑब्जेक्ट" भाग तक नहीं पहुंचा था, यह निष्कर्ष निकालता है कि यह सिर्फ एक निम्न-स्तर पर अधिक अनुकूलित लेकिन कम बहुमुखी अनुक्रमिक था। मैं शर्म और हंसी की टोपी का दान करता हूं "फिर भी नॉनस्पैरेबल्स को साझा नहीं कर सकते हैं और पार्सल को परेशान करना" :)
kaay

17

जैसा कि इस सवाल में CommonsWare ने नोट किया है https://stackoverflow.com/a/16064418/1319061 , यह त्रुटि तब भी हो सकती है यदि आप एक फ़्रैगमेंट का एक अनाम उपवर्ग बना रहे हैं, क्योंकि अनाम वर्ग में कंस्ट्रक्टर नहीं हो सकते हैं।

फ्रैगमेंट के अनाम उपवर्गों को मत बनाओ :-)


1
या, जैसा कि कॉमन्सवेयर ने उस पोस्ट में उल्लेख किया है, सुनिश्चित करें कि आप इस त्रुटि से बचने के लिए एक आंतरिक गतिविधि / फ्रैगमेंट / प्राप्तकर्ता को "स्थिर" घोषित करते हैं।
टोनी विकम

7

हां, जैसा कि आप देख सकते हैं कि सपोर्ट-पैकेज अंशों को भी त्वरित करता है (जब वे नष्ट हो जाते हैं और फिर से खोले जाते हैं)। आपके Fragmentउपवर्गों को एक सार्वजनिक खाली कंस्ट्रक्टर की आवश्यकता है क्योंकि यह वही है जिसे फ्रेमवर्क द्वारा बुलाया जा रहा है।


खाली टुकड़ा निर्माता को सुपर () कंस्ट्रक्टर को कॉल करना चाहिए या नहीं? मैं यह इसलिए पूछ रहा हूं क्योंकि मुझे लगता है कि खाली सार्वजनिक कंस्ट्रक्टर अनिवार्य है। अगर सुपर कॉलिंग () खाली सार्वजनिक कंस्ट्रक्टर के लिए कोई मतलब नहीं है
TN34

@ टीएनआर के रूप में सभी फ्रैगमेंट एब्स्ट्रक्शन में एक खाली कंस्ट्रक्टर super()बेकार होता है, क्योंकि मूल वर्ग ने खाली सार्वजनिक कंस्ट्रक्टर नियम को तोड़ दिया है। तो नहीं, आपको super()अपने कंस्ट्रक्टर के अंदर जाने की जरूरत नहीं है ।
क्रिस। जेनकिंस

4
वास्तव में यह एक फ्रैगमेंट में एक खाली कंस्ट्रक्टर को स्पष्ट रूप से परिभाषित करने की आवश्यकता नहीं है। हर जावा वर्ग में वैसे भी एक अंतर्निहित डिफ़ॉल्ट निर्माता होता है। से लिया गया: docs.oracle.com/javase/tutorial/java/javaOO/constructors.html ~ "कंपाइलर स्वचालित रूप से बिना किसी वर्ग के किसी भी वर्ग के लिए नो-तर्क, डिफ़ॉल्ट कंस्ट्रक्टर प्रदान करता है।"
इगोरगानापोलस्की

-6

यहाँ मेरा सरल समाधान है:

1 - अपने टुकड़े को परिभाषित करें

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

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

2 - अपना नया टुकड़ा बनाएँ और पैरामीटर आबाद करें

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

3 - आनंद लें!

जाहिर है आप प्रकार और मापदंडों की संख्या को बदल सकते हैं। जल्द और आसान।


5
हालांकि यह सिस्टम द्वारा खंड के पुनः लोडिंग को नियंत्रित नहीं करता है।
विद्या
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.