Android Fragment 进阶

01.什么是内存重启

  • 安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)
  • 在系统要把app回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate方法调用后紧接着恢复(从onAttach生命周期开始)。

02.Fragment常用方法

  • Fragment 的动态添加、删除等操作都需要借助于 FragmentTransaction 类来完成,比如上面提到的 commit() 操作,下面是几种常用的方法:
    • add() 系列:添加 Fragment 到 Activity 界面中;
    • remove():移除 Activity 中的指定 Fragment;
    • replace() 系列:通过内部调用 remove() 和 add() 完成 Fragment 的修改;
    • hide() 和 show():隐藏和显示 Activity 中的 Fragment;
    • addToBackStack():添加当前事务到回退栈中,即当按下返回键时,界面回归到当前事物状态;
    • commit():提交事务,所有通过上述方法对 Fragment 的改动都必须通过调用 commit() 方法完成提交
  • replace()和hide()区别
    • replace()和hide()都可以动态的在Activity中显示多个Fragment,并且可以来回灵活的切换,但是它们有很大的区别,replace() 方法不会保留 Fragment 的状态,也就是说诸如 EditText 内容输入等用户操作在 remove() 时会消失;但是hide()却不会,能完整的保留用户的处理信息。
  • addToBackStack()退栈
    • 当用户按下返回键时,如果回退栈中保存有之前的事务,会先执行事务回退,然后再执行Activity的finish()方法 。
  • add(), show(), hide(), replace()区别
    • 它们之间区别
      • show(),hide()最终是让Fragment的View setVisibility(true还是false),不会调用生命周期;
      • replace()的话会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期;
      • add()和 replace()不要在同一个阶级的FragmentManager里混搭使用。
    • 使用场景
      • 如果你有一个很高的概率会再次使用当前的Fragment,建议使用show(),hide(),可以提高性能。
      • 在我使用Fragment过程中,大部分情况下都是用show(),hide(),而不是replace
      • 注意:如果你的app有大量图片,这时更好的方式可能是replace,配合你的图片框架在Fragment视图销毁时,回收其图片所占的内存。

03.onHiddenChanged回调时机

  • 当使用add()+show(),hide()跳转新的Fragment时,旧的Fragment回调onHiddenChanged(),不会回调onStop()等生命周期方法,而新的Fragment在创建时是不会回调onHiddenChanged(),这点要切记。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if(activity!=null){
    if(hidden){
    //当该页面隐藏时
    }else {
    //当页面展现时
    }
    }
    }

04.传递和接收参数

4.1 最常用的方法

  • 对Fragment传递数据,建议使用setArguments(Bundle args),而后在onCreate中使用getArguments()取出

    • 在 “内存重启”前,系统会帮你保存数据,不会造成数据的丢失。和Activity的Intent恢复机制类似。
    • 使用newInstance(参数) 创建Fragment对象,优点是调用者只需要关系传递的哪些数据,而无需关心传递数据的Key是什么。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static WyNewsFragment newInstance(String param) {
    WyNewsFragment fragment = new WyNewsFragment();
    Bundle args = new Bundle();
    args.putString(TYPE, param);
    fragment.setArguments(args);
    return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
    mType = getArguments().getString(TYPE);
    }
    }

4.2 Fragment与Activity之间传值

  • Fragment与Activity之间是如何传值的?
    • 1.Activity向Fragment传值:
      • 步骤:
      • 要传的值,放到bundle对象里;
      • 在Activity中创建该Fragment的对象fragment,通过调用
      • fragment.setArguments()传递到fragment中;
      • 在该Fragment中通过调用getArguments()得到bundle对象,就能得到里面的值。
    • 2.Fragment向Activity传值:
      • 第一种:
        • 在Activity中调用getFragmentManager()得到fragmentManager,,调用findFragmentByTag(tag)或者通过findFragmentById(id)
        • FragmentManager fragmentManager = getFragmentManager();
        • Fragment fragment = fragmentManager.findFragmentByTag(tag);
      • 第二种:
        • 通过回调的方式,定义一个接口(可以在Fragment类中定义),接口中有一个空的方法,在fragment中需要的时候调用接口的方法,值可以作为参数放在这个方法中,然后让Activity实现这个接口,必然会重写这个方法,这样值就传到了Activity中。

4.3 Fragment与Fragment之间传值

  • Fragment与Fragment之间是如何传值的?
    • 第一种:
      • 通过findFragmentByTag得到另一个的Fragment的对象,这样就可以调用另一个的方法了。
    • 第二种:
      • 通过接口回调的方式。
    • 第三种:
      • 通过setArguments,getArguments的方式。

4.4 为何不构造传值

  • 为什么fragment传递数据不用构造方法传递?
    • activity给fragment传递数据一般不通过fragment的构造方法来传递,会通过setArguments来传递,因为当横竖屏会调用fragment的空参构造函数,数据丢失。

05.FragmentManager栈视图

  • 简要的关系图
    • 每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
  • 获取FragmentManager对象
    • 对于宿主Activity,getSupportFragmentManager()获取的FragmentActivity的FragmentManager对象;
    • 对于Fragment,getFragmentManager()是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()是获取自己的FragmentManager对象。

06.Fragment之懒加载使用

  • Fragment之懒加载使用
    • 懒加载,其实也就是延迟加载,就是等到该页面的UI展示给用户时,再加载该页面的数据(从网络、数据库等),而不是依靠ViewPager预加载机制提前加载两三个,甚至更多页面的数据。这样可以提高所属Activity的初始化速度,也可以为用户节省流量.而这种懒加载的方式也已经/正在被诸多APP所采用。
    • 具体可以参考这篇博客,Android 懒加载优化:https://www.jianshu.com/p/cf1f4104de78
  • 使用FragmentPagerAdapter+ViewPager时
    • 使用FragmentPagerAdapter+ViewPager时,切换回上一个Fragment页面时(已经初始化完毕),不会回调任何生命周期方法以及onHiddenChanged(),只有setUserVisibleHint(boolean isVisibleToUser)会被回调,所以如果你想进行一些懒加载,需要在这里处理。
  • 在给ViewPager绑定FragmentPagerAdapter时
    • 在给ViewPager绑定FragmentPagerAdapter时,new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保证正确,如果ViewPager是Activity内的控件,则传递getSupportFragmentManager(),如果是Fragment的控件中,则应该传递getChildFragmentManager()。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。

07.首页Fragment使用

  • 首页tab对应4个fragment,用法如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    private void showFragment(int index) {
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    hideFragment(ft);
    position = index;
    switch (index) {
    case FRAGMENT_HOME:
    /**
    * 如果Fragment为空,就新建一个实例
    * 如果不为空,就将它从栈中显示出来
    */
    if (homeFragment == null) {
    homeFragment = BaseFragmentFactory.getInstance().getHomeFragment();
    ft.add(R.id.fl_main, homeFragment, HomeFragment.class.getName());
    } else {
    ft.show(homeFragment);
    }
    break;
    case FRAGMENT_MOVIE:
    if (mMovieFragment == null) {
    mMovieFragment = BaseFragmentFactory.getInstance().getMovieFragment();
    ft.add(R.id.fl_main, mMovieFragment, MovieFragment.class.getName());
    } else {
    ft.show(mMovieFragment);
    }
    break;
    case FRAGMENT_VIDEO:
    if (videoFragment == null) {
    videoFragment = BaseFragmentFactory.getInstance().getVideoFragment();
    ft.add(R.id.fl_main, videoFragment, VideoFragment.class.getName());
    } else {
    ft.show(videoFragment);
    }
    break;
    case FRAGMENT_ME:
    if (meFragment == null) {
    meFragment = BaseFragmentFactory.getInstance().getMeFragment();
    ft.add(R.id.fl_main, meFragment, MeFragment.class.getName());
    } else {
    ft.show(meFragment);
    }
    break;
    case FRAGMENT_NEWS:
    if (newsFragment == null) {
    newsFragment = BaseFragmentFactory.getInstance().getNewsFragment();
    ft.add(R.id.fl_main, newsFragment, MeFragment.class.getName());
    } else {
    ft.show(newsFragment);
    }
    break;
    default:
    break;
    }
    ft.commit();
    }


    private void hideFragment(FragmentTransaction ft) {
    // 如果不为空,就先隐藏起来
    if (homeFragment != null) {
    setHide(ft,homeFragment);
    }
    if (newsFragment != null) {
    setHide(ft,newsFragment);
    }
    if (mMovieFragment != null) {
    setHide(ft,mMovieFragment);
    }
    if (videoFragment != null) {
    setHide(ft,videoFragment);
    }
    if (meFragment != null) {
    setHide(ft,meFragment);
    }
    }

    private void setHide(FragmentTransaction ft, Fragment fragment) {
    if(fragment.isAdded()){
    ft.hide(fragment);
    }
    }

08.思考Fragment能否不依赖Activity吗

  • Fragment能否不依赖于Activity存在?

    • Fragment不能独立存在,它必须嵌入到activity中,而且Fragment的生命周期直接受所在的activity的影响。

      • transaction只是记录了从一个状态到另一个状态的变化过程,即比如从FragmentA替换到FragmentB的过程,当通过函数transaction.addToBackStack(null)将这个事务添加到回退栈,则会记录这个事务的状态变化过程,如从FragmentA —>FragmentB,当用户点击手机回退键时,因为transaction的状态变化过程被保存,则可以将事务的状态变化过程还原,即将FragmentB —> FragmentA.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // Create new fragment and transaction
      Fragment newFragment = new ExampleFragment();
      FragmentTransaction transaction = getFragmentManager().beginTransaction();

      // Replace whatever is in the fragment_container view with this fragment,
      // and add the transaction to the back stack
      transaction.replace(R.id.fragment_container, newFragment);
      transaction.addToBackStack(null);

      // Commit the transaction
      transaction.commit();