Fragment在Android3.0开始提供,并且在兼容包中也提供了Fragment特性的支持。Fragment的推出让我们编写和管理用户界面更快捷更方便了。
但当我们实例化自定义Fragment时,为什么官方推荐Fragment.setArguments(Bundle bundle)这种方式来传递参数,而不推荐通过构造方法直接来传递参数呢?为了弄清这个问题,我们可以做一个测试,分别测试下这两种方式的不同
首先,我们来测试下通过构造方法传递参数的情况
[java] view plain copy  
  1. public class FramentTestActivity extends ActionBarActivity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. if (savedInstanceState == null) {
  7. getSupportFragmentManager().beginTransaction()
  8. .add(R.id.container, new TestFragment("param")).commit();
  9. }
  10. }
  11. public static class TestFragment extends Fragment {
  12. private String mArg = "non-param";
  13. public TestFragment() {
  14. Log.i("INFO", "TestFragment non-parameter constructor");
  15. }
  16. public TestFragment(String arg){
  17. mArg = arg;
  18. Log.i("INFO", "TestFragment construct with parameter");
  19. }
  20. @Override
  21. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  22. Bundle savedInstanceState) {
  23. View rootView = inflater.inflate(R.layout.fragment_main, container,
  24. false);
  25. TextView tv = (TextView) rootView.findViewById(R.id.tv);
  26. tv.setText(mArg);
  27. return rootView;
  28. }
  29. }
  30. }

可以看到我们传递过来的数据正确的显示了,现在来考虑一个问题,如果设备配置参数发生变化,这里以横竖屏切换来说明问题,显示如下

发生了什么问题呢?我们传递的参数哪去了?为什么会显示默认值?不急着讨论这个问题,接下来我们来看看Fragment.setArguments(Bundle bundle)这种方式的运行情况
[java] view plain copy  
  1. public class FramentTest2Activity extends ActionBarActivity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout. activity_main);
  6. if (savedInstanceState == null) {
  7. getSupportFragmentManager().beginTransaction()
  8. .add(R.id. container, TestFragment.newInstance("param")).commit();
  9. }
  10. }
  11. public static class TestFragment extends Fragment {
  12. private static final String ARG = "arg";
  13. public TestFragment() {
  14. Log. i("INFO", "TestFragment non-parameter constructor" );
  15. }
  16. public static Fragment newInstance(String arg){
  17. TestFragment fragment = new TestFragment();
  18. Bundle bundle = new Bundle();
  19. bundle.putString( ARG, arg);
  20. fragment.setArguments(bundle);
  21. return fragment;
  22. }
  23. @Override
  24. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  25. Bundle savedInstanceState) {
  26. View rootView = inflater.inflate(R.layout. fragment_main, container,
  27. false);
  28. TextView tv = (TextView) rootView.findViewById(R.id. tv);
  29. tv.setText(getArguments().getString( ARG));
  30. return rootView;
  31. }
  32. }
  33. }
我们再来看看横竖屏切换后的运行情况

看到了吧,我们传递的参数在横竖屏切换的情况下完好保存了下来,正确的显示给用户
那么这到底是怎么回事呢,我们知道设备横竖屏切换的话,当前展示给用户的Activity默认情况下会重新创建并展现给用户,那依附于Activity的Fragment会进行如何处理呢,我们可以通过源码来查看
先来看看Activity的onCreate(Bundle saveInstance)方法

[java] view plain copy  
  1. protected void onCreate(Bundle savedInstanceState) {
  2. if (DEBUG_LIFECYCLE ) Slog.v( TAG, "onCreate " + this + ": " + savedInstanceState);
  3. if (mLastNonConfigurationInstances != null) {
  4. mAllLoaderManagers = mLastNonConfigurationInstances .loaders ;
  5. }
  6. if (mActivityInfo .parentActivityName != null) {
  7. if (mActionBar == null) {
  8. mEnableDefaultActionBarUp = true ;
  9. } else {
  10. mActionBar .setDefaultDisplayHomeAsUpEnabled( true);
  11. }
  12. }
  13. if (savedInstanceState != null) {
  14. Parcelable p = savedInstanceState.getParcelable( FRAGMENTS_TAG );
  15. mFragments .restoreAllState(p, mLastNonConfigurationInstances != null
  16. ? mLastNonConfigurationInstances .fragments : null);
  17. }
  18. mFragments .dispatchCreate();
  19. getApplication().dispatchActivityCreated( this , savedInstanceState);
  20. mCalled = true ;
  21. }
由于我们的Fragment是由FragmentManager来管理,所以可以跟进FragmentManager.restoreAllState()方法,通过对当前活动的Fragmnet找到下面的代码块
[html] view plain copy  
  1. for (int i=0; i<fms.mActive.length; i++) {
  2. FragmentState fs = fms.mActive[i];
  3. if (fs != null) {
  4. Fragment f = fs.instantiate(mActivity, mParent);
  5. if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
  6. mActive.add(f);
  7. // Now that the fragment is instantiated (or came from being
  8. // retained above), clear mInstance in case we end up re-restoring
  9. // from this FragmentState again.
  10. fs.mInstance = null;
  11. } else {
  12. mActive.add(null);
  13. if (mAvailIndices == null) {
  14. mAvailIndices = new ArrayList<Integer>();
  15. }
  16. if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
  17. mAvailIndices.add(i);
  18. }
  19. }
接下来我们可以看看FragmentState.instantitate()方法的实现
[html] view plain copy  
  1. public Fragment instantiate(Activity activity, Fragment parent) {
  2. if (mInstance != null) {
  3. return mInstance ;
  4. }
  5. if (mArguments != null) {
  6. mArguments .setClassLoader(activity.getClassLoader());
  7. }
  8. mInstance = Fragment.instantiate(activity, mClassName , mArguments );
  9. if (mSavedFragmentState != null) {
  10. mSavedFragmentState .setClassLoader(activity.getClassLoader());
  11. mInstance .mSavedFragmentState = mSavedFragmentState ;
  12. }
  13. mInstance .setIndex(mIndex , parent);
  14. mInstance .mFromLayout = mFromLayout ;
  15. mInstance .mRestored = true;
  16. mInstance .mFragmentId = mFragmentId ;
  17. mInstance .mContainerId = mContainerId ;
  18. mInstance .mTag = mTag ;
  19. mInstance .mRetainInstance = mRetainInstance ;
  20. mInstance .mDetached = mDetached ;
  21. mInstance .mFragmentManager = activity.mFragments;
  22. if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
  23. "Instantiated fragment " + mInstance );
  24. return mInstance ;
  25. }
可以看到最终转入到Fragment.instantitate()方法
[java] view plain copy  
  1. public static Fragment instantiate(Context context, String fname, Bundle args) {
  2. try {
  3. Class<?> clazz = sClassMap .get(fname);
  4. if (clazz == null) {
  5. // Class not found in the cache, see if it's real, and try to add it
  6. clazz = context.getClassLoader().loadClass(fname);
  7. sClassMap .put(fname, clazz);
  8. }
  9. Fragment f = (Fragment)clazz.newInstance();
  10. if (args != null) {
  11. args.setClassLoader(f.getClass().getClassLoader());
  12. f. mArguments = args;
  13. }
  14. return f;
  15. } catch (ClassNotFoundException e) {
  16. throw new InstantiationException( "Unable to instantiate fragment " + fname
  17. + ": make sure class name exists, is public, and has an"
  18. + " empty constructor that is public" , e);
  19. } catch (java.lang.InstantiationException e) {
  20. throw new InstantiationException( "Unable to instantiate fragment " + fname
  21. + ": make sure class name exists, is public, and has an"
  22. + " empty constructor that is public" , e);
  23. } catch (IllegalAccessException e) {
  24. throw new InstantiationException( "Unable to instantiate fragment " + fname
  25. + ": make sure class name exists, is public, and has an"
  26. + " empty constructor that is public" , e);
  27. }
通过此方法可以看到,最终会通过反射无参构造实例化一个新的Fragment,并且给mArgments初始化为原先的值,而原来的Fragment实例的数据都丢失了,并重新进行了初始化

通过上面的分析,我们可以知道Activity重新创建时,会重新构建它所管理的Fragment,原先的Fragment的字段值将会全部丢失,但是通过Fragment.setArguments(Bundle bundle)方法设置的bundle会保留下来。所以尽量使用Fragment.setArguments(Bundle bundle)方式来传递参数

延伸阅读:android中方便为fragment写入参数的FragmentArgs简介

Android开发有时候会令人头痛。你不得不为诸如建立fragment这样简单的事情写很多代码。幸运的是java支持一个强大的工具:注释处理器(Annotation Processors)。

Fragment的问题是你不得不设置很多参数,从而让它正常运行。很多android开发新手通常这样写:

01 public class MyFragment extends Fragment
02 {
03 private int id;
04 private String title;
05  
06 public static MyFragment newInstance(int id, String title)
07 {
08 MyFragment f = new MyFragment();
09 f.id = id;
10 f.title = title;
11 return f;
12 }
13  
14 @Override
15 public View onCreateView(LayoutInflater inflater, ViewGroup container,
16 Bundle savedInstanceState)
17 {
18 Toast.makeText(getActivity(), "Hello " + title.substring(03),
19 Toast.LENGTH_SHORT).show();
20 }
21 }

这样做怎么了?我已经在自己的设备上尝试过了,它很好用?

它的却能工作,但是你有没有试过把你的设备从竖向改为横向?你的app将会因为NullPointerException而崩溃,当你试图访问id或title时。

我的app是正常的,因为我把app设置为竖向。所以我从来没遇到过这个问题。

随便你!Android是一个真正的多任务操作系统。多个app在同一时间运行,同时如果需要内存android系统将会销毁activity(和其中包含的fragment)。可能你在日常的app开发中不会注意这些问题。然而,当你在play store中发布后,你将会注意到你的app崩溃了,但你不知道什么原因。使用你app的用户可能同时间使用多个app,很有可能你的app在后台被销毁了。例如:A 用户打开你的app,MyFragment在屏幕上显示。下一步你的用户按了home键(这是你的app在后台运行),并且打开了其它应用。你的app可能会因为释放内存而被销毁。之后,用户返回你的app,例如通过多任务按钮。所以,Android现在会怎么做?Android会恢复之前的app状态,同时恢复MyFragment,这就是问题所在。Fragment试图访问title,但title是null,因为它不是被永久保存的。

我知道了,所以我需要把它们保存在onSaveInstanceState(Bundle)中?

不是。官方的文档有一些不清楚,但是onSaveInstanceState(Bundle)的使用方法应该跟你用Activity.onSaveInstanceState(Bundle)一样:你使用这个方法保存实例的“临时”状态,例如去处理屏幕的方向(从竖向到横向,反之亦然)。所以说当app在后台被杀掉时fragment的实例状态并不能被保存成持久数据,它的作用是再一次返回前台时恢复数据。它的作用跟Activity.onSaveInstanceState(Bundle)在Activity中的作用相同,它们用于“临时”保存实例状态。然而,持久的参数是通过intent外部数据传输的。

所以我应该在Activity中得Intent保存Fragment的参数?

不需要,Fragment有它自己的机制。有两个方法:Fragment.setArguments(Bundle)和Fragment.getArguments(),你必须通过这两个方法来确保参数被持久保存。这就是我上面提到的痛苦之处。需要有大量的代码这样写。第一,你要创建一个Bundle,然后你需要放入键值对,最后调用Fragment.setArguments()。不幸的是,你的工作还没有结束,你必须通过Fragment.getArguments()来读出Bundle。一些这样的工作:

01 public class MyFragment extends Fragment
02 {
03 private static String KEY_ID = "key.id";
04 private static String KEY_TITLE = "key.title";
05 private int id;
06 private String title;
07  
08 public static MyFragment newInstance(int id, String title)
09 {
10 MyFragment f = new MyFragment();
11 Bundle b = new Bundle();
12 b.putInt(KEY_ID, id);
13 b.putString(KEY_TITLE, title);
14 f.setArguments(b);
15 return f;
16 }
17  
18 @Override
19 public void onCreate(Bundle savedInstanceState)
20 {
21 // onCreate it's a good point to read the arguments
22 Bundle b = getArguments();
23 this.id = b.getInt(KEY_ID);
24 this.title = b.getString(KEY_TITLE);
25 }
26  
27 @Override
28 public View onCreate(LayoutInflater inflater, ViewGroup container,
29 Bundle savedInstanceState)
30 {
31 // No NullPointer here, because onCreate() is called before this
32 Toast.makeText(getActivity(), "Hello " + title.substring(03),
33 Toast.LENGTH_SHORT).show();
34 }
35 }

我希望你现在能明白我所说的“痛苦”。在你的应用中你将会为每一个fragment写很多代码。如果有人为你写这样的代码,这将不让人满意。注释处理允许你在编译的时候生成java代码。注意我们并不是在讨论评价在运行时间使用反射的注释。

FragmentArgs是一个轻量的包,用于为你的fragment生成精确的java代码。

01 import com.hannesdorfmann.fragmentargs.FragmentArgs;
02 import com.hannesdorfmann.fragmentargs.annotation.Arg;
03  
04 public class MyFragment extends Fragment
05 {
06 @Arg
07 int id;
08 @Arg
09 String title;
10  
11 @Override
12 public void onCreate(Bundle savedInstanceState)
13 {
14 super.onCreate(savedInstanceState);
15 FragmentArgs.inject(this); // read @Arg fields
16 }
17  
18 @Override
19 public View onCreateView(LayoutInflater inflater, ViewGroup container,
20 Bundle savedInstanceState)
21 {
22 Toast.makeText(getActivity(), "Hello " + title, Toast.LENGTH_SHORT)
23 .show();
24 }
25 }

只需要在你的Fragment类中加入注释字段,FragmentArgs就会生成引用代码。在你的Activity中你将使用生成的Builder类(你的fragment的后缀是”Builder”),而不是使用new MyFragment()或静态的MyFragment.newInstance(int id,String title)方法。

例如:

01 public class MyActivity extends Activity
02 {
03 public void onCreate(Bundle savedInstanceState)
04 {
05 super.onCreate(savedInstanceState);
06 int id = 123;
07 String title = "test"// Using the generated Builder
08 Fragment fragment = new MyFragmentBuilder(id, title).build(); // Fragment Transaction
09 getFragmentManager().beginTransaction().replace(R.id.container,fragment).commit();
10 }
11 }

你可能已经注意到在Fragment.onCreate(Bundle)中声明的FragmentArgs.inject(this)。这个调用使你的fragment获得了生成代码的连接。你可能会问你自己:“我需不需要在每一个Fragment中的onCreate(Bundle)中加入inject()方法”。答案是no。你只需要在你的fragment基类中插入这一句就可以,并且在所有的fragment中继承它。

01 public class BaseFragment extends Fragment
02 {
03 @Override
04 public void onCreate(Bundle savedInstanceState)
05 {
06 super.onCreate(savedInstanceState);
07 FragmentArgs.inject(this); // read @Arg fields
08 }
09 }
10  
11 public class MyFragment extends BaseFragment
12 {
13 @Arg
14 String title;
15  
16 @Override
17 public View onCreateView(LayoutInflater inflater, ViewGroup container,
18 Bundle savedInstanceState)
19 {
20 Toast.makeText(getActivity(), "Hello " + title, Toast.LENGTH_SHORT)
21 .show();
22 }
23 }

信用:一部分的注释生成代码是基于Hugo Visser的Bundles项目。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com

Android Fragment传递参数Fragment.setArguments(Bundle bundle)相关推荐

  1. Android开发之fragment传递参数的两种方法

    非常简单一种是通过bundle,另外一种是通过fragment提供的instantiate(过时方法),都是kotlin版本,直接上代码吧: 方法一: //fragment传递参数方法一val fra ...

  2. android fragment传递参数_fragment之间传值的两种方法

    在Activity中加载Fragment的时候.有时候要使用多个Fragment切换.并传值到另外一个Fragment.也就是说两个Fragment之间进行参数的传递.查了很多资料.找到两种方法.一种 ...

  3. android app传递参数,Android APP调起另外一个APP并传值

    概述 为了增加用户体验,可能要求在一个APP中打开另外一个APP的需求,一般分为三种: 显式调用跳转 隐式调用跳转 URL Scheme跳转 代码 用到的一些公共方法,当打开APP时,检测到第三方AP ...

  4. android意图传递参数返回结果(六)

    例如,有两页a,b. a参数传递到页面b页面,b后,将获得的参数的处理页,然后将结果传回与参数的页面a. 1.a主页MainActivity的代码类型,如以下: private Button butt ...

  5. android广播传递参数,Android调试助手(AndroidDebugger)

    测试:xxx工,你的程序又出bug了!界面切换几下就不行了. 开发:没道理啊,我都测过好几遍了,应该不会 测试:我这复现好几次了. 开发:拉个Log来分析下啦. 测试:拉取日志命令输入中....... ...

  6. 【Android 应用开发】 Fragment 详解

    作者 : 韩曙亮 转载请著名出处 : http://blog.csdn.net/shulianghan/article/details/38064191 本博客代码地址 : -- 单一 Fragmen ...

  7. Android app包下fragment详细使用

    文章目录 前言 一.什么是Fragment? 二.Fragment与Activity的区别与优势 1.生命周期不同 2.Fragment的使用优势 三.Fragment的生命周期 四.Fragment ...

  8. Android解惑 - 为什么要用Fragment.setArguments(Bundle bundle)来传递参数

     Android解惑 - 为什么要用Fragment.setArguments(Bundle bundle)来传递参数 时间 2014-04-19 23:05:55 CSDN博客 原文  http ...

  9. android bundle传递参数,android – 使用bundle将数据从一个活动传递...

    我目前正在尝试通过REST API调用获取数据,解析它以获取我需要的信息,然后将该信息传递给新活动.我正在使用loopj.com中的异步HTTP客户端作为REST客户端,然后分别使用以下代码将onCl ...

最新文章

  1. 2021年大数据Flink(一):乘风破浪的Flink-Flink概述
  2. linux shell /dev/tcp/${HOST}/${PORT} 简介
  3. poj3258 River Hopscotch (二分搜索,考虑最大值最小问题)
  4. java中.of,java中的sizeof
  5. win8oracle10g安装报错,Win8电脑安装Oracle 10g提示程序异常终止的解决方法
  6. 修改数据包欺骗服务器,Fiddler协议捕获编辑工具与Session欺骗原理详解
  7. [HNOI2008 Tree]
  8. linux超过cpu负载重启脚本,linux下Web服务器CPU负载超过阀值自动重启脚本
  9. 课外题:需要排序的子数组
  10. 2018蓝桥杯A组:方格计数(3种方法)
  11. 想将有色彩的视频进行去色处理就这样做
  12. 【java支付一 】java整合银联支付
  13. 读史可以明智_明智之举:获得满意的广告
  14. java 爬取网页的数据_java爬取网页数据
  15. 八字易经算法之用JAVA实现生旺死绝和十神
  16. 维特智能IMU 接入ROS发布IMU数据类型话题
  17. 一点资讯app signature
  18. WICC | 风口之下,音视频社交出海的三大新机遇
  19. keras实现seq2seq做Chatbot
  20. 2018年5月出海记录(HYPACK、SES2000、Klein3000)

热门文章

  1. js获取上个月今天或下个月今天
  2. 女生头发怎么画?日系二次元女生头发画法
  3. 英语笔记(单词来自Shiro文档)01
  4. 四川大学计算机研究生学习氛围,川大清退上百名研究生,校方回应
  5. ROC 曲线讲解 (Receiver Operarating Curve)
  6. 求解:使用Antv x6引入插件出现‘ToolItem’问题
  7. 双屏 = 双倍快乐?ThinkBook Plus 评测
  8. 2015 年全国房价会呈什么趋势?
  9. plupload插件使用
  10. 穿戴式眼镜显示屏方案