简书同步发布:http://www.jianshu.com/p/13fd3648cf9b
fragment很多优势,但也很多坑。
公司的项目切换fragment时都使用了replace(),我想这样会不会让fragment反复地实例化呢?能不能优化呢?于是开始了探索。
分析可能有点乱,先直接上结论:使用add()/show()可避免反复实例化。但是条件是不和addtobackstack()同时使用,并且重写onSaveInstanceState(),当然也不能反复new fragment()。
public class MainActivity extends BaseActivity{
private HomeFragment homeFragment;
private ChatFragment chatFragment;
private HomeFragment getHomeFragment(){
if(homeFragment == null){
homeFragment = new HomeFragment();
}
return homeFragment;
}
private ChatFragment chatFragment(){
if(chatFragment == null){
chatFragment = new ChatFragment();
}
return chatFragment;
}
protected void onCreate(Bundle savedInstanceState) {
......
//switchFragment()是自己写的方法,下文会有补充
switchFragment(getHomeFragment());
......
}
@Override
protected void onSaveInstanceState(Bundle outState) {
//这里不用调用super.onSaveInstanceState(outState);
}
}
public class BaseActivity extends Activity{
private Fragment currentFragment;
protected void switchFragment(Fragment targetFragment){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
//第一次使用switchFragment()时currentFragment为null,所以要判断一下
if (currentFragment != null) {
transaction.hide(currentFragment);
}
//如已添加过,则show就行了
if (targetFragment.isAdded()) {
transaction.show(targetFragment);
} else {
transaction.add(R.id.container, targetFragment);
}
transaction.commit();
currentFragment = targetFragment;
}
}
这是个基础知识,我们应优先使用add()/show()。因为使用replace(),被隐藏的fragment就会被销毁。 经过本人测试,如果fragment A被fragment B replace掉,那么A是会被销毁的,会走onDestroy() 。再切换回A(即使还是同一个对象),A会被重新创建并且触发onCreate()。所以replace是不行的。而add()/show()则不会被销毁和重新实例化,所以应该用add()/show()。
如果add()/show()正常工作,我们就省心多了。公司的APP的注册流程一共有三个步骤,我用了三个fragment界面,并用上add()/show()的同时使用了addtobackstack()方法。这时候就出现了诡异的现象,界面重叠、点返回没反应、show()下一界面没效果等等。本人一时搞不懂,也要赶项目没有深入探索,留坑待填,也希望有前辈能指点。
考虑到一般用户只会注册一次,频率很小,注册流程我就直接用replace()了。其他业务界面还是可以用add()/show()的。
在不需要addtobackstack()的地方我还是可以用add()/show()替换replace()的。公司项目里不仅全用了replace(),还不断的通过new来创建fragment。
ft.replace(R.id.container, new HomeFragment());
显然这里也有优化空间。
一开始我是用懒汉模式保证fragment是单例,但是这有一些弊端。
public class ExampleFragment extends Fragment{
private static ExampleFragment exampleFragment;
private ExampleFragment(){}
public static ExampleFragment getExampleFragment(){
if(exampleFragment == null){
exampleFragment = new ExampleFragment();
}
return exampleFragment;
}
}
首先系统不能回收这个静态的引用,如果所有fragment类都使用了这种懒汉模式,那么可能占了很多内存不能被回收。另外还有一个更大的问题,就是有时这个fragment引用还在,但是它指向堆内存的实例对象被回收了,我们却不知情。这时候就会出现界面空白,因为这个fragment实例实际上被销毁了,但是却不会奔溃报NullPointer。网友指出解决办法可以是在onDetach()把静态引用设为null。这的确不会出现空白情况了,但是却违背了我们的初衷:避免不断地重新实例化fragment,因为我在实际使用中经常会出现空白的情况,这时把静态引用设为null,下次用到这个fragment时又会重新实例化。。。实在是哭笑不得。。。
于是我用另外一种方法,在activity中持有fragment 的实例。
public class MainActivity extends BaseActivity{
private HomeFragment homeFragment;
private HomeFragment getHomeFragment(){
if(homeFragment == null){
homeFragment = new HomeFragment();
}
return homeFragment;
}
protected void onCreate(Bundle savedInstanceState) {
......
//switchFragment()是自己写的方法,下文会有补充
switchFragment(getHomeFragment());
......
}
}
另外,网上也有朋友指出另一个坑:activity被意外销毁然后恢复时,会出现重叠问题。最简单的解决方法是重写onSaveInstanceState(),但是方法里面什么也不做。
public class MainActivity extends BaseActivity{
@Override
protected void onSaveInstanceState(Bundle outState) {
//这里不用调用super.onSaveInstanceState(outState);
}
}
当我们重复打开某一个Activity时,fragment还是会重复实例化的。因为fragment实例是被activity持有的,如果那个activity被销毁或重新创建,那么fragment也会随之被销毁或重新创建。希望有朋友能提出更好的解决方法。
不过这种方案也有用武之地,如果贵公司的APP也是采用类似微信底部导航栏的界面,并且不能左滑/右滑到相邻的页面,那么用这种方案就很适合了。你想想,主界面使用频率这么高,每次按到一个分页都要重新实例化,重新加载数据,重新显示界面,那用户体验也挺感人的,对吧?
要避免反复实例化fragment,就用add()/show()吧。在activity中持有fragment 的实例并避免反复new fragment,fragment一些天然的坑我们也要避开。
fragment还是有很多优势的。它启动速度比activity快多了,能提高用户体验。如果我们的APP要改需求,使用fragment改起来也是很轻松的。我以前的公司的项目是直接用activity显示内容的,主页是带侧滑页slidemenu的,然后要改成微信底部导航栏那种界面,改起来那酸爽。。。同样的改动在现在的公司又发生了,可是我们都是用fragment显示内容,改起来轻松很多。
add()/show()和addtobackstack()同时使用是不是一定有坑我也不确定,原因就更不清楚了,不过我的确是碰上坑了,而换成replace和addtobackstack搭配就没问题,在此抛砖引玉哈。最后,本人从事Android开发一年多,经验不足,这文章中如有谬误,多多指教哈~