1.有问题的代码:

MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private HomeFragment mHomeFragment;
private FindFragment mFindFragment;
private NewFragment mNewFragment;
private MessageFragment mMessageFragment; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); findViewById(R.id.home_rb).setOnClickListener(this);
findViewById(R.id.find_rb).setOnClickListener(this);
findViewById(R.id.new_rb).setOnClickListener(this);
findViewById(R.id.message_rb).setOnClickListener(this); // 默认一进入页面就添加主Fragment
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
mHomeFragment = new HomeFragment();
fragmentTransaction.add(R.id.main_tab_fl, mHomeFragment);
// 最后记得提交
fragmentTransaction.commit();
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.home_rb:
homeRbClick();
break;
case R.id.find_rb:
findRbClick();
break;
case R.id.new_rb:
newRbClick();
break;
case R.id.message_rb:
messageRbClick();
break;
}
} private void homeRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
// 替换成当前页面
fragmentTransaction.replace(R.id.main_tab_fl, mHomeFragment);
fragmentTransaction.commit();
} private void findRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (mFindFragment == null) {
mFindFragment = new FindFragment();
}
fragmentTransaction.replace(R.id.main_tab_fl, mFindFragment);
fragmentTransaction.commit();
} private void newRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (mNewFragment == null) {
mNewFragment = new NewFragment();
}
fragmentTransaction.replace(R.id.main_tab_fl, mNewFragment);
fragmentTransaction.commit();
} private void messageRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (mMessageFragment == null) {
mMessageFragment = new MessageFragment();
}
fragmentTransaction.replace(R.id.main_tab_fl, mMessageFragment);
fragmentTransaction.commit();
}
}

目前的效果是这个样子的,看似没有任何的问题,这个也是最简单的方式

别的思路问题:一般的思路我们会换实现方法,当然其他方式肯定也可以实现如ViewPager+Fragment但是我们需要预加载要不然也会出问题,一旦预加载就需要去访问网络,即使用户可能不切换Fragment就退出App了这个时候其实加载了所有Fragment的数据,而且主页一旦复杂有可能会崩溃或造成内存溢出的问题。

2.源码分析:

add方法其实只是设置了一些必要参数,并没有做任何的处理,这也是说google为什么一定要我们不要忘记commit()的原因:

final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
public FragmentTransaction add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
} private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
fragment.mFragmentManager = mManager; // tag可以说是唯一标识我们可以通过它从FragmentManager中找到对应的Fragment
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 != ) {
if (fragment.mFragmentId != && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
// 把Fragment的ContainerId和FragmentId指定为我们传递过来的布局中的ViewGroup的id。
fragment.mContainerId = fragment.mFragmentId = containerViewId;
} // 见名思意 Op是什么?就当是一些基本参数吧
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
} void addOp(Op op) {
if (mHead == null) {
mHead = mTail = op;
} else {
op.prev = mTail;
mTail.next = op;
mTail = op;
}
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
mNumOp++;
}

既然add方法只是设置了一些参数而已,那么肯定就在commit()中做了些什么,找啊找啊找啊找,找到这么个方法(有些代码我就省略):

void moveToState(Fragment f, int newState, int transit,
int transitionStyle, boolean keepActive){
// ... 省略部分代码
f.onAttach(mHost.getContext());
// 这个方法一调用就会执行Fragment的onAttach(Activity activity)这个生命周期方法
if (f.mParentFragment == null) {
mHost.onAttachFragment(f);
} if (!f.mRetaining) {
f.performCreate(f.mSavedFragmentState);
// 执行生命周期onCreate(savedInstanceState);
}
f.mRetaining = false;
if (f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != ) {
//从activity中找到我们需要存放Fragment的ViewGroup布局
container = (ViewGroup)mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
throwException(new IllegalArgumentException(
"No view found for id 0x"
+ Integer.toHexString(f.mContainerId) + " ("
+ f.getResources().getResourceName(f.mContainerId)
+ ") for fragment " + f));
}
}
// For fragments that are part of the content view
// layout, we need to instantiate the view immediately
// and the inflater will take care of adding it.
f.mView = f.performCreateView(f.getLayoutInflater(
f.mSavedFragmentState), null, f.mSavedFragmentState);
// 这个方法过后会执行onCreateView()生命周期且f.mView就是我们自己覆盖Fragment返回的View
if (f.mView != null) {
f.mInnerView = f.mView;
// v4包兼容11以下的版本我还是没说错啊
if (Build.VERSION.SDK_INT >= ) {
ViewCompat.setSaveFromParentEnabled(f.mView, false);
} else {
f.mView = NoSaveStateFrameLayout.wrap(f.mView);
} if (container != null) {
Animation anim = loadAnimation(f, transit, true,
transitionStyle);
if (anim != null) {
setHWLayerAnimListenerIfAlpha(f.mView, anim);
f.mView.startAnimation(anim);
}
// 如果ViewGroup不等于null就把从onCreateView()生命周期中获得的View添加到该布局中
// 最主要的就是这个方法,其实我们可以把Fragment理解成一个自定义的类
// 通过onCreateView()获取的到View添加到一个FragmentActivity的一个ViewGroup中
// 只不过它有自己的生命周期而已......
container.addView(f.mView);
}
// 如果是隐藏那就设置为不可见
if (f.mHidden) f.mView.setVisibility(View.GONE);
// 执行onViewCreated()生命周期方法
f.onViewCreated(f.mView, f.mSavedFragmentState);
} else {
f.mInnerView = null;
} f.performActivityCreated(f.mSavedFragmentState);
if (f.mView != null) {
f.restoreViewState(f.mSavedFragmentState);
}
f.mSavedFragmentState = null;
}
// 代码省略......
}
}
// 后面的我们就不看了,这上面的代码我自己做了一些整合,把它连贯起来了
// 因为我们把add方法写在了Activity中的onCreate()方法中所以做了一些处理......

到这里应该能够了解Fragment的工作流程了吧,接下来我们看replace方法中究竟做了?其实和add差不多只是把int opcmd变成了OP_REPLACE替换操作:

public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
if (containerViewId == ) {
throw new IllegalArgumentException("Must use non-zero containerViewId");
} doAddOp(containerViewId, fragment, tag, OP_REPLACE);
return this;
}
这个时候去commit会调用mManager.removeFragment(old, transition, transitionStyle)方法把原来的移除,然后把当前的Fragment添加进去,那岂不是每点击一个上一就被销毁了,那之前动画到哪里来了做了写什么事都被干掉重新创建了

if (mManager.mAdded != null) {
for (int i = mManager.mAdded.size() - ; i >= ; 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 = exitAnim;
if (mAddToBackStack) {
old.mBackStackNesting += ;
}
mManager.removeFragment(old, transition, transitionStyle);
}
}
}
}
if (f != null) {
f.mNextAnim = enterAnim;
mManager.addFragment(f, false);
}
 

3.优化代码:

  到这里源码就以完毕有兴趣的小伙伴可以自己仔细去看看源码,接下来我们就来解决问题,我们肯定在调用replace方法的时候希望它不要移除原来的,那怎么办改Android的底层源码吗?那就只能换方法了,思路就是如果该Fragment不存在FragmentManager中我们就去添加,否则我们把之前的隐藏而不是替换移除,把当前的显示即可,最后代码就是:

private void homeRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment fragment : fragments) {
fragmentTransaction.hide(fragment);
} fragmentTransaction.show(mHomeFragment);
fragmentTransaction.commit();
} @OnClick(R.id.find_rb)
private void findRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment fragment : fragments) {
fragmentTransaction.hide(fragment);
} if(mFindFragment == null){
mFindFragment = new FindFragment();
fragmentTransaction.add(R.id.main_tab_fl,mFindFragment);
}else {
fragmentTransaction.show(mFindFragment);
} fragmentTransaction.commit();
} @OnClick(R.id.new_rb)
private void newRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment fragment : fragments) {
fragmentTransaction.hide(fragment);
} if(mNewFragment == null){
mNewFragment = new NewFragment();
fragmentTransaction.add(R.id.main_tab_fl,mNewFragment);
}else {
fragmentTransaction.show(mNewFragment);
} fragmentTransaction.commit();
} @OnClick(R.id.message_rb)
private void messageRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment fragment : fragments) {
fragmentTransaction.hide(fragment);
} if(mMessageFragment == null){
mMessageFragment = new MessageFragment();
fragmentTransaction.add(R.id.main_tab_fl,mMessageFragment);
}else {
fragmentTransaction.show(mMessageFragment);
} fragmentTransaction.commit();
}
封装
 public class FragmentManagerHelper {
// 管理类FragmentManager
private FragmentManager mFragmentManager;
// 容器布局id containerViewId
private int mContainerViewId; /**
* 构造函数
* @param fragmentManager 管理类FragmentManager
* @param containerViewId 容器布局id containerViewId
*/
public FragmentManagerHelper(@Nullable FragmentManager fragmentManager, @IdRes int containerViewId) {
this.mFragmentManager = fragmentManager;
this.mContainerViewId = containerViewId;
} /**
* 添加Fragment
*/
public void add(Fragment fragment){
// 开启事物
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
// 第一个参数是Fragment的容器id,需要添加的Fragment
fragmentTransaction.add(mContainerViewId, fragment);
// 一定要commit
fragmentTransaction.commit();
} /**
* 切换显示Fragment
*/
public void switchFragment(Fragment fragment){
// 开启事物
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction(); // 1.先隐藏当前所有的Fragment
List<Fragment> childFragments = mFragmentManager.getFragments();
for (Fragment childFragment : childFragments) {
fragmentTransaction.hide(childFragment);
} // 2.如果容器里面没有我们就添加,否则显示
if(!childFragments.contains(fragment)){
fragmentTransaction.add(mContainerViewId,fragment);
}else{
fragmentTransaction.show(fragment);
} // 替换Fragment
// fragmentTransaction.replace(R.id.main_tab_fl,mHomeFragment);
// 一定要commit
fragmentTransaction.commit();
}
}

Fragment 源码解析add()和replace()方法的更多相关文章

  1. jquery源码解析:jQuery工具方法Callbacks详解

    我们首先来讲下Callbacks是如何使用的:第一个例子 function a(){} function b(){} var cb = $.Callbacks(); cb.add(a); cb.add ...

  2. jquery源码解析:jQuery工具方法when详解

    我们先来看when方法是如何使用的: var cb = $.when();   //when方法也是返回一个延迟对象,源码是return deferred.promise();返回的延迟对象不能修改状 ...

  3. 【Java实战】源码解析为什么覆盖equals方法时总要覆盖hashCode方法

    1.背景知识 本文代码基于jdk1.8分析,<Java编程思想>中有如下描述: 另外再看下Object.java对hashCode()方法的说明: /** * Returns a hash ...

  4. jquery源码解析:jQuery扩展方法extend的详解

    jQuery中要扩展方法或者属性都是通过extend方法实现的.所谓的jQuery插件也是通过extend方法实现的. jQuery.extend扩展的是工具方法,也就是静态方法.jQuery.fn. ...

  5. jquery源码解析:jQuery原型方法init的详解

    先来了解几个jQuery方法: <li></li> <li></li> <li></li> $("li") ...

  6. 从源码解析LinkedList集合

         上篇文章我们介绍了ArrayList类的基本的使用及其内部的一些方法的实现原理,但是这种集合类型虽然可以随机访问数据,但是如果需要删除中间的元素就需要移动一半的元素的位置,效率低下.并且它内 ...

  7. CopyOnWriteArrayList源码解析

    Java并发包提供了很多线程安全的集合,有了他们的存在,使得我们在多线程开发下,可以和单线程一样去编写代码,大大简化了多线程开发的难度,但是如果不知道其中的原理,可能会引发意想不到的问题,所以知道其中 ...

  8. Scala 深入浅出实战经典 第65讲:Scala中隐式转换内幕揭秘、最佳实践及其在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  9. laravel源码解析

    本专栏系列文章已经收录到 GitBooklaravel源码解析 Laravel Passport——OAuth2 API 认证系统源码解析(下)laravel源码解析 Laravel Passport ...

随机推荐

  1. POJ 2379

    #include <iostream> #include <algorithm> #define MAXN 1005 using namespace std; struct n ...

  2. WPF快速实现XML可视化编辑工具

    虽然最近业余时间主要都放在研究AngularJS上了,不过由于正好要帮朋友做一个生成XML的小工具,顺便又温顾了一下WPF.虽然这个时代相对于Web应用和移动App,Windows应用程序是越来越少了 ...

  3. Storm原理及安装

    http://my.oschina.net/leejun2005/blog/147607 http://www.storm-geek.com/forum.php http://www.zhangjih ...

  4. 渐进增强与优雅降级 && css3中普通属性和前缀属性的书写顺序

     什么是渐进增强与优雅降级? 服务器和浏览器是不同的.当服务器有新版本时,开发人员直接使用新版本的服务器提供服务即可:但是浏览器端,不同的用户使用的浏览器版本不同,型号差异大,我们不可能让用户强制更新 ...

  5. Spring Security构建Rest服务-1100-单机Session管理

    Session失效时间: springboot配置session失效时间,只需要在application.properties里配置 #session超时时间,低于60秒按60秒server.sess ...

  6. 【Java并发编程】:多线程环境中安全使用集合API

    在集合API中,最初设计的Vector和Hashtable是多线程安全的.例如:对于Vector来说,用来添加和删除元素的方法是同步的.如果只有一个线程与Vector的实例交互,那么,要求获取和释放对 ...

  7. 极高效内存池实现 (cpu-cache)

    视频请看 : http://edu.csdn.net/course/detail/627 1.内存池的目的 提高程序的效率 减少运行时间 避免内存碎片 2.原理   要解决上述两个问题,最好的方法就是 ...

  8. 代码阅读——十个C开源项目

    1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连 ...

  9. Linux多线程 - 基本操作

    0. 线程 vs 进程 何为线程?线程即轻量级进程,如何理解轻量级这个概念? 我们知道,Linux的资源分为用户空间资源和内核空间资源: 用户空间资源:用来存放用户自定义的一些数据,用户可直接控制: ...

  10. [转]一步步学习EF Core(2.事务与日志)

    本文转自:http://www.cnblogs.com/GuZhenYin/p/6862505.html 上节我们留了一个问题,为什么EF Core中,我们加载班级,数据并不会出来 其实答案很简单,~ ...