Các mảnh thực sự cần một nhà xây dựng trống?


258

Tôi có một Fragmenthàm tạo có nhiều đối số. Ứng dụng của tôi hoạt động tốt trong quá trình phát triển, nhưng trong sản xuất, người dùng của tôi đôi khi gặp sự cố này:

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

Tôi có thể tạo một hàm tạo trống như thông báo lỗi này gợi ý, nhưng điều đó không có ý nghĩa với tôi kể từ đó tôi sẽ phải gọi một phương thức riêng để hoàn tất thiết lập Fragment.

Tôi tò mò về lý do tại sao vụ tai nạn này chỉ thỉnh thoảng xảy ra. Có lẽ tôi đang sử dụng ViewPagerkhông chính xác? Tôi tự khởi tạo tất cả các Fragmentbản thân và lưu chúng trong một danh sách bên trong Activity. Tôi không sử dụng FragmentManagercác giao dịch, vì các ViewPagerví dụ tôi đã thấy không yêu cầu và mọi thứ dường như đang hoạt động trong quá trình phát triển.


22
trong một số phiên bản của Android (ít nhất là ICS), bạn có thể đi tới cài đặt -> tùy chọn nhà phát triển và bật "Không giữ hoạt động". Làm điều này sẽ cung cấp cho bạn một cách xác định để kiểm tra các trường hợp trong đó một hàm tạo không có đối số là cần thiết.
Keith

tôi đã có vấn đề tương tự. Tôi đã chỉ định dữ liệu gói thay cho các biến thành viên (sử dụng một ctor không mặc định). Chương trình của tôi không gặp sự cố khi tôi tắt ứng dụng - nó chỉ xảy ra khi người lập lịch đưa ứng dụng của tôi lên bộ đốt ngược để "tiết kiệm không gian". Cách tôi phát hiện ra điều này là bằng cách vào Nhiệm vụ Mgr và mở một tấn ứng dụng khác, sau đó mở lại ứng dụng của tôi trong gỡ lỗi. Nó bị rơi mỗi lần. Vấn đề đã được giải quyết khi tôi sử dụng câu trả lời của Chris Jenkins để sử dụng gói args.
wizurd

Bạn có thể quan tâm đến chủ đề này: stackoverflow.com/questions/15519214/iêu
Stefan Haustein

5
Một lưu ý phụ cho các độc giả trong tương lai: nếu Fragmentlớp con của bạn không khai báo bất kỳ hàm tạo nào, thì theo mặc định, một hàm tạo công khai trống sẽ được tạo ra cho bạn (đây là hành vi Java tiêu chuẩn ). Bạn không phải khai báo rõ ràng một hàm tạo trống trừ khi bạn cũng khai báo các hàm tạo khác (ví dụ: các hàm có đối số).
Tony Chan

Tôi sẽ chỉ đề cập rằng IntelliJ IDEA, ít nhất là cho phiên bản 14.1, đưa ra cảnh báo cảnh báo bạn về thực tế rằng bạn không nên có một hàm tạo không mặc định trong một đoạn.
RenniePet

Câu trả lời:


349

Có họ làm.

Dù sao thì bạn cũng không nên ghi đè lên hàm tạo. Bạn nên có một newInstance()phương thức tĩnh được xác định và truyền bất kỳ tham số nào thông qua các đối số (gói)

Ví dụ:

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

Và tất nhiên lấy các đối số theo cách này:

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

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

Sau đó, bạn sẽ khởi tạo từ trình quản lý phân đoạn của mình như vậy:

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

Cách này nếu tách rời và gắn lại trạng thái đối tượng có thể được lưu trữ thông qua các đối số. Giống như các gói gắn liền với Ý định.

Lý do - Đọc thêm

Tôi nghĩ rằng tôi sẽ giải thích tại sao cho mọi người tự hỏi tại sao.

Nếu bạn kiểm tra: https://android.googlesource.com/pl platform / frameworks / base / + / master / core / java / android / app / Fragment.java

Bạn sẽ thấy instantiate(..)phương thức trong Fragmentlớp gọi newInstancephương thức:

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 () Giải thích lý do, ngay lập tức, nó kiểm tra xem publictrình truy cập đó và trình nạp lớp đó cho phép truy cập vào nó.

Đây là một phương pháp khá khó chịu, nhưng nó cho phép FragmentMangergiết và tái tạo lại Fragmentsvới các trạng thái. (Hệ thống con Android thực hiện những điều tương tự với Activities).

Lớp mẫu

Tôi được hỏi rất nhiều về cuộc gọi newInstance. Đừng nhầm lẫn điều này với phương thức lớp. Ví dụ toàn lớp này sẽ hiển thị việc sử dụng.

/**
 * 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
Nếu bạn tạm dừng hoạt động hoặc phá hủy nó. Vì vậy, bạn đi đến màn hình chính và hoạt động sau đó bị Android giết để tiết kiệm phòng. Trạng thái các đoạn sẽ được lưu (sử dụng các đối số) sau đó gc đối tượng (bình thường). Vì vậy, khi quay trở lại hoạt động, các đoạn nên cố gắng được tạo lại bằng trạng thái đã lưu, Mặc định mới () sau đó onCreate, v.v ... Ngoài ra nếu hoạt động đang cố lưu tài nguyên (điện thoại mem thấp) Nó có thể xóa các đối tượng vừa bị tạm dừng .. Commonsguy sẽ có thể giải thích tốt hơn. Tóm lại, bạn không biết! :)
Chris.Jenkins

1
@mahkie Thực sự nếu bạn cần ALOT các Đối tượng / Mô hình, bạn nên lấy chúng không đồng bộ từ Cơ sở dữ liệu hoặc ContentProvider.
Chris.Jenkins

1
@ Chris.Jenkins Xin lỗi nếu tôi không rõ ràng ... quan điểm của tôi là, không giống như Hoạt động, Mảnh vỡ không làm rõ rằng các nhà xây dựng không được sử dụng để truyền / chia sẻ dữ liệu. Và trong khi bán phá giá / khôi phục là tốt, tôi tin rằng việc giữ một vài bản sao dữ liệu đôi khi có thể chiếm nhiều bộ nhớ hơn so với phá hủy xem có thể lấy lại. Trong một số trường hợp, có thể hữu ích khi có tùy chọn coi một tập hợp các Hoạt động / Mảnh vỡ như một đơn vị, bị phá hủy toàn bộ hoặc không hoàn toàn - sau đó chúng ta có thể truyền dữ liệu qua các nhà xây dựng. Hiện tại, liên quan đến vấn đề này, một nhà xây dựng trống là người duy nhất có.
kaay

3
Tại sao bạn sẽ giữ một vài bản sao của dữ liệu? Gói | Parcelable thực sự vượt qua tham chiếu bộ nhớ khi có thể giữa các trạng thái / đoạn / hoạt động, (thực sự gây ra một số vấn đề trạng thái kỳ lạ), lần duy nhất Parcelable thực sự "sao chép" dữ liệu là giữa các quy trình và vòng đời đầy đủ. Ví dụ: nếu bạn chuyển một đối tượng cho các đoạn của bạn từ hoạt động của bạn, thì tham chiếu vượt qua của bạn không phải là một bản sao. Chi phí phụ thực sự duy nhất của bạn là các đối tượng mảnh bổ sung.
Chris.Jenkins

1
@ Chris.Jenkins Vâng, đó, sau đó, là sự thiếu hiểu biết của tôi về Parcelable. Sau khi đọc đoạn javadoc ngắn của Parcelable và một phần của Parcel không vượt quá từ "được xây dựng lại", tôi đã không đạt được phần "Đối tượng hoạt động", kết luận rằng nó chỉ là một Trình tuần tự hóa được tối ưu hóa thấp hơn nhưng linh hoạt hơn. Tôi xin tặng mũ sự xấu hổ và lẩm bẩm "Vẫn không thể chia sẻ những thứ không tương xứng và làm cho bưu kiện có thể là một điều phiền phức" :)
kaay

17

Như CommonsWare đã lưu ý trong câu hỏi này https://stackoverflow.com/a/16064418/1319061 , lỗi này cũng có thể xảy ra nếu bạn đang tạo một lớp con ẩn danh của Fragment, vì các lớp ẩn danh không thể có các hàm tạo.

Đừng tạo các lớp con ẩn danh của Fragment :-)


1
Hoặc, như CommonsWare đã đề cập trong bài đăng đó, hãy đảm bảo bạn khai báo một Activity / Fragment / Reciever bên trong là "tĩnh" để tránh lỗi này.
Tony Wickham

7

Có, như bạn có thể thấy gói hỗ trợ cũng khởi tạo các mảnh vỡ (khi chúng bị phá hủy và mở lại). Các Fragmentlớp con của bạn cần một hàm tạo trống công khai vì đây là thứ được gọi bởi khung công tác.


Công cụ xây dựng phân đoạn trống có nên gọi siêu () Công cụ xây dựng hay không? Tôi đang hỏi điều này khi tôi chứng minh rằng Nhà xây dựng công cộng trống là bắt buộc. nếu gọi super () không có nghĩa đối với nhà xây dựng công cộng trống rỗng
TNR

@TNR vì tất cả các trừu tượng phân đoạn có một hàm tạo rỗng super()sẽ không có kết quả, vì lớp cha đã phá vỡ quy tắc hàm tạo công khai trống. Vì vậy, không bạn không cần phải vượt qua super()bên trong nhà xây dựng của bạn.
Chris.Jenkins

4
Trong thực tế, đó không phải là một yêu cầu để xác định rõ ràng một hàm tạo rỗng trong một Đoạn. Mỗi lớp Java đều có một hàm tạo mặc định ngầm định. Lấy từ: docs.oracle.com/javase/tutorial/java/javaOO/constructor.html ~ "Trình biên dịch tự động cung cấp một hàm tạo không có đối số, mặc định cho bất kỳ lớp nào không có hàm tạo."
IgorGanapolsky

-6

Đây là giải pháp đơn giản của tôi:

1 - Xác định đoạn của bạn

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

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

2 - Tạo đoạn mới của bạn và điền tham số

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

3 - Tận hưởng đi!

Rõ ràng bạn có thể thay đổi loại và số lượng tham số. Nhanh chóng và dễ dàng.


5
Điều này không xử lý việc tải lại các đoạn của hệ thống mặc dù.
Vidia
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.