フラグメントには本当に空のコンストラクタが必要ですか?


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。私が見FragmentManagerViewPager例はそれを必要とせず、開発中にすべてが機能しているように見えたので、私はトランザクションを使用しません。


22
Androidの一部のバージョン(少なくともICS)では、[設定]-> [開発者向けオプション]に移動して、[アクティビティを保持しない]を有効にできます。これを行うと、引数のないコンストラクタが必要な場合をテストする確定的な方法が得られます。
Keith、

私も同じ問題を抱えていました。代わりにバンドルデータをメンバー変数に割り当てていました(デフォルト以外のctorを使用)。アプリを強制終了してもプログラムがクラッシュしませんでした。スケジューラがアプリをバックバーナーに配置して「スペースを節約」したときにのみ発生しました。これを発見した方法は、タスクマネージャーに移動して他の多数のアプリを開き、アプリをデバッグで再度開くことです。毎回クラッシュしました。この問題は、バンドル引数を使用するためにChris Jenkinsの回答を使用したときに解決されました。
wizurd 2014

このスレッドに興味がされることがあります。stackoverflow.com/questions/15519214/...
ステファンHaustein

5
将来の読者のための補足:Fragmentサブクラスがコンストラクターをまったく宣言しない場合、デフォルトで空のパブリックコンストラクターが暗黙的に作成されます(これは標準のJavaの動作です)。他のコンストラクター(例えば、引数を持つコンストラクター)も宣言していない限り、空のコンストラクターを明示的に宣言する必要ありませ
Tony Chan

IntelliJ IDEAは、少なくともバージョン14.1では、フラグメントにデフォルト以外のコンストラクターを含めてはならないという警告を提供することだけを述べます。
RenniePet 2015年

回答:


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、2012

1
@mahkie実際にオブジェクト/モデルのALOTが必要な場合は、データベースまたはContentProviderから非同期で取得する必要があります。
Chris.Jenkins、2012

1
@ Chris.Jenkinsわからない場合は申し訳ありません...私のポイントは、アクティビティとは異なり、フラグメントはデータの受け渡しや共有にコンストラクタを使用してはならないということを明確にしていないということです。ダンプ/復元は問題ありませんが、データのコピーをいくつか保持すると、ビューの破壊が回復できるよりも多くのメモリを消費することがあります。場合によっては、アクティビティ/フラグメントのコレクションを1つの単位として扱い、全体として破棄するか、まったく破棄しないかを選択できるオプションがあると便利です。その後、コンストラクタを介してデータを渡すことができます。とりあえず、この問題に関しては、空のコンストラクターだけが必要です。
kaay

3
データのコピーをいくつか保持しているのはなぜですか?バンドル| Parcelableは、状態/フラグメント/アクティビティ間でメモリ参照を渡すことができます(実際に奇妙な状態の問題が発生します)。Parcelableが実際に効果的にデータを「複製」するのは、プロセスと完全なライフサイクルの間だけです。たとえば、アクティビティからフラグメントにオブジェクトを渡す場合、渡す参照はクローンではありません。あなたの唯一の追加のオーバーヘッドは、追加のフラグメントオブジェクトです。
Chris.Jenkins、2013

1
@ Chris.Jenkinsさて、それで、それはParcelableを私の無知だった。Parcelableの短いjavadocを読み、Parcelの一部である「reconstructed」という単語をさかのぼらずに読んだところ、「Active Objects」の部分には到達していませんでした。私はここに恥とつぶやきの帽子をかぶります。「それでも小包を共有することはできず、小包を作ることは面倒なことです」:)
kaay

17

CommonsWareがこの質問https://stackoverflow.com/a/16064418/1319061で述べたように、匿名クラスはコンストラクターを持つことができないため、Fragmentの匿名サブクラスを作成している場合にもこのエラーが発生する可能性があります。

Fragmentの匿名サブクラスを作成しないでください:-)


1
または、CommonsWareがその投稿で述べたように、このエラーを回避するには、内部のActivity / Fragment / Recieverを「静的」として宣言してください。
Tony Wickham

7

はい、サポートパッケージでフラグメントがインスタンス化されていることがわかります(フラグメントが破棄されて再度開かれたとき)。あなたのFragmentこれはフレームワークによって呼び出されているものであるとして、サブクラスは、公共空のコンストラクタが必要です。


空のフラグメントコンストラクターは、super()コンストラクターを呼び出す必要がありますか?空のパブリックコンストラクターが必須であることを知ったので、これを求めています。super()の呼び出しが空のパブリックコンストラクタに対して意味をなさない場合
TNR

super()親クラスが空のパブリックコンストラクタールールに違反しているため、すべてのFragment抽象化に空のコンストラクターがある@TNRは無意味です。したがってsuper()、コンストラクターの内部を渡す必要はありません。
Chris.Jenkins 2013

4
実際、Fragmentで空のコンストラクターを明示的に定義する必要はありません。とにかく、すべてのJavaクラスには暗黙のデフォルトコンストラクタがあります。取得元docs.oracle.com/javase/tutorial/java/javaOO/constructors.html〜「コンパイラーは、コンストラクターのないクラスに対して、引数のないデフォルトのコンストラクターを自動的に提供します。」
IgorGanapolsky 2013

-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.