ชิ้นส่วนต้องการตัวสร้างที่ว่างเปล่าจริงๆหรือ


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 Activityฉันไม่ได้ใช้FragmentManagerธุรกรรมเนื่องจากViewPagerตัวอย่างที่ฉันเห็นไม่จำเป็นต้องใช้และทุกอย่างดูเหมือนจะทำงานในระหว่างการพัฒนา


22
ใน Android บางรุ่น (อย่างน้อย ICS) คุณสามารถไปที่การตั้งค่า -> ตัวเลือกของนักพัฒนาและเปิดใช้งาน "ไม่เก็บกิจกรรม" การทำเช่นนี้จะช่วยให้คุณสามารถกำหนดวิธีทดสอบกรณีที่จำเป็นต้องใช้ตัวสร้างแบบไม่มีอาร์กิวเมนต์
Keith

ฉันมีปัญหาเดียวกันนี้ ฉันกำหนดข้อมูลชุดข้อมูลให้กับตัวแปรสมาชิก (โดยใช้ ctor ที่ไม่ใช่ค่าเริ่มต้น) โปรแกรมของฉันไม่หยุดทำงานเมื่อฉันฆ่าแอปมันเกิดขึ้นก็ต่อเมื่อตัวกำหนดตารางเวลาวางแอปของฉันบน backburner เพื่อ "ประหยัดพื้นที่" วิธีที่ฉันค้นพบสิ่งนี้คือการไปที่ Task Mgr และเปิดแอพอื่น ๆ อีกมากมายจากนั้นเปิดแอปของฉันใหม่ในการดีบัก มันล้มเหลวทุกครั้ง ปัญหาได้รับการแก้ไขเมื่อฉันใช้คำตอบของ Chris Jenkins ในการใช้ชุดข้อมูล
wizurd

คุณอาจสนใจในหัวข้อนี้: stackoverflow.com/questions/15519214/…
Stefan Haustein

5
หมายเหตุด้านสำหรับผู้อ่านในอนาคต: หากFragmentคลาสย่อยของคุณไม่ประกาศตัวสร้างใด ๆ เลยโดยปริยายตัวสร้างสาธารณะที่ว่างเปล่าจะถูกสร้างขึ้นโดยปริยายสำหรับคุณ (นี่เป็นพฤติกรรม Java มาตรฐาน ) คุณไม่จำเป็นต้องประกาศ Constructor ว่างเปล่าอย่างชัดเจนเว้นแต่คุณจะประกาศ Constructor อื่น ๆ
Tony Chan

ฉันจะพูดถึงว่า IntelliJ IDEA อย่างน้อยสำหรับรุ่น 14.1 มีคำเตือนแจ้งเตือนคุณถึงความจริงที่ว่าคุณไม่ควรมีคอนสตรัคเตอร์ที่ไม่ใช่ค่าเริ่มต้นในส่วน
RenniePet

คำตอบ:


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

วิธีนี้หากแยกออกและแนบสถานะวัตถุอีกครั้งสามารถจัดเก็บผ่านอาร์กิวเมนต์ เหมือนมัดที่แนบมากับ Intents

เหตุผล - อ่านเพิ่มเติม

ฉันคิดว่าฉันจะอธิบายว่าทำไมผู้คนถึงสงสัยว่าทำไม

หากคุณตรวจสอบ: 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 ()อธิบายว่าทำไมเมื่อสร้างอินสแตนซ์มันจะตรวจสอบว่า accessor เป็นpublicและตัวโหลดคลาสนั้นอนุญาตให้เข้าถึงได้

มันเป็นวิธีที่น่ารังเกียจทีเดียว แต่ก็ช่วยให้FragmentMangerสามารถฆ่าและสร้างใหม่Fragmentsด้วยสถานะ (ระบบย่อย Android ทำสิ่งที่คล้ายกัน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
หากคุณหยุดกิจกรรมชั่วคราวหรือทำลายมัน ดังนั้นคุณไปที่หน้าจอหลักจากนั้นกิจกรรมจะถูกฆ่าโดย Android เพื่อประหยัดพื้นที่ สถานะของแฟรกเมนต์จะถูกบันทึกไว้ (โดยใช้ args) จากนั้น gc วัตถุ (ปกติ) ดังนั้นเมื่อกลับสู่กิจกรรมชิ้นส่วนควรพยายามที่จะสร้างขึ้นใหม่โดยใช้สถานะที่บันทึกไว้ค่าเริ่มต้นใหม่ () จากนั้น onCreate ฯลฯ ... นอกจากนี้หากกิจกรรมพยายามประหยัดทรัพยากร (โทรศัพท์ mem ต่ำ) มันอาจลบวัตถุที่เพิ่งหยุดชั่วคราว .. คอมมอนส์ควรอธิบายได้ดีขึ้น ในระยะสั้นคุณไม่รู้! :)
Chris.Jenkins

1
@mahkie จริง ๆ ถ้าคุณต้องการมากของวัตถุ / รูปแบบคุณควรคว้าพวกเขาแบบอะซิงโครนัสจากฐานข้อมูลหรือ ContentProvider
Chris.Jenkins

1
@ Chris.Jenkins ขออภัยถ้าฉันไม่ชัดเจน ... ประเด็นของฉันคือที่แตกต่างจากกิจกรรมชิ้นส่วนไม่ได้ทำให้ชัดเจนว่าตัวสร้างจะต้องไม่ถูกใช้สำหรับการส่ง / แบ่งปันข้อมูล และในขณะที่การถ่ายโอนข้อมูล / คืนค่าเป็นเรื่องปกติฉันเชื่อว่าบางครั้งการเก็บข้อมูลหลายสำเนาอาจใช้หน่วยความจำได้มากกว่าการทำลายมุมมอง ในบางกรณีอาจมีประโยชน์ในการมีตัวเลือกในการเก็บรวบรวมกิจกรรม / ชิ้นส่วนเป็นหน่วยที่จะถูกทำลายทั้งหมดหรือไม่เลย - จากนั้นเราสามารถส่งผ่านข้อมูลผ่านตัวสร้าง สำหรับตอนนี้เกี่ยวกับปัญหานี้ตัวสร้างที่ว่างเปล่าเป็นสิ่งเดียวที่มี
kaay

3
เหตุใดคุณจึงต้องเก็บสำเนาหลายชุดของข้อมูล การรวมกลุ่ม | พัสดุจริงผ่านการอ้างอิงหน่วยความจำเมื่อมันสามารถระหว่างรัฐ / ชิ้นส่วน / กิจกรรม (มันทำให้เกิดปัญหาบางรัฐแปลกจริง), เวลาเพียงอย่างเดียว Parcelable ได้อย่างมีประสิทธิภาพข้อมูล "ซ้ำกัน" อยู่ระหว่างกระบวนการและวงจรชีวิตเต็ม เช่นถ้าคุณส่งวัตถุไปยังชิ้นส่วนของคุณจากกิจกรรมของคุณการส่งผ่านของคุณไม่ใช่โคลน ค่าใช้จ่ายเพิ่มเติมที่แท้จริงเพียงอย่างเดียวของคุณคือชิ้นส่วนเพิ่มเติม
Chris.Jenkins

1
@ Chris.Jenkins เอาล่ะนั่นคือความไม่รู้ของฉันเกี่ยวกับ Parcelable หลังจากอ่าน javadoc สั้น ๆ ของ Parcelable และเป็นส่วนหนึ่งของ Parcel ซึ่งอยู่ไม่ไกลจากคำว่า "reconstructed" ฉันไม่ได้ไปถึงส่วน "Active Objects" โดยสรุปว่ามันเป็นแค่ระดับต่ำที่ปรับให้เหมาะสมมากขึ้น ฉันจึงสวมหมวกแห่งความละอายและพึมพำ "ยังไม่สามารถแบ่งปันสิ่งของที่ไม่สามารถจัดทำได้และทำให้พัสดุกลายเป็นเรื่องน่ารำคาญ" :)
kaay

17

ตามที่ระบุไว้โดย CommonsWare ในคำถามนี้https://stackoverflow.com/a/16064418/1319061ข้อผิดพลาดนี้สามารถเกิดขึ้นได้หากคุณกำลังสร้างคลาสย่อยที่ไม่ระบุชื่อของ Fragment เนื่องจากคลาสที่ไม่ระบุชื่อไม่สามารถมีคอนสตรัคเตอร์ได้

อย่าสร้าง subclasses ที่ไม่ระบุชื่อของ Fragment :-)


1
หรือตามที่กล่าวไว้ใน CommonsWare ในโพสต์นั้นตรวจสอบให้แน่ใจว่าคุณได้ประกาศกิจกรรม / Fragment / Reciever ภายในเป็น "static" เพื่อหลีกเลี่ยงข้อผิดพลาดนี้
Tony Wickham

7

ใช่อย่างที่คุณเห็นแพคเกจการสนับสนุนจะทำให้ชิ้นส่วนแตกหักทันที (เมื่อถูกทำลายและเปิดใหม่) Fragmentคลาสย่อยของคุณต้องมีตัวสร้างสาธารณะที่ว่างเปล่าเนื่องจากนี่คือสิ่งที่ถูกเรียกโดยกรอบงาน


ตัวสร้างส่วนที่ว่างเปล่าควรเรียก super () Constructor หรือไม่? ฉันถามสิ่งนี้เพราะฉันรู้ว่า Constructor สาธารณะที่ว่างเปล่าเป็นข้อบังคับ หากการเรียก super () ไม่สมเหตุสมผลสำหรับตัวสร้างสาธารณะที่ว่างเปล่า
TNR

@TNR เนื่องจาก abstractions แฟรกเมนต์ทั้งหมดมี constructor ว่างเปล่าsuper()จะไร้ผลเนื่องจากคลาสพาเรนต์ได้ทำลายกฎ constructor สาธารณะที่ว่างเปล่า ดังนั้นไม่คุณไม่จำเป็นต้องผ่านsuper()ตัวสร้างของคุณ
Chris.Jenkins

4
ในความเป็นจริงมันไม่ได้เป็นข้อกำหนดในการกำหนดนวกรรมิกเปล่าใน Fragment ทุกคลาส Java มี Constructor เริ่มต้นโดยปริยาย นำมาจาก: docs.oracle.com/javase/tutorial/java/javaOO/constructors.html ~ "คอมไพเลอร์จะจัดเตรียมตัวสร้างที่ไม่มีอาร์กิวเมนต์ให้โดยอัตโนมัติสำหรับคลาสใด ๆ ที่ไม่มีตัวสร้าง"
IgorGanapolsky

-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
นี่ไม่ได้จัดการกับการโหลดของแฟรกเมนต์โดยระบบแม้ว่า
Vidia
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.