一份超详细的 Fragment 知识总结

  • Fragment生命周期
  • 添加Fragment
  • 旋转屏幕后的数据保留问题
  • Fragment与Activity的通信
  • DialogFragment创建
  • FragmentManager与FragmentTransaction底层实现

本文主要是对Fragment的常用知识点做个总结,文章比较长,可以通过上面的大纲点击跳转,来查询自己感兴趣的。

(一)Fragment生命周期

关于Fragment的内容我们也可以查看官网介绍,Fragment依赖于Activity而存在,所以Fragment的状态也间接反映了Activity的状态,两者的生命周期的关键区别在于,Activity的生命周期是系统调用的,而Fragment的生命周期是由Activity调用的,是Activity自己内部的事情,具体的说,是activity的FragmentManager负责调用队列中Fragment的生命周期方法。下面我们来看一下Fragment的生命周期图:

一份超详细的 Fragment 知识总结_第1张图片

Fragment有一些和Activity不同的生命周期方法,下面我们就来单独看看其的含义,如果你的英文不错,可以去阅读官网,可能理解的会更准确

Fragment生命周期方法 含义
onAttach(…) 一旦Fragment与Activity关联就会调用
onCreateView(…) 创建并返回与Fragment相关联的视图层次结构
onActivityCreated(…) 告诉Fragment,Activity的onCreate方法调用完成了
onDestroyView(…) 与onCreateView想对应,允许Fragment清理与碎片视图相关联的资源
onDetach(…) 与onAttach相对应,当Fragment与Activity取消关联时调用

既然Fragment依赖Activity而存在,那么其生命周期之间必然会存在某种联系

一份超详细的 Fragment 知识总结_第2张图片

我们来拿几种具体的情况来看一下,当我们加载一个Fragment显示到屏幕时,Activity和Fragment所经历的生命周期

        /**
         * 注意这是我们Activity的onCreate方法:
         @Override protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                Log.i("Tag","Activity onCreate() begin");
                setContentView(R.layout.activity_main);
                Log.i("Tag","Activity onCreate() end");
            }
         */
        Activity onCreate() begin>>
        Fragment onAttach()>>Fragment onCreate()>>Fragment onCreateView()
        >>Activity onCreate() end>>
        Fragment onActivityCreated()
        >>Activity onStart()>>Fragment onStart()
        >>Activity onResume()>>Fragment onResume()
        //需要注意的是,只有静态添加,才会有Activity onCreate() begin>>...>>Activity onCreate() end

当我们返回键销毁掉正在运行的Activity及它的Fragment时,其所经历的生命周期

        Fragment onPause()>>Activity onPause()
        >>Fragment onStop()>>Activity onStop()
        >>Fragment onDestroyView()>>Fragment onDestroy()>>Fragment onDetach()>>Activity onDestroy()

当我们在Activity运行状态下,动态添加一个Fragment时,其所经历的的生命周期

        //在添加Fragment之前,Activity已经经历了onCreate()>>onStart()>>onResume()
        //接下来添加Fragment,生命周期流程如下:
        Fragment onAttach()>>Fragment onCreate()>>Fragment onCreateView()>>Fragment onActivityCreated()
        >>Activity onStart()>> Activity onResume()
        //没有执行Activity onRestart()方法
        //这些情况下,FragmentManager会立即驱使fragment快速跟上activity的步伐,直到与activity最新状态保存一致

我们也可以在Activity获得焦点前添加一个Fragment,即在Activity的onStart()方法之中添加,FragmentManager同样也会立即驱使fragment快速跟上activity的步伐,其所经历的生命周期

         Activity onCreate()>>Activity onStart()
         >>Fragment onAttach()>>Fragment onCreate()>>Fragment onCreateView()>>Fragment onActivityCreated()
         >>Fragment onStart()
         >>Activity onResume()>>Fragment onResume()
         //同样的你也可以在其它生命周期方法中添加,思路一样

我们也可以在失去焦点时添加一个Fragment,但这好像失去了意义啊,之所以我在这里说上这个,是因为这种情况下生命周期是有那么一点不同

        //直接从运行状态开始了
        Activity onPause()
        >>Fragment onAttach()>>Fragment onCreate()>>Fragment onCreateView()>>Fragment onActivityCreated()
        >>Fragment onStart()
        >>Fragment onStop()>>Activity onStop()
        >>Fragment onDestroyView()>>Fragment onDestroy()>>Fragment onDetach()>>Activity onDestroy()
        //在这里我们可以看到,fragment并没有执行onResume()和onParse()方法

当我们在Activity运行状态下,横竖屏切换,其生命周期过程

        Fragment onPause()>>Activity onPause()
        >>Fragment onStop()>>Activity onStop()
        >>Fragment onDestroyView()>>Fragment onDestroy()>>Fragment onDetach()>>Activity onDestroy()
        >>Fragment onAttach()>>Fragment onCreate()>>Activity onCreate()>>Fragment onCreateView()>>Fragment onActivityCreated()
        >>Activity onStart()>>Fragment onStart()
        >>Activity onResume()>>Fragment onResume()
        //需要注意的是,Activity.onCreate()在Fragment.onCreate()方法之后

还有一点需要注意的是,如果使用了支持库,那么Fragment生命周期的某些方法的调用顺序会略有不同,如在Activity.onCreate()中添加一个Fragment,那么Fragment.onActivityCreated()方法在Activity.onStart()方法之后调用,因为在3.0版本之前,立即从FragmentActivity中调用onActivityCreated()方法是不可能的。

(二)添加Fragment

Fragment是一种控制器对象,Activity可委派它完成一些任务。通常这些任务就是管理用户界面,受管的界面可以是一整屏或者是整屏的一部分。Activity视图含有可供Fragment视图插入的位置,如果有多个Fragment要插入,Activity视图也可提供多个位置。根据用户或设备的需要,Activity界面可以在运行时组装甚至重新组装,Activity自身并不具备这样的灵活性,Activity视图可以在运行时切换,但控制视图的代码必须在Activity中实现,各个Activity和特定的用户屏幕牢牢地绑定在了一起。正是采用这种Fragment而不是Activity进行应用的UI管理,使用Fragment及Activity来组装或重新组装用户界面,在整个生命周期过程中,技术上来说Activity视图并没有改变,所以我们就可以绕开Android系统Activity规则的限制,这段定义摘自《Android 编程权威指南》,这里就不过多讨论基础概念了,我们主要来看下经常被用到的Fragment创建方法。

静态添加Fragment

这种方式添加主要分为两步,首先创建待添加的Fragment并关联其相应的xml布局;之后在Activity布局xml文件之中添加我们需要的fragment,这样我们就可以在Activity代码中直接使用该Fragment了,很简单,但这样也就必然会丧失其灵活性,因为这样相当于将fragment的视图与activity的视图牢牢绑在了一起,在Activity中我们根本无法动态切换fragment视图,下面我们来简单看下例子:

这是我们创建的碎片MyFragment1,因为Fragment是在API11(Android 3.0)才被引入的,如果需要向后兼容旧版本设备,那么我们可以使用v4支持库内的android.support.v4.app.Fragment,该类可以使用在任何API4级及更高的版本上

    public class MyFragment1 extends Fragment {

        //这里的Fragment为android.app.Fragment
        @Nullable @Override public View onCreateView(LayoutInflater inflater
                , @Nullable ViewGroup container
                , Bundle savedInstanceState) {
            //第一个参数:布局的资源id,即Fragment自己对应的layout
            //第二个参数:视图的父视图,通常我们需要父视图来正确配置组件
            //第三个参数:告知布局生成器是否将生成的布局添加给父视图
            View view = inflater.inflate(R.layout.fragment_layout1, container, false);
            return view;
        }
    }

这是我们的Activity布局activity_main,这里需要注意的是name属性

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <fragment
            android:id="@+id/my_fragment1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:name="zmj.myfragments.MyFragment1"
            android:layout_marginTop="15dp"
            />
    </RelativeLayout>

这是我们的活动MainActivity,在这里也存在一个版本问题,在Android 3.0之后的版本中,我们可以直接使用Activity来管理fragment,但是如果我们想要兼容老版本,则必须使用FragmentActivity类,因为在Android 3.0之前的版本中,Activity的内部还没有实现管理Fragment的代码,我们这里的AppCompatActivity就继承自FragmentActivity类

    public class MainActivity extends AppCompatActivity {

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

动态添加Fragment

动态添加Fragment主要分为三个步骤,首先创建待添加的Fragment并关联其相应的xml布局;之后在Activity布局xml文件之中添加< FrameLayout >容器视图用来托管我们需要的Fragment;最后在Activity代码中添加我们需要的fragment。下面是我们的Fragment2,关于版本兼容问题在静态添加时已经说了,我们来直接看下代码

    public class MyFragment2 extends Fragment {

        //这里的Fragment为android.app.Fragment
        @Nullable @Override public View onCreateView(LayoutInflater inflater
                , @Nullable ViewGroup container
                , Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_layout2, container, false);
            return view;
        }
    }

这是我们的活动MainActivity的布局activity_main.xml,注意动态添加的时候不需要为< FrameLayout >绑定Fragment,即name属性

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <FrameLayout
            android:id="@+id/my_fragment2"
            android:layout_width="match_parent"
            android:layout_height="150dp"
            android:background="#f0f"
            />
    </RelativeLayout>

这是我们的活动MainActivity,关于版本问题在静态添加Fragment的介绍中已经提过了,在这里就不赘述了,相对于静态添加Fragment来说,动态添加Fragment就要灵活多了,我们可以在运行时动态的控制Fragment,也可以添加fragment,可以用其他的fragment替换当前的fragment,也可以移除fragment

    public class MainActivity extends AppCompatActivity {

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

        private void addFragment() {
            //这里的android.app.FragmentManager
            FragmentManager fm = getFragmentManager();
            MyFragment2 fragment2 = (MyFragment2) fm.findFragmentById(R.id.my_fragment2);
            if(fragment2 == null){
                fragment2 = new MyFragment2();
                fm.beginTransaction()
                        .add(R.id.my_fragment2, fragment2)
                        .commit();
            }
        }
    }

(三)旋转后的数据保留问题

当我们旋转屏幕或者设备配置发生变化的时候,Fragment和Activity都会被销毁并被重建,在这个过程中就会使之前的数据丢失或成员变量被初始化从而产生问题,所以接下来我们就简单分析下解决方法。

解决这个问题我们首先想到的当然是onSaveInstanceState(Bundle bundle)方法

        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putString("TAG_COUNT", "保存的内容");
        }
        // 当前在Fragment中
        @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            if(savedInstanceState != null){
                (String) savedInstanceState.get("TAG_COUNT");
            }
        }

虽然onSaveInstanceState(…)方法很方便,但却也有力有不逮的时候,例如在旋转屏幕前我们想要保存Fragment内所有的对象呢,甚至这个Fragment异常复杂呢,那么像我们之前的通过Bundle保存就有点不现实了,好在系统给fragment提供了retainInstance的属性值,默认值为false,调用setRetainInstance(true)方法可保留fragment,而已保留的fragment不会随着activity一起销毁,相反,它会被一直保留并在需要的时候原封不动的传递给新的activity。

正常情况下,当设备发生旋转时,FragmentManager会立即销毁该fragment实例,随后旋转完成后,新的activity的新的FragmentManager会立即新建一个新的Fragment及其视图,而当我们设置了retainInstance属性true时,当设备发生旋转时,该fragment关联的视图会被销毁,但是fragment本身不会被销毁,当重建的时候,新的FragmentManager会找到这个被保留的fragment,并重新创建它的视图。

        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // 很简单就一句话,测试时可以打印一个对象地址
            // 你会发现旋转成功后其地址没有改变,说明它还是原来的对象,亲测
            setRetainInstance(true);
        }

既然retainInstance属性这么方便,那么我们为什么有时还要使用onSaveInstanceState(…)方法呢,其实两者还是有区别的,主要区别在于数据保存的时间长短,如果使用retainInstance,只有当activity因设备发生改变被销毁时,fragment才会短时间处于被保留状态,如果activity是因系统需要回收内存而被销毁,那么所有被保留的fragment也会被随之销毁;如需持久地保存数据,那么就得使用onSaveInstanceState(…)方法了,例如用户暂时离开应用后,系统因回收内存需要销毁activity,那么保留的fragment也会随之销毁。

(四)DialogFragment创建

DialogFragment是Fragment的子类,跟其他Fragment一样,DialogFragment实例也是由托管Activity的FragmentManager管理着的,DialogFragment可通过调用show(…)方法被FragmentManager放置到屏幕上,下面我们就来具体的看下

DialogFragment创建

其实DialogFragment的用法非常简单,下面我们来简单看个例子,这是我们定义的MyDialogFragment类

    public class MyDialogFragment extends DialogFragment{
        //在这里我们就创建一个简单的AlterDialog,重点不是AlterDialog
        @Override public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new AlertDialog.Builder(getActivity())
                    .setTitle("这是我们定义的AlterDialog")
                    .setPositiveButton(android.R.string.ok, null)
                    .create();
        }
    }

随后在我们的MyActivity4中使用即可

    public class MyActivity4 extends AppCompatActivity{

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

        private void addClickListener() {
            findViewById(R.id.tv_show_dialog).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    //弹出对话框
                    addDialogFragment();
                }
            });
        }

        private void addDialogFragment() {
            //虽然DialogFragment也是一个Fragment,但是在使用上还是有些差别
            FragmentManager fm = getFragmentManager();
            MyDialogFragment dialogFragment = new MyDialogFragment();
            dialogFragment.show(fm, "俺乃String tag");
        }
    }

为什么有AliterDialog了,我们还要多此一举的使用DialogFragment呢,主要原因有两方面,一方面是因为既然DialogFragment是Fragment,所以其也就具备了Fragment的优点,有自己的生命周期管理、易复用、能够通过FragmentManager使用更多配置选项显示对话框;另一方面,当设备发生旋转时,DialogFragment也不会像AlterDialog那样在旋转后消失。

你可能感兴趣的:(android,随笔,Fragment,FragmentManager,BackStackRecord,回退栈)