我看到了两种在应用程序中实例化新片段的一般实践:
1
| Fragment newFragment = new MyFragment(); |
和
1
| Fragment newFragment = MyFragment.newInstance(); |
第二个选项使用静态方法newInstance(),通常包含以下方法。
1 2 3 4 5
| public static Fragment newInstance()
{
MyFragment myFragment = new MyFragment();
return myFragment;
} |
起初,我认为主要的好处是我可以重载newInstance()方法以在创建片段的新实例时提供灵活性,但我也可以通过为片段创建重载的构造函数来实现这一点。
我错过什么了吗?
一种方法比另一种方法有什么好处?或者只是个好的练习?
- 当有参数时,就没有选择了,这在这里得到了广泛的回答。尽管如此,这个问题仍然存在,对于片段的无论点构造。
- 在了解了工厂模式以及不实例化对象本身的调用类如何帮助将它们解耦之后,我认为这将是newInstance()方法的一个优点。我说的不对吗?我还没有看到这种具体的论据被认为是有益的。
如果Android稍后决定重新创建片段,它将调用片段的无参数构造函数。因此,重载构造函数不是解决方案。
这么说来,把东西传递到你的片段中,以便在Android重新创建一个片段后它们可用,方法是将一个包传递到setArguments方法。
因此,例如,如果我们想将一个整数传递给片段,我们将使用如下方法:
1 2 3 4 5 6 7 8 9
| public static MyFragment newInstance(int someInt) {
MyFragment myFragment = new MyFragment();
Bundle args = new Bundle();
args.putInt("someInt", someInt);
myFragment.setArguments(args);
return myFragment;
} |
稍后在片段onCreate()中,您可以使用以下方法访问该整数:
1
| getArguments().getInt("someInt", 0); |
即使这个片段是由Android以某种方式重新创建的,这个捆绑包也是可用的。
另请注意:只有在片段附加到活动之前,才能调用setArguments。
这种方法也记录在Android开发者参考资料中:https://developer.android.com/reference/android/app/fragment.html
- 然后他们应该让newInstance()供我们重写
- @不幸的是,静态方法不能被重写。
- @我想我这里遗漏了一些东西,你不能在这里使用一个构造函数吗,它创建了包并调用setArguments(),因为它只会被你的代码调用(而不是当Android重新创建你的片段时)?
- 啊,我看到lint给出了一个警告,如果您是一个非默认的构造函数。
- 这在frag docs中也有很好的记录:developer.android.com/reference/android/app/…
- 在这里使用捆绑包有什么好处吗?直接设置myfragment的int值不是很容易吗?例如,myfragment.somint=somint;返回myfragment;
- @如果希望在稍后重新创建片段时数据可用,则必须使用包。
- @如果你想传递对象怎么办?应该用线程来代替吗?我已经成功地通过accesors将对象从我的主活动传递到3个不同的片段中,但我担心在某个时刻,即使主活动是父活动,我也会得到一个空指针。
- @实际上,最好的方法是实现parcelable,然后将parcelable放到包中(您也可以使用serializable,但不推荐使用serializable)。
- @谢谢,我一定会调查的。
- 强制为片段创建无参数构造函数可能是所有编程中任何地方最大的一个方法。它强制在对象创建和初始化中进行完全的范式转换。如果你是一个刚接触Android的人,并且偶然发现了这个线索,请一遍又一遍地阅读上面的答案。
- 可以通过调用setRetainInstance(true)来保留片段及其数据,例如传递给构造函数的数据。这个方法确保片段不会在配置改变方向时调用onCreate和onDestroy。stackoverflow.com/questions/11182180/…
- @Yydl回答得很好,我完全理解。你说"如果Android决定稍后重新创建你的片段"。你能举出一些例子或者指出Android决定在哪里做这个的文档吗?
- +1要获得更好的答案,此方法还适用于使用fragmentmanager.add()动态添加的片段。
- @rmirabelle这就是(ny)框架的工作方式,它对可以使用的类/对象提供约束。Activity.onCreate而不是构造函数,与春天的afterPropertiesSet很相似。您可以尝试实现在爪哇中用任意构造函数实例化类的类型安全方法,但我认为这是不可能的。
- 我会反驳这种说法。首先,类型安全性是一个语言问题,而不是框架问题。其次,在我看来,这个框架正在进入"你的API绝不能做的事情"的领域。如果我想将国会图书馆传递给我的片段构造器,那么应该允许我这样做。"no-args"构造函数契约基本上终止了片段中依赖注入的使用——这是一个非常糟糕的问题。
- @fd我完全同意,初始化包并使用setArguments()的非默认构造函数也可以做到这一点。您无论如何都不会在这里使用任何参数,但是在onCreateView中,所以当系统使用默认的构造函数(必须声明)创建片段时,这不是问题。那为什么是静态工厂方法呢?
- @使用setarguments的joffrey只是战斗的一半。您还需要使用getArguments访问这些内容。
- @在任何情况下,您都必须使用getArguments,因为这是系统的工作方式。使用静态工厂方法或重载构造函数进行setArguments调用的事实不会影响您必须访问数据的约束。使用重载的构造函数不会改变这里的任何内容。
- @另一方面,即使使用静态工厂方法,也可以使用setter来设置片段的一些属性。这和从重载的构造函数中这样做一样糟糕。使用sfm并不强制程序员调用setarguments,您可以像使用构造函数那样做很多坏事。
- 杰弗里是真的。我想说的是,通过强制这种奇怪的模式,Android正在强调这里的事情是奇怪的。从本质上说,是在驱使人们去做这个问题和答案。
- @是的。这两种方法都有效,但我想使用谷歌推荐的方法可能更好,至少在其他程序员读取代码时。
- @我还不明白的是,使用静态方法的好处是什么。为什么我不能使用默认的构造函数,然后直接从代码中创建和发送包,而不是调用执行完全相同操作的静态方法呢?Otoh,使用静态方法创建片段是一个主要问题,当您需要创建通用代码时,该代码将被重写,因为您不能重载静态方法。如果整个想法是强制"发送者"发送正确的参数,您可以定义一个包含片段需要的参数的对象。
- 不同意你的意见
- 我正在尝试为抽象片段类执行此操作,但在baseListFragment myFragment=new baseListFragment();行中出现了一个错误,即我无法实例化抽象类的对象。怎么走?
- @你可以在碎片的任何地方使用getArguments().getInt …
- 顺便问一下,fragment的oncreate()在哪里?
- 什么时候可以使用android:configchanges="orientation keyboardhidden screensize‌&8203;"?
- 使用android studio,如果您使用辅助片段创建过程(文件>新建>片段),您将拥有一个已经实现此方法的片段。
- 此外,您还可以使用模型视图演示者模式,这样即使片段或活动重新启动,内存中的数据也可以被持久化。这里有一个基本的解释。github.com/konmik/konmik.github.io/wiki/…
- 使用这种方法,您不需要将任何对象分解成一个原语吗?我想将一个日期对象传递给fragment类,但没有办法这样做。唯一的解决方案是将日期分解为日、月和年整数吗?如果这看起来很疯狂,我必须销毁这个物体,然后在片段中重建它。
- @2013年7月30日,我的评论指向"@bensewards"。基本上,您可以通过使用parcelable(和serializable)传递任何可以放入包中的对象。对于日期对象,它看起来只实现可序列化,但请参见此处。
我看到的使用newInstance()的唯一好处是:
您将有一个单独的地方,在那里片段使用的所有参数都可以打包,并且您不必每次实例化片段时都编写下面的代码。
1 2 3 4 5
| Bundle args = new Bundle();
args.putInt("someInt", someInt);
args.putString("someString", someString);
// Put any other arguments
myFragment.setArguments(args); |
这是告诉其他类它希望哪些参数能够忠实地工作的一个好方法(尽管如果片段实例中没有绑定任何参数,您应该能够处理案例)。
因此,我的看法是,使用静态newInstance()来实例化片段是一个很好的实践。
- 1)这与将逻辑放入构造函数有什么不同?两者都是包含此逻辑的单一位置。2)静态工厂的参数与构造函数的参数有什么不同?这两个参数都告诉我们需要什么样的参数。我的观点是,这是一个不同的范例,当然,但是使用构造函数并没有明显的好处。
- 不能对片段使用自定义构造函数。框架使用无参数构造函数还原片段。
- 是的,我同意你的看法。我在概念上说,使用静态工厂模式而不是使用重载构造函数没有好处,反之亦然。你的两个观点在这两种模式中都是有效的;使用其中一个没有好处。Android强制您使用静态工厂模式——但是使用其中一种模式没有好处。
- pastebin.com/eyjzes0j
- @RJuthBertson的一个可能的好处是能够创建和返回静态工厂方法类的子类,即为这种情况返回适当的子类。
还有另一种方法:
1
| Fragment.instantiate(context, MyFragment.class.getName(), myBundle) |
- 如果我没有弄错,这只有在您使用Android支持库时才可能。
- 在没有支持库的情况下也是可能的。
- 在支持库中尝试了这个方法,但是在onCreateView(在我的片段中)中,传递的bundle为空,所以我使用setArguments/getArguments选项,它起作用(对于任何阅读此内容的人)。
- 有趣的是,我以前没有见过这种方法。与其他方法相比,它在实例化片段方面有什么优势吗?
- 从开发人员文件中,instantiate()Creates a new instance of a Fragment with the given class name. This is the same as calling its empty constructor.。
- 尽管他们提到了调用空构造函数。"args.setClassLoader(f.getClass().getClassLoader());"在下面调用bundle参数
- @igorganapolsky我的猜测是,当片段在XML中时,他们使用的方法是:。
- @igorganapolsky或者它有与intent.setclassname相同的用例,即没有类引用,因为它在另一个(android)包中?
虽然@yydl给出了一个令人信服的理由来解释为什么newInstance方法更好:
If Android decides to recreate your Fragment later, it's going to call
the no-argument constructor of your fragment. So overloading the
constructor is not a solution.
仍然可以使用构造函数。要了解这是为什么,首先我们需要了解为什么Android使用上述解决方案。
在使用片段之前,需要一个实例。android调用YourFragment()(无参数构造函数)来构造片段的实例。这里,您编写的任何重载构造函数都将被忽略,因为Android不知道要使用哪个构造函数。
在活动的生命周期中,碎片会像上面一样被创建,并被Android销毁多次。这意味着,如果将数据放入片段对象本身,那么一旦片段被销毁,它将丢失。
为了解决这个问题,Android要求您使用Bundle(调用setArguments())存储数据,然后可以从YourFragment访问该数据。参数Bundles受Android保护,因此保证是持久的。
设置这个包的一种方法是使用静态newInstance方法:
1 2 3 4 5 6 7 8
| public static YourFragment newInstance (int data) {
YourFragment yf = new YourFragment()
/* See this code gets executed immediately on your object construction */
Bundle args = new Bundle();
args.putInt("data", data);
yf.setArguments(args);
return yf;
} |
但是,构造函数:
1 2 3 4 5
| public YourFragment(int data) {
Bundle args = new Bundle();
args.putInt("data", data);
setArguments(args);
} |
可以做与newInstance方法完全相同的事情。
当然,这会失败,这也是Android希望您使用newInstance方法的原因之一:
1 2 3
| public YourFragment(int data) {
this.data = data; // Don't do this
} |
作为进一步的解释,这里是Android的fragment类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| /**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* creation.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
} |
请注意,android要求只在构造时设置参数,并保证这些参数将被保留。
编辑:正如在@ JHH的注释中指出的,如果您提供了一个需要一些参数的自定义构造函数,那么Java不会为您的片段提供一个没有ARG的默认构造函数。因此,这需要您定义一个no-arg构造函数,这是使用newInstance工厂方法可以避免的代码。
编辑:Android不再允许对片段使用重载的构造函数。您必须使用newInstance方法。
- 什么时候可以使用android:configchanges="orientation keyboardhidden screensize‌&8203;"?
- android studio现在在片段中为所有非默认构造函数抛出一个错误,因此这不再有效。
- 天哪,我想知道有多少机器人开发者曾经在机器人之外编写过代码。这太疯狂了,我们不能用你描述的方法。对于为什么我们必须使用静态工厂方法,任何评论都没有令人信服的论据。更令人不安的是,它们会在编译时引发错误。这绝对是提供的最佳答案,表明SFM没有任何好处。
- 嗯,有一个微妙的原因。您可以自由地用参数创建自己的构造函数,但仍然需要一个无参数的构造函数。因为类总是有一个隐式的no arg构造函数,除非显式定义了一个带有arg的构造函数,这意味着您必须显式定义arg构造函数和no arg构造函数,否则系统将无法调用任何no arg构造函数。我相信这就是为什么建议使用静态工厂方法的原因——它只是降低了忘记定义无参数构造函数的风险。
- @jhh在编译时会失败,所以不会有那么大的风险。然而,这里的问题是构造函数重载,一个关键的编程范例,被Android拒绝了。
- 它在编译时不会失败。android lint会抱怨缺少默认的构造函数,但它不会阻止调试构建。如果重新创建片段,将导致运行时异常。
- @我不知道安卓是如何构造这些碎片的细节。我想强调的是,因为它仍然会导致运行时异常,所以很容易纠正。但是,如果您将数据存储在参数包之外,则很难跟踪到它。但是,对默认构造函数的要求也是一个很好的理由。
- 事实上,片段只在少数情况下重新创建,例如在内存不足的情况下或由于配置更改,这使得很容易忘记添加默认的构造函数。这就是为什么他们加了皮棉警告,我猜。通常,您自己用参数创建片段,可能不使用默认的构造函数,并且默认的构造函数仅在异常情况下由系统使用。如果使用静态工厂方法,则基本上使用的代码与系统将使用的代码相同,即没有后面跟有setarguments的arg构造函数。
我不同意YYDI的回答说:
If Android decides to recreate your Fragment later, it's going to call
the no-argument constructor of your fragment. So overloading the
constructor is not a solution.
我认为这是一个很好的解决方案,这正是Java核心语言开发的原因。
的确,Android系统可以摧毁并重新创建你的Fragment。所以你可以这样做:
1 2 3 4 5 6 7 8 9
| public MyFragment() {
// An empty constructor for Android System to use, otherwise exception may occur.
}
public MyFragment(int someInt) {
Bundle args = new Bundle();
args.putInt("someInt", someInt);
setArguments(args);
} |
即使系统重新创建了Fragment,它也允许您从getArguments()后者中拉出someInt。这比static构造函数更优雅。
依我看,static构造器是无用的,不应该使用。另外,如果将来您希望扩展这个Fragment,并向构造函数添加更多功能,它们也会限制您。使用static构造函数,您不能这样做。
更新:
Android增加了检查,将所有非默认构造函数标记为一个错误。出于上述原因,我建议禁用它。
- 使用静态方法的另一个好处是,我上面没有提到,您不能意外地从中设置属性。
- 另外,关于您关于"扩展这个片段"的观点,如果您扩展这个类,这个方法实际上是非常糟糕的。调用super将导致setarguments()调用仅对子级或父级有效,但不能同时对两者都有效!
- @为什么不两个都要?
- 好吧,这取决于您在子级实现中调用super(…)的时间,但基本上重点是setarguments()只对设置的最后一个包有效。
- @您可以通过调用get参数来初始化子包来避免这种情况。Java的方式总是更好的。
- 没错,但这也是鼓励人们使用谷歌建议的模式的另一个原因。当然,我们都同意你的解决方案在技术上是100%可行的。同样的,有很多方法可以做很多事情。然而,问题是它是否是最好的。我强烈地感觉到使用构造函数并不能代表它应该如何工作的真正本质。
- 我同意@yydl的观点,静态创建更好。另一个好处是对未来新依赖项的依赖项注入——构造函数不适合这种情况,可能会导致更多的代码更改(或添加更多的构造函数)。
- android studio现在在片段中为所有非默认构造函数抛出一个错误,因此这不再有效。
- @Sheharyar我在答案中添加了它,我建议您禁用此检查。
- 重新创建片段时,调用非默认构造函数,对吗?那么这有什么帮助呢?您自己引用了它,因此重载构造函数不是解决方案
- 我引用了CRKETETH07,因为我不同意它,这里我提供了本地Java方法来解决这个问题。
- 我想我明白,但是操作系统不会打电话给public MyFragment(int someInt),所以…是啊
- @Cricket_007听起来你不懂setArguments方法。当您调用它时,它将在本地保存数据,因此当系统从默认承包商重新创建片段时,您将拥有在非默认构造函数中设置的参数。
- 不,我明白这一点。我想我看到了您现在所得到的——您可以用一个重载的构造函数来补充默认的构造函数,而静态的构造函数是无用的。
- @板球007是的,就是这个
- 有些人只是忘记了实现no-arg-one,这就是静态解决方案存在的原因。两者都是"原生Java方法"。
- 静态解决方案被认为是一个构造函数,但您不能重写它。所以这是一个非常重要的问题
一些Kotlin代码:
1 2 3 4 5 6 7 8 9 10
| companion object {
fun newInstance(first: String, second: String) : SampleFragment {
return SampleFragment().apply {
arguments = Bundle().apply {
putString("firstString", first)
putString("secondString", second)
}
}
}
} |
你可以用这个得到参数:
1 2
| val first: String by lazy { arguments?.getString("firstString") ?:"default"}
val second: String by lazy { arguments?.getString("secondString") ?:"default"} |
在android中使用参数的实例片段的最佳实践是在片段中使用静态工厂方法。
1 2 3 4 5 6 7 8 9 10
| public static MyFragment newInstance(String name, int age) {
Bundle bundle = new Bundle();
bundle.putString("name", name);
bundle.putInt("age", age);
MyFragment fragment = new MyFragment();
fragment.setArguments(bundle);
return fragment;
} |
您应该避免使用片段的实例设置字段。因为每当Android系统重新创建片段时,如果它觉得系统需要更多的内存,那么它将使用不带参数的构造函数重新创建片段。
您可以在这里找到更多关于用参数实例化片段的最佳实践的信息。
由于关于最佳实践的问题,我想补充一点,在使用一些RESTWeb服务时,使用混合方法创建片段通常是个好主意。
为了显示用户片段,我们不能传递复杂的对象,例如一些用户模型。
但我们能做的就是签入那个用户的onCreate!=null,如果不是,则从数据层将其引入,否则使用现有的。
这样,我们既可以在Android进行碎片创建时通过user id重新创建,也可以在用户操作时快速创建,还可以通过保持对象本身或仅其id来创建碎片。
类似这样的事情:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class UserFragment extends Fragment {
public final static String USER_ID="user_id";
private User user;
private long userId;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userId = getArguments().getLong(USER_ID);
if(user==null){
//
// Recreating here user from user id(i.e requesting from your data model,
// which could be services, direct request to rest, or data layer sitting
// on application model
//
user = bringUser();
}
}
public static UserFragment newInstance(User user, long user_id){
UserFragment userFragment = new UserFragment();
Bundle args = new Bundle();
args.putLong(USER_ID,user_id);
if(user!=null){
userFragment.user=user;
}
userFragment.setArguments(args);
return userFragment;
}
public static UserFragment newInstance(long user_id){
return newInstance(null,user_id);
}
public static UserFragment newInstance(User user){
return newInstance(user,user.id);
}
} |
- 你说:"我们不能传递复杂的对象,例如一些用户模型。"—这不是真的,我们可以。这样:User user = /*...*/把用户放在包中:Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user);并从参数中获取用户:User user = getArguments().getParcelable("some_user");对象必须实现parcelable接口。链接
- 是的,但是当类很复杂并且包含指向其他对象的引用者时…我个人更喜欢保持简单,要么我有对象,要么我没有,然后需要得到它。
setArguments()是无用的。它只会带来混乱。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class MyFragment extends Fragment {
public String mTitle;
public String mInitialTitle;
public static MyFragment newInstance(String param1) {
MyFragment f = new MyFragment();
f.mInitialTitle = param1;
f.mTitle = param1;
return f;
}
@Override
public void onSaveInstanceState(Bundle state) {
state.putString("mInitialTitle", mInitialTitle);
state.putString("mTitle", mTitle);
super.onSaveInstanceState(state);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
if (state != null) {
mInitialTitle = state.getString("mInitialTitle");
mTitle = state.getString("mTitle");
}
...
}
} |
- 除非您被强制重写另一个方法并生成一个可能被隔离到onViewCreated作用域的字段。我想这很方便,有很多方法可以做同样的事情。此外,这也是一种检查用户所做更新的简单方法(比较getArguments和onSaveInstanceState的包)
- @Asagen,我喜欢你关于比较初始值和用户值的评论。我编辑了代码,并认为它仍然是统一的和清晰的。那么onViewCreated范围呢…我们可以在那里恢复状态包。但我更喜欢让onCreateView轻便、快速,在onActivityCreated内进行所有的重初始化,因为Fragment.getActivity()有时会返回null,也因为onAttach()更改了新版本的api 23。
- 你在这里所做的就是把set和getArguments移入saveInstanceState。你做的基本上和"在引擎盖下"做的一样。
- @板球,或者正好相反。使用saveInstanceState是"在发动机罩下"。使用Arguments是使您进行双重检查的功能的重复:首先是Arguments值,然后是saveInstanceState值。因为你必须以任何方式使用saveInstanceState。那Arguments呢…它们不是必需的。
- 参数是片段的意向附加值。它们不是无用的,它们包含与当前状态不同的初始参数。
实例化片段的最佳方法是使用默认片段。实例化方法或创建工厂方法来实例化片段。警告:总是在片段中创建一个空的构造函数,而还原片段内存将引发运行时异常。
我相信我有一个更简单的解决办法。
1 2 3 4 5 6 7 8 9 10 11
| public class MyFragment extends Fragment{
private String mTitle;
private List<MyObject> mObjects;
public static MyFragment newInstance(String title, List<MyObject> objects)
MyFragment myFrag = new MyFragment();
myFrag.mTitle = title;
myFrag.mObjects = objects;
return myFrag;
} |
- 如果myfragment被重新创建,mobjects将被清除(用户进入设备主屏幕,稍后打开在myfragment中断的应用程序)。您可以通过发送myfragment包作为参数来保留mobject。
- 另外,静态方法如何访问非静态成员变量?
- @你是对的,用一个包就是到这里的方法。
- @或者,根据这个示例代码,静态方法不能访问成员变量。MyFragment的实例正在访问其成员。这里没有错误。但是,我不建议任何人回答这个问题,因为当Android操作系统从内存中删除您的片段以打开一些空间时,在重新启动活动之后,这个片段将使用默认的空构造函数创建,而不分配ant变量。
- @冈汉,你说得对!不是这样。不好意思弄混了:)