Android开发中,fragment 的replace方法使用问题

什么是Activity??

官方文档解释如下:

/**
 * An activity is a single, focused thing that the user can do.  Almost all
 * activities interact with the user, so the Activity class takes care of
 * creating a window for you in which you can place your UI with
 * {@link #setContentView}.  */

简单解释:
Activity是一个独立的、可聚焦的东东,几乎所有的Activity都与用户进行交互。更简单的说,我们在Android看到的每一个全屏界面,几乎都是一个Activity。它是UI的载体。

什么是Fragment??

官方文档解释如下:

 /**
  * A Fragment is a piece of an application's user interface or behavior
  * that can be placed in an {@link Activity}.  Interaction with fragments
  * is done through {@link FragmentManager}, which can be obtained via
  * {@link Activity#getFragmentManager() Activity.getFragmentManager()} and
  * {@link Fragment#getFragmentManager() Fragment.getFragmentManager()}.*/

简单解释:
用户接口或行为的一个区块,它可以放到Activity中。

Fragment能做什么??

如下图所示:
红色区域Topfragment 、蓝色区域Fragment1、白色区域Fragment2,是三个不同的区域。他们可以分别做不同的事情,比如Topfragment 播放视频、Fragment1 轮播图片、Fragment2 展示列表。


遇到了什么问题??

时间背景:编写向导App的时候。
向导App包括几大部分:
- 蓝牙连接
- 语言设置
- Wifi 连接(包括几个子界面)
- 安装方式介绍(包括几个子界面)

旧的代码解决方案 :


旧方案

如上图所示:一次性把所有Fragment全部添加进来,显示Fragment1的时候,隐藏 Fragment2、Fragment3、Fragment4,显示Fragment2的时候,隐藏Fragment1、Fragment3、Fragment4,等等以此类推。

接口方法: Add()、Hide()、Show()
缺点:

  • 如果有很多很多界面,一次性添加进来,需要很大的资源消耗,就会遇到我们常说的“程序很卡”
  • Show 与 Hide 的逻辑复杂,添加新界面容易出错!!

新的代码解决方案 :
接口方法:Replace()
每次只是初始化一个Fragment,不需要考虑跟别的Fragment的关系。

replace( ) 的接口说明

 /**
 * Replace an existing fragment that was added to a container.  This is
 * essentially the same as calling {@link #remove(Fragment)} for all
 * currently added fragments that were added with the same containerViewId
 * and then {@link #add(int, Fragment, String)} with the same arguments
 * given here. */
public FragmentTransaction replace(@IdRes int containerViewId,Fragment fragment,  String tag);

说明:先移除所有存在的Fragment, 然后把新的Fragment 添加进来。

问题:
当前容器里有4个Fragment,但是当调用replace接口之后,只有其中的2个Fragment被释放掉了,其余的两个还是继续存在,why??这已经和文档的说明相矛盾了!!

先休息一下眼睛


Framework 代码追查

重新创建App,专门来研究这个问题(replace不能删除之前所有)
Activity中主要测试代码如下:

    @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mFragmentA = new FragmentA();
    mFragmentB = new FragmentB();
    mFragmentC = new FragmentC();
    mFragmentD = new FragmentD();
    mFragmentE = new FragmentE();
    
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    
    
    ft.add(R.id.container, mFragmentA);
    ft.add(R.id.container, mFragmentB);
    ft.add(R.id.container, mFragmentC);
    ft.add(R.id.container, mFragmentD);
    
    ft.commit();
    CustomLog.d(TAG , "getFragmentManager() name =" + getFragmentManager().getClass().getName());
    CustomLog.d(TAG , "FragmentTransaction name =" + ft.getClass().getName());
    
}
  public void onBtnClick(View v){
    Toast.makeText(this, "on click~~~", Toast.LENGTH_LONG).show();
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    ft.replace(R.id.container, mFragmentE);
    ft.commit();
}

App运行,log如下:



说明:四个Fragment都完成了创建
然后点击替换按钮;



Log结果看来,E成功添加进来,但是只有A C 被终止掉,BD 还在。

探究transaction.replace到底做了什么

repalce()方法来自 FragmentTransaction抽象类,小伙伴们都知道抽象类是不能直接使用的,他的实现类是 BackStackRecord.java。

对过框架层代码进行了对比,发现 Android 4.0 Android 5.0 Android 6.0 ,关于这一块的代码,都是一样的。

一路追查代码,最终实现的地方在BackStackRecord 的run()方法里:
下面筛选了最核心的代码,

public void run() {
...
...
  switch (op.cmd) {

            caseOP_REPLACE: {

                Fragment f = op.fragment;

                if (mManager.mAdded !=null) {

                    for (int i=0;i<mManager.mAdded.size(); i++) {

                        Fragment old =mManager.mAdded.get(i);

                        if (f == null ||old.mContainerId == f.mContainerId) {

                            if (old== f) {

                                op.fragment = f =null;

                            } else {                  

                                mManager.removeFragment(old,mTransition,mTransitionStyle); //delete  all using for

                            }

                        }

                    }

                }

                if (f !=null) {

                         mManager.addFragment(f,false); //Add the new one

                }

            } break;
   ...
   ...
}

可以看到
replace 则是先删除fragmentmanager中所有已添加的fragment,然后再添加当前fragment;

得出结论:
replace 会先删除所有fragment ,然后再添加传入的fragment对象;

好,问题来了:
点击replace按钮只有A C 被终止掉,BD 还在,并没有删除全部,这又是为什么?

带着疑问的态度进行了一次调试,在调试中终于找到了原因,问题就在这段代码:

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

mManager.mAdded 是一个ArrayList<Fragment> 列表,在遍历的时候调用了mManager.removeFragment方法,而该方法调用了ArrayList的remove方法;

public void removeFragment(Fragmentfragment, int transition, inttransitionStyle) {
            mAdded.remove(fragment);
}  

也就是说在用for循环遍历ArrayList列表的时候使用了remove;
For循环遍历过程删除会造成ArrayList.size()不断变小,所以造成删除不完全的问题;你是否也被坑过。。。


Android 7.0核心代码如下:

public void run() {
...
...
case OP_REPLACE: {
                Fragment f = op.fragment;
                int containerId = f.mContainerId;
                if (mManager.mAdded != null) {
                    for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
                        Fragment old = mManager.mAdded.get(i);
                        if (old.mContainerId == containerId) {
                            if (old == f) {
                                op.fragment = f = null;
                            } else {
                                if (op.removed == null) {
                                    op.removed = new ArrayList<Fragment>();
                                }
                                op.removed.add(old);
                                old.mNextAnim = op.exitAnim;
                                if (mAddToBackStack) {
                                    old.mBackStackNesting += 1;
                                }
                                mManager.removeFragment(old, mTransition, mTransitionStyle);  //delete  all using for
                            }
                        }
                    }
                }
                if (f != null) {
                    f.mNextAnim = op.enterAnim;
                    mManager.addFragment(f, false);  //Add the new one 
                }
            }
            break;
      ...
      ...
}

聪明的你,应该能看出,在Android7.0上,问题已经修复。

问题总结:

虽然在7.0已经修复这个framework bug,但是为了向下兼容,我们在使用replace() 方法之前,还是要用remove 方法移除掉所有。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容