Android Confusion - Why pass parameters with Fragment.setArguments(Bundle bundle)

Keywords: Fragment Android Java

Reprint:
http://blog.csdn.net/tu_bingbing/article/details/24143249


Fragments have been available since Android 3.0, and Fragments features are also supported in compatible packages. The introduction of Fragment makes it easier and faster to write and manage user interfaces.

But when we instantiate custom Fragments, why do we officially recommend Fragment. setArguments (Bundle bundles) as a way to pass parameters instead of directly passing parameters through constructors? To find out, we can do a test to test the difference between the two ways.

First, let's test how the parameters are passed through the construction method.
public class FramentTestActivity extends ActionBarActivity {  
      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
  
        if (savedInstanceState == null) {  
            getSupportFragmentManager().beginTransaction()  
                    .add(R.id.container, new TestFragment("param")).commit();  
        }  
          
    }  
  
    public static class TestFragment extends Fragment {  
  
        private String mArg = "non-param";  
          
        public TestFragment() {  
            Log.i("INFO", "TestFragment non-parameter constructor");  
        }  
          
        public TestFragment(String arg){  
            mArg = arg;  
            Log.i("INFO", "TestFragment construct with parameter");  
        }  
  
        @Override  
        public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                Bundle savedInstanceState) {  
            View rootView = inflater.inflate(R.layout.fragment_main, container,  
                    false);  
            TextView tv = (TextView) rootView.findViewById(R.id.tv);  
            tv.setText(mArg);  
            return rootView;  
        }  
    }  
  
}  


You can see that the data we passed is displayed correctly. Now let's consider a problem. If the configuration parameters of the device change, we can use horizontal and vertical screen switching to illustrate the problem, as shown below.

What's the problem? Where are the parameters we passed? Why does the default value appear? Let's not rush to discuss this. Let's take a look at Fragment.setArguments(Bundle). bundle) How this works
public class FramentTest2Activity extends ActionBarActivity {  
         
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
              super.onCreate(savedInstanceState);  
             setContentView(R.layout. activity_main);  
  
              if (savedInstanceState == null) {  
                    getSupportFragmentManager().beginTransaction()  
                                 .add(R.id. container, TestFragment.newInstance("param")).commit();  
             }  
  
       }  
  
        public static class TestFragment extends Fragment {  
  
              private static final String ARG = "arg";  
               
              public TestFragment() {  
                    Log. i("INFO", "TestFragment non-parameter constructor" );  
             }  
  
              public static Fragment newInstance(String arg){  
                    TestFragment fragment = new TestFragment();  
                    Bundle bundle = new Bundle();  
                    bundle.putString( ARG, arg);  
                    fragment.setArguments(bundle);  
                     return fragment;  
             }  
               
              @Override  
              public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                           Bundle savedInstanceState) {  
                    View rootView = inflater.inflate(R.layout. fragment_main, container,  
                                  false);  
                    TextView tv = (TextView) rootView.findViewById(R.id. tv);  
                    tv.setText(getArguments().getString( ARG));  
                     return rootView;  
             }  
       }  
  
}  

Let's take a look at the operation of the horizontal and vertical screen after switching.

See, the parameters we passed are well preserved and correctly displayed to the user when the horizontal and vertical screen is switched.
So what's the matter? We know that if the device is switched horizontally and vertically, the Activity currently displayed to the user will be recreated and displayed to the user by default. How will the Fragment attached to the Activity be handled? We can see it through the source code.
Let's first look at Activity's onCreate (Bundle save Instance) method
protected void onCreate(Bundle savedInstanceState) {  
    if (DEBUG_LIFECYCLE ) Slog.v( TAG, "onCreate " + this + ": " + savedInstanceState);  
    if (mLastNonConfigurationInstances != null) {  
        mAllLoaderManagers = 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);  
    mCalled = true ;  
}  
Since our Fragments are managed by FragmentManager, you can follow the FragmentManager.restoreAllState() method to find the following code block through the current active Fragmnet
for (int i=0; i<fms.mActive.length; i++) {  
           FragmentState fs = fms.mActive[i];  
           if (fs != null) {  
              Fragment f = fs.instantiate(mActivity, mParent);  
               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<Integer>();  
               }  
               if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);  
               mAvailIndices.add(i);  
           }  
}  
Next we can look at the implementation of the FragmentState.instantitate() method
public Fragment instantiate(Activity activity, Fragment parent) {  
        if (mInstance != null) {  
            return mInstance ;  
        }  
         
        if (mArguments != null) {  
            mArguments .setClassLoader(activity.getClassLoader());  
        }  
         
        mInstance = Fragment.instantiate(activity, mClassName , mArguments );  
         
        if (mSavedFragmentState != null) {  
            mSavedFragmentState .setClassLoader(activity.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 .mFragmentManager = activity.mFragments;  
        if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,  
                "Instantiated fragment " + mInstance );  
  
        return mInstance ;  
    } 
You can see the final transition to the Fragment.instantitate() method
public static Fragment instantiate(Context context, String fname, 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);  
           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);  
   }  
As can be seen from this method, a new Fragment will eventually be instantiated by reflective parametric construction, and mArgments will be initialized to the original value, while the data of the original Fragment instance will be lost and re-initialized.

From the above analysis, we can know that when Activity is recreated, it will rebuild the Fragment it manages. The original Fragment field values will be lost, but through Fragment.setArguments(Bundle). The bundle set by the method will be retained. So try to use Fragment.setArguments(Bundle bundle) to pass parameters




Posted by dlcmpls on Mon, 17 Dec 2018 15:06:04 -0800