首页 文章

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

提问于
浏览
612

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

Fragment newFragment = new MyFragment();

Fragment newFragment = MyFragment.newInstance();

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

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

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

我错过了什么?

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

11 回答

  • 2

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

  • 0

    我相信我有一个非常简单的解决方案 .

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

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

    在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的信息 .

  • 1004

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

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

    但我们可以做的是检查 onCreate 该用户!= 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(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);
        }
    }
    
  • -10

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

    • 您将只有一个地方可以捆绑片段使用的所有参数,并且每次实例化片段时都不必编写下面的代码 .
    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    args.putString("someString", someString);
    // Put any other arguments
    myFragment.setArguments(args);
    
    • 这是告诉其他类忠实工作的哪些参数的好方法(尽管如果在片段实例中没有捆绑参数,你应该能够处理案例) .

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

  • 9

    还有另一种方式:

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

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

    即使系统重新创建了 Fragment ,它也允许您从 getArguments() 后拉 someInt . 这是比 static 构造函数更优雅的解决方案 .

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

    Update:

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

  • 13

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

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

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

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

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

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

  • 3

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

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

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

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

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

    要解决此问题,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的片段类:

    /**
     * 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要求在构造时将参数设置为 only ,并保证这些参数将被保留 .

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

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

  • 37

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

相关问题