Fragment二:Fragment使用的各类问题及解决方案

概述:

fragment有诸多的优势有点,但也存在着不足和一些隐藏的问题,以下是在开发过程中碰到的fragment各类问题,做以总结和分析:

目录:
* 1.fragment出栈以及同时多个出栈会导致的问题
* 2.Fragment回退监听问题
* 3.Fragment布局透明、布局重叠问题
* 4.Fragment页面动画跳转导致的问题
* 5.界面点击事件穿透问题
* 6.getActivity空指针问题
* 7.各个Fragment之间数据传递问题
* 8.Fragment的replace切换页面导致回退栈出现问题
* 9.Fragment嵌套内存泄漏问题

fragment出栈以及同时多个出栈会导致的问题

在源码中fragment的实例对象存储list集合中保存,在单次弹出操作过程如下图:

image

在单次弹出的时候通常不会出现问题,但是在pop栈顶的fragment后,然后又直接调用FragmentManager的getFragments()获取尾角标也就是栈顶的Fragment,会发现是null,原因就是在集合中被移除的fragemnt实质上被置为了null,而不是直接被移除

同样在多个fragment同时出栈的时候,这个问题更明显,如下图展示:
image2

综上来说,整个队列的顺序是不存在问题的,只是在出栈进栈的过程中会导致有null的存在,所以,我们在操作存储fragment实例对象的list集合的时候,需要做如下操作:

// 将系统默认集合中的null进行剔除,然后调用重新存储的集合进行操作
public ArrayList<Fragment> getFragments() {
        ArrayList<Fragment> fragments1 = new ArrayList<>();
        @SuppressLint("RestrictedApi") List<Fragment> fragments = mActivity.getSupportFragmentManager().getFragments();
        for (Fragment fragment : fragments) {
            if (fragment != null) {
                fragments1.add(fragment);
            }
        }
        return fragments1;
    }
Fragment回退监听问题

在activity中我们可以很清晰的通过实现复写onBackPressed()而获取到当前activity的退出操作,在onBackPressed()函数中实现我们退出前需要执行的逻辑代码,但fragment源生是并没有对回退做监听的函数,尤其是在项目中高度使用fragment的情况下,fragment没有回退监听是非常的痛苦。

在各种调试下给出了以下的解决方案:
1. 监听activity的回退键监听
2. 通过FragmentManager获取所有的fragment实例对象的集合,获取集合尾部,也就是fragment回退栈栈顶的fragment实例对象
3. 执行fragment中对应的回退监听函数,实现对回退的监听。(整个封装,为了通用性,当前回调函数需要在所有调用的fragment的baseFragment中空实现一个回退需要执行的函数,由需要实现的监听回退的子类去复写实现)

**注意:需要说明的一点是,当前的解决方式仅适合于add方式且addToBackStack()添加入回退栈的加载,而不适合replace方式**
  • 监听activity的回退键监听
    首先我们需要知道,点击回退键执行的函数的调用流程顺序:

    dispatchKeyEvent -> onUserInteraction -> onKeyDown -> onBackPressed

为了能够有最高的可控性,所以我们就选择dispatchKeyEvent

@SuppressLint("RestrictedApi")
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {//点击的是返回键
            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
            //按键的按下事件,就在此处获取栈顶fragment和监听fragment的回退执行的函数    
            } 
            return true;
        }
        return super.dispatchKeyEvent(event);
    }
  • 获取回退栈栈顶的fragment

    1. 需要通过FragmentManager的getFragments()函数获取到当前所有被add的fragment的list集合
    2. list集合添加的插入顺序是从队列尾部插入,所以取尾角标就是最上面一个fragment
      注意:此处也会存在1问题中的问题,所以也需要解决fragment为null的问题
  • 执行fragment中对应的回退监听函数

//BaseFragment 中需要实现的回退监听的空函数

    /**
     * Explain : 后退键的监听
     *@return : 当子类返回true,则认为当前后退由子类自己执行,不执行后退操作,全权交给子类自己去处理
     *           当子类返回false,则认为当前子类不做处理,直接退出当前fragment
     * @author LiXaing create at 2017/7/19 17:54
     */
    protected boolean onBackPressed() {
        return false;
    }
    /**        Explain : Fragment的回退监听
    * @author LiXiang create at 2018/1/30 19:03*/
    @SuppressLint("RestrictedApi")
    public void onBackPressedListener(){
    android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
    // >1,当存在多个fragment的时候,就执行栈顶fragment的回退监听函数
        if (getFragments() > 1) {
            @SuppressLint("RestrictedApi")
            BaseFragment fragment = (BaseFragment) 
            //获取当前子类的是否复写返回true有自己的操作,当返回false的时候就默认弹栈
            getFragments().get(getFragments()-1);
             if (!fragment.onBackPressed()) {
                        fm.popBackStack();
                    }
            }
        }else {
        //当只有一个fragment的时候,也就是回到了主界面,所以直接关闭当前app
             mActivity.finish();
        }
    }

//获取当前管理Fragment实例对象且不存在null的list集合
public ArrayList<Fragment> getFragments() {
        ArrayList<Fragment> fragments1 = new ArrayList<>();
        @SuppressLint("RestrictedApi") List<Fragment> fragments = getFragmentManager(false).getFragments();
        for (Fragment fragment : fragments) {
            if (fragment != null) {
                fragments1.add(fragment);
            }
        }
        return fragments1;
    }
Fragment布局透明、布局重叠问题

导致fragment重叠的问题主要是两方面导致的:

  1. fragment不会设定给布局的默认色,所以,最外层的布局,如果没有设定任何颜色的话,展示出来,就会是透明色,可以看到当下面界面的视图,当前的解决方案就是给最外层视图,设定指定色
  2. 没有很好的运用show().hide()方法而导致的布局重叠解决方案如下:
/**
     * show一个Fragment,hide另一个/多个Fragment ; 主要用于类似微信主页那种 切换tab的情况
     *
     * @param showFragment    需要show的Fragment
     * @param hideFragment    需要hide的Fragment
     * @param isChildFragment 当时Fragment是否是在Fragment中的fragment
     */
    public void showHideFragment(Fragment showFragment, Fragment hideFragment) {

        if (showFragment == hideFragment) return;
        FragmentManager fm = mActivity.getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        if (hideFragment == null) {
            @SuppressLint("RestrictedApi") List<Fragment> fragmentList = fm.getFragments();
            if (fragmentList != null) {
                for (Fragment fragment : fragmentList) {
                    if (fragment != null && fragment != showFragment) {
                        ft.hide(fragment);
                    }
                }
            }
        } else {
            ft.hide(hideFragment);
        }
        ft.show(showFragment);
        ft.commitAllowingStateLoss();
    }
Fragment页面动画跳转导致的问题

在fragmet页面跳转的时候,例如使用了进场动画,出场动画,在当前fragment在调用popBackStack()出栈,执行出场动画过程中,这时再popBackStack()会出现问题或者执行mActivity.getSupportFragmentManager().getFragments()会发现size的大小没有变动,获取尾角标的fragment会发现还是当前的fragment的实例对象。

解决方案:pop后的操作可以通过handler发送一个动画时长的延时操作。(另:在support-25.4.0版本,google对当前问题进行了修复。)

界面点击事件穿透问题

fragment在没有处理这一问题的时候,假如当前点击的位置并未有响应区,则点击事件会继续向下传递,假如在下面层的界面的当前位置刚好点击的响应区,则会去执行这一非当前界面的响应操作。

解决方案:

//1.  在XML布局的根布局中添加属性:
android:clickable="true"
//2. 在Fragment中获取布局的根视图,对其设定touch
mView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return true;
            }
        });
getActivity空指针问题

在Fragemnt通常的使用中,getActivity空指针碰到的几率还是比较少,而导致这个问题的出现,更多的情况是不规范引起的。

例如网络请求等异步请求操作,当前Fragment执行了popBackStack()方法,并且代码运行已经经过onDetach()生命周期,fragment已经剥离了宿主activity,在这之后又调用到activity,就会导致空指针
解决方案:
1. 尽可能的避免在界面关闭后也中断相关的异步操作。
2. 在fragment的基类里,设定一个全局变量的mActivity,使用全局的mActivity代替getActivity(),即使Fragment在onDetach()后依旧可以获取到mActivity而不会导致空指针。

各个Fragment之间数据传递问题

在fragment中,Fragment之间数据传源生支持setArguments(),getArguments()进行页面之间数据的相互传递,但这样的方式在实际的开发中具有很高的耦合和使用的不方便,在后期的维护中,业务逻辑也展示得非常的不清晰,时间久了可能就并不知道是从哪里来到哪里去;

例如假如有这么一个需求:
有五个Fragment需要连续跳转,Afragment中产生的数据需要在EFragment中使用,那么这部数据从A到E时如下图这样进行传递的:
image

也就是中间的Fragment需要去获取然后重新设定再传递给下一步,非常的不方便。

这里给出的解决方案原理是这样的:

image

创建一个专门存储数据的存储池,在任意Fragemnt中都可以向pool中存放数据,任意页面也可以从池中去获取数据。
这样一来,数据就由原来的逐级传递变为通过pool间接传递,解耦了中间层Fragemnt。

封装的工具类如下:



import android.support.v4.util.ArrayMap;

/**
 * Created by lixiang on 2017/6/7.
 */

/**        Explain : 在Fragment之间,总是会有数据的传递情况,总是会需要在初始化页面的时候获取argment,
 *                   所以导致Fragmnt的耦合度较高。
 *
 *                   通过建造一个中间缓存池,可降低Fragment之间的耦合,由Fragment与Fragment之间的交互转换为
 *                   Fragment与中间缓存池的交互。
 *
 *                   所有的Fragment之间的数据传递统一面向当前中间缓存池。
* @author LiXaing create at 2017/6/7 16:27*/
public class ClassCachePool {


    private static ArrayMap classObject = new ArrayMap<String, Object>();
    private ClassCachePool(){};

    private static ClassCachePool classCachePool = new ClassCachePool();

    public static ClassCachePool getInstance() {
        return classCachePool;
    }



    public void  put(String key , Object object){
        if (classObject.containsKey(key)) {
            classObject.remove(key);
        }
        classObject.put(key,object);
    }


    public Object get(String key){
         return classObject.get(key);
    }

    public synchronized Object remove(String key){
        return classObject.remove(key);
    }
}
Fragment的replace页面导致回退栈出现问题

replace实现的逻辑是:在add当前需要的fragment前,需要将之前的fragment都remove掉。
而这个问题的产生,就是在源码remove部分:

for (int i=0;i<mManager.mAdded.size(); i++) {  
    Fragment old = mManager.mAdded.get(i);  
    ...
    ...
    ...
    mManager.removeFragment(old, mTransition, mTransitionStyle);  
} 

假如当前有ABCDE五个Fragment,当i=0移除的是A,当前队列是BCDE,当i=1移除的是C。

可以发现,移除并不是顺序移除而是有跳跃才导致的这个问题

在support-25.0.0版本下发现remove这部分代码是有修改,对上一问题有修复:

通过倒叙的方式,每次remove掉的都是当前的列表的尾角标,从而避免了上面的问题,但对此没有去做测试,可能还会存在其他问题

                for(; i >= 0; --i) {
                    Fragment old = (Fragment)added.get(i);
                    if(old.mContainerId == containerId) {
                        if(old == f) {
                            alreadyAdded = true;
                        } else {
                        ...
                        ...
                        ....
                            added.remove(old);
                            ++opNum;
                        }
                    }
                }
Fragment的replace页面导致回退栈出现问题

如图:

image

在fragment创建childFragment的时候,需要注意的是:使用 getFragmentManager()会导致内存泄漏,导致泄漏的原因是在,Fragment在销毁时,会检查ChildFragmentManager,而被嵌套的Fragmetn隶属于当前fragment的childFragment,所以,被嵌套的fragment如果使用FragmentManager(),其实例对象是不会被销毁的,而外层的会被销毁;

所以:在嵌套的Fragment中,内部的fragment创建,需要使用getChildFragmentManager()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值