1. 程式人生 > >防止fragment反覆例項化

防止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;
        }
}

防止fragment重複例項化.png

首先是選擇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開發一年多,經驗不足,這文章中如有謬誤,多多指教哈~