zoukankan      html  css  js  c++  java
  • Fragment: 使用newInstance()来实例化fragment(转)

    先说结论:

    1. 当需要给Fragment传值时,使用newInstance()方式来实例化一个Fragment,能够更好的将该Fragment使用的参数捆绑起来,不必每次实例化时都写下面的代码:

    Bundle args = new Bundle();

    2. 由于是在工厂方法内部封装了传参的方法,所以传入的参数将会得到保留,即使Fragment旋转重建也能够重新获取到这些传入参数。


    Android日常研发中不可避免的肯定要用到Fragment,你如何使用的呢?Compare the two methods of use,是否觉得第二种更加简洁。
    这时很多人肯定提出疑问:这两种使用方式有何区别,我的代码中到底使用哪种方式更好一些,以及为什么要使用这种方式 and so on,各位看官稍安勿躁,且听老衲娓娓道来。

    Usage 1:

    @Override
        public void initView(Bundle savedInstanceState) {
            BlankFragment mFragment = new BlankFragment();
            Bundle bundle = new Bundle();
            bundle.putString("arg1", "a");
            bundle.putString("arg2", "b");
            bundle.putString("arg3", "c");
            mFragment.setArguments(bundle);
            getFragmentManager().beginTransaction().replace(R.id.frame, mFragment).commit();
        }

    Usage 2:

    @Override
        public void initView(Bundle savedInstanceState) {
            getFragmentManager().beginTransaction().replace(R.id.frame, BlankFragment.newInstance("a", "b")).commit();
        }

    首先我们新建一个fragment,我们一起来看一下android建议的fragment如何编写(请严格按照截图的来步步创建哦)

    package com.itbird.utils;
    
    import android.content.Context;
    import android.net.Uri;
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    
    import com.itbird.base.R;
    
    /**
     * A simple {@link Fragment} subclass.
     * Activities that contain this fragment must implement the
     * {@link BlankFragment.OnFragmentInteractionListener} interface
     * to handle interaction events.
     * Use the {@link BlankFragment#newInstance} factory method to
     * create an instance of this fragment.
     */
    public class BlankFragment extends Fragment {
        // TODO: Rename parameter arguments, choose names that match
        // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
        private static final String ARG_PARAM1 = "param1";
        private static final String ARG_PARAM2 = "param2";
    
        // TODO: Rename and change types of parameters
        private String mParam1;
        private String mParam2;
    
        private OnFragmentInteractionListener mListener;
    
        public BlankFragment() {
            // Required empty public constructor
        }
    
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment BlankFragment.
         */
        // TODO: Rename and change types and number of parameters
        public static BlankFragment newInstance(String param1, String param2) {
            BlankFragment fragment = new BlankFragment();
            Bundle args = new Bundle();
            args.putString(ARG_PARAM1, param1);
            args.putString(ARG_PARAM2, param2);
            fragment.setArguments(args);
            return fragment;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (getArguments() != null) {
                mParam1 = getArguments().getString(ARG_PARAM1);
                mParam2 = getArguments().getString(ARG_PARAM2);
            }
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_blank, container, false);
        }
    
        // TODO: Rename method, update argument and hook method into UI event
        public void onButtonPressed(Uri uri) {
            if (mListener != null) {
                mListener.onFragmentInteraction(uri);
            }
        }
    
        @Override
        public void onAttach(Context context) {
            super.onAttach(context);
            if (context instanceof OnFragmentInteractionListener) {
                mListener = (OnFragmentInteractionListener) context;
            } else {
                throw new RuntimeException(context.toString()
                        + " must implement OnFragmentInteractionListener");
            }
        }
    
        @Override
        public void onDetach() {
            super.onDetach();
            mListener = null;
        }
    
        /**
         * This interface must be implemented by activities that contain this
         * fragment to allow an interaction in this fragment to be communicated
         * to the activity and potentially other fragments contained in that
         * activity.
         * <p>
         * See the Android Training lesson <a href=
         * "http://developer.android.com/training/basics/fragments/communicating.html"
         * >Communicating with Other Fragments</a> for more information.
         */
        public interface OnFragmentInteractionListener {
            // TODO: Update argument type and name
            void onFragmentInteraction(Uri uri);
        }
    }

    上述代码其实就是在一个Fragment的newInstance方法中传递两个参数,并且通过fragment.setArgument保存在它自己身上,而后通过onCreate()调用的时候将这些参数取出来。这样写没什么特殊的啊,不就是用静态工厂方法传个参数么,用构造器传参数不一样处理么?No,No,No,如果仅仅是个静态工厂而已,又怎么能成为谷歌推荐呢。

    实践是检验真理的唯一标准,我们一起通过一个样例来实际操作一番
    fragment_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <framelayout
            android:id="@+id/layout_top"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
    
        <framelayout
            android:id="@+id/layout_bottom"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
    </LinearLayout>

    由图和代码可知,我们在xml中定义两个FrameLayout,平分整个屏幕高度

    MainActivity.java

    package com.itbird.myapplication;
    
    import android.os.Bundle;
    import android.support.v4.app.FragmentTransaction;
    
    
    public class MainActivity extends BaseActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.fragment_main);
            if (savedInstanceState == null) {
                FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
                transaction.add(R.id.layout_top, new BlankFragment("顶部的Fragment", "test"));
                transaction.add(R.id.layout_bottom, BlankFragment.newInstance("底部的Fragment", "test"));
                transaction.commit();
            }
        }
    }

    BlankFragment.java

    package com.itbird.myapplication;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.net.Uri;
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.TextView;
    
    public class BlankFragment extends Fragment {
        // TODO: Rename parameter arguments, choose names that match
        // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
        private static final String ARG_PARAM1 = "param1";
        private static final String ARG_PARAM2 = "param2";
    
        // TODO: Rename and change types of parameters
        private String mParam1;
        private String mParam2;
    
    
        public BlankFragment() {
            // Required empty public constructor
        }
    
        @SuppressLint("ValidFragment")
        public BlankFragment(String mParam1, String mParam2) {
            this.mParam1 = mParam1;
            this.mParam2 = mParam2;
        }
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment BlankFragment.
         */
        // TODO: Rename and change types and number of parameters
        public static BlankFragment newInstance(String param1, String param2) {
            BlankFragment fragment = new BlankFragment();
            Bundle args = new Bundle();
            args.putString(ARG_PARAM1, param1);
            args.putString(ARG_PARAM2, param2);
            fragment.setArguments(args);
            return fragment;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (getArguments() != null) {
                mParam1 = getArguments().getString(ARG_PARAM1);
                mParam2 = getArguments().getString(ARG_PARAM2);
            }
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            View view = inflater.inflate(R.layout.fragment_blank, container, false);
            TextView textView = view.findViewById(R.id.text);
            textView.setText(mParam1 + mParam2);
            return view;
        }
    }

    fragment_blank.xml

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center" />
    
    </FrameLayout>

    通过阅读代码可知,我们通过两种不同的方式创建fragment,同样在其中心textview中展示相应拼接字段。

    嗯,效果如预期的一样完美,此时,我们把屏幕横过来,看看会出现怎样的状况

    My god,顶部的fragment 文本内容咋都变成null了。。。

    我们来分析一下产生上述情况的原因:当我们横竖屏切换的时候,activity会重建,相应的,依附于它上面的Fragment也会重新创建。好,顺着这个思路,进activity的onCreate方法中看看:
    protected void onCreate(@Nullable Bundle savedInstanceState) {
            if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
            if (mLastNonConfigurationInstances != null) {
                mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
            }
            if (mActivityInfo.parentActivityName != null) {
                if (mActionBar == null) {
                    mEnableDefaultActionBarUp = true;
                } else {
                    mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
                }
            }
            if (savedInstanceState != null) {
                Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
                mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
                        ? mLastNonConfigurationInstances.fragments : null);
            }
            mFragments.dispatchCreate();
            getApplication().dispatchActivityCreated(this, savedInstanceState);
            if (mVoiceInteractor != null) {
                mVoiceInteractor.attachActivity(this);
            }
            mCalled = true;
        }

    显而易见,fragment的重建是在restoreAllState方法中,跟进
    FragmentController.java

     /**
         * Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment
         * instances retained across configuration changes, including nested fragments
         *
         * @see #retainNestedNonConfig()
         */
        public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
            mHost.mFragmentManager.restoreAllState(state, nonConfig);
        }

    继续跟进
    FragmentManager.java

     void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
            // If there is no saved state at all, then there can not be
            // any nonConfig fragments either, so that is that.
            if (state == null) return;
            FragmentManagerState fms = (FragmentManagerState)state;
            if (fms.mActive == null) return;
    
            List<FragmentManagerNonConfig> childNonConfigs = null;
    
            // First re-attach any non-config instances we are retaining back
            // to their saved state, so we don't try to instantiate them again.
            ...
            
            // Build the full list of active fragments, instantiating them from
            // their saved state.
            mActive = new ArrayList<>(fms.mActive.length);
            if (mAvailIndices != null) {
                mAvailIndices.clear();
            }
            for (int i=0; i<fms.mActive.length; i++) {
                FragmentState fs = fms.mActive[i];
                if (fs != null) {
                    FragmentManagerNonConfig childNonConfig = null;
                    if (childNonConfigs != null && i < childNonConfigs.size()) {
                        childNonConfig = childNonConfigs.get(i);
                    }
                    Fragment f = fs.instantiate(mHost, mParent, childNonConfig);
                    if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
                    mActive.add(f);
                    // Now that the fragment is instantiated (or came from being
                    // retained above), clear mInstance in case we end up re-restoring
                    // from this FragmentState again.
                    fs.mInstance = null;
                } else {
                    mActive.add(null);
                    if (mAvailIndices == null) {
                        mAvailIndices = new ArrayList<>();
                    }
                    if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
                    mAvailIndices.add(i);
                }
            }
            
            // Update the target of all retained fragments.
           ...
    
            // Build the list of currently added fragments.
           ...
            
            // Build the back stack.
            ...
        }

    通过阅读, 找到关键代码

     Fragment f = fs.instantiate(mHost, mParent, childNonConfig);

    然后锲而不舍跟进
    FragmentManager.java

    public Fragment instantiate(FragmentHostCallback host, Fragment parent,
                FragmentManagerNonConfig childNonConfig) {
            if (mInstance == null) {
                final Context context = host.getContext();
                if (mArguments != null) {
                    mArguments.setClassLoader(context.getClassLoader());
                }
    
                mInstance = Fragment.instantiate(context, mClassName, mArguments);
    
                if (mSavedFragmentState != null) {
                    mSavedFragmentState.setClassLoader(context.getClassLoader());
                    mInstance.mSavedFragmentState = mSavedFragmentState;
                }
                mInstance.setIndex(mIndex, parent);
                mInstance.mFromLayout = mFromLayout;
                mInstance.mRestored = true;
                mInstance.mFragmentId = mFragmentId;
                mInstance.mContainerId = mContainerId;
                mInstance.mTag = mTag;
                mInstance.mRetainInstance = mRetainInstance;
                mInstance.mDetached = mDetached;
                mInstance.mHidden = mHidden;
                mInstance.mFragmentManager = host.mFragmentManager;
                if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
                        "Instantiated fragment " + mInstance);
            }
            mInstance.mChildNonConfig = childNonConfig;
            return mInstance;
        }

    跟进到这里,终于有点头绪了,至少看到fragment实例化的地方了,迫不及待的再次点击去view一下下
    Fragment.java

    public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
            try {
                Class<?> clazz = sClassMap.get(fname);
                if (clazz == null) {
                    // Class not found in the cache, see if it's real, and try to add it
                    clazz = context.getClassLoader().loadClass(fname);
                    if (!Fragment.class.isAssignableFrom(clazz)) {
                        throw new InstantiationException("Trying to instantiate a class " + fname
                                + " that is not a Fragment", new ClassCastException());
                    }
                    sClassMap.put(fname, clazz);
                }
                Fragment f = (Fragment)clazz.newInstance();
                if (args != null) {
                    args.setClassLoader(f.getClass().getClassLoader());
                    f.mArguments = args;
                }
                return f;
            } catch (ClassNotFoundException e) {
                throw new InstantiationException("Unable to instantiate fragment " + fname
                        + ": make sure class name exists, is public, and has an"
                        + " empty constructor that is public", e);
            } catch (java.lang.InstantiationException e) {
                throw new InstantiationException("Unable to instantiate fragment " + fname
                        + ": make sure class name exists, is public, and has an"
                        + " empty constructor that is public", e);
            } catch (IllegalAccessException e) {
                throw new InstantiationException("Unable to instantiate fragment " + fname
                        + ": make sure class name exists, is public, and has an"
                        + " empty constructor that is public", e);
            }
        }

    山重水复疑无路,柳暗花明又一村
    原来Fragment对象被反射创建之后,会调用这么一句代码
    f.mArguments = args;
    哦,なるほど(原来如此),Fragment在重新创建的时候只会调用无参的构造方法,并且如果之前通过fragment.setArguments(bundle)这种方式设置过参数的话,Fragment重建时会得到这些参数,所以,在onCreate中我们可以通过getArguments()的方式拿到我们之前设置的参数。同时由于Fragment在重建时并不会调用我们自定义的带参数的构造方法,所以我们传递的参数它也就获取不到了。

    也许有网友依然会继续追问,重新set时,mArguments确定不会为空吗?Fragment销毁时,这个变量不会置空吗?我们通过源码看一下:
    Fragment.java

     /**
         * Called when the view previously created by {@link #onCreateView} has
         * been detached from the fragment.  The next time the fragment needs
         * to be displayed, a new view will be created.  This is called
         * after {@link #onStop()} and before {@link #onDestroy()}.  It is called
         * <em>regardless</em> of whether {@link #onCreateView} returned a
         * non-null view.  Internally it is called after the view's state has
         * been saved but before it has been removed from its parent.
         */
        @CallSuper
        public void onDestroyView() {
            mCalled = true;
        }
    
        /**
         * Called when the fragment is no longer in use.  This is called
         * after {@link #onStop()} and before {@link #onDetach()}.
         */
        @CallSuper
        public void onDestroy() {
            mCalled = true;
            //Log.v("foo", "onDestroy: mCheckedForLoaderManager=" + mCheckedForLoaderManager
            //        + " mLoaderManager=" + mLoaderManager);
            if (!mCheckedForLoaderManager) {
                mCheckedForLoaderManager = true;
                mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
            }
            if (mLoaderManager != null) {
                mLoaderManager.doDestroy();
            }
        }

    看到此处,相信各位看官已经有“了然大明白”的感觉了,我就不再多说了。

    总结

    1.通过对比两种使用方式,我们知道两种方式别无其他,只是事关风格而已(代码”整”“洁”之道)
    2.使用Fragment过程中在涉及到传参时,千万不要通过构造方法或者setParam方式直接赋值传入参数,必须使用setArguments来传参,否则程序在某些应用情景下,会丢参
    强烈建议:两者虽无严格的对错之分,都可以使用,但是newInstance方式无论从代码整洁之道还是程序规范的稳定性而言,都是每个程序员应该学习使用的方式。

    本文转自:【Android面试】为什么要用newInstance来实例化Fragment
  • 相关阅读:
    PgSQL定时备份
    如何从源码包安装软件?
    PostgreSQL PointInTime Recovery (Incremental Backup)
    Better PostgreSQL backups with WAL archiving
    安装GTK全攻略
    WEB前端开发规范文档
    Linux开机自动启动脚本方法
    安装编译postgresql与pgagent的相关操作
    PostgreSQL: 如何查询表的创建时间?
    什么是编程
  • 原文地址:https://www.cnblogs.com/yongdaimi/p/12012011.html
Copyright © 2011-2022 走看看