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

252 android android-fragments

我有一个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
Run Code Online (Sandbox Code Playgroud)

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

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

Chr*_*ins 339

是的他们这样做.

无论如何,你不应该真的重写构造函数.你应该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;
}
Run Code Online (Sandbox Code Playgroud)

当然,这样抓住args:

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

    //...
    //etc
    //...
}
Run Code Online (Sandbox Code Playgroud)

然后你将从你的片段管理器中实例化如下:

@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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这种方式如果分离并重新附加对象状态,则可以通过参数存储.就像附加到Intents的捆绑包一样.

理由 - 额外阅读

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

如果您查看: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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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...
}
Run Code Online (Sandbox Code Playgroud)

  • 你为什么要拿几份数据?Bundles | Parcelable实际上可以在状态/片段/活动之间传递内存引用(实际上会导致一些奇怪的状态问题),Parcelable实际上有效地"复制"数据的唯一时间是进程和完整生命周期之间.例如,如果您将对象传递给活动中的片段,则传递的引用不是克隆.您唯一真正的额外开销是额外的片段对象. (3认同)
  • 如果您暂停活动或销毁它.因此,您进入主屏幕然后Android会杀死活动以节省空间.片段状态将被保存(使用args)然后gc对象(通常).所以在返回活动时,片段应该尝试使用保存状态重新创建,新的Default()然后onCreate等...此外,如果活动试图节省资源(低mem手机)它可能会删除刚刚暂停的对象.. Commonsguy应该能够更好地解释.简而言之你不知道!:) (2认同)

Jes*_*erB 16

正如CommonsWare在此问题/sf/answers/1124509291/中所述,如果要创建Fragment的匿名子类,也会发生此错误,因为匿名类不能具有构造函数.

不要制作片段的匿名子类:-)


Sve*_*ken 5

是的,正如您所看到的,support-package也会实例化片段(当它们被销毁并重新打开时).您的Fragemnt子类需要一个公共的空构造函数,因为这是框架调用的内容.

  • 实际上,并不要求在Fragment中显式定义空构造函数.无论如何,每个Java类都有一个隐式的默认构造函数.取自:http://docs.oracle.com/javase/tutorial/java/javaOO/constructors.html~"编译器自动为没有构造函数的任何类提供无参数的默认构造函数." (4认同)