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

时间:2012-02-12 00:42:12

标签: android android-fragments

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

Fragment newFragment = new MyFragment();

Fragment newFragment = MyFragment.newInstance();

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

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

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

我错过了什么吗?

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

15 个答案:

答案 0 :(得分:1047)

如果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只能在片段附加到活动之前调用。

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

答案 1 :(得分:94)

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

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

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

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

答案 2 :(得分:61)

还有另一种方式:

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

答案 3 :(得分:40)

虽然@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的片段类:

/**
 * 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将不会为你的片段提供一个 no arg < / em>默认构造函数。因此,这需要您定义一个 no arg 构造函数,这是您可以使用newInstance工厂方法避免的代码。

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

答案 4 :(得分:16)

不同意与yydi answer说:

  

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

即使someInt已由系统重新创建,也可以让getArguments()Fragment开启static。这是比static构造函数更优雅的解决方案。

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

<强>更新

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

答案 5 :(得分:12)

一些 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"}

答案 6 :(得分: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系统重新创建你的片段时,如果觉得系统需要更多内存,那么它将通过使用没有参数的构造函数来重新创建你的片段。

您可以在此处找到有关best practice to instantiate fragments with arguments的更多信息。

答案 7 :(得分:2)

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

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

但我们可以做的是检查onCreate该用户!= null,如果没有 - 然后将他从数据层中删除,否则 - 使用现有。

通过这种方式,我们既可以获得由userId重新创建的功能,也可以通过Android进行片段重新创建和用户操作的快捷方式,以及通过保持对象本身或仅通过它来创建片段的能力

有点像这样:

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

答案 8 :(得分: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");
  

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

快乐代码

答案 9 :(得分:1)

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

如果您正在使用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
}

快乐的编码。

答案 10 :(得分:0)

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

答案 11 :(得分:0)

您可以像这样使用smth:

val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, YourFragment::class.java.name)

因为此answer现在已弃用

答案 12 :(得分:0)

  1. 理想情况下,我们不应在片段构造函数中传递任何内容,片段构造函数应为空或默认。
  2. 现在第二个问题是,如果我们想传递接口变量或参数怎么办——
    1. 我们应该使用 Bundle 来传递数据。
    2. 对于接口,我们可以在包中putParceble并使该接口实现parceble
    3. 如果可能,我们可以在 Activity 和片段中实现该接口,我们可以在 OnAttach 中初始化侦听器,其中我们有 context[ (context) Listener]。

这样在配置更改(例如字体更改)期间,Activity 娱乐侦听器就不会uninitialize 并且我们可以避免空指针异常。

答案 13 :(得分:-1)

public BleMeshTopologyCtrlBlock() { devPiconet = new ArrayList<BleMeshDevicePiconet>(2); private void AddMastertoSlave(BleMeshDevice linkMaster, BleMeshDevice linkSlave, int devRole) { devPiconet.add(new BleMeshDevicePiconet(devRole, linkSlave)); devPiconet.get(devPiconet.size() -1).getDevSlaves().add(linkMaster); for (BleMeshDevGatewayTable aRow : getDevPiconet().get(0) .getGatewaytable()) { if (aRow.getPicRemoteMaster().equals(aNeighPiconetMaster) && aRow.getPicLocalGatewayDev().getDevMiddleware() .getTopologyCtrlBlock().getCapacity() > 0 && aRow.getPicRemoteGatewayDev().getDevMiddleware() .getTopologyCtrlBlock().getCapacity() > 0 没用。它只会带来一团糟。

setArguments()

答案 14 :(得分:-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;
   }