防止fragment反覆例項化
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;
}
}
首先是選擇replace()還是add()/show()。
這是個基礎知識,我們應優先使用add()/show()。因為使用replace(),被隱藏的fragment就會被銷燬。 經過本人測試,如果fragment A被fragment B replace掉,那麼A是會被銷燬的,會走onDestroy() 。再切換回A(即使還是同一個物件),A會被重新建立並且觸發onCreate()。所以replace是不行的。而add()/show()則不會被銷燬和重新例項化,所以應該用add()/show()。
用add()/show()會遇上什麼問題?
如果add()/show()正常工作,我們就省心多了。公司的APP的註冊流程一共有三個步驟,我用了三個fragment介面,並用上add()/show()的同時使用了addtobackstack()方法。這時候就出現了詭異的現象,介面重疊、點返回沒反應、show()下一介面沒效果等等。本人一時搞不懂,也要趕專案沒有深入探索,留坑待填,也希望有前輩能指點。
考慮到一般使用者只會註冊一次,頻率很小,註冊流程我就直接用replace()了。其他業務介面還是可以用add()/show()的。
如何避免不斷地new fragment()
在不需要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());
......
}
}
用add()/show()的注意點
另外,網上也有朋友指出另一個坑: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開發一年多,經驗不足,這文章中如有謬誤,多多指教哈~