ChatGPT解决这个技术问题 Extra ChatGPT

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

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

Fragment newFragment = new MyFragment();

Fragment newFragment = MyFragment.newInstance();

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

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

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

我错过了什么?

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

当有参数时,别无选择,这在这里得到了广泛的回答。尽管如此,对于片段的无参数构造仍然存在问题。
在了解了工厂模式以及不实例化对象本身的调用类如何帮助将它们解耦之后,我认为这将是 newInstance() 方法的强项。我错了吗?我还没有看到这个具体的论点被称为好处。

V
Vasily Kabunov

如果 Android 决定稍后重新创建您的 Fragment,它将调用您的 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;
}

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

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

即使 Android 以某种方式重新创建了 Fragment,此 Bundle 也将可用。

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

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


@Vlasto 不幸的是,静态方法不能被覆盖。
@yydl我想我在这里遗漏了一些东西,无论如何你不能在这里使用构造函数,创建Bundle并调用 setArguments() 仍然因为它只会被你的代码调用(而不是当Android重新创建你的分段)?
@mgibson 如果您希望稍后重新创建片段时数据可用,则必须使用捆绑包。
被迫为片段创建一个无参数的构造函数可能是所有编程中最大的问题,任何地方。它强制在对象创建和初始化方面进行彻底的范式转变。如果您是 Android 新手并且偶然发现了此线程,请一遍又一遍地阅读上面的答案。
我会反对这种说法。首先,类型安全是语言问题,而不是框架问题。其次,IMO,该框架正在进入“你的 API 绝不能做的事情”领域。如果我想将国会库传递给我的片段构造函数,那么我应该被允许。 “无参数”构造函数合约基本上消除了在片段中使用依赖注入 - 主要是恶心。
S
Sufian

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

您将有一个地方,可以将片段使用的所有参数捆绑在一起,并且您不必在每次实例化片段时都编写下面的代码。捆绑参数 = 新捆绑(); args.putInt("someInt", someInt); args.putString("someString", someString); // 放置任何其他参数 myFragment.setArguments(args);这是告诉其他类它希望忠实工作的参数的好方法(尽管如果片段实例中没有捆绑任何参数,您应该能够处理情况)。

因此,我认为使用静态 newInstance() 实例化片段是一种很好的做法。


1)这与将逻辑放入构造函数有什么不同?两者都是包含此逻辑的单个位置。 2) 静态工厂的参数与构造函数的参数有何不同?两者都说明了预期的论点。我的观点是它是一个不同的范式,当然,但与使用构造函数相比,这并没有明显的好处。
您不能对片段使用自定义构造函数。框架使用无参数构造函数来恢复片段。
是的,我同意你的看法。我在概念上说使用静态工厂模式而不是使用重载的构造函数没有任何好处,反之亦然。您的两个观点在两种模式中都有效;使用其中一个没有任何好处。 Android 会强制您使用静态工厂模式——但使用其中一种并没有任何好处。
@RJCuthbertson 一个可能的好处是能够创建和返回静态工厂方法类的子类,即返回适合该情况的子类。
T
Taryn

还有另一种方式:

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

用支持库试过这个,但是在 onCreateView (在我的片段中),传递的包是空的,所以我选择了 setArguments/getArguments 选项,它工作了(对于任何阅读这个的人)。
有趣的是,我以前没有见过这种方法。与其他实例化片段的方法相比,它有什么优势吗?
developer docsinstantiate() 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 参数调用
instantiate(...) methods 在 API 28 中已弃用。另外,我不认为这是创建片段的好方法。
H
Heath Borders

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

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

仍然很有可能使用构造函数。要了解这是为什么,首先我们需要了解 Android 使用上述解决方法的原因。

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

在 Activity 的生命周期中,片段被如上所述创建并被 Android 多次销毁。这意味着如果你把数据放在fragment对象本身,一旦fragment被销毁,数据就会丢失。

作为解决方法,android 要求您使用 Bundle(调用 setArguments())存储数据,然后可以从 YourFragment 访问。参数 bundle 受 Android 保护,因此保证是persistent

设置此捆绑包的一种方法是使用静态 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 不会为您的片段提供 no arg默认构造函数。因此,这需要您定义一个 no arg 构造函数,这是您可以使用 newInstance 工厂方法避免的代码。

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


什么时候可以证明使用 android:configChanges="orientation|keyboardHidden|screenSize" ?
Android Studio 现在会为片段中的所有非默认构造函数引发错误,因此这不再有效。
圣人,我想知道有多少机器人开发人员曾经在机器人之外编写过代码。这太疯狂了,我们不能使用您描述的方法。关于我们为什么必须使用静态工厂方法的任何评论都没有令人信服的论据。更令人不安的是,它们在编译时会引发错误。这绝对是提供的最佳答案,并表明对 sfm 没有任何好处。
嗯,有一个微妙的原因。你可以自由地创建你自己的带参数的构造函数,但仍然需要一个无参数的构造函数。由于类总是有一个隐式的无参数构造函数,除非显式定义了带有 args 的构造函数,这意味着您必须显式定义 arg 构造函数和无参数构造函数,否则系统将无法调用任何无参数构造函数。我相信这就是为什么建议改用静态工厂方法的原因——它只是降低了忘记定义无参数构造函数的风险。
@JHH 本身会在编译时失败,所以风险不大。然而,这里的问题是构造函数重载,一种关键的编程范式,被 Android 拒绝了。
J
Jemshit Iskenderov

一些科特林代码:

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

使用 @JvmStatic 注释是最佳做法吗? @JvmStatic fun newInstance(bundle: Bundle) = SomeFragment().apply { arguments = bundle }
Z
Ziem

不同意 yydi answer 说:

如果 Android 决定稍后重新创建您的 Fragment,它将调用您的 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() 中提取 someInt,即使 Fragment 已被系统重新创建。这是比 static 构造函数更优雅的解决方案。

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

更新:

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


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

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

如果您使用的是 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")

您可以通过以下方式扩展子片段类中的父初始化操作:

fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

快乐编码。


G
Gunhan

在 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 的更多信息。


T
Tigra

由于关于最佳实践的问题,我要补充一点,在使用某些 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);
    }
}

你说:“我们不能传递复杂的对象,例如一些 User 模型,” - 这不是真的,我们可以。 像这样:User user = /*...*/ 将用户放入包中:{3 } 并从参数中获取用户:User user = getArguments().getParcelable("some_user"); 对象必须实现 Parcelable 接口。 link
嗯,是的,但是当类很复杂并且包含指向另一个对象的引用时......我个人更喜欢保持简单,要么我有对象,要么我没有,然后需要得到它
A
Amin Emadi

使用此代码 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");

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

快乐代码


M
Mahesh

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


M
Morozov

你可以像这样使用 smth:

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

因为这个 answer 现在已弃用


K
Kelvin Schoofs

理想情况下,我们不应该在片段构造函数中传递任何东西,片段构造函数应该是空的或默认的。现在第二个问题是,如果我们要传递接口变量或参数怎么办——我们应该使用 Bundle 来传递数据。对于Interface,我们可以将Parceble放入bundle中并使该接口实现parceble如果可能的话,我们可以在活动和片段中实现该接口,我们可以在OnAttach中初始化监听器,其中我们有上下文[(上下文)监听器]。

这样在配置更改(例如字体更改)期间,Activity 重新创建侦听器不会转到 uninitialize,我们可以避免空指针异常。


V
Vadim Star

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 的捆绑包)
@Asagen,我喜欢您关于比较初始值和用户值的评论。我编辑了代码并认为它仍然是统一且清晰的,没有 getArguments 的东西。 onViewCreated 范围呢?我们可以在那里恢复状态包。但我更喜欢让 onCreateView 轻而快,并在 onActivityCreated 内进行所有繁重的初始化,因为 Fragment.getActivity() 有时喜欢返回 null 并且因为新版本的 API 23 中的 onAttach() 发生了变化。
您在此处所做的只是移动 setget Arguments 进入 saveInstanceState。您基本上在做与“幕后”相同的事情
@cricket_007,或者只是相反。使用 saveInstanceState 是“在幕后”。使用 Arguments 是重复的功能,让您仔细检查:首先是 Arguments 值,然后是 saveInstanceState 值。因为您必须以任何方式使用 saveInstanceStateArguments 怎么样...它们不是必需的。
参数相当于片段的 Intent extras。它们不是无用的,它们包含与当前状态不同的初始参数。
S
Stefan Bogaard

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

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。
另外,静态方法如何访问非静态成员变量?
@ynnadkrap你是对的,使用捆绑包是去这里的方式。
@OrhanC1 根据此示例代码,静态方法未访问成员变量。 MyFragment 的实例正在访问其成员。这里没有错误。但是,我不向任何人推荐此答案,因为当您的片段从内存中删除以通过 android 操作系统打开一些空间时,然后在重新启动活动后,将使用默认的空构造函数创建此片段,而无需分配 ant 变量。