3

我的應用程序由幾個片段組成。到目前爲止,我已經將它們存儲在自定義應用程序對象中,但我開始認爲我做錯了什麼。當我意識到取向改變後,我的所有片段對mActivity引用變爲空在定向更改後,片段對mA的參考變爲空。無效片段狀態維護

我的問題開始。因此,當我在方向更改後調用getActivity()時,會引發NullPointerException。 我已經在調用getActivity()之前檢查過片段的onAttach(),但它仍然返回null。

以下是我的MainActivity的剝離版本,這是我的應用程序中唯一的活動。

public class MainActivity extends BaseActivity implements OnItemClickListener, 
     OnBackStackChangedListener, OnSlidingMenuActionListener { 

    private ListView mSlidingMenuListView; 
    private SlidingMenu mSlidingMenu; 

    private boolean mMenuFragmentVisible; 
    private boolean mContentFragmentVisible; 
    private boolean mQuickAccessFragmentVisible; 

    private FragmentManager mManager; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 

     /* 
     * Boolean variables indicating which of the 3 fragment slots are visible at a given time 
     */ 
     mMenuFragmentVisible = findViewById(R.id.menuFragment) != null; 
     mContentFragmentVisible = findViewById(R.id.contentFragment) != null; 
     mQuickAccessFragmentVisible = findViewById(R.id.quickAccessFragment) != null; 

     if(!savedInstanceState != null) { 
      if(!mMenuFragmentVisible && mContentFragmentVisible) { 
       setupSlidingMenu(true); 
      } else if(mMenuFragmentVisible && mContentFragmentVisible) { 
       setupSlidingMenu(false); 
      } 

      return; 
     } 

     mManager = getSupportFragmentManager(); 
     mManager.addOnBackStackChangedListener(this); 

     final FragmentTransaction ft = mManager.beginTransaction(); 
     ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 

     if (!mMenuFragmentVisible && mContentFragmentVisible) { 
      /* 
      * Only the content fragment is visible, will enable sliding menu 
      */ 
      setupSlidingMenu(true); 
      onToggle(); 

      ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG); 

     } else if (mMenuFragmentVisible && mContentFragmentVisible) { 
      setupSlidingMenu(false); 
      /* 
      * Both menu and content fragments are visible 
      */ 
      ft.replace(R.id.menuFragment, getCustomApplication().getMenuFragment(), MenuFragment.TAG); 
      ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG); 
     } 

     if (mQuickAccessFragmentVisible) { 
      /* 
      * The quick access fragment is visible 
      */ 
      ft.replace(R.id.quickAccessFragment, getCustomApplication().getQuickAccessFragment()); 
     } 

     ft.commit(); 
    } 

    private void setupSlidingMenu(boolean enable) { 
     /* 
     * if enable is true, enable sliding menu, if false 
     * disable it 
     */ 
    } 

    @Override 
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 

     // launch the fragment that was clicked from the menu 
    } 

    @Override 
    public void onBackPressed() { 
     // Will let the user press the back button when 
     // the sliding menu is open to display the content. 
     if (mSlidingMenu != null && mSlidingMenu.isMenuShowing()) { 
      onShowContent(); 
     } else { 
      super.onBackPressed(); 
     } 
    } 

    @Override 
    public void onBackStackChanged() { 
     /* 
     * Change selected position when the back stack changes 
     */ 
     if(mSlidingMenuListView != null) { 
      mSlidingMenuListView.setItemChecked(getCustomApplication().getSelectedPosition(), true);  
     } 
    } 

    @Override 
    public void onToggle() { 
     if (mSlidingMenu != null) { 
      mSlidingMenu.toggle(); 
     } 
    } 

    @Override 
    public void onShowContent() { 
     if (mSlidingMenu != null) { 
      mSlidingMenu.showContent(); 
     } 
    } 
} 

以下是CustomApplication的剝離版本。我在這個實現背後的想法是在整個應用程序的生命週期中只保證每個片段的一個實例。

public class CustomApplication extends Application { 

    private Fragment mSsportsFragment; 
    private Fragment mCarsFragment; 
    private Fragment mMusicFragment; 
    private Fragment mMoviesFragment; 

    public Fragment getSportsFragment() { 
     if(mSsportsFragment == null) { 
      mSsportsFragment = new SportsFragment(); 
     } 

     return mSsportsFragment; 
    } 

    public Fragment getCarsFragment() { 
     if(mCarsFragment == null) { 
      mCarsFragment = new CarsFragment(); 
     } 

     return mCarsFragment; 
    } 

    public Fragment getMusicFragment() { 
     if(mMusicFragment == null) { 
      mMusicFragment = new MusicFragment(); 
     } 

     return mMusicFragment; 
    } 

    public Fragment getMoviesFragment() { 
     if(mMoviesFragment == null) { 
      mMoviesFragment = new MoviesFragment(); 
     } 

     return mMoviesFragment; 
    } 
} 

我對如何最好地實現多個片段以及如何維護其狀態的提示非常感興趣。爲了您的信息,我的應用程序目前包含15個以上的片段。 我已經做了一些研究,似乎FragmentManager.findFragmentByTag()是一個很好的選擇,但我一直沒能成功實現它。

我的實現似乎工作除了事實方向變化後mActivity引用變得無效,這讓我相信,我可能有一些內存泄漏問題以及良好。

如果您需要查看更多的代碼,請讓我知道。我故意避免包含片段代碼,因爲我堅信問題與我的活動和應用程序實現有關,但我可能是錯的。

謝謝你的時間。

回答

10

我的這個實施背後的想法是,以保證各只有一個片段的實例在我的應用程序的生命週期

這可能是一部分,如果不是全部,你的困難之源。

在配置更改上,Android將通過使用公共零參數構造函數創建新實例來重新創建片段。因此,您的全局範圍片段不會「只保證每個片段的一個實例」。

請刪除此自定義Application類。請允許片段自然地重新創建,或者如果他們需要在單個活動中生活,請使用setRetainInstance(true)。不要試圖在活動中重複使用片段。

+0

感謝您的快速響應!我將根據需要進行必要的修改:)只是一個簡單的問題:當我的應用程序被Android銷燬以達到資源目的時,我如何保證當用戶重新啓動我的應用程序時,最後一個片段(在應用程序被銷燬之前)顯示,而不是默認的「開始片段」?當發生這種情況時,我還需要使用「菜單」片段來選擇正確的位置。首選項出現在腦海中,但你可能有更好的主意? –

+3

@MortenSalte:「我怎麼能保證,當用戶重新啓動我的應用程序,他的最後一個片段(在應用程序被銷燬之前)顯示在」onPause()'或'onStop()'中,寫出狀態信息('SharedPreferences',文件,數據庫),在你的進程終止之後這些信息將會持續存在。 「首先想到的,但你可能有一個更好的主意?」 - 偏好將是這樣的典型解決方案,但任何持久性商店都應該有效。 – CommonsWare

2

要解決這個問題,你必須使用由片段onAttach方法提供,所以當你改變方向片段重新使活動對象onAttach給你目前的參考

0

如果你不」 t setRetainInstance(true)onCreate ...應用程序類中的集合例如List<Object>, Vector<Object>將得到空值。確保你setRetainInstance(true)讓他們活着。

0

可以使用onAttach(Context context)在片段上改變方向創建一個私有的上下文變量這樣

@Override 
public void onAttach(Context context) { 
    this.context = context; 
    super.onAttach(context); 
} 

,onAttach爲您提供了新的參考背景下,如果你想引用活動,您可以強制轉換上下文活動。