《一个Android工程的从零开始》阶段总结与修改7-BaseFragmentActivity

先扯两句

之前那么些天没有写博客,这总算赶上一个假期,打算把阶段性修改的部分都总结一下发出来,这应该是最后一篇了,其中当然也有不完整的地方,那就是之前写的博客会再写一下BaseFragment封装的部分,BaseFragment系列与BaseActivity系列基本功能都是一致的,整体差别不大,所以这里也就不多做说明了,大家可以直接取我的github查源码,有什么需要也可以在博客中留言沟通,这里就不多加赘述了。
好了,闲言少叙,老规矩还是先上我的Git,然后开始正文吧。 MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)

正文

首先,关于BaseFragmentActivity从BaseActivity中拆解出来是早就已经定好的部分,这部分主要封装的都是一些Activity调用Fragment的方法,当然,这部分没有包含ViewPager的使用,只是单纯的添加Fragment而已,关于ViewPager的部分后续会做考虑,有需要的大家只能先自行封装一下了,比较浅显的运用,我之前编写的一个小demo中有所涉猎,大家也可以去看一下,或许能有所帮助。
下面真的开始进入正文了。

添加

private FragmentTransaction transaction;

/**
 * 添加Fragment
 *
 * @param containerViewId 对应布局的id
 * @param fragments       所要添加的Fragment,可以添加多个
 */
public void addFragment(int containerViewId, Fragment... fragments) {
    if (null == transaction) {
        transaction = getSupportFragmentManager().beginTransaction();
    }
    if (fragments != null) {
        for (int i = 0; i < fragments.length; i++) {
            transaction.add(containerViewId, fragments[i]);
            if (i != fragments.length - 1) {
                transaction.hide(fragments[i]);
            }
        }
        transaction.commit();
    } else {
        Logger.e(getName(), "没有Fragment");
    }
}

这个部分主要针对的是APP主页的添加,一次批量添加所有的Fragment,参数比较简单,其一是Fragment所要替换的占位布局的id,其二是所要加载的Fragment,其余的就是判空之类的处理,并不复杂。

展示与隐藏

/**
 * 显示Fragment
 *
 * @param fragment 所要显示的Fragment
 */
public void showFragment(Fragment fragment) {
    if (null == transaction) {
        transaction = getSupportFragmentManager().beginTransaction();
    }
    if (fragment != null) {
        transaction.show(fragment).commit();
    } else {
        Logger.e(getName(), "ragment不存在");
    }
}

/**
 * 隐藏Fragment
 *
 * @param fragments 所要隐藏的Fragment,可以添加多个
 */
public void hideFragment(Fragment... fragments) {
    if (null == transaction) {
        transaction = getSupportFragmentManager().beginTransaction();
    }
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            transaction.hide(fragment);
        }
        transaction.commit();
    } else {
        Logger.e(getName(), "没有Fragment");
    }
}

添加后,我们需要将展示其中的某一个Fragment,而其他的则需要隐藏起来,其实当我们调用了某一个的显示后,对应Fragment自定会被置于组上层,呈现在用户眼前。不过开发中,我们或许会需要判断当前Fragment是否处于显示状态,或者隐藏状态,所以最好还是调用一下这两个方法,方便操作以及手机适配。
显隐状态监听方法:

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if(hidden){
        //TODO now visible to user
    } else {
        //TODO now invisible to user
    }
}

移除

/**
 * 移除Fragment
 *
 * @param fragments 所要移除的Fragment,可以添加多个
 */
public void removeFragment(Fragment... fragments) {
    if (null == transaction) {
        transaction = getSupportFragmentManager().beginTransaction();
    }
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            transaction.hide(fragment);
        }
        transaction.commit();
    } else {
        Logger.e(getName(), "没有Fragment");
    }
}

有添加自然会有删除,不过这个方法从开发到现在,记忆中是没有使用过,不过存在即合理,还是一并封装了,总比需要用的时候没有要强很多啊。

替换

/**
 * 替换Fragment
 *
 * @param containerViewId 对应布局的id
 * @param fragment        用于替代原有Fragment的心Fragment
 */
public void replaceFragment(int containerViewId, Fragment fragment) {
    if (null == transaction) {
        transaction = getSupportFragmentManager().beginTransaction();
    }
    if (fragment != null) {
        transaction.replace(containerViewId, fragment).commit();
    } else {
        Logger.e(getName(), "ragment不存在");
    }
}

上面说的方法主要都是批量处理的,也就是在同一个占位控件处,添加了多层Fragment,用代码控制其隐藏或者显示的状态来操作,好处是可以更流畅的切换Fragment而不需要每次切换都重新加载,以及数据获取。但是得到便利的同时,付出的代价就是资源的耗费。浅显点理解就是同一时间只显示一个Fragment,其余Fragment的持有都是资源的浪费。同样,还有一个问题就是当APP长时间后台运行时,较多的Fragment很容易被GC回收,再次打开APP时导致APP的崩溃。

而使用replace则不会有上述被回收的问题,以及资源的浪费。可同样,它付出的代价就是APP Fragment切换时的流畅体验,之前看过一篇APP测试报告,要求首页不同页面间随机快速切换20次、无卡顿、无崩溃。这个时候如果使用replace,那绝对会是一个悲剧。

至于具体如何选择,还是那句话,看业务需求吧,这已经不是能随便看心情的事了。

划重点

不要吐槽这个标题,我也不想啊,实在是池子的脱口秀看多了。
其实上述这些真的没有什么可写的,只是代码的堆积,最多就是根据我浅显的工作经验区分一下应用场景以及差异,所以险些一激动直接不写这篇博客了,毕竟有源码大家也都能看出个所以然来。
不过懒汉最该做的事就是老老实实的闲着,哪怕发呆也好啊。我这种闲不住的懒汉就悲催了,非要去看什么《阿里巴巴Android开发手册》,结果就发现了之前封装中的问题(前面发的是已经修改过的),这下好了,还得写一篇博客,没办法好好偷懒了!先看看阿里巴巴的大牛是怎么说的。

Activity 可 能 因 为 各 种 原 因 被 销 毁 , Android 支 持 页 面 被 销 毁 前 通 过
Activity#onSaveInstanceState() 保 存 自 己 的 状 态 。 但 如 果
FragmentTransaction.commit()发生在 Activity 状态保存之后,就会导致 Activity 重
建、恢复状态时无法还原页面状态,从而可能出错。为了避免给用户造成不好的体
验,系统会抛出 IllegalStateExceptionStateLoss 异常。推荐的做法是在 Activity 的
onPostResume() 或 onResumeFragments() ( 对 FragmentActivity ) 里 执 行
FragmentTransaction.commit(),如有必要也可在 onCreate()里执行。不要随意改用
FragmentTransaction.commitAllowingStateLoss()或者直接使用 try-catch 避免
crash,这不是问题的根本解决之道,当且仅当你确认 Activity 重建、恢复状态时,
本次 commit 丢失不会造成影响时才可这么做。

翻译一下,就是当使用commit的时候,可能会因为Fragment被回收,而导致“Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
”异常,而网上查到的大多数讲解都是将commit替换成commitAllowingStateLoss方法,可实际上这是治标不治本的玩法,并没有从根本上解决问题,所以还不如让APP崩溃,重新运行的好。

当发现以我现在的实力实在说不明白这两者之间的区别怎么办呢?当然是求助大神了Android commit和commitAllowingStateLoss区别及应用场景(别问我为什么这次没有给作者链接,这就是人间的gitPage的链接,如果不是不会玩,我都想搭一个了),这篇博客中有比较详细的说明,所谓公说公有理婆说婆有理,而这篇博客中的说法还算是比较中肯的了,具体使用哪种方式,各位自行决断吧。

附录

《一个Android工程的从零开始》- 目录

    原文作者:半寿翁
    原文地址: https://www.jianshu.com/p/8be9efb1a709
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞