編輯:關於Android編程
一、引言
在移動應用程序的架構設計中,界面與數據即不可分割又不可混淆。在絕大部分的開發經歷中,我們都是使用Fragment來進行界面編程,即使保存數據基本上也只是界面相關控件的數據,很少做其他的數據保存,畢竟這樣與開發原則相背,而今天這一篇博客就要來介紹一下Fragment的另類用法,只是用來保存數據而沒有任何界面元素。
二、實現背景
對於Fragment的數據保存方法,不難想到還是與setRetainInstance有關系的。這樣一來所處的背景也是在屏幕旋轉或其他配置改變時需要用到。無論在開發中我們的界面是用Activity還是Fragment生成的,在屏幕發生旋轉時,都會在生命周期onSaveInstanceState中做控件狀態和必要數據的緩存工作。通常情況下,會用到Bundle來存儲數據。如Bundle的官方介紹所說,Bundle是一個用來存儲String及其他序列化數據類型的map。同樣Android中也存在著這樣的一個異常:http://developer.android.com/intl/zh-cn/reference/android/os/TransactionTooLargeException.html
這個異常從字面上看不難理解,是傳輸數據過大異常。在描述中可知,現行Android系統中對於應用程序的傳輸數據大小限制在1Mb以內。所以如果在屏幕旋轉過程中使用Bundle緩存大數據並不是十分安全的。這樣的大數據在Android中很經典的代表之一就是Bitmap,即使Bitmap已經是序列化數據,能夠方便的使用Bundle作為緩存媒介,但是筆者還是強烈不建議這樣做。下邊,就提供一個簡單的解決途徑。
三、實現過程
首先,創建一個用來保存數據的Fragment:
public class BitmapDataFragment extends Fragment { public static final String TAG = "bitmapsaver"; private Bitmap bitmap; private BitmapDataFragment(Bitmap bitmap) { this.bitmap = bitmap; } public static BitmapDataFragment newInstance(Bitmap bitmap) { return new BitmapDataFragment(bitmap); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } public Bitmap getData() { return bitmap; } }
這個Fragment沒有任何界面,在onCreate生命周期中使用setRetainInstance(true)確保不會隨載體銷毀,從而確保數據的安全性。
創建完成後,實踐一下使用過程,假設其使用者是Activity:
@Override protected void onSaveInstanceState(Bundle outState) { if (mBitmap != null) { getSupportFragmentManager().beginTransaction() .add(BitmapDataFragment.newInstance(mBitmap), BitmapDataFragment.TAG) .commit(); outState.putBoolean(SENSE_IMAGE_KEY, true); } else { outState.putBoolean(SENSE_IMAGE_KEY, false); } super.onSaveInstanceState(outState); }
在設備發生旋轉時,檢測當前界面中顯示的某個Bitmap,如果確實有數據,則new出一個我們剛剛創建的Fragment,將Bitmap數據放置進去,然後將這個Fragment添加到FragmentManager中並指定tag,這樣我們在恢復狀態後就可以方便的找到它。
在恢復時候,Activity的生命周期走到了onCreate()中,在這裡我們可以通過檢測Bundle參數來確定是否有Bitmap數據待取:
if (savedInstanceState.getBoolean(SENSE_IMAGE_KEY)) { BitmapDataFragment fragment = (BitmapDataFragment) getSupportFragmentManager() .findFragmentByTag(BitmapDataFragment.TAG); bitmap = fragment.getData(); getSupportFragmentManager().beginTransaction().remove(fragment).commit(); }
PS:在取出我們所需的Bitmap數據後不要忘記把作為數據容器的這個Fragment從FragmentManager中移除掉,釋放其占用的系統內存。
四、Fragment的非中斷保存
1.setRetaineInstance
首先,要明確什麼叫“非中斷保存”。熟悉Fragment的開發人員都知道,Fragment是依附於Activity的。當Activity銷毀時,Fragment會隨之銷毀。而當Activity配置發生改變(如屏幕旋轉)時候,舊的Activity會被銷毀,然後重新生成一個新屏幕旋轉狀態下的Activity,自然而然的Fragment也會隨之銷毀後重新生成,而新生成的Fragment中的各個對象也與之前的那個Fragment不一樣,伴隨著他們的動作、事件也都不一樣。所以,這時候如果想保持原來的Fragment中的一些對象,或者想保持他們的動作不被中斷的話,就迫切的需要將原來的Fragment進行非中斷式的保存。
2.生命周期
Activity的生命周期在配置發生改變時:
onPuase->onStop->onDestroy->onStart->onResume
比如在Activity中發生屏幕旋轉,其生命周期就是如此。而在onDestroy中,Activity會將其FragmentManager所包含的Fragment都銷毀掉(默認狀態),即Fragment的生命周期為:
onDestroyView->onDestroy->onDetach
通過查看FragmentManager.java的代碼,可以發現在Fragment生命周期執行到onDestroyView時候,狀態會由正常的ACTIVITY_CREATED變為CREATED。而到了onDestroy生命周期時候,執行的代碼出現了有意思的事情:
if (!f.mRetaining) { f.performDestroy(); } f.mCalled = false; f.onDetach(); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onDetach()"); } if (!keepActive) { if (!f.mRetaining) { makeInactive(f); } else { f.mActivity = null; f.mParentFragment = null; f.mFragmentManager = null; } }
當Fragment的mRetaining被置true的時候,Destroy生命周期並不會執行,而Fragment的mRetaining狀態是通過其retainNonConfig()來配置的,配置條件是Fragment不為空且Framgnet的mRetainInstance為true。到這裡就能看到,如果想要自己的Fragment不被銷毀掉,就要讓這個mRetainInstance為true。
通過查閱Fragment.java源碼發現,通過API setRetainInstance和getRetainInstance可以對其進行操作。同樣,Android文檔中對這兩個接口也有了一定的描述。
這裡結合Fragment.java中setRetainInstance的注釋進行一下Fragment非中斷保存的總結。原注釋如下:
/** * Control whether a fragment instance is retained across Activity * re-creation (such as from a configuration change). This can only * be used with fragments not in the back stack. If set, the fragment * lifecycle will be slightly different when an activity is recreated: * <ul> * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still * will be, because the fragment is being detached from its current activity). * <li> {@link #onCreate(Bundle)} will not be called since the fragment * is not being re-created. * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b> * still be called. * </ul> */ public void setRetainInstance(boolean retain) { if (retain && mParentFragment != null) { throw new IllegalStateException( "Can't retain fragements that are nested in other fragments"); } mRetainInstance = retain; }
如果想叫自己的Fragment即使在其Activity重做時也不進行銷毀那麼就要設置setRetainInstance(true)。進行了這樣的操作後,一旦發生Activity重組現象,Fragment會跳過onDestroy直接進行onDetach(界面消失、對象還在),而Framgnet重組時候也會跳過onCreate,而onAttach和onActivityCreated還是會被調用。需要注意的是,要使用這種操作的Fragment不能加入backstack後退棧中。並且,被保存的Fragment實例不會保持太久,若長時間沒有容器承載它,也會被系統回收掉的。
五、總結
很簡單的Fragment非主流用法,相比直接使用Bundle保存數據確實是復雜了些,但是能夠更安全的進行數據轉移對應用來說還是很好的一件事。推薦指數五顆星★★★★★!
關於定位,相信大家都不陌生。現在很多App基本都少不了定位的功能,Android本身也提供了定位的功能(LocationManager),但是由於Google牆太厚了,所
一、概述最近項目准備嘗試使用webp來縮小包的體積,於是抽空對相關知識進行了調研和學習。至於什麼是webp,使用webp有什麼好處我就不贅述了,具體可以參考騰訊isux上
本篇文章講的是Android 自定義ViewGroup之實現標簽流式布局-FlowLayout,開發中我們會經常需要實現類似於熱門標簽等自動換行的流式布局的功能,網上也有
一.Service簡介service可以說是一個在後台運行的Activity,它不是一個單獨的進程,它只需要應用告訴它要在後台做什麼就可以了,它要實現和用戶的交互的話需要