第二章 Fragment

文章目录

第二章 Fragment

(一)定义

Fragment指碎片。是Activity界面中的一部分,可理解为模块化的Activity。
(1)Fragment不能独立存在,必须嵌入到Activity中
(2)Fragment具有自己的生命周期,接收它自己的事件,并可以在Activity运行时被添加或删除
(3)Fragment的生命周期直接受所在的Activity的影响。如:当Activity暂停时,它拥有的所有Fragment们都暂停

(二)作用

为了解决不同屏幕分辩率的动态和灵活UI设计。
Fragment可理解为一个"View控制器",将自身承载的View展示到容器View中,自身有 生命周期,且能添加逻辑控制视图。
Fragments 在活动Activity中为不同的屏幕尺寸修改布局配置(小屏幕可能每次显示一个片段,而大屏幕则可以显示两个或更多)
(1)Fragment可以使你能够将activity分离成多个可重用的组件,每个都有它自己的生命周期和UI。
(2)Fragment可以轻松得创建动态灵活的UI设计,可以适应于不同的屏幕尺寸。从手机到平板电脑。
(3)Fragment是一个独立的模块,紧紧地与activity绑定在一起。可以运行中动态地移除、加入、交换等。
(4)Fragment提供一个新的方式让你在不同的安卓设备上统一你的UI。
(5)Fragment 解决Activity间的切换不流畅,轻量切换。
(6)Fragment 替代TabActivity做导航,性能更好。
(7)Fragment 在4.2.版本中新增嵌套fragment使用方法,能够生成更好的界面效果。
(8)Fragment做局部内容更新更方便,原来为了到达这一点要把多个布局放到一个activity里面,现在可以用多Fragment来代替,只有在需要的时候才加载Fragment,提高性能。
(9)可以从startActivityForResult中接收到返回结果,但是View不能。
可总结为以下三点:

  • 模块化+复用
    一个Fragment相当于一个Activity模块,可将业务逻辑分离,并且为多个Activity使用
  • UI灵活
    能够比较容易适配手机和平板,根据屏幕的宽度决定Fragment的放置
  • 占内存小
    Fragment轻量不消耗手机资源,启动顺滑流畅。而Activity的启动设置Android系统对ActivityManager的调度,会关联许多资源和进行诸多复杂运算。

但Fragment有许多坑,且生命周期繁多难以管理,Fragment的嵌套更是坑中之坑。

(三)生命周期

(1)生命周期方法详解

在这里插入图片描述
1、 onAttach:Fragment和Activity建立关联的时候调用,可以获得对应的Context或Activity,这里拿到的Activity是mHost.getActivity()
2、 onCreate:Fragment对象初始创建,用于执行初始化操作。
由于Fragment的onCreate调用时,关联的Activity可能没有创建好,所以不要有依赖外部Activity布局的操作。依赖Activity的操作可以放在onActivityCreate中。
3、 onCreateView:为Fragment创建视图(加载布局)时调用(给当前的fragment绘制UI布局,可以使用线程更新UI)
4、 onActivityCreated:当与Fragment关联的Activity中的onCreate方法执行完后调用(表示activity执行onCreate方法完成了的时候会调用此方法)
这个方法里做些和布局、状态恢复有关的操作,如
onViewStateRestored(Bundle)用于一个Fragment在从就状态回复,获取saveInstanceState恢复状态。
以上4步同步于Activity的onCreate
5、 onStart:Fragment可见时调用,将Fragment对象显示给用户。同步于Activity的onStart
6、 onResume:Fragment对象可见并可与用户交互时调用。同步于Activity的onResume
7、 onPause:Fragment对象与用户不再交互。同步于Activity的onPause
8、 onStop:Fragment对象不再显示给用户。同步于Activity的onStop
9、 onDestroyView:Fragment中的布局被移除时调用(表示fragment销毁相关联的UI布局)
10、onDestroy:Fragment状态清理完成
11、 onDetach:Fragment和Activity解除关联的时候调用(脱离activity)

(2)生命周期调用场景

  • fragment被创建
    onAttach()–>onCreate()–>onCreateView()–>onActivityCreated()
  • fragment显示
    onStart()–>onResume()
  • fragment进入后台模式
    onPause()–>onStop()–>onDestroyView()
  • fragment被销毁(持有它的activity被销毁)
    onPause()–>onStop()–>onDestroyView()–>onDestroy()–>onDetach()
  • fragment重新恢复
    onCreateView()–>onActivityCreated()–>onStart()–>onResume()

(3)Fragment与Activity生命周期对比

在这里插入图片描述
从这个图上可以看出activity的状态决定了fragment可能接收到的回调函数。
当activity处于Resumed状态时,可以自由地添加和移除fragment,也即是说,只有activity在Resumed状态时,fragment的状态可以独立改变。
但是,当activity离开Resumed状态,fragment的生命周期被activity控制。

(3.1)Activity嵌套Fragment生命周期变化

由于Fragment依赖Activity的存在而存在,Activity的状态决定了Fragment可能接收到的回调函数,故在Activity生命周期中的方法一般均先于Fragment生命周期中的方法执行。
事实上,Fragment的生命周期除了它第一次创建或销毁之外,都是由Activity启动。

1、首次打开界面
Activity:onCreate()
Fragment:onAttach()->onCreate()->onCreateView()->onActivityCreated()

Activity:onStart()
Fragment:onStart()

Activity:onResume()
Fragment:onResume()

2、点击Home返回主界面&返回
Activity:onPause()
Fragment:onPause()

Activity:onSaveInstanceState()
Fragment:onSaveInstanceState()

Activity:onStop()
Fragment:onStop()

Activity:onRestart()
Activity:onStart()
Fragment:onStart()

Activity:onResume()
Fragment:onResume()

onSaveInstanceState()保存状态的方法是在onPause()之后,onStop()之前,Activity与Fragment在这里是同步的,这个方法在onPause和onStop间任意时刻调用
再次从最近活动返回时,activity会多执行一个onRestart()方法,Fragment无此方法

3、正常销毁(后退键返回)
Activity:onPause()
Fragment:onPause()

Activity:onSaveInstanceState()
Fragment:onSaveInstanceState()

Activity:onStop()
Fragment:onStop()

Activity:onStop()
Fragment:onStop()

Fragment:onDestroyView()
Activity:onDestroy()
Fragment:onDestroy()
Fragment:onDetach()

4、后台意外销毁&返回(横竖屏切换)
Activity:onPause()
Fragment:onPause()

Activity:onSaveInstanceState()
Fragment:onSaveInstanceState()

Activity:onStop()
Fragment:onStop()

Fragment:onDestroyView()
Activity:onDestroy()
Fragment:onDestroy()
Fragment:onDetach()

Activity:onCreate()
Fragment:onAttach()->onCreate()->onCreateView()->onActivityCreated()

Activity:onStart()
Fragment:onStart()

Activity:onRestoreInstanceState()

Activity:onResume()
Fragment:onResume()

Activity会额外执行一个还原状态的方法onRestoreInstanceState,介于onStart和onResume之间

(3.2)Fragment切换生命周期变化

情况1:通过add、hide、show方式切换Fragment
1、初始化:
Fragment1载入:onCreate()->onCreateView()->onStart()->onResume()
2、Fragment1切换到Fragment2:
Fragment1隐藏:onHiddenChanged()
Fragment2载入:onCreate()->onCreateView()->onStart()->onResume()
3、Fragment2切换到Fragment1:
Fragment1:onHiddenChanged()
Fragment2:onHiddenChanged()

通过add、hide、show方式切换Fragment时所有的view都会保存在内存,不会销毁与重建

情况2:通过replace方式切换Fragment
1、初始化
Fragment1载入:onCreate()->onCreateView()->onStart()->onResume()
2、Fragment1切换到Fragment2:
Fragment1销毁:onPause()->onStop()->onDestroyView()->onDestroy()
Fragment2载入:onCreate()->onCreateView()->onStart()->onResume()
3、Fragment2切换到Fragment1:
Fragment2销毁:onPause()->onStop()->onDestroyView()->onDestroy()
Fragment1载入:onCreate()->onCreateView()->onStart()->onResume()

通过 replace 方法进行替换的时,Fragment 都是进行了销毁,重建的过程,相当于走了一整套的生命周期

情况3:通过ViewPager方式切换Fragment
1、初始化
Fragment1载入:onCreate()->onCreateView()->onStart()->onResume()
Fragment2载入:onCreate()->onCreateView()->onStart()->onResume()
2、Fragment1切换到Fragment2:
Fragment1不可见:setUserVisVleHint(false)
Fragment2可见:setUserVisVleHint(true)
3、Fragment2切换到Fragment1:
Fragment1可见:setUserVisVleHint(true)
Fragment2不可见:setUserVisVleHint(false)

1、使用ViewPager与Fragment切换时,Fragment会进行预加载操作,即所有的Fragment都会提前初始化。
2、 setUserVisVleHint()方法在 Fragment 1 第一次加载的时候不走,只有在切换的时候 走该方法。
3、主动调用 setUserVisibleHint()方法来控制第一次不会调用setUserVisibleHint方法的问题。
setUserVisibleHint()方法优先onCreateView方法,当onCreateView方法调用后还会再次调用setUserVisibleHint方法。
此时要对是否调用了onCreateView()方法进行标记判断。

/**
 * 标志位,标志已经初始化完成
 */
private boolean isPrepared;

/**
*第一个 Fragment 需要处理  setUserVisVleHint()方法,设置为 setUserVisibleHint(true);
*否则会产空指针异常,因为 setUserVisVleHint()方法的优先级高于 onCreate()方法。
*
* @param savedInstanceState
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    setUserVisibleHint(true);
    super.onActivityCreated(savedInstanceState);
}

主动调用 setUserVisibleHint()方法来控制第一次不会调用setUserVisibleHint方法的问题。
setUserVisibleHint()方法优先onCreateView方法,当onCreateView方法调用后还会再次调用setUserVisibleHint方法。
此时要对是否调用了onCreateView()方法进行标记判断。

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_a, container, false);

    //已经初始化
    isPrepared = true;
    return view;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);

    //可见的并且是初始化之后才加载
    if (isPrepared && isVisibleToUser) {

         getList();

    }
}

(四)Fragment方法详解

(1)FragmentManager管理

public abstract class FragmentManager {...}

FragmentManager 是一个抽象类,定义了一些和 Fragment 相关的操作和内部类/接口。
1、方法

public abstract FragmentTransaction beginTransaction();//开启一系列对 Fragments 的操作
public abstract boolean executePendingTransactions();//FragmentTransaction.commit() 是异步执行的,如果你想立即执行,可以调用这个方法
public abstract Fragment findFragmentById(@IdRes int id);//根据 ID 找到从 XML 解析出来的或者事务中添加的 Fragment
public abstract Fragment findFragmentByTag(String tag);//跟上面的类似,不同的是使用 tag 进行查找
public abstract void popBackStack();//弹出回退栈中栈顶的 Fragment,异步执行的
public abstract boolean popBackStackImmediate();//立即弹出回退栈中栈顶的,直接执行
public abstract void popBackStack(String name, int flags);//返回栈顶符合名称的,如果传入的 name 不为空,在栈中间找到了 Fragment,那将弹出这个 Fragment 上面的所有 Fragment(类似singleTask)异步执行
public abstract boolean popBackStackImmediate(String name, int flags);//同上,同步执行
public abstract void popBackStack(int id, int flags);
public abstract boolean popBackStackImmediate(int id, int flags);
public abstract int getBackStackEntryCount();//获取回退栈中的元素个数
public abstract BackStackEntry getBackStackEntryAt(int index);//根据索引获取回退栈中的某个元素
public abstract void addOnBackStackChangedListener(OnBackStackChangedListener listener);
public abstract void removeOnBackStackChangedListener(OnBackStackChangedListener listener);//添加或者移除一个监听器
public abstract void putFragment(Bundle bundle, String key, Fragment fragment);//还定义了将一个 Fragment 实例作为参数传递
public abstract Fragment getFragment(Bundle bundle, String key);
public abstract List<Fragment> getFragments();//获取 manager 中所有添加进来的 Fragment

2、内部类/接口

  • BackStackEntry:Fragment 后退栈中的一个元素。
  • onBackStackChangedListener:后退栈变动监听器,回退栈中有变化时调用。
  • FragmentLifecycleCallbacks: FragmentManager 中所有的 Fragment 生命周期监听。

3、实现类FragmentManagerImpl
FragmentManager 定义的任务是由 FragmentManagerImpl 实现的。

FragmentManager定义了一个对Activity/Fragment中添加进来的Fragment列表、Fragment回退栈的操作与管理。包括Fragment的查找、获取,及监听等。

(2)FragmentTransaction事务

通过FragmentManager获取FragmentTransaction

@Override
public FragmentTransaction beginTransaction() {
    return new BackStackRecord(this);
}

beginTransaction() 返回一个新的 BackStackRecord ,继承自FragmentTransaction

final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}

1、方法

public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment,
        @Nullable String tag);//在Activity添加Fragment,第一个参数表示Fragment的布局id,第二个参数表示要添加的Fragment,第三个参数为为Fragment设置的tag,后续可以用这个tag进行查询
public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment,
        @Nullable String tag);//替换宿主中一个已经存在的 fragment,这一个方法等价于先调用 remove(), 再调用 add()
public abstract FragmentTransaction remove(Fragment fragment);//移除一个已经存在的 fragment,如果之前添加到宿主上,那它的布局也会被移除
public abstract FragmentTransaction hide(Fragment fragment);//隐藏一个已存的 fragment,其实就是将添加到宿主上的布局隐藏
public abstract FragmentTransaction show(Fragment fragment);//显示前面隐藏的 fragment,这只适用于之前添加到宿主上的 fragment
public abstract FragmentTransaction detach(Fragment fragment);//将指定的 fragment 将布局上解除,当调用这个方法时,fragment 的布局已经销毁了
public abstract FragmentTransaction attach(Fragment fragment);//当前面解除一个 fragment 的布局绑定后,调用这个方法可以重新绑定,这将导致该 fragment 的布局重建,然后添加、展示到界面上

2、事务提交的四种方式

  • commit()
    commit() 在主线程中异步执行,其实也是 Handler 抛出任务,等待主线程调度执行。

注意:
commit() 需要在宿主 Activity 保存状态之前调用,否则会报错。
这是因为如果 Activity 出现异常需要恢复状态,在保存状态之后的 commit() 将会丢失,这和调用的初衷不符,所以会报错。

  • commitAllowingStateLoss()
    commitAllowingStateLoss() 也是异步执行,但它的不同之处在于,允许在 Activity 保存状态之后调用,也就是说它遇到状态丢失不会报错。
    因此我们一般在界面状态出错是可以接受的情况下使用它。
    这种方式是不安全的,因为事务提交时Activity可能还没有恢复,会丢失提交的事务。
  • commitNow()
    commitNow() 是同步执行的,立即提交任务。
    前面提到 FragmentManager.executePendingTransactions() 也可以实现立即提交事务。但我们一般建议使用 commitNow(), 因为另外那位是一下子执行所有待执行的任务,可能会把当前所有的事务都一下子执行了,这有可能有副作用。
    和 commit() 一样,commitNow() 也必须在 Activity 保存状态前调用,否则会抛异常。

此外,这个方法提交的事务可能不会被添加到 FragmentManger 的后退栈,因为你这样直接提交,有可能影响其他异步执行任务在栈中的顺序。

  • commitNowAllowingStateLoss()
    同步执行
    3、实现类BackStackRecord
    事务真正实现/回退栈 BackStackRecord
    BackStackRecord 既是对 Fragment 进行操作的事务的真正实现,也是 FragmentManager 中的回退栈的实现
final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}

关键成员

final FragmentManagerImpl mManager;

//Op 可选的状态值
static final int OP_NULL = 0;
static final int OP_ADD = 1;
static final int OP_REPLACE = 2;
static final int OP_REMOVE = 3;
static final int OP_HIDE = 4;
static final int OP_SHOW = 5;
static final int OP_DETACH = 6;
static final int OP_ATTACH = 7;

ArrayList<Op> mOps = new ArrayList<>();//mOps就是栈中所有的Fragment
static final class Op {//Op就是添加了状态和动画信息的Fragment
    int cmd;    //状态
    Fragment fragment;
    int enterAnim;
    int exitAnim;
    int popEnterAnim;
    int popExitAnim;
}

int mIndex = -1;    //栈中最后一个元素的索引

具体事务实现举例:添加一个Fragment到布局add()

@Override
public FragmentTransaction add(int containerViewId, Fragment fragment) {
    doAddOp(containerViewId, fragment, null, OP_ADD);
    return this;
}

@Override
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
    doAddOp(containerViewId, fragment, tag, OP_ADD);
    return this;
}
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
    final Class fragmentClass = fragment.getClass();
    final int modifiers = fragmentClass.getModifiers();
    if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
            || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
        throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                + " must be a public static class to be  properly recreated from"
                + " instance state.");
    }

    //1.修改添加的 fragmentManager 为当前栈的 manager
    fragment.mFragmentManager = mManager;

    if (tag != null) {
        if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
            throw new IllegalStateException("Can't change tag of fragment "
                    + fragment + ": was " + fragment.mTag
                    + " now " + tag);
        }
        fragment.mTag = tag;
    }

    if (containerViewId != 0) {
        if (containerViewId == View.NO_ID) {
            throw new IllegalArgumentException("Can't add fragment "
                    + fragment + " with tag " + tag + " to container view with no id");
        }
        if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
            throw new IllegalStateException("Can't change container ID of fragment "
                    + fragment + ": was " + fragment.mFragmentId
                    + " now " + containerViewId);
        }
        //2.设置宿主 ID 为布局 ID
        fragment.mContainerId = fragment.mFragmentId = containerViewId;
    }

    //3.构造 Op
    Op op = new Op();
    op.cmd = opcmd;    //状态
    op.fragment = fragment;
    //4.添加到数组列表中
    addOp(op);
}
void addOp(Op op) {
    mOps.add(op);
    op.enterAnim = mEnterAnim;
    op.exitAnim = mExitAnim;
    op.popEnterAnim = mPopEnterAnim;
    op.popExitAnim = mPopExitAnim;
}

修改 fragmentManager 和 ID,构造成 Op,设置状态信息,然后添加到列表里。

FragmentTransaction是对于Fragment回退栈中事务操作,包括添加add、移除remove、replace替代、hide隐藏、show显示、detach解除、attach附属
主要是执行Fragment栈中操作,用于完成Fragment对其宿主Activity/Fragment的绑定/解除。

(3)Fragment、FragmentManager、FragmentTransaction区别

  • Fragment
    其实是对 View 的封装,它持有 view, containerView, fragmentManager, childFragmentManager 等信息
  • FragmentManager
    是一个抽象类,它定义了对一个 Activity/Fragment 中 添加进来的 Fragment 列表、Fragment 回退栈的操作、管理方法,还定义了获取事务对象的方法,具体实现在 FragmentImpl 中
  • FragmentTransation
    定义了对 Fragment 添加、替换、隐藏等操作,还有四种提交方法,具体实现是在 BackStackRecord 中

(五)将Fragment添加到Activity(2种方式)

Fragment添加本质:
在 onCreateView() 中返回一个 布局,然后在 FragmentManager 中拿到这个布局,添加到要绑定容器(Activity/Fragment)的 ViewGroup 中,然后设置相应的状态值。

(1)在Activity的layout.xml布局文件中静态添加

步骤1:Activity布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

// 该fragment类定义在包名为"com.skywang.app"中的FragmentLayoutTest类的内部类ExampleFragment中
<fragment android:name="com.skywang.app.FragmentLayoutTest$ExampleFragment"
    android:id="@+id/list"
    android:layout_weight="1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

</LinearLayout>

步骤2:Fragment布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
    android:text="@string/example_fragment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

</LinearLayout>

步骤3:Activity的.java文件

// 在Activity使用Fragment时,需要考虑版本兼容问题
// 1. Android 3.0后,Activity可直接继承自Activity,并且在其中嵌入使用Fragment
// 2. Android 3.0前,Activity需FragmentActivity(其也继承自Activity),同时需要导入android-support-v4.jar兼容包,这样在Activity中才能嵌入Fragment

public class FragmentLayoutTest extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_layout_test);
        // 设置上述布局文件
    }

    // 继承自Fragment
    // 布局文件中的Fragment通过该FragmentLayoutTest的内部类ExampleFragment实现
    public static class ExampleFragment extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {

            return inflater.inflate(R.layout.example_fragment, container, false);
            // 将example_fragment.xml作为该Fragment的布局文件
            // 即相当于FragmentLayoutTest直接调用example_fragment.xml来显示到Fragment中
        }
    }
}

(2)在Activity的.java文件中动态添加

步骤1:Activity布局文件定义1占位符(FragmentLayout)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<FrameLayout
    android:id="@+id/about_fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

</LinearLayout>

步骤2:设置Fragment布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
    android:text="@string/example_fragment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

</LinearLayout>

步骤3:在Activity的.java文件中动态添加Fragment

public class FragmentTransactionTest extends Activity {

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

        // 步骤1:获取FragmentManager
        FragmentManager fragmentManager = getFragmentManager();

        // 步骤2:获取FragmentTransaction        
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        // 步骤3:创建需要添加的Fragment :ExampleFragment
        ExampleFragment fragment = new ExampleFragment();

        // 步骤4:动态添加fragment
        // 即将创建的fragment添加到Activity布局文件中定义的占位符中(FrameLayout)
        fragmentTransaction.add(R.id.about_fragment_container, fragment);
        fragmentTransaction.commit();
    }

    // 继承与Fragment
    public static class ExampleFragment extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {

            return inflater.inflate(R.layout.example_fragment, container, false);
            // 将example_fragment.xml作为该Fragment的布局文件
        }
    }
}

(3)嵌套Fragment

Fragment 内部有一个 childFragmentManager,通过它管理子 Fragment。在添加子 Fragment 时,把子 Fragment 的布局 add 到父 Fragment 即可。

(六)Activity与Fragment通信

(1)Activity传递数据到Fragment——Bundle

1、若Fragment存在,则可通过FindFragmentById直接获得Fragment并调用Fragment的共有函数,否则在创建Fragment时将数据放入bundle,并通过setArgument传递bundle对象

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...

    public void onArticleSelected(int position) {
        // 用户选中HeadlinesFragment中的头标题后
        // 做一些必要的业务操作

        ArticleFragment articleFrag = (ArticleFragment)
                getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            // 如果 article frag 不为空,那么我们在同时显示两个fragmnet的布局中...

            // 调用ArticleFragment中的方法去更新它的内容
            articleFrag.updateArticleView(position);
        } else {
            // 否则,我们就是在仅包含一个fragment的布局中并需要交换fragment...

            // 创建fragment并给他一个跟选中的文章有关的参数
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);
        
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            // 用这个fragment替换任何在fragment_container中的东西
            // 并添加事务到back stack中以便用户可以回退到之前的状态
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);

            // 提交事务
            transaction.commit();
        }
    }
}

2、设置Fragment

public class mFragment extends Fragment {
    Button button;
    TextView text;
    Bundle bundle;
    String message;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View contentView = inflater.inflate(R.layout.fragment, container, false);
        // 设置布局文件
        button = (Button) contentView.findViewById(R.id.button);
        text = (TextView) contentView.findViewById(R.id.text);
        // 步骤1:通过getArgments()获取从Activity传过来的全部值
        bundle = this.getArguments();
        // 步骤2:获取某一值
        message = bundle.getString("message");
        // 步骤3:设置按钮,将设置的值显示出来
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // 显示传递过来的值
                text.setText(message);

            }
        });
        return contentView;
    }
}

(2)Fragment传递数据到Activity——接口回调

接口回调方式:把实现了某一接口的类所创建的对象的引用 赋给 该接口声明的变量,通过该接口变量 调用 该实现类对象的实现的接口方法。
1、定义接口
定义回调接口,用于Activity与Fragment通信

public interface OnFragmentInteractionListener {
    //接口实现方法
    void onFragmentInteraction(String data);
}

2、接收方实现接口
接收方通过implements实现接口,并在回调函数中实现具体业务

public class FragmentTestActivity extends AppCompatActivity implements OnFragmentInteractionListener{
...
    @Override
    public void onFragmentInteraction(String data) {
        Toast.makeText(FragmentTestActivity.this,data,Toast.LENGTH_LONG).show();
    }
}

3、发送方执行回调函数发送数据
发送方获取实现接口的接收方,并在某个事件(如点击事件)中调用回调方法发送数据

public class Test1Fragment extends Fragment{
...
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View contentView =  inflater.inflate(R.layout.fragment_test1, container, false);
        btn1 = (Button)contentView.findViewById(R.id.btn_fragment_1);
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onButtonPressed();
            }
        });
        return contentView;
    }
    
    public void onButtonPressed() {
        if (mListener != null) {
            mListener.onFragmentInteraction("Hello 我是FragmentTest1");
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            //获取Activity,其中Activity必须实现OnFragmentInteractionListener接口
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }
}

4、接收方接受数据
此时接收方的回调方法被执行,收到了发送方的数据

理解回调函数 回调函数分为接口、发送方与接收方。
1、定义接口:定义接口回调函数
2、接收方实现接口:接收方通过implements实现接口,并在回调函数中实现具体业务
3、发送方执行回调函数发送数据:发送方获取实现接口的接收方,并在某个事件(如点击事件)中调用回调方法发送数据
4、接收方接受数据: 此时接收方的回调方法被执行,收到了发送方的数据

(3)总结

在这里插入图片描述

(七)Fragment之间传递数据方式

(1)通过FragmentManager标签获得Fragment对象

在MenuFragment中的ListView条目点击事件中通过标签获取到MainFragment,并调用对应的setData()方法,将数据设置进去,从而达到数据传递的目的。

lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        MainFragment mainFragment =
        (MainFragment) getActivity()
        .getSupportFragmentManager()
        .findFragmentByTag("mainFragment");
        mainFragment.setData(mDatas.get(position));
        }
        });

(2)采取接口回调方式进行数据传递

step1: 在Menuragment中创建一个接口以及接口对应的set方法:

//MenuFragment.java文件中
public interface OnDataTransmissionListener {
    public void dataTransmission(String data);
}
    public void setOnDataTransmissionListener(OnDataTransmissionListener mListener) {
        this.mListener = mListener;
}

step2: 在MenuFragment中的ListView条目点击事件中进行接口进行接口回调:

//MenuFragment.java文件中
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        if (mListener != null) {
        mListener.dataTransmission(mDatas.get(position));
        }
        }
        });

step3: 在MainActivity中根据menuFragment获取到接口的set方法,在这个方法中进行进行数据传递,具体如下:

//在MainActivity.java中
menuFragment.setOnDataTransmissionListener(new MenuFragment.OnDataTransmissionListener() {
@Override
public void dataTransmission(String data) {
        mainFragment.setData(data);  //注:对应的mainFragment此时应该要用final进行修饰
        }
        });

(3)第三方开源框架:EventBus

简单来说,EventBus是一款针对Android优化的发布/订阅(publish/subscribe)事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息。简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小,代码更优雅,以及将发送者和接收者解耦。比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求都可以通过EventBus实现。

(八)Fragment界面实现方式

(1)show、hide、replace区别

1、区别

show(),hide()最终是让Fragment的View setVisibility(true还是false),不会调用生命周期;replace()的话会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期;
add()和 replace()不要在同一个阶级的FragmentManager里混搭使用。

2、使用场景

如果你有一个很高的概率会再次使用当前的Fragment,建议使用show(),hide(),可以提高性能。
在我使用Fragment过程中,大部分情况下都是用show(),hide(),而不是replace()。
注意:如果你的app有大量图片,这时更好的方式可能是replace,配合你的图片框架在Fragment视图销毁时,回收其图片所占的内存。

3、add与replace实例

(1)add:切换fragment时将fragment隐藏了而不是销毁再创建,故不会让fragment重新刷新
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

//第一种方式(add),初始化fragment并添加到事务中,如果为null就new一个
  if(f1 == null){
                f1 = new MyFragment("消息");
                transaction.add(R.id.main_frame_layout, f1);
                }
                //隐藏所有fragment
               hideFragment(transaction);
                //显示需要显示的fragment
                transaction.show(f1);
                //提交事务
                transaction.commit();

(2)replace:切换fragment时重新创建fragment,会让fragment重新刷新
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

    //第二种方式(replace),初始化fragment
    if(f1 == null){
        f1 = new MyFragment("消息");
    }
    transaction.replace(R.id.main_frame_layout, f1);
    //提交事务
    transaction.commit();
4、Fragment如何实现布局的添加与替换

(4.1)add

@Override
public FragmentTransaction add(int containerViewId, Fragment fragment) {
    doAddOp(containerViewId, fragment, null, OP_ADD);
    return this;
}

@Override
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
    doAddOp(containerViewId, fragment, tag, OP_ADD);
    return this;
}
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
    final Class fragmentClass = fragment.getClass();
    final int modifiers = fragmentClass.getModifiers();
    if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
            || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
        throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                + " must be a public static class to be  properly recreated from"
                + " instance state.");
    }

    //1.修改添加的 fragmentManager 为当前栈的 manager
    fragment.mFragmentManager = mManager;

    if (tag != null) {
        if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
            throw new IllegalStateException("Can't change tag of fragment "
                    + fragment + ": was " + fragment.mTag
                    + " now " + tag);
        }
        fragment.mTag = tag;
    }

    if (containerViewId != 0) {
        if (containerViewId == View.NO_ID) {
            throw new IllegalArgumentException("Can't add fragment "
                    + fragment + " with tag " + tag + " to container view with no id");
        }
        if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
            throw new IllegalStateException("Can't change container ID of fragment "
                    + fragment + ": was " + fragment.mFragmentId
                    + " now " + containerViewId);
        }
        //2.设置宿主 ID 为布局 ID
        fragment.mContainerId = fragment.mFragmentId = containerViewId;
    }

    //3.构造 Op
    Op op = new Op();
    op.cmd = opcmd;    //状态
    op.fragment = fragment;
    //4.添加到数组列表中
    addOp(op);
}
void addOp(Op op) {
    mOps.add(op);
    op.enterAnim = mEnterAnim;
    op.exitAnim = mExitAnim;
    op.popEnterAnim = mPopEnterAnim;
    op.popExitAnim = mPopExitAnim;
}

(4.2)replace

@Override
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
    return replace(containerViewId, fragment, null);
}

@Override
public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
    if (containerViewId == 0) {
        throw new IllegalArgumentException("Must use non-zero containerViewId");
    }

    doAddOp(containerViewId, fragment, tag, OP_REPLACE);
    return this;
}

也是调用上面刚提到的 doAddOp(),不同之处在于第四个参数为 OP_REPLACE

通过获得当前 Activity/Fragment 的 FragmentManager/ChildFragmentManager,进而拿到事务的实现类 BackStackRecord,它将目标 Fragment 构造成 Ops(包装Fragment 和状态信息),然后提交给 FragmentManager 处理。
如果是异步提交,就通过 Handler 发送 Runnable 任务,FragmentManager 拿到任务后,先处理 Ops 状态,然后调用 moveToState() 方法根据状态调用 Fragment 对应的生命周期方法,从而达到 Fragment 的添加、布局的替换隐藏等。

(2)getFragmentManager,getSupportFragmentManager ,getChildFragmentManager三者之间的区别

  • getFragmentManager()所得到的是所在fragment 的父容器的管理器。
  • getChildFragmentManager()所得到的是在fragment 里面子容器的管理器。
  • getSupportFragmentManager()主要用于支持 3.0以下android系统API版本,3.0以上系统可以直接调用getFragmentManager()
    注:getFragmentManager到的是activity对所包含fragment的Manager,而如果是fragment嵌套fragment,那么就需要利用getChildFragmentManager()

(3)FragmentPagerAdapter与FragmentStatePagerAdapter的区别与使用场景

(1)FragmentPagerAdapter
使用FragmentPagerAdapter 时,Fragment对象会一直存留在内存中,所以当有大量的显示页时,就不适合用FragmentPagerAdapter了,FragmentPagerAdapter 适用于只有少数的page情况,像选项卡。
原因:步长外的页面会调用destroyItem,但只有onDestroyView调用了,没有调用onDestory,也没有调用onDetach,所以fragment只是把上面的view销毁了,fragment并没有销毁,下次再创建的时候,只会调用onCreateView和onActivityCreated
(2)FragmentStatePagerAdapter
这个时候你可以考虑使用FragmentStatePagerAdapter ,当使用FragmentStatePagerAdapter 时,如果Fragment不显示,那么Fragment对象会被销毁,(滑过后会保存当前界面,以及下一个界面和上一个界面(如果有),最多保存3个,其他会被销毁掉)
原因:会真正销毁(同时销毁view和fragment,调用onDestroyView以及其后面的所有销毁方法),重建时会从最初的onAttach开始一直到onActivityCreated。但在回调onDestroy()方法之前会回调onSaveInstanceState(Bundle outState)方法来保存Fragment的状态,下次Fragment显示时通过onCreate(Bundle savedInstanceState)把存储的状态值取出来,FragmentStatePagerAdapter 比较适合页面比较多的情况,像一个页面的ListView 。

(4)Fragment实现实例——Fragment与ViewPager的搭配使用

通常情况下我们开发应用最常见的使用情况是TabLayout+ViewPager+Fragment的使用方式,下面通过一个实例展示:
在这里插入图片描述

(4.1)TabLayout+ViewPager+Fragment的简单用法总结

步骤1:引入工具包

    implementation 'com.android.support:design:27.1.1'
    implementation 'com.android.support:support-v4:27.1.1'

步骤2:书写布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activities.TabLayoutActivity">

    <android.support.design.widget.TabLayout
        android:id="@+id/tl_tabs"
        android:layout_width="match_parent"
        android:layout_height="40dp" />

    <android.support.v4.view.ViewPager
        android:id="@+id/vp_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

步骤3:实现TabLayout+ViewPager+Fragment
使用流程:
1、创建存储多个Fragment实例的列表
2、创建PagerAdapter实例并关联到Viewpager中
3、将ViewPager关联到Tablayout中
4、根据需求改写Tablayout属性

public class TabLayoutActivity extends AppCompatActivity implements MyFragment.OnFragmentInteractionListener {
    TabLayout tabLayout;
    ViewPager viewPager;
    
    List<Fragment> fragments = new ArrayList<>();//步骤1:创建存储多个Fragment实例的列表
    List<String> titles = new ArrayList<>();

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

        tabLayout = findViewById(R.id.tl_tabs);
        viewPager = findViewById(R.id.vp_content);

        fragments.add(MyFragment.newInstance("11111", "11111"));
        fragments.add(MyFragment.newInstance("22222", "22222"));
        fragments.add(MyFragment.newInstance("33333", "33333"));
        fragments.add(MyFragment.newInstance("44444", "44444"));
        fragments.add(MyFragment.newInstance("55555", "55555"));
        titles.add("fragment1");
        titles.add("fragment2");
        titles.add("fragment3");
        titles.add("fragment4");
        titles.add("fragment5");
        //步骤2:创建PagerAdapter实例并关联到Viewpager中
        viewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                return fragments.get(position);
            }

            @Override
            public int getCount() {
                return fragments.size();
            }

            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                super.destroyItem(container, position, object);
            }

            @Nullable
            @Override
            public CharSequence getPageTitle(int position) {

                return titles.get(position);
            }
        });
//步骤3:将ViewPager关联到Tablayout中
        tabLayout.setupWithViewPager(viewPager);
    }

    @Override
    public void onFragmentInteraction(Uri uri) {

    }
}
(4.2)两种PagerAdapter区别与使用场景

PagerAdapter是一个抽象类,它有两个实现子类供我们使用,分别是FragmentStatePagerAdapter和FragmentPagerAdapter。创建这两个类的实例需要传入一个FragmentManager对象,像代码那样处理就行了,从类名就可以看出来它俩的最大差别就在“State-状态”上,什么意思呢?指的是所包含存储的Fragment对象的状态是否保存。看源码可以发现,FragmentStatePagerAdapter中比FragmentPagerAdapter多维护着两个列表:

	private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
    private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

而这两个列表带来的最大差别则体现在void destroyItem(ViewGroup container, int position, Object object)这个函数之中。(销毁Fragment)
ViewPager还有一个比较重要的函数是:

viewPager.setOffscreenPageLimit(int limit);

Google在开发ViewPager时,考虑到如果滑动的时候才创建Fragment实例时会带来一定程度的卡顿,因此为ViewPager设置了缓存机制,而上述函数则是设置缓存Fragment的数量,示意图如下:
在这里插入图片描述
limit的值代表着还要缓存当前Fragment左右各limit个Fragment,一共会创建2*limit+1个Fragment。超出这个limit范围的Fragment就会被销毁,而上述两种PagerAdapter的差别就是销毁的流程不同!
FragmentPagerAdapter在销毁Fragment时不会调用onDestroy()方法,而带了State的Adapter则会调用Fragment的onDestroy()方法,换言之,前者仅仅是销毁了Fragment的View视图而没有销毁Fragment这个对象,但是后者则彻彻底底地消灭了Fragment对象。
因此,对于PagerAdapter的选择:
FragmentPagerAdapter适用于Fragment比较少的情况,它会把每一个Fragment保存在内存中,不用每次切换的时候,去保存现场,切换回来在重新创建,所以用户体验比较好。而对于Fragment比较多的情况,需要切换的时候销毁以前的Fragment以释放内存,就可以使用FragmentStatePagerAdapter。

(4.3)懒加载策略

在这里插入图片描述
TabLayout+ViewPager+Fragment 的页面结构中ViewPager中会采用预加载机制。

1、为什么要懒加载

Android的View绘制流程是最消耗CPU时间片的操作,尤其是在ViewPager缓存Fragment的情况下,如果在View绘建的同时还进行多个Fragment的数据加载,那用户体验简直是爆炸(不仅浪费流量,而且还造成不必要的卡顿)因此,需要对Fragment们进行懒加载策略。

2、什么是懒加载

就是被动加载,当Fragment页面可见时,才从网络加载数据并显示出来。
ViewPager为了优化用户体验,默认加载相邻两页,来尽可能保证滑动的流畅性。它自身提供了一个方法:mViewPager.setOffscreenPageLimit()。它的意思就是设置 ViewPager 左右两侧缓存页的数量,默认是1。

3、懒加载的生命周期

(1)setUserVisibleHint 这个方法可能会在 onAttach 之前就调用
(2)在滑动中设置缓存页数之内的页会被创建,在滑动中设置缓存页数之外的页会被销毁

setUserVisibleHint通过ViewPager中PagerAdapter方法主动调用:
位置1:instantiateItem
在我的数据集合中取出对应 positon 的 Fragment,直接给它的 setUserVisibleHint 设置为 false,然后才把它 add 进 FragmentTransaction 中
位置2:setPrimaryItem
得到当前 ViewPager 正在展示的 Fragment,并且将上一个 Fragment 的 setUserVisibleHint 置为 false,将要展示的 setUserVisibleHint 置为 true。

4、如何懒加载

实行懒加载必须满足的条件

  • (1)View视图加载完毕,即onCreateView()执行完成

(setUserVisibleHint函数是游离在Fragment生命周期之外的,它的执行有可能早于onCreate和onCreateView,然而既然要时间数据的加载,就必须要在onCreateView创建完视图过后才能使用,不然就会返回空指针崩溃)

  • (2)当前Fragment可见,即setUserVisibleHint()的参数为true
  • (3)初次加载,即防止多次滑动重复加载

故在Fragment全局变量中增加对应的三个标志参数并设置初始值

boolean mIsPrepare = false;		//视图还没准备好=>onCreateView
boolean mIsVisible= false;		//不可见=>setUserVisibleHint
boolean mIsFirstLoad = true;	//第一次加载

当然在onCreateView中确保了View已经准备好时,将mPrepare置为true,在setUserVisibleHint中确保了当前可见时,mIsVisible置为true,第一次加载完毕后则将mIsFirstLoad置为false,避免重复加载。

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    mIsPrepare = true;
    lazyLoad();
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    //isVisibleToUser这个boolean值表示:该Fragment的UI 用户是否可见
    if (isVisibleToUser) {
        mIsVisible = true;
        lazyLoad();
    } else {
        mIsVisible = false;
    }
}

最后,贴上懒加载的lazyLoad()代码(只要标志位改变,就要进行lazyLoad()函数的操作)

private void lazyLoad() {
    //这里进行三个条件的判断,如果有一个不满足,都将不进行加载
    if (!mIsPrepare || !mIsVisible||!mIsFirstLoad) {
    return;
    }
        loadData();
        //数据加载完毕,恢复标记,防止重复加载
        mIsFirstLoad = false;
    }
    
  private void loadData() {
    //这里进行网络请求和数据装载
    }

最后,如果Fragment销毁的话,还应该将三个标志位进行默认值初始化:

 @Override
    public void onDestroyView() {
        super.onDestroyView();
        mIsFirstLoad=true;
        mIsPrepare=false;
        mIsVisible = false;
    }

为什么在onDestroyView中进行而不是在onDestroy中进行呢?这又要提到之前Adapter的差异,onDestroy并不一定会调用。

(4.4)卡顿及性能优化建议

Fragment的加载最为耗时的步骤主要有两个,一个是Fragment创建(尤其是创建View的过程),另一个就是读取数据填充到View上的过程。懒加载能够解决后者所造成的卡顿,但是针对前者来说,并没有效果。
Google为了避免用户因翻页而造成卡顿,采用了缓存的形式,但是其实缓不缓存,只要该Fragment会显示,都会进行Fragment创建,都会耗费相应的时间,换言之,缓存只不过将本应该在翻页时的卡顿集中在启动该Activity的时候一起卡顿。

优化方案一:设置缓存页面数

viewPager.setOffscreenPageLimit(int limit) 能够有效地一次性缓存多个Fragment,这样就能够解决在之后每次切换时不会创建实例对象,看起来也会流畅。但是这样的做法,最大的缺点就是容易造成第一次启动时非常缓慢!如果第一次启动时间满足要求的话,就使用这种简单地办法吧。

优化方案二:避免Fragment的销毁

不管是FragmentStatePagerAdapter还是FragmentPagerAdapter,其中都有一个方法可以被覆写:

            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
               // super.destroyItem(container, position, object);
            }

把中间的代码注释掉就行了,这样就可以避免Fragment的销毁过程,一般情况下能够这样使用,但是容易出现一个问题,我们再来看看FragmentStatePagerAdapter的源码:

  @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);

        mCurTransaction.remove(fragment);
    }

看到没?这个过程之中包含了对FragmentInstanceState的保存!这也是FragmentStatePagerAdapter的精髓之处,如果注释掉,一旦Activity被回收进入异常销毁状态,Fragment就无法恢复之前的状态,因此这种方法也是有纰漏和局限性的。FragmentPagerAdapter的源代码就留给大家自己去研究分析,也会发现一些问题的哦。

优化方案三:避免重复创建View

优化Viewpager和Fragment的方法就是尽可能地避免Fragment频繁创建,当然,最为耗时的都是View的创建。所以更加优秀的优化方案,就是在Fragment中缓存自身有关的View,防止onCreateView函数的频繁执行,我就直接上源码了:

public class MyFragment extends Fragment {
	View rootView;
	
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        if (rootView == null) {
            rootView = inflater.inflate(R.layout.fragment_my, container, false);
        }
        return rootView;

   @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG, "onDestroyView: " + mParam1);
        mIsFirstLoad=true;
        mIsPrepare=false;
        mIsVisible = false;
        if (rootView != null) {
            ((ViewGroup) rootView.getParent()).removeView(rootView);
        }
}

onCreateView中将会对rootView进行null判断,如果为null,说明还没有缓存当前的View,因此会进行过缓存,反之则直接利用。当然,最为重要的是需要在onDestroyView() 方法中及时地移除rootView,因为每一个View只能拥有一个Parent,如果不移除,将会重复加载而导致程序崩溃。

其实ViewPager+Fragment的方式,ViewPager中显示的就是Fragment中所创建的View,Fragment只是一个控制器,并不会直接显示于ViewPager之中,这一点容易被忽略。

(九)Fragment有哪些坑?如何解决?

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

内存重启本质就是Android系统(而非开发人员手动)杀死进程并重启进程的过程。会在进程结束前调用onSaveInstanceState保存Activity现场状态,同时会在进程创建后恢复。

1、getActivity()空指针

(1)异常
内存重启/pop了Fragment后Fragment的异步任务仍在执行,且执行时调用了getActivity方法,此时会报空指针异常。
(2)原因
调用了getActivity()时,当前的Fragment已经onDetach()了宿主Activity。
(3)解决
3.1)避免在Fragment已onDetach后再去调用宿主Activity。
3.2)在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)里赋值,使用mActivity代替getActivity(),保证Fragment即使在onDetach后,仍持有Activity的引用(有引起内存泄露的风险,但是异步任务没停止的情况下,本身就可能已内存泄漏,相比Crash,这种做法“安全”些)

protected Activity mActivity;

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    this.mActivity = (Activity)context;
}

2、异常:Can not perform this action after onSaveInstanceState

(1)异常
Activity在调用onSaveInstanceState()保存当前Activity的状态后,直到Activity状态恢复之前,你commit 一个FragmentTransaction,就会抛出该异常——导致Activity状态丢失
(2)原因
当框架调用onSaveInstanceState()的时候,它会向这个方法传递一个Bundle参数,Activity可以用这个参数来保存页面、对话框、Fragments和视图的状态。当onSaveInstanceState返回时,会将一个Bundle对象序列化之后通过Binder接口传递给系统服务进程,并安全的保存起来。当系统晚一点想要重启Activity的时候, 它会把之前的Bundle对象传递回应用,并用来恢复Activity之前的状态。
所以为什么会抛出之前的异常呢?问题的根源在于Bundle这个对象仅仅是Activity在onSaveInstanceState()方法被调用那一刻的快照。这就意味着当你如果在onSaveInstanceState()之后再调用FragmentTransaction.commit()的话,由于这次Transaction没有被作为Activity状态的一部分来保存,自然也就丢失掉了。从用户的角度来说,Transaction的遗失就导致意外的UI状态丢失。那么为了维护良好的用户体验,Android系统会不惜一切代价的避免页面状态的丢失,所以在这种情况发生的时候就直接抛出了IllegalStateException。
(3)解决
(3.1)谨慎的在Activity的生命周期方法中调用transaction的commit方法。
大多数应用只会在第一次调用onCreate()或者响应用户输入的时候去commit transaction, 这样不会有什么问题。但是, 当您把transaction放到其他Activity的生命周期方法中时,比如onActivityResult(), onStart() 和onResume(),事情就会变得有点复杂。举个栗子,您不应该在FragmentActivity#onResume()中去commit transaction,因为在某些情况下,这个方法可能会在Activity状态恢复之前被调用(看这里))。如果您的应用需要在onCreate()之外的其他生命周期方法中去commit transaction,那就请在 FragmentActivity#onResumeFragments()或者Activity#onPostResume()中去commit。这两个方法保证会在Activity恢复状态之后才会被调用,所以就能完全避免状态丢失的可能性。(作为例子,可以查看我在StackOverflow上,关于怎样在Activity#onActivityResult()中commit FragmentTransactions 的回答)。
(3.2)避免在异步回调中去处理transaction。
包括使用AsyncTask#onPostExecute()和 LoaderManager.LoaderCallbacks#onLoadFinished()方法等。这样做的问题在于,您不会知道这些异步方法在调用的时候,当前的Activity究竟处于生命周期的哪一个状态。比如说,下面这一系列事件:

  • Activity执行一个AsyncTask。
  • 当用户按下Home键,导致Activity的onSaveInstanceState()和onStop()执行。
  • AsyncTask执行完成并且onPostExecute()被调用,其不知道Activity这个时候已经被stopped。
  • FragmentTransaction在onPostExecute()中被调用,导致异常被抛出。

总体来说,在这种情况下,最好避免异常的方法是不要在异步回调中去commit transaction。Google的工程师们看起来也同意这个观点。根据Android开发者团队邮件组的这篇文章,Android团队认为在异步方法中去调用commit transaction而产生的UI切换,会导致不好的用户体验。 如果您的应用确实需要在异步方法中处理transaction,并且没有简单的方法可以保证回调在onSaveInstanceState()之前被调用的话,你可能只有使用commitAllowingStateLoss(),并且自己处理可能发生的状态丢失了(请参考StackOverflow,这里,还有这里)。
(3.3)只在不得已的情况下,才使用commitAllowingStateLoss()。
commit()和commitAllowingStateLoss()唯一的不同是,后者当状态丢失时,不会抛出异常。通常由于存在状态丢失的可能性,您不会希望使用这个方法。更好的解决方法是,在您的应用中使用commit(),并保证在Activity的状态保存之前调用它,来获取更好的用户体验。除非状态丢失的可能性不能被避免,否则您不应该使用commitAllowingStateLoss()。

3、Fragment界面重叠

(Support 24.0.0及以上版本已修复)
(1)异常
满足下面2个条件可能会发生Fragment重叠:1、采用add方式添加Fragment并使用hide|show切换Fragment时 2、发生内存重启 会导致Fragment重叠
(2)原因
Activity在调用onSaveInstanceState方法时,系统保存了Fragment的现场状态。但是在重启后恢复时,视图的可见状态没帮我们保存,而Fragment默认的是show状态,所以产生了Fragment重叠现象。
FragmentManager对Fragment进行管理,当发生"内存重启"时。他会从栈底向栈顶的顺序一次性恢复Fragment;但由于没有保存Fragment的mHidden属性,默认为false,即show状态,所有的Fragment都以show形式恢复。所以界面发生重叠。
(3)解决
即在add()或者replace()时绑定一个tag,一般我们是用fragment的类名作为tag,然后在发生“内存重启”时,通过findFragmentByTag找到对应的Fragment,并hide()需要隐藏的fragment。(回退栈已存在该Fragment)

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

    TargetFragment targetFragment;
    HideFragment hideFragment;

    if (savedInstanceState != null) {  // “内存重启”时调用
        targetFragment = getSupportFragmentManager().findFragmentByTag(TargetFragment.class.getName);
        hideFragment = getSupportFragmentManager().findFragmentByTag(HideFragment.class.getName);
        // 解决重叠问题
        getFragmentManager().beginTransaction()
                .show(targetFragment)
                .hide(hideFragment)
                .commit();
    }else{  // 正常时
        targetFragment = TargetFragment.newInstance();
        hideFragment = HideFragment.newInstance();

        getFragmentManager().beginTransaction()
                .add(R.id.container, targetFragment, targetFragment.getClass().getName())
                .add(R.id,container,hideFragment,hideFragment.getClass().getName())
                .hide(hideFragment)
                .commit();
    }
}

如果你想恢复到用户离开时的那个Fragment的界面,你还需要在onSaveInstanceState(Bundle outState)里保存离开时的那个可见的tag或下标,在onCreate“内存重启”代码块中,取出tag/下标,进行恢复。

4、Fragment嵌套的那些坑

(1)原因
对嵌套的栈视图产生混乱
(2)解决
理清栈视图关系,做好恢复相关工作以及正确选择是使用getFragmentManager()还是getChildFragmentManager()就可以避免这些问题。
FragmentManager栈视图:
在这里插入图片描述
(1)每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
(2)对于宿主Activity,getSupportFragmentManager()获取的FragmentActivity的FragmentManager对象;
对于Fragment,getFragmentManager()是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()是获取自己的FragmentManager对象。

5、未必靠谱的出栈方法remove()

如果你想让某一个Fragment出栈,使用remove()在加入回退栈时并不靠谱。

6、多个Fragment同时出栈的深坑BUG

(support-25.4.0版本修复)

7、深坑 Fragment转场动画

(1)坑1:pop多个Fragment时转场动画 带来的问题(25.4.0版本修复)
(2)坑2:进入新的Fragment并立刻关闭当前Fragment 时的一些问题:可能会闪屏

  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李一恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值