我有一个带多个参数的构造函数的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

我可以像这个错误消息建议的那样做一个空构造函数,但这对我来说没有意义,因为我必须调用一个单独的方法来完成设置片段。

我很好奇为什么这种崩溃只是偶尔发生。也许我没有正确地使用ViewPager ?我自己实例化了所有的片段,并将它们保存在活动中的一个列表中。我不使用FragmentManager事务,因为我所看到的ViewPager示例并不需要它,而且在开发过程中一切似乎都在工作。


是的,正如您所看到的,支持包也实例化了片段(当它们被销毁并重新打开时)。你的Fragment子类需要一个公共空构造函数,因为这是由框架调用的。


是的,他们有。

你不应该重写构造函数。你应该定义一个newInstance()静态方法,并通过参数(bundle)传递任何参数

例如:

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

这样,如果分离和重新附加对象状态,可以通过参数存储。很像连接到intent的bundle。

原因-额外阅读

我想我可以解释给那些想知道为什么的人。

如果你检查:https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

你会看到Fragment类中的instantiate(..)方法调用了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()解释了为什么,在实例化时,它检查访问器是公共的,并且类装入器允许访问它。

总的来说,这是一个非常讨厌的方法,但它允许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...
}

正如CommonsWare在这个问题https://stackoverflow.com/a/16064418/1319061中所指出的,如果您正在创建Fragment的匿名子类,也会发生这个错误,因为匿名类不能有构造函数。

不要创建Fragment的匿名子类:-)


以下是我的简单解决方案:

定义你的片段

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 -享受它!

显然,您可以更改参数的类型和数量。 又快又简单。


看看官方文档:Fragment: https://developer.android.com/reference/android/app/Fragment

Fragment的所有子类都必须包含一个公共的无参数构造函数。框架通常会在需要时重新实例化一个片段类,特别是在状态恢复期间,并且需要能够找到这个构造函数来实例化它。如果无参数构造函数不可用,在某些情况下,在状态恢复期间将发生运行时异常。