实例化新的Android Fragment的最佳做法

我已经看到了两个通用实践来在应用程序中实例化一个新的片段:

Fragment newFragment = new MyFragment(); 

 Fragment newFragment = MyFragment.newInstance(); 

第二个选项使用静态方法newInstance()通常包含以下方法。

 public static Fragment newInstance() { MyFragment myFragment = new MyFragment(); return myFragment; } 

起初,我认为主要的好处是,我可以重载newInstance()方法来创建一个Fragment的新实例时的灵活性 – 但是我也可以通过为Fragment创建一个重载的构造函数来实现。

我错过了什么?

一种方法比另一种方法有什么好处? 或者这只是一个好习惯?

如果Android决定稍后重新创建Fragment,它将调用片段的无参数构造函数。 所以重载构造函数不是一个解决方案。

就这样说,把东西传递给你的Fragment的方式,以便在Android重新创建片段之后它们是可用的,就是将一个包传递给setArguments方法。

所以,例如,如果我们想要传递一个整数给片段,我们可以使用如下的东西:

 public static MyFragment newInstance(int someInt) { MyFragment myFragment = new MyFragment(); Bundle args = new Bundle(); args.putInt("someInt", someInt); myFragment.setArguments(args); return myFragment; } 

在后面的片段onCreate()您可以使用以下命令访问该整数:

 getArguments().getInt("someInt", 0); 

即使片段被Android重新创建,这个Bundle也是可用的。

另请注意: setArguments只能在Fragment附加到Activity之前调用。

这个方法也被记录在android开发人员参考中: https : //developer.android.com/reference/android/app/Fragment.html

使用我看到的newInstance()的唯一好处是以下几点:

  1. 您将拥有一个可以将片段使用的所有参数捆绑在一起的单独位置,而且每次实例化片段时都不必写下面的代码。

     Bundle args = new Bundle(); args.putInt("someInt", someInt); args.putString("someString", someString); // Put any other arguments myFragment.setArguments(args); 
  2. 它是一个很好的方法来告诉其他类什么它期望忠实地工作(虽然你应该能够处理的情况下,如果没有参数在片段实例绑定)。

所以,我的意思是使用静态newInstance()来实例化一个片段是一个好习惯。

还有另一种方法:

 Fragment.instantiate(context, MyFragment.class.getName(), myBundle) 

虽然@yydl给出了一个令人信服的理由,为什么newInstance方法更好:

如果Android决定稍后重新创建Fragment,它将调用片段的无参数构造函数。 所以重载构造函数不是一个解决方案。

它仍然很有可能使用一个构造函数 。 要明白这是为什么,首先我们需要明白为什么Android使用上述解决方法。

在可以使用片段之前,需要一个实例。 Android调用YourFragment()no参数构造函数)来构造片段的一个实例。 在这里,你写的任何重载的构造函数都会被忽略,因为Android不知道使用哪一个。

在一个Activity的生命周期里,片段被创建,并被Android多次销毁。 这意味着如果你把数据放入片段对象本身,一旦片段被破坏,它将会丢失。

要解决这个问题,android会要求你使用Bundle (调用setArguments() )来存储数据,然后可以从YourFragment访问YourFragment 。 参数bundle受Android保护,因此保证持久性

设置这个包的一种方法是使用静态的newInstance方法:

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

但是,一个构造函数:

 public YourFragment(int data) { Bundle args = new Bundle(); args.putInt("data", data); setArguments(args); } 

可以做newInstance方法完全相同的事情。

当然,这会失败,并且是Android希望使用newInstance方法的原因之一:

 public YourFragment(int data) { this.data = data; // Don't do this } 

作为进一步的解释,这里是Android的片段类:

 /** * 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构造函数,这是你可以用newInstance工厂方法避免的代码。

编辑 :Android不允许使用重载构造函数的碎片了。 您必须使用newInstance方法。

不同意 yydi 回答说:

如果Android决定稍后重新创建Fragment,它将调用片段的无参数构造函数。 所以重载构造函数不是一个解决方案。

我认为这是一个很好的解决方案,这正是Java核心语言开发的原因。

Android系统真的可以销毁和重新创建你的Fragment 。 所以你可以这样做:

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

它将允许你从getArguments()取出getArguments() ,即使系统重新创建了Fragment 。 这比static构造函数更优雅。

我认为static构造函数是无用的,不应该使用。 如果将来你想扩展这个Fragment并为构造函数增加更多的function,它们也会限制你。 用static构造函数,你不能这样做。

更新:

Android添加了检查,标记所有非默认构造函数的错误。
我建议禁用它,出于上述原因。

在android中使用参数实例碎片的最佳实践是在碎片中使用静态工厂方法。

 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系统重新创建你的片段,如果感觉系统需要更多的内存,它将通过使用没有参数的构造函数重新创建你的片段。

您可以在这里find更多关于最佳实践的信息来实例化带有参数的片段 。

由于关于最佳实践的问题,我想补充一下,在使用某些REST Web服务时,使用混合方法创建片段通常是个好主意

对于显示用户片段的情况,我们不能传递复杂的对象,例如一些用户模型

但是我们可以做的就是检查用户!= null,如果没有,那么把他从数据层拿来,否则使用现有的。

通过这种方式,我们既可以通过userId重新创建,也可以通过Android重新创建片段以及用户操作的快捷方式,以及通过持有对象本身或者仅仅是id来创建片段的能力

东西喜欢这个:

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

setArguments()是没用的。 它只会带来一团糟。

 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"); } ... } } 

一些科特林代码:

 companion object { fun newInstance(first: String, second: String) : SampleFragment { return SampleFragment().apply { arguments = Bundle().apply { putString("firstString", first) putString("secondString", second) } } } } 

你可以得到这样的论点:

 val first: String by lazy { arguments.getString("firstString") } val second: String by lazy { arguments.getString("secondString") } 

实例化片段的最佳方法是使用默认的Fragment.instantiate方法或创建工厂方法来实例化片段
警告:在fragment中总是创建一个空的构造函数,同时恢复片段内存会抛出运行时exception。

我相信我有一个更简单的解决方案。

 public class MyFragment extends Fragment{ private String mTitle; private List mObjects; public static MyFragment newInstance(String title, List objects) MyFragment myFrag = new MyFragment(); myFrag.mTitle = title; myFrag.mObjects = objects; return myFrag; }