片段真的需要一个空的构造函数吗? [英] Do fragments really need an empty constructor?

查看:29
本文介绍了片段真的需要一个空的构造函数吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有多个参数的构造函数的 Fragment.我的应用在开发过程中运行良好,但在生产中,我的用户有时会看到此崩溃:

android.support.v4.app.Fragment$InstantiationException:无法实例化片段确保类名存在,是公共的,并且有一个公共的空构造函数

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

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

解决方案

是的.

无论如何你都不应该重写构造函数.您应该定义一个 newInstance() 静态方法并通过参数(包)传递任何参数

例如:

public static final MyFragment newInstance(int title, String message) {MyFragment f = new MyFragment();捆绑 bdl = 新捆绑 (2);bdl.putInt(EXTRA_TITLE, 标题);bdl.putString(EXTRA_MESSAGE, message);f.setArguments(bdl);返回 f;}

当然也可以这样获取参数:

@Overridepublic void onCreate(Bundle savedInstanceState) {标题 = getArguments().getInt(EXTRA_TITLE);消息 = getArguments().getString(EXTRA_MESSAGE);//...//等等//...}

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

@Overridepublic void onCreate(Bundle savedInstanceState) {if (savedInstanceState == null){getSupportFragmentManager().beginTransaction().replace(R.id.content, MyFragment.newInstance(R.string.alert_title,哦不,发生错误!")).犯罪();}}

这种方式如果分离和重新附加对象状态可以通过参数存储.很像附加到 Intent 的捆绑包.

原因 - 额外阅读

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

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

您将看到 Fragment 类中的 instantiate(..) 方法调用了 newInstance 方法:

public static Fragment instanceiate(Context context, String fname, @Nullable Bundle args) {尝试 {类clazz = sClassMap.get(fname);if (clazz == null) {//缓存中没有找到类,看看是不是真的,尝试添加clazz = context.getClassLoader().loadClass(fname);如果 (!Fragment.class.isAssignableFrom(clazz)) {throw new InstantiationException("试图实例化一个类" + fname+ " 那不是 Fragment", new ClassCastException());}sClassMap.put(fname, clazz);}Fragment f = (Fragment) clazz.getConstructor().newInstance();如果(参数!= null){args.setClassLoader(f.getClass().getClassLoader());f.setArguments(args);}返回 f;} catch (ClassNotFoundException e) {throw new InstantiationException("无法实例化片段" + fname+ ": 确保类名存在,是公开的,并且有一个"+ " 公共的空构造函数", e);} catch (java.lang.InstantiationException e) {throw new InstantiationException("无法实例化片段" + fname+ ": 确保类名存在,是公开的,并且有一个"+ " 公共的空构造函数", e);} catch (IllegalAccessException e) {throw new InstantiationException("无法实例化片段" + fname+ ": 确保类名存在,是公开的,并且有一个"+ " 公共的空构造函数", e);} catch (NoSuchMethodException e) {throw new InstantiationException("无法实例化片段" + fname+ ": 找不到 Fragment 构造函数", e);} catch (InvocationTargetException e) {throw new InstantiationException("无法实例化片段" + fname+ ": 调用 Fragment 构造函数导致异常", e);}}

http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance() 解释为什么,在实例化时它会检查访问器是 public 和那个类loader 允许访问它.

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

示例类

我经常被问到关于调用 newInstance 的问题.不要将此与类方法混淆.整个类示例应该显示用法.

/*** 由 Chris 于 21/11/2013 创建*/公共类 StationInfoAccessibilityFragment 扩展 BaseFragment 实现 JourneyProviderListener {public static final StationInfoAccessibilityFragment newInstance(String crsCode) {StationInfoAccessibilityFragment fragment = new StationInfoAccessibilityFragment();最终捆绑包 args = 新捆绑包(1);args.putString(EXTRA_CRS_CODE, crsCode);fragment.setArguments(args);返回片段;}//视图LinearLayout mLinearLayout;/*** 布局充气机*/私人 LayoutInflater mInflater;/*** 站 Crs 代码*/私人字符串 mCrsCode;@覆盖public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mCrsCode = getArguments().getString(EXTRA_CRS_CODE);}@覆盖公共视图 onCreateView(LayoutInflater inflater,ViewGroup 容器,Bundle savedInstanceState) {mInflater = 充气机;返回 inflater.inflate(R.layout.fragment_station_accessibility, container, false);}@覆盖public void onViewCreated(View view, Bundle savedInstanceState) {super.onViewCreated(view,savedInstanceState);mLinearLayout = (LinearLayout)view.findViewBy(R.id.station_info_accessibility_linear);//做东西}@覆盖公共无效 onResume() {super.onResume();getActivity().getSupportActionBar().setTitle(R.string.station_info_access_mobility_title);}//其他方法等...}

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

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.

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

解决方案

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)

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:

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

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

You will see the instantiate(..) method in the Fragment class calls the newInstance method:

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 public and that that class loader allows access to it.

It's a pretty nasty method all in all, but it allows the FragmentManger to kill and recreate Fragments with states. (The Android subsystem does similar things with 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.

/**
 * 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...
}

这篇关于片段真的需要一个空的构造函数吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆