实例化新Android片段的最佳实践

Gra*_*ith 677 android android-fragments

我已经看到了两个在应用程序中实例化新Fragment的一般实践:

Fragment newFragment = new MyFragment();
Run Code Online (Sandbox Code Playgroud)

Fragment newFragment = MyFragment.newInstance();
Run Code Online (Sandbox Code Playgroud)

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

public static Fragment newInstance() 
{
    MyFragment myFragment = new MyFragment();
    return myFragment;
}
Run Code Online (Sandbox Code Playgroud)

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

我错过了什么?

一种方法比另一种方法有什么好处?还是只是好习惯?

yyd*_*ydl 1090

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

话虽如此,将内容传递给Fragment以便在Android重新创建Fragment之后可用的setArguments方法是将包传递给方法.

因此,例如,如果我们想要将一个整数传递给片段,我们将使用类似的东西:

public static MyFragment newInstance(int someInt) {
    MyFragment myFragment = new MyFragment();

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    myFragment.setArguments(args);

    return myFragment;
}
Run Code Online (Sandbox Code Playgroud)

稍后在Fragment中,onCreate()您可以使用以下命令访问该整数:

getArguments().getInt("someInt", 0);
Run Code Online (Sandbox Code Playgroud)

即使Fragment以某种方式由Android重新创建,也可以使用此Bundle.

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

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

  • FORCED为片段创建无参数构造函数可能是所有编程中的最大单一陷阱.它迫使对象创建和初始化完成范式转换.如果你是Android的新手并且偶然发现了这个帖子,请一遍又一遍地阅读上面的答案. (103认同)
  • @mgibson如果您希望稍后重新创建片段时数据可用,则*必须*使用捆绑包. (9认同)
  • @yydl我想我在这里遗漏了一些东西,你不能在这里使用一个构造函数,一个创建Bundle并调用setArguments()仍然因为它只会被你的代码调用(而不是当Android重新创建你的分段)? (8认同)
  • 我会争辩这个断言.首先,类型安全是一种语言问题,而不是框架问题.其次,IMO,框架正在进入"你的API绝不能做的事情"这个领域.如果我想将会议库传递给我的片段构造函数,那么我应该被允许."no-args"构造函数契约基本上杀死了片段中依赖注入的使用 - 主要是yuck. (8认同)
  • 遗憾的是,@ Vlasto无法覆盖静态方法. (7认同)
  • 这在frag文档中也有相当好的记录:http://developer.android.com/reference/android/app/Fragment.html#Fragment() (5认同)
  • @yydl很棒的答案,我完全得到了解决方案.您说"如果Android决定稍后重新创建您的片段".你能给出一些例子或指向Android确实决定这样做的文档吗? (4认同)
  • @yydl,如果你想传递对象怎么办?应该用线程来完成吗?我已经成功地将主要活动中的对象传递给了3个不同的片段,但是我担心在某些时候即使主要活动是父活动,我也会得到一个空指针. (3认同)
  • @fd我完全同意初始化Bundle并使用`setArguments()`的非默认构造函数也可以做到这一点.你无论如何都不会在这里使用任何参数,但在onCreateView中,所以当系统使用默认构造函数(你必须**声明)创建片段时,这不是问题.为什么采用静态工厂方法呢? (3认同)
  • 然后他们应该让 newInstance() 为我们覆盖 (2认同)
  • 在这里使用 Bundle 有什么好处吗?直接设置 myFragment 的 int 值不是更容易吗?例如 myFragment.someInt = someInt; 返回我的片段; (2认同)
  • @BenSewards实际上,最好的方法可能是[实现Parcelable](http://developer.android.com/reference/android/os/Parcelable.html)然后将putParcelable放在Bundle上(你也可以使用)可序列化,但不推荐) (2认同)
  • @yydl无论如何,你必须使用`getArguments`,因为这是系统的工作方式.使用静态工厂方法或重载构造函数进行`setArguments`调用的事实不会影响访问数据所需的约束.使用重载的构造函数不会在这里改变任何东西. (2认同)
  • @乔弗里真实。我的意思是说,通过强制采用这种奇怪的模式,Android 正在强调这里的事情很奇怪。本质上是促使人们回答这个问题。 (2认同)
  • @Joffrey 我仍然不明白的是使用静态方法的优势是什么。为什么我不能只使用默认构造函数,然后直接从我的代码创建和发送包,而不是调用完全相同的静态方法?OTOH,当您需要创建然后被覆盖的通用代码时,使用静态方法创建片段是一个主要问题,因为您不能重载静态方法。如果整个想法是强制“发送者”发送正确的参数,您可以定义一个包含片段所需参数的对象。 (2认同)
  • @anticafe您实际上可以在片段中的任何位置使用`getArguments().getInt` (2认同)
  • @CaseyHancock 请参阅我在 2013 年 7 月 30 日针对“@BenSewards”的评论。基本上,您可以使用 Parcelable(和 Serializable)传递可以放入包中的任何对象。对于 Date 对象,它看起来只实现了 Serializable,但是 [see here](/sf/ask/1471218311/)。 (2认同)

500*_*865 95

使用newInstance()我所看到的唯一好处如下:

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

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    args.putString("someString", someString);
    // Put any other arguments
    myFragment.setArguments(args);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 它是一种很好的方式告诉其他类它什么样的参数预计忠实地工作(虽然你应该能够处理的情况下,如果没有参数的片段中的实例绑定).

所以,我的看法是使用静态newInstance()来实例化片段是一种很好的做法.

  • 是的,我同意你的意见.我在概念上说使用静态工厂模式而不是使用重载构造函数没有任何好处,反之亦然.你的两点都适用于两种模式; 使用一个而不是另一个没有好处.Android会强制您使用静态工厂模式 - 但使用其中一种模式没有任何好处. (5认同)
  • 您不能为片段使用自定义构造函数。框架使用无参数构造函数来恢复片段。 (4认同)
  • 1)这与将逻辑放在构造函数中有什么不同?两者都是包含此逻辑的单个位置.2)静态工厂上的参数与构造函数上的参数有何不同?两者都说明了预期的论点.我的观点是,它是一个不同的范例,当然,但使用构造函数对此没有明显的好处. (3认同)

小智 61

还有另一种方式:

Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
Run Code Online (Sandbox Code Playgroud)

  • 来自[developer docs](http://developer.android.com/reference/android/app/Fragment.html),`instantiate()``使用给定的类名创建Fragment的新实例.这与调用其空构造函数相同 (21认同)
  • `instantiate(...)` [方法](https://developer.android.com/reference/android/app/Fragment.html#instantiate(android.content.Context,%20java.lang.String)) 是 * *在 API 28 中已弃用**。另外,我认为这不是创建片段的好方法。 (3认同)
  • 尝试使用支持库,但是在onCreateView(在我的片段中),传递的包是null,所以我使用了setArguments/getArguments选项并且它有效(对于读这个的人). (2认同)
  • 尽管他们提到与调用空构造器相同。“ args.setClassLoader(f.getClass()。getClassLoader());” 在Bundle参数下方被称为 (2认同)

ps9*_*s95 44

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

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

它仍然可以使用构造函数.要了解其原因,首先我们需要了解Android为何使用上述解决方法.

在使用片段之前,需要一个实例.Android调用YourFragment()(无参数构造函数)来构造片段的实例.在这里,您编写的任何重载构造函数都将被忽略,因为Android无法知道要使用哪一个.

在Activity的生命周期中,片段如上所述创建并被Android多次销毁.这意味着如果将数据放入片段对象本身,则一旦片段被销毁,它将丢失.

为解决方法,android要求您使用Bundle(调用setArguments())存储数据,然后可以从中访问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;
}
Run Code Online (Sandbox Code Playgroud)

但是,构造函数:

public YourFragment(int data) {
    Bundle args = new Bundle();
    args.putInt("data", data);
    setArguments(args);
}
Run Code Online (Sandbox Code Playgroud)

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

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

public YourFragment(int data) {
    this.data = data; // Don't do this
}
Run Code Online (Sandbox Code Playgroud)

作为进一步的解释,这里是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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,Android要求在构造时设置参数,并保证这些参数将被保留.

编辑:正如@JHH的评论所指出的,如果你提供一个需要一些参数的自定义构造函数,那么Java将不会为你的片段提供一个没有arg的默认构造函数.因此,这需要您定义一个无参数构造函数,这是您可以使用newInstance工厂方法避免的代码.

编辑:Android不允许再使用重载的构造函数.您必须使用该newInstance方法.

  • 神圣的人,我想知道有多少机器人开发人员曾在机器人之外编写代码.我们无法使用您描述的方法,这是疯了.关于为什么我们必须使用静态工厂方法,任何评论都没有令人信服的论据.更令人不安的是,他们在编译时会引发错误.这绝对是提供的最佳答案,并表明sfm没有任何好处. (5认同)
  • 嗯,有一个微妙的原因.您可以自由地使用参数创建自己的构造函数,但仍然需要*也是一个无参数的构造函数*.由于类总是有一个隐式的无参数构造函数*,除非显式定义带有args的构造函数*,这意味着你必须明确定义你的arg-constructor*和*no-arg构造函数,否则系统不会能够调用任何no-arg构造函数.我相信这就是为什么建议使用静态工厂方法 - 它只是降低了忘记定义无参数构造函数的风险. (2认同)

Ily*_*man 17

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

即使系统重新创建,它也允许您someIntgetArguments()后者Fragment开始.这是比static构造函数更优雅的解决方案.

对于我的意见,static构造函数是无用的,不应该使用.如果将来你想扩展它Fragment并为构造函数添加更多功能,它们也会限制你.使用static构造函数,您无法执行此操作.

更新:

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

  • 没错,但这是鼓励人们使用谷歌建议模式的另一个原因.当然,我们都同意您的解决方案在技术上100%可行.有很多方法可以做很多事情.但问题是,它是否是最好的.我强烈认为使用构造函数并不代表它应该如何工作的真实性质. (9认同)
  • 使用静态方法的另一个好处是,我之前没有提到过,你不能不小心从中设置属性. (4认同)
  • 另外,关于你关于"扩展这个片段"的观点,如果你扩展这个类,这个方法实际上会非常糟糕.调用super会导致setArguments()调用仅对子节点或父节点有效,但不能同时对两者都有效! (4认同)
  • @yydl为什么不两个? (3认同)
  • 我同意@yydl的观点,即静态创建更好.另一个好处是依赖注入未来的新依赖项 - 构造函数不合适,并且可能会导致更多代码更改(或者添加更多构造函数). (3认同)
  • @yydle你可以通过调用get参数初始化子Bundle来避免这种情况.Java方式总是更好. (2认同)

Raf*_*ols 14

一些kotlin代码:

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

你可以得到这个论点:

val first: String by lazy { arguments?.getString("firstString") ?: "default"}
val second: String by lazy { arguments?.getString("secondString") ?: "default"}
Run Code Online (Sandbox Code Playgroud)


小智 5

我最近来了。但我刚刚知道的一些事情可能会对你有所帮助。

如果您使用 Java,则无需进行太多更改。但对于 Kotlin 开发者来说,我认为以下一些片段可以让你成为一个可以运行的地下室:

  • 父片段:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
    return T::class.java.newInstance().apply {
        arguments = Bundle().also { it.putString("key_text_arg", text) }
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 正常通话
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
Run Code Online (Sandbox Code Playgroud)
  • 您可以通过以下方式扩展子片段类中的父 init 操作:
fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

Run Code Online (Sandbox Code Playgroud)

快乐编码。