我有一个带有多个参数的构造函数的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
内的列表中。我没有使用FragmentManager
个交易,因为我见过的ViewPager
个例子并不需要它,而且一切似乎都在开发过程中起作用。
答案 0 :(得分:328)
是的,他们这样做。
无论如何,你不应该真的重写构造函数。您应该定义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;
}
当然,这样抓住了args:
@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();
}
}
这种方式如果分离并重新附加对象状态,则可以通过参数存储。就像附加到Intents的捆绑包一样。
原因 - 额外阅读
我想我会解释为什么人们想知道为什么。
您会看到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...
}
答案 1 :(得分:15)
如CommonsWare在此问题https://stackoverflow.com/a/16064418/1319061中所述,如果要创建Fragment的匿名子类,也会发生此错误,因为匿名类不能包含构造函数。
不要制作片段的匿名子类: - )
答案 2 :(得分:5)
是的,正如您所看到的,support-package也会实例化片段(当它们被销毁并重新打开时)。您的Fragment
子类需要一个公共空构造函数,因为这是框架调用的内容。
答案 3 :(得分:-5)
这是我的简单解决方案:
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 - 享受吧!
显然你可以改变参数的类型和数量。 快速而简单。