Android 片段真的需要一个空的构造函数吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10450348/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 03:48:08  来源:igfitidea点击:

Do fragments really need an empty constructor?

androidandroid-fragments

提问by Chris.Jenkins

I have a Fragmentwith a constructor that takes multiple arguments. My app worked fine during development, but in production my users sometimes see this crash:

我有一个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

I could make an empty constructor as this error message suggests, but that doesn't make sense to me since then I would have to call a separate method to finish setting up the Fragment.

我可以按照此错误消息的提示创建一个空的构造函数,但这对我来说没有意义,因为从那时起我将不得不调用一个单独的方法来完成Fragment.

I'm curious as to why this crash only happens occasionally. Maybe I'm using the ViewPagerincorrectly? I instantiate all the Fragments myself and save them in a list inside the Activity. I don't use FragmentManagertransactions, since the ViewPagerexamples I have seen did not require it and everything seemed to be working during development.

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

回答by Chris.Jenkins

Yes they do.

是的,他们这样做。

You shouldn't really be overriding the constructor anyway. You should have a newInstance()static method defined and pass any parameters via arguments (bundle)

无论如何,您不应该真正覆盖构造函数。您应该newInstance()定义一个静态方法并通过参数(包)传递任何参数

For example:

例如:

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

And of course grabbing the args this way:

当然,以这种方式获取 args:

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

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

Then you would instantiate from your fragment manager like so:

然后你会像这样从你的片段管理器实例化:

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

This way if detached and re-attached the object state can be stored through the arguments. Much like bundles attached to Intents.

这样,如果分离并重新附加,则可以通过参数存储对象状态。很像附加到 Intent 的捆绑包。

Reason - Extra reading

原因 - 额外阅读

I thought I would explain why for people wondering why.

我想我会解释为什么人们想知道为什么。

If you check: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

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

You will see the instantiate(..)method in the Fragmentclass calls the newInstancemethod:

您将看到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()Explains why, upon instantiation it checks that the accessor is publicand that that class loader allows access to it.

http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance()解释了为什么,在实例化时它会检查访问器是否存在public以及该类加载器是否允许访问它。

It's a pretty nasty method all in all, but it allows the FragmentMangerto kill and recreate Fragmentswith states. (The Android subsystem does similar things with Activities).

总而言之,这是一种非常讨厌的方法,但它允许使用状态FragmentManger杀死和重新创建Fragments。(Android 子系统与 做类似的事情Activities)。

Example Class

示例类

I get asked a lot about calling newInstance. Do not confuse this with the class method. This whole class example should show the usage.

我被问到很多关于打电话的问题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...
}

回答by JesperB

As noted by CommonsWare in this question https://stackoverflow.com/a/16064418/1319061, this error can also occur if you are creating an anonymous subclass of a Fragment, since anonymous classes cannot have constructors.

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

Don't make anonymous subclasses of Fragment :-)

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

回答by Sveinung Kval Bakken

Yes, as you can see the support-package instantiates the fragments too (when they get destroyed and re-opened). Your Fragmentsubclasses need a public empty constructor as this is what's being called by the framework.

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

回答by Alecs

Here is my simple solution:

这是我的简单解决方案:

1 - Define your fragment

1 - 定义你的片段

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

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

2 - Create your new fragment and populate the parameter

2 - 创建新片段并填充参数

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

3 - Enjoy it!

3 - 享受吧!

Obviously you can change the type and the number of parameters. Quick and easy.

显然,您可以更改参数的类型和数量。快捷方便。