Fragment 使用 之我们真的了解它吗?

看了这边博客感觉挺全面的所以这里记录下详情请看此链接http://www.jianshu.com/p/662c46cd3b5f

一、Fragment的生命周期

Fragment必须是依存于Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期,他们存在依赖关系。官网这张图很好的说明了俩者的关系:
这里写图片描述
1,onAttach(Activity)
当Activity与Fragment发生关联时调用
2,onCreateView(LayoutInflater,ViewGroup,Bundle);
创建该Fragment的视图
3,onActivityCreate(bundle);
当Activity的onCreate();方法返回时调用
4,onDestoryView();
与onCreateView相对应,当改Fragment被移除时调用
5,onDetach();
与onAttach()相对应,当Fragment与Activity的关联被取消时调用
注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现。

二、Fragment的使用

简单的动态替换(activity和fragment的使用)

  FragmentManager manager = getFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();

        exampleFragment= new ContentFragment();
                //参数一:被替换的布局 ,参数二,用来替换布局的fragment
        transaction.replace(R.id.id_content, exampleFragment);
        transaction.commit();

*注意:Fragment家族常用的API

1、获取FragmentManage的方式:
getFragmentManager() // v4中,getSupportFragmentManager
2、主要的操作都是FragmentTransaction的方法
FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务
3,transaction.add()
往Activity中添加一个Fragment
4,transaction.remove()
从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈,这 个Fragment实例将会被销毁。
5,transaction.replace()
使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体
6,transaction.hide()
隐藏当前的Fragment,仅仅是设为不可见,并不会销毁,
7,transaction.show()
显示之前隐藏的Fragment

**注意:1,当使用add(),show(),hide()跳转新的Fragment时,旧的Fragment回调onHiddenChanged(),不会回调onStop()等生命周期方法,而新的Fragment在创建时是不会回调onHiddenChanged(),这点要切记。2,常用Fragment的哥们,可能会经常遇到这样Activity状态不一致:State loss这样的错误。主要是因为:commit方法一定要在Activity.onSaveInstance()之前调用。

**
8,detach()
会将view从UI中移除,和remove()不同,此时fragment的状态依然FragmentManager维护。
9,attach()
重建view视图,附加到UI上并显示。
10,transatcion.commit()
提交一个事务

一些使用建议:

1,对Fragment传递数据,建议使用setArguments(Bundle args),而后在onCreate中使用getArguments()取出,在 “内存重启”前,系统会帮你保存数据,不会造成数据的丢失。和Activity的Intent原理一致。

2,使用newInstance(参数)创建Fragment对象,优点是调用者只需要关系传递的哪些数据,而无需关心传递数据的Key是什么。

3,如果你需要在Fragment中用到宿主Activity对象,建议在你的基类Fragment定义一个Activity的全局变量,在onAttach中初始化,这不是最好的解决办法,但这可以有效避免一些意外Crash。下面会说到此问题的解决方案。
。

三、Fragment的通信方案

Activity与Fragment之间是存在依赖关系的,通信方案有如下
handler,广播,EvnetBus,setArguments,接口等.

1,handler方案:

public class MainActivity extends FragmentActivity{ 
      //声明一个Handler 
      public Handler mHandler = new Handler(){       
          @Override
           public void handleMessage(Message msg) { 
                super.handleMessage(msg);
                 ...相应的处理代码
           }
     }
     ...相应的处理代码
   } 

    public class MainFragment extends Fragment{ 
          //保存Activity传递的handler
           private Handler mHandler;
           @Override
           public void onAttach(Activity activity) { 
                super.onAttach(activity);
               //这个地方已经产生了耦合,若还有其他的activity,这个地方就得修改 
                if(activity instance MainActivity){ 
                      mHandler =  ((MainActivity)activity).mHandler; 
                }
           }
           ...相应的处理代码
     }

该方案存在的缺点:

Fragment对具体的Activity存在耦合,不利于Fragment复用
不利于维护,若想删除相应的Activity,Fragment也得改动
没法获取Activity的返回数据

2,广播方案:

具体的代码就不写了,说下该方案的缺点:
用广播解决此问题有点大材小用了
广播性能肯定会差(不要和我说性能不是问题,对于手机来说性能是大问题)
传播数据有限制(必须得实现序列化接口才可以)

3,EventBus方案:

具体的EventBus的使用可以自己搜索下:
EventBus是用反射机制实现的,性能上会有问题
EventBus难于维护代码
没法获取Activity的返回数据

4,SetArguments方案:

public class ExampleFragment extends Fragment{

    private String type;

    public static ExampleFragment newInstance(String type) {
        ExampleFragment myFragment = new ExampleFragment();
        //创建bundle对象
        Bundle args = new Bundle();
        args.putString("type", type);//存入数据
        //设置数据
        myFragment.setArguments(args);
        return myFragment;
        }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        //获取到之前设置的argument对象获取传递过来的数据
        type=getArguments().getString("type");
        ...相应的处理代码
    }
}

这种activity向fragment传值方法是最简单有效的,而且在fragment异常重建的时候bundle将参数保存了下来

5,接口方案

使用最多的一种方案

 //MainActivity实现MainFragment开放的接口 
  public class MainActivity extends FragmentActivity implements FragmentListener{ 
        @override
         public void toH5Page(){ }
       ...其他处理代码省略
   } 

    public class MainFragment extends Fragment{

         public FragmentListener mListener;  
        //MainFragment开放的接口 
        public static interface FragmentListener{ 
            //跳到h5页面
           void toH5Page();
         }

         @Override 
        public void onAttach(Activity activity) { 
              super.onAttach(activity); 
              //对传递进来的Activity进行接口转换
               if(activity instance FragmentListener){
                   mListener = ((FragmentListener)activity); 
              }
         }
         ...其他处理代码省略 
  }

这种方案应该是既能达到复用,又能达到很好的可维护性,并且性能很好(推荐使用),

四、Fragment的常见错误及解决方法(重点)

安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候或者应用出现bug奔溃的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)

在系统要把app回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate方法调用后紧接着恢复(从onAttach生命周期开始)。

1.getActivity()空指针

可能你遇到过getActivity()返回null,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null,报了空指针异常。

大多数情况下的原因:
你在调用了getActivity()时,当前的Fragment已经onDetach()了宿主Activity。比如:你在pop了Fragment之后,该Fragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针。
解决办法:
在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)里赋值,使用mActivity代替getActivity(),保证Fragment即使在onDetach后,仍持有Activity的引用(有引起内存泄露的风险,但是相比空指针闪退,这种做法“安全”些),
即:
protected Activity mActivity;
@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    this.mActivity = activity;
}

/**
*  如果你用了support 23的库,上面的方法会提示过时,有强迫症的小伙伴,可以用下面的方法代替
*/
@Override
public void onAttach(Context context) {
    super.onAttach(context);
    this.mActivity = (Activity)context;

2.Fragment重叠异常
如果你add()了几个Fragment,使用show()、hide()方法控制,比如微信、QQ的底部tab等情景,如果你什么都不做的话,在“内存重启”后回到前台,app的这几个Fragment界面会重叠。

原因:FragmentManager帮我们管理Fragment,当发生“内存重启”,他会从栈底向栈顶的顺序一次性恢复Fragment;

但是因为没有保存Fragment的mHidden属性,默认为false,即show状态,所以所有Fragment都是以show的形式恢复,我们看到了界面重叠。
(如果是replace,恢复形式和Activity一致,只有当你pop之后上一个Fragment才开始重新恢复,所有使用replace不会造成重叠现象)
解决方案:
目前就我所知主要有以前三种解决方案

这里只写两种方式

1,使用getSupportFragmentManager().getFragments()恢复

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity);

    TargetFragment targetFragment;
    HideFragment hideFragment;

    if (savedInstanceState != null) {  // “内存重启”时调用
        List<Fragment> fragmentList = getSupportFragmentManager().getFragments();
        for (Fragment fragment : fragmentList) {
            if(fragment instanceof TartgetFragment){
               targetFragment = (TargetFragment)fragment; 
            }else if(fragment instanceof HideFragment){
               hideFragment = (HideFragment)fragment;
            }
        }
        // 解决重叠问题
        getFragmentManager().beginTransaction()
                .show(targetFragment)
                .hide(hideFragment)
                .commit();
    }else{  // 正常时
        targetFragment = TargetFragment.newInstance();
        hideFragment = HideFragment.newInstance();

        // 这里add时,tag可传可不传
        getFragmentManager().beginTransaction()
                .add(R.id.container)
                .add(R.id,container,hideFragment)
                .hide(hideFragment)
                .commit();
    }
}

2,自己保存Fragment的Hidden状态(号称:9行代码让你App内的Fragment对重叠说再见) //建议使用这种

发生Fragment重叠的根本原因在于FragmentState没有保存Fragment的显示状态,即mHidden,导致页面重启后,该值为默认的false,即show状态,所以导致了Fragment的重叠。此方案由 由Fragment自己来管理自己的Hidden状态!

基类Fragment代码:

public class BaseFragment extends Fragment {
    private static final String STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN";

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    if (savedInstanceState != null) {
        boolean isSupportHidden = savedInstanceState.getBoolean(STATE_SAVE_IS_HIDDEN);

        FragmentTransaction ft = getFragmentManager().beginTransaction();
        if (isSupportHidden) {
            ft.hide(this);
        } else {
            ft.show(this);
        }
        ft.commit();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        ...
        outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden());
    }
}

优点:不管多深的嵌套Fragment、同级Fragment等场景,全都可以正常工作,不会发生重叠!

缺点:其实也不算缺点,就是需要在加载根Fragment时判断savedInstanceState是否为null才初始化fragment,否则重复加载,导致重叠,判断如下:

public class MainActivity ... {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        // 这里一定要在save为null时才加载Fragment,Fragment中onCreateView等生命周里加载根子Fragment同理
        // 因为在页面重启时,Fragment会被保存恢复,而此时再加载Fragment会重复加载,导致重叠
        if(saveInstanceState == null){
              // 这里加载根Fragment
        }
    }
}

猜你喜欢

转载自blog.csdn.net/tongzhengtong/article/details/52150700