实例化新的Android片段的最佳做法


705

我已经看到了两种在应用程序中实例化新Fragment的常规做法:

Fragment newFragment = new MyFragment();

Fragment newFragment = MyFragment.newInstance();

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

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

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

我错过了什么?

一种方法比另一种方法有什么好处?还是只是好的做法?


如果有参数,则别无选择,这里将广泛回答。尽管如此,对于片段的无参数构造仍然存在问题。
rds 2015年

1
在了解了工厂模式以及调用类不实例化对象本身如何帮助它们解耦之后,我认为这对于newInstance()方法将是一个优势。我错了吗?我还没有看到这个特定的论点有什么好处。
移动应用程序

Answers:


1136

如果Android决定稍后再创建您的片段,它将调用片段的无参数构造函数。因此,重载构造函数不是解决方案。

话虽这么说,将内容传递给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;
}

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

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

即使Fragment是由Android重新创建的,此Bundle仍然可用。

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

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


7
不幸的是,@ Vlasto静态方法不能被覆盖。
AJD

8
@yydl我想我在这里丢失了一些东西,无论如何您不能在这里使用一个构造函数,该构造函数创建Bundle并调用setArguments(),因为它只能由您的代码调用(而不是在Android重新创建您的代码时调用)分段)?
Mike Tunnicliffe 2013年

9
@mgibson 如果要在以后重新创建片段时使数据可用,则必须使用捆绑软件。
yydl

114
被迫为片段创建无参数构造函数可能是所有编程中任何地方的最大难题。它在对象创建和初始化中强制进行彻底的范式转换。如果您是Android的新手,并且迷失了这个线程,请反复阅读以上答案。
rmir​​abelle

9
我会说那个主张。首先,类型安全是语言问题,而不是框架问题。其次,IMO,该框架正在进入“您的API绝对不能做的事情”领域。如果我要将国会图书馆传递给我的片段构造函数,则应该允许我这样做。“ no-args”构造函数合同基本上终止了片段中依赖注入的使用-大麻烦。
rmir​​abelle

95

newInstance()我看到的使用的唯一好处是:

  1. 您将有一个地方可以捆绑该片段使用的所有参数,并且不必在每次实例化一个片段时都在下面编写代码。

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    args.putString("someString", someString);
    // Put any other arguments
    myFragment.setArguments(args);
    
  2. 这是告诉其他类它希望忠实工作的参数的好方法(尽管如果片段实例中没有捆绑参数,则您应该能够处理个案)。

因此,我的看法是,使用静态newInstance()实例化片段是一个好习惯。


4
1)与将逻辑放入构造函数有何不同?两者都是您包含此逻辑的地方。2)静态工厂上的参数与构造函数上的参数有何不同?两者都说明了预期的参数。我的观点是,这是一个不同的范例,但是与使用构造函数相比,这没有明显的好处。
RJ Cuthbertson

2
您不能将自定义构造函数用于片段。框架使用no参数构造函数来还原片段。
500865年

5
是的,我在那里同意你的看法。从概念上讲,使用静态工厂模式而不是使用重载的构造函数没有好处,反之亦然。您的两种观点在两种模式中都是有效的;使用一个没有另一个的好处。Android会强制您使用静态工厂模式-但是使用其中一个没有任何好处。
RJ Cuthbertson'8

pastebin.com/EYJzES0j
RJ Cuthbertson

@RJCuthbertson一个可能的好处是可以创造和回报能力的子类的静态工厂方法的类即返回适当的子类的情况。
紧急x

62

还有另一种方法:

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

如果我没记错的话,这只有在使用Android支持库时才有可能。
2013年

2
在支持库中对此进行了尝试,但是在onCreateView(在我的片段中)中,传递的捆绑包为null,因此我使用了setArguments / getArguments选项,并且该选项有效(对于所有阅读此书的人来说)。
Jrop 2013年

1
有趣的是,我之前从未见过这种方法。与实例化Fragment的其他方法相比,它有什么优势吗?
IgorGanapolsky

22
来自开发人员的文档instantiate() Creates a new instance of a Fragment with the given class name. This is the same as calling its empty constructor.
Brian Bowman 2014年

2
尽管他们提到与调用空构造器相同。“ args.setClassLoader(f.getClass()。getClassLoader());” 是捆绑参数调用下面
格克汗阿克尔巴里斯

49

尽管@yydl给出了为什么该newInstance方法更好的令人信服的理由:

如果Android决定稍后再创建您的片段,它将调用片段的无参数构造函数。因此,重载构造函数不是解决方案。

仍然有可能使用构造函数。要了解其原因,首先我们需要了解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;
}

但是,构造函数:

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的Fragment类:

/**
 * 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构造函数,该代码可以通过newInstancefactory方法避免。

编辑:Android不允许再使用重载的构造函数来存储片段。您必须使用该newInstance方法。


什么时候可以使用android:configChanges =“ orientation | keyboardHidden | screenSize”进行证明?
卢克·艾莉森

1
现在,Android Studio会对片段中的所有非默认构造函数引发错误,因此不再起作用。
Sheharyar '16

6
天哪,我想知道有多少droid开发人员曾经在droid之外编写过代码。我们无法使用您描述的方法,这太疯狂了。关于为什么我们必须使用静态工厂方法的任何评论都没有令人信服的论据。更令人不安的是,它们在编译时会引发错误。这绝对是提供的最佳答案,表明sfm没有任何好处。
MPavlak

3
好吧,有一个微妙的原因。您可以自由地使用参数创建自己的构造函数,但是仍然需要一个无参数的构造函数。由于类始终具有隐式的no-arg构造函数,除非显式定义了带有args的构造函数,这意味着您必须同时定义arg-constructor no-arg构造函数,否则系统将无法调用任何无参数构造函数。我认为这就是为什么建议改用静态工厂方法的原因-它只是减少了忘记定义无参数构造函数的风险。
JHH

@JHH本身会在编译时失败,因此风险不大。但是,这里的问题是Android拒绝了构造函数重载(一种关键的编程范例)。
ps95 '16

20

不同意 yydi的回答

如果Android决定稍后再创建您的片段,它将调用片段的无参数构造函数。因此,重载构造函数不是解决方案。

我认为这是一个解决方案,也是一个很好的解决方案,这正是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);
}

即使系统重新创建了它,它也允许您someIntgetArguments()后者Fragment开始。这比static构造函数更优雅。

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

更新:

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


4
我上面没有提到的使用静态方法的另一个好处是,您不能从中意外设置属性。
yydl 2014年

4
此外,关于“扩展此片段”的观点,如果您扩展了该类,则此方法实际上是非常糟糕的。调用super将导致setArguments()调用仅对子项或父项有效,而对两者均无效!
yydl 2014年

2
@yydle您可以通过调用get参数来初始化子Bundle来避免这种情况。Java方式总是更好。
伊利亚·加兹曼(Illa Gazman)2014年

9
是的,但这是鼓励人们使用Google建议的模式的另一个原因。当然,我们都同意您的解决方案在技术上是100%可行的。差不多,有很多方法可以做很多事情。但问题是,这是否是最好的。我强烈感到使用构造函数并不能代表它应该如何工作的真正本质。
yydl 2014年

3
我同意@yydl,认为静态创建更好。另一个好处是将来新依赖项的依赖项注入-构造函数不适合这样做,可能会导致更多代码更改(或添加更多构造函数)。
Boon 2015年

19

一些kotlin代码:

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") ?: "default"}
val second: String by lazy { arguments?.getString("secondString") ?: "default"}

3

在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系统重新创建您的片段时,如果感觉到系统需要更多的内存,它将比不使用不带参数的构造函数来重新创建您的片段。

您可以在此处找到有关最佳实践的更多信息,以实例化带有参数的片段


2

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

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

但是我们可以做的就是签入onCreate该用户!= null,如果没有,则从数据层将其带入,否则使用现有用户。

这样一来,我们既获得了通过Android重新创建片段的情况下通过userId重新创建的能力,又获得了对用户操作的敏锐度,并获得了通过持有对象本身或仅是其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(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);
    }
}

3
您说:“我们不能传递复杂的对象,例如某些用户模型,”- 事实并非如此,我们可以。像这样: User user = /*...*/ 将用户置于bundle中: Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user); 并从参数中获取用户: User user = getArguments().getParcelable("some_user"); 该对象必须实现Parcelable接口。链接
Adam Varhegyi 2015年

3
好吧,是的,但是当类很复杂并且包含对另一个对象的引用者时……我个人更喜欢保持简单,要么我有对象,要么我没有,然后需要获取它
Tigra

1

使用此代码100%修复您的问题

firstFragment中输入此代码

public static yourNameParentFragment newInstance() {

    Bundle args = new Bundle();
    args.putBoolean("yourKey",yourValue);
    YourFragment fragment = new YourFragment();
    fragment.setArguments(args);
    return fragment;
}

此样本发送布尔数据

和在SecendFragment

yourNameParentFragment name =yourNameParentFragment.newInstance();
   Bundle bundle;
   bundle=sellDiamondFragments2.getArguments();
  boolean a= bundle.getBoolean("yourKey");

第一个片段中的必须值是静态的

快乐的代码


0

实例化片段的最佳方法是使用默认的Fragment.instantiate方法或create factory方法来实例化片段
注意:始终在片段中创建一个空的构造函数,同时还原片段存储器将抛出运行时异常。


0

我最近在这里。但是我刚刚知道的一些内容可能会对您有所帮助。

如果您使用的是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) }
    }
}
  • 正常通话
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
  • 您可以通过以下方式扩展子片段类中的父init操作:
fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

快乐的编码。


-2

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

除非您只是被迫重写另一种方法,并且使一个本可以隔离到onViewCreated作用域的字段。我想很方便,有很多方法可以做同样的事情。这也是检查用户更新的简便方法(比较的捆绑包getArguments和的捆绑包onSaveInstanceState
Overclover

@Asagen,我喜欢您对比较初始值和用户值的评论。我编辑了代码,并认为它仍然是统一且清晰的getArguments东西。什么onViewCreated范围...我们可以捆绑有恢复状态。但我只是喜欢化妆onCreateView轻便快捷等做一个内部的所有重初始化onActivityCreated,因为Fragment.getActivity()有时想回null也因为onAttach()在23 API新版本的变化
瓦迪姆星

所有你在这里做的举措 setget ArgumentssaveInstanceState。您实际上所做的就是“在
幕后

1
@ cricket_007,或相反。使用saveInstanceState是“幕后”。使用of Arguments是功能重复,您需要仔细检查:首先是Arguments值,然后是saveInstanceState值。因为您必须使用saveInstanceState任何方式。那么Arguments...它们不是必需的。
瓦迪姆之星,

参数相当于片段的Intent Extras。它们不是无用的,它们包含与当前状态不同的初始参数。
BladeCoder

-12

我相信我对此有一个非常简单的解决方案。

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

12
如果恰好重新创建MyFragment,则mObjects将被清除(用户进入设备主屏幕,然后打开在MyFragment处保留的应用程序)。您可以通过将MyFragment包作为参数发送来保留mObjects。
ynnadkrap 2015年

1
另外,静态方法如何访问非静态成员变量?
OrhanC15年

2
@ynnadkrap你是正确的,使用捆绑包是这里的方法。
Stefan Bogaard,2015年

2
@ OrhanC1根据此示例代码,静态方法无法访问成员变量。MyFragment实例正在访问其成员。这里没有错误。但是,我不建议任何人回答此问题,因为当您的片段从内存中删除以通过android os打开一些空间,然后在重新启动活动后,此片段将使用默认的空构造函数创建而无需分配ant变量。
Gunhan

@Gunhan你是对的!不是。抱歉给您带来的困惑:)
OrhanC1年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.