조각에는 실제로 빈 생성자가 필요합니까?


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잘못 사용하고 있습니까? 나는 모든 Fragments를 인스턴스화하고 내부의 목록에 저장합니다 Activity. 내가 본 예제에서 요구하지 않았고 개발 중에 모든 것이 작동하는 것처럼 보였으 FragmentManager므로 트랜잭션을 사용 ViewPager하지 않습니다.


22
일부 안드로이드 버전 (적어도 ICS)에서는 설정-> 개발자 옵션으로 이동하여 "활동을 유지하지 마십시오"를 활성화 할 수 있습니다. 이렇게하면 인수없는 생성자가 필요한 경우를 테스트하는 결정적인 방법이 제공됩니다.
Keith

나는이 같은 문제가 있었다. 번들 데이터를 멤버 변수 (기본이 아닌 ctor 사용) 대신 할당했습니다. 응용 프로그램을 종료했을 때 프로그램이 중단되지 않았습니다. 스케줄러가 응용 프로그램을 백 버너에 "공간을 절약하기 위해"넣을 때만 발생했습니다. 내가 발견 한 방법은 Task Mgr로 이동하여 수많은 다른 앱을 연 다음 디버그에서 내 앱을 다시 여는 것입니다. 매번 추락했다. Chris Jenkins의 답변을 사용하여 번들 인수를 사용하면 문제가 해결되었습니다.
wizurd

당신이 스레드에 관심이있을 수 있습니다 stackoverflow.com/questions/15519214/...을
스테판 HAUSTEIN을

5
미래 독자를위한 참고 사항 : Fragment하위 클래스가 생성자를 전혀 선언하지 않으면 기본적으로 빈 공개 생성자 가 암시 적으로 만들어집니다 ( 표준 Java 동작 ). 다른 생성자를 선언하지 않는 한 빈 생성자 를 명시 적으로 선언 할 필요는 없습니다 (예 : 인수가있는 생성자).
Tony Chan

적어도 14.1 버전의 IntelliJ IDEA는 프래그먼트에 기본이 아닌 생성자가 없어야한다는 사실을 경고하는 경고를 제공한다고 언급합니다.
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();
    }
}

이런 식으로 분리하고 다시 연결하면 객체 상태를 인수를 통해 저장할 수 있습니다. 인 텐트에 첨부 된 번들과 매우 유사합니다.

이유-추가 자료

나는 사람들이 왜 궁금해하는지 설명 할 것이라고 생각했습니다.

확인하면 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. (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합니다 (일반적으로). 따라서 액티비티로 돌아 오면 저장된 상태, new Default (), onCreate 등을 사용하여 프래그먼트를 다시 생성해야합니다. .. Commonsguy는 더 잘 설명 할 수 있어야합니다. 요컨대 당신은 모른다! :)
Chris.Jenkins

1
@mahkie 실제로 많은 객체 / 모델이 필요하면 데이터베이스 나 ContentProvider에서 비동기 적으로 가져와야합니다.
Chris.Jenkins

1
@ Chris.Jenkins 죄송하지 않다면 죄송합니다 ... 내 요점은 액티비티와 달리 조각이 생성자를 사용하여 데이터를 전달 / 공유해서는 안된다는 것을 명확하게하지는 않았다는 것입니다. 덤프 / 복원은 괜찮지 만 여러 데이터 사본을 보유하면 뷰 파괴가 회복하는 것보다 더 많은 메모리를 차지할 수 있다고 생각합니다. 어떤 경우에는 액티비티 / 프래그먼트 모음을 하나의 단위로 취급하고 전체적으로 또는 전혀 파괴하지 않는 옵션을 갖는 것이 유용 할 수 있습니다. 그러면 생성자를 통해 데이터를 전달할 있습니다. 현재이 문제와 관련하여 비어있는 생성자가 유일합니다.
kaay

3
왜 여러 개의 데이터 사본을 보유하고 있습니까? Bundles | Parcelable은 실제로 상태 / 조각 / 활동 사이에있을 때 메모리 참조를 전달합니다 (실제로 이상한 상태 문제가 발생 함). Parcelable이 실제로 데이터를 효과적으로 "중복"하는 유일한 시간은 프로세스와 전체 수명주기 사이입니다. 예를 들어 액티비티에서 조각으로 객체를 전달하면 전달 참조는 복제본이 아닙니다. 유일한 추가 오버 헤드는 추가 조각 개체입니다.
Chris.Jenkins

1
@ Chris.Jenkins 글쎄요, 그건 소포에 대한 나의 무지였습니다. Parcelable의 짧은 javadoc과 "Reconstructed"라는 단어를 지나치지 않은 Parcel의 일부를 읽은 후 "Active Objects"부분에 도달하지 못해서 저수준의 최적화되었지만 덜 다재다능한 Serializable이라는 결론을 내 렸습니다. 나는 부끄러운 모자를 쓰고 "아직 소포를 공유 할 수없고 소포를 만드는 것은 귀찮을 수있다":)
kaay

17

이 질문 https://stackoverflow.com/a/16064418/1319061 에서 CommonsWare가 지적한 것처럼 익명 클래스에는 생성자를 가질 수 없으므로 Fragment의 익명 서브 클래스를 작성하는 경우 에도이 오류가 발생할 수 있습니다.

Fragment의 익명 서브 클래스를 만들지 마십시오 :-)


1
또는 해당 게시물에 언급 된 CommonsWare에서와 같이이 오류를 방지하려면 내부 활동 / 조회 / 수신자를 "정적"으로 선언해야합니다.
Tony Wickham

7

예, support-package는 프래그먼트도 인스턴스화합니다 (파손되고 다시 열릴 때). 당신 Fragment이 프레임 워크에 의해 호출되는 무슨이기 때문에 서브 클래스는 공개 비어있는 생성자가 필요합니다.


빈 조각 생성자는 super () 생성자를 호출해야합니까? 나는 빈 공개 생성자가 필수라는 것을 잊었을 때 이것을 요구하고 있습니다. 빈 public 생성자에 super () 호출이 의미가없는 경우
TNR

super()부모 클래스가 빈 공개 생성자 규칙을 어 겼기 때문에 모든 Fragment 추상화에 빈 생성자 가 있으므로 @TNR은 효과가 없습니다. 따라서 super()생성자 내부 를 전달할 필요가 없습니다 .
Chris.Jenkins

4
실제로 Fragment에 빈 생성자를 명시 적으로 정의 할 필요는 없습니다. 모든 Java 클래스에는 암시 적 기본 생성자가 있습니다. :에서 촬영 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.