目錄
1.1running-----paused------stopped-----killed
2.1從A頁面Activity跳轉到B頁面Activity,然後關閉B頁面Activity,回到A頁面Activity
1.Activity的運行狀態
1.1running-----paused------stopped-----killed
- running->當前顯示在屏幕的activity(位於任務棧的頂部),用戶可見狀態。
- paused->依舊在用戶可見狀態,但是界面焦點已經失去,此Activity無法與用戶進行交互。
- stopped->用戶看不到當前界面,也無法與用戶進行交互完全被覆蓋.
- killed->當前界面被銷燬,等待這系統被回收
由上圖我們得知:
- Starting ——–>Running 所執行的生命週期順序 onCreate()->onstart()->onResume()
當前稱爲活動狀態(Running),此activity所處於任務棧的top中,可以與用戶進行交互。
- Running ——>Paused 所執行Activity生命週期中的onPause()
當前稱爲暫停狀態(Paused),該Activity已失去了焦點但仍然是可見的狀態(包括部分可見)。
- Paused ——>Running所執行的生命週期爲:OnResume()
當前重新回到活動狀態(Running),此情況用戶操作home鍵,然後重新回到當前activity界面發生。
- Paused ——>Stoped所執行的生命週期爲:onStop()
該Activity被另一個Activity完全覆蓋的狀態,該Activity變得不可見,所以系統經常會由於內存不足而將該Activity強行結束。
- Stoped——>killed所執行的生命週期爲:onDestroy()
該Activity被系統銷燬。當一個Activity處於暫停狀態或停止狀態時就隨處可能進入死亡狀態,因爲系統可能因內存不足而強行結束該Activity。
注:還有一種情況由於系統內存不足可能在Paused狀態中直接被系統殺死達到killed狀態。
上面圖概括了android生命週期的各個環節,描述了activity從生成到銷燬的過程。
- onCreate():
當我們點擊activity的時候,系統會調用activity的oncreate()方法,在這個方法中我們會初始化當前佈局setContentLayout()方法。
- onStart():
onCreate()方法完成後,此時activity進入onStart()方法,當前activity是用戶可見狀態,但沒有焦點,與用戶不能交互,一般可在當前方法做一些動畫的初始化操作。
- onResume():
onStart()方法完成之後,此時activity進入onResume()方法中,當前activity狀態屬於運行狀態 (Running),可與用戶進行交互。
- onPause():
當另外一個activity覆蓋當前的acitivty時,此時當前activity會進入到onPause()方法中,當前activity是可見的,但不能與用戶交互狀態。
- onStop():
onPause()方法完成之後,此時activity進入onStop()方法,此時activity對用戶是不可見的,在系統內存緊張的情況下,有可能會被系統進行回收。所以一般在當前方法可做資源回收。
- onDestory():
onStop()方法完成之後,此時activity進入到onDestory()方法中,結束當前activity。
- onRestart():
onRestart()方法在用戶按下home()之後,再次進入到當前activity的時候調用。調用順序 onPause()->onStop()->onRestart()->onStart()->onResume().
2.頁面跳轉及一些特殊情況
2.1從A頁面Activity跳轉到B頁面Activity,然後關閉B頁面Activity,回到A頁面Activity
①針對開啓的B頁面Activity,第一次啓動,回調如下:onCreate()->onStart()->onResume()
②用戶打開B頁面Activiy的時候,A頁面的Activity【處於不可見】的回調如下:onPause()->onStop()
③再次從B頁面回到A頁面原Activity時,A頁面【從不可見到可見】回調如下:onRestart()->onStart()->onResume()
④按back鍵回退時,B頁面Activity回調如下:onPause()->onStop()->onDestory()
⑤按Home鍵切換到桌面後又回到A頁面該Actitivy,回調如下:onPause()->onStop()->onRestart()->onStart()->onResume()
⑥調用finish()方法後,回調如下:onDestory()(以在onCreate()方法中調用爲例,不同方法中回調不同,通常都是在onCreate()方法中調用)
2.2 橫豎屏切換
第一種情況,銷燬當前的Activity後重建,這種也儘量避免。
- 在橫豎屏切換的過程中,會發生Activity被銷燬並重建的過程。
- 在瞭解這種情況下的生命週期時,首先應該瞭解這兩個回調:onSaveInstanceState和onRestoreInstanceState。
- 在Activity由於異常情況下終止時,系統會調用onSaveInstanceState來保存當前Activity的狀態。這個方法的調用是在onStop之前,它和onPause沒有既定的時序關係,該方法只在Activity被異常終止的情況下調用。當異常終止的Activity被重建以後,系統會調用onRestoreInstanceState,並且把Activity銷燬時onSaveInstanceState方法所保存的Bundle對象參數同時傳遞給onRestoreInstanceState和onCreate方法。因此,可以通過onRestoreInstanceState方法來恢復Activity的狀態,該方法的調用時機是在onStart之後。其中onCreate和onRestoreInstanceState方法來恢復Activity的狀態的區別: onRestoreInstanceState回調則表明其中Bundle對象非空,不用加非空判斷。onCreate需要非空判斷。建議使用onRestoreInstanceState。
- 橫豎屏切換的生命週期:onPause()->onSaveInstanceState()-> onStop()->onDestroy()->onCreate()->onStart()->onRestoreInstanceState->onResume()
第二種情況,當前的Activity不銷燬,設置Activity的屬性。
應用場景:視頻播放器就經常會涉及屏幕旋轉場景。
- 可以通過在AndroidManifest文件的Activity中指定如下屬性:
-
<activity android:name=".activity.VideoDetailActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:screenOrientation="portrait"/>
- 爲了避免橫豎屏切換時,Activity的銷燬和重建,而是回調了下面的方法:
-
//重寫旋轉時方法,不銷燬activity @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); }
第三種情況:資源內存不足導致優先級低的Activity被殺死
- Activity優先級的劃分和下面的Activity的三種運行狀態是對應的。
- (1) 前臺Activity——正在和用戶交互的Activity,優先級最高。
- (2) 可見但非前臺Activity——比如Activity中彈出了一個對話框,導致Activity可見但是位於後臺無法和用戶交互。
- (3) 後臺Activity——已經被暫停的Activity,比如執行了onStop,優先級最低。
- 當系統內存不足時,會按照上述優先級從低到高去殺死目標Activity所在的進程。我們在平常使用手機時,能經常感受到這一現象。這種情況下數組存儲和恢復過程和上述情況一致,生命週期情況也一樣。
3.Activity的啓動模式
Android提供了四種Activity啓動方式:
- 標準模式(standard)
- 棧頂複用模式(singleTop)
- 棧內複用模式(singleTask)
- 單例模式(singleInstance)
詳情請移步:Activity四種啓動模式
4.Fragment
4.1什麼是Fragment?
- 什麼是Fragment
- 可以簡單的理解爲,Fragment是顯示在Activity中的Activity。它可以顯示在Activity中,然後它也可以顯示出一些內容。因爲它擁有自己的生命週期,可以接受處理用戶的事件,並且你可以在一個Activity中動態的添加,替換,移除不同的Fragment,因此對於信息的展示具有很大的便利性。
- 作爲 view 界面的一部分,Fragment 的存在必須依附於 FragmentActivity使用,並且與 FragmentActivity 一樣,擁有自己的獨立的生命週期,同時處理用戶的交互動作。同一個 FragmentActivity 可以有一個或多個 Fragment 作爲界面內容,同樣Fragment也可以擁有多個子Fragment,並且可以動態添加、刪除 Fragment,讓UI的重複利用率和易修改性得以提升,同樣可以用來解決部分屏幕適配問題。
- Fragment是組件還是控件
- 嚴格意義上來說Fragment並不是一個顯示控件,而只是一個顯示組件。爲什麼這麼說呢?其實像我們的Activity,Dialog,PopupWindow以及Toast類的內部都管理維護着一個Window對象,這個Window對象不但是一個View組件的集合管理對象,它也實現了組件的加載與繪製流程,而我們的Fragment組件如果看過源碼的話,嚴格意義上來說,只是一個View組件的集合並通過控制變量實現了其特定的生命週期,但是其由於並沒有維護Window類型的成員變量,所以其不具備組件的加載與繪製功能,因此其不能單獨的被繪製出來,這也是我把它稱之爲組件而不是控件的原因。
- 使用條件
- 宿主Activity 必須繼承自 FragmentActivity;
- 使用getSupportFragmentManager() 方法獲取 FragmentManager 對象;
4.2 Fragment生命週期
- ragment是依附於Activity存在的,因此它的生命週期收到Activity的生命週期影響
- Fragment比Activity多了幾個生命週期的回調方法
- onAttach(Activity) 當Fragment與Activity發生關聯的時候調用
- onCreateView(LayoutInflater, ViewGroup, Bundle) 創建該Fragment的視圖
- onViewCreated(View view, Bundle savedInstanceState) 試圖創建後調用該方法
- onActivityCreated(Bundle) 當Activity的onCreated方法返回時調用
- onDestroyView() 與onCreateView方法相對應,當該Fragment的視圖被移除時調用
- onDetach() 與onAttach方法相對應,當Fragment與Activity取消關聯時調用
- 注意:除了onCreateView,其他的所有方法如果你重寫了,必須調用父類對於該方法的實現
4.3 Fragment使用
- Activity創建Fragment的方式是什麼?
- 靜態創建具體步驟
- 首先我們同樣需要註冊一個xml文件,然後創建與之對應的java文件,通過onCreatView()的返回方法進行關聯,最後我們需要在Activity中進行配置相關參數即在Activity的xml文件中放上fragment的位置。
- 動態創建具體步驟
- (1)創建待添加的碎片實例
- (2)獲取FragmentManager,在活動中可以直接通過調用 getSupportFragmentManager()方法得到。
- (3)開啓一個事務,通過調用beginTransaction()方法開啓。
- (4)向容器內添加或替換碎片,一般使用repalce()方法實現,需要傳入容器的id和待添加的碎片實例。
- (5)提交事務,調用commit()方法來完成。
- 靜態創建具體步驟
- 接下來就分別詳細地理解一下兩種創建fragment的方式的細節。
4.3.1靜態使用
- 大概步驟:
- ① 創建一個類繼承Fragment,重寫onCreateView方法,來確定Fragment要顯示的佈局
- ② 在Activity中聲明該類,與普通的View對象一樣
- 代碼演示
- 繼承Frgmanet的類MyFragment【請注意導包的時候導v4的Fragment的包】
public class MyFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/*
* 參數1:佈局文件的id
* 參數2:容器
* 參數3:是否將這個生成的View添加到這個容器中去
* 作用是將佈局文件封裝在一個View對象中,並填充到此Fragment中
* */
View v = inflater.inflate(R.layout.item_fragment, container, false);
return v;
}
}
- Activity對應的佈局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.yczbj.fragment.MainActivity">
<fragment
android:id="@+id/search_fragment"
android:name="com.yczbj.fragment.MyFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
- Activity中的顯示和隱藏fragment代碼
phoneNumFragment = (PhoneNumFragment) getSupportFragmentManager()
.findFragmentById(R.id.search_fragment);
//處理返回鍵
@Override
public void onBackPressed() {
if (phoneNumFragment != null && !phoneNumFragment.isHidden()) {
hideSearch();
return;
}
super.onBackPressed();
}
private void hideSearch() {
getSupportFragmentManager()
.beginTransaction()
.hide(phoneNumFragment)
.commit();
}
private void showSearch() {
getSupportFragmentManager()
.beginTransaction()
.show(phoneNumFragment)
.commit();
}
4.3.2 動態使用
代碼如下所示,這種是平時開發最常用的
OrderStatesFragment fragment = new OrderStatesFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("confirmOrderModel",confirmOrderModel);
fragment.setArguments(bundle);
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.push_bottom_in, 0);
//transaction.show(fragment); //暫時不用這個
transaction.add(android.R.id.content, fragment, "OrderStatesFragment");
transaction.commitAllowingStateLoss();
部分代碼說明
- ① 創建一個fragment的實例
- ② 通過getSupportFragmentManager()方法新建Fragment管理器對象
- ③ 然後通過Fragment管理器對象調用beginTransaction()方法,實例化FragmentTransaction對象,有人稱之爲事務
- ④ FragmentTransaction對象【以下直接用transaction代替】,transaction的方法主要有以下幾種:
- transaction.add() 向Activity中添加一個Fragment
- transaction.remove() 從Activity中移除一個Fragment,如果被移除的Fragment沒有添加到回退棧(回退棧後面會詳細說),這個Fragment實例將會被銷燬
- transaction.replace() 使用另一個Fragment替換當前的,實際上就是remove()然後add()的合體
- transaction.hide() 隱藏當前的Fragment,僅僅是設爲不可見,並不會銷燬,它只會觸發onHiddenChange()方法。
- transaction.show() 顯示之前隱藏的Fragment,它只會觸發onHiddenChange()方法。
- detach() 會將view從UI中移除,和remove()不同,此時fragment的狀態依然由FragmentManager維護
- attach() 重建view視圖,附加到UI上並顯示
- ransatcion.commit() 提交事務
- 注意:在add/replace/hide/show以後都要commit其效果纔會在屏幕上顯示出來
4.4Fragment回退棧
- Fragment的回退棧是用來保存每一次Fragment事務發生的變化
- 如果你將Fragment任務添加到回退棧,當用戶點擊後退按鈕時,將看到上一次的保存的Fragment。一旦Fragment完全從後退棧中彈出,用戶再次點擊後退鍵,則退出當前Activity。
- 在某個activity上添加fragment,如果不處理宿主activity中返回鍵邏輯,點擊返回鍵,關閉了fragment同時也關閉了activity。
OrderStatesFragment fragment = new OrderStatesFragment(); FragmentManager fragmentManager = activity.getSupportFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.setCustomAnimations(R.anim.push_bottom_in, 0); //transaction.show(fragment); //暫時不用這個 transaction.add(android.R.id.content, fragment, "OrderStatesFragment"); transaction.commitAllowingStateLoss();
- 在某個activity上添加fragment,如果不處理宿主activity中返回鍵邏輯,點擊返回鍵,關閉了fragment,回到宿主activity頁面。
OrderStatesFragment fragment = new OrderStatesFragment(); FragmentManager fragmentManager = activity.getSupportFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.setCustomAnimations(R.anim.push_bottom_in, 0); //transaction.show(fragment); //暫時不用這個 transaction.add(android.R.id.content, fragment, "OrderStatesFragment"); transaction.commitAllowingStateLoss(); transaction.addToBackStack(null);
05.Fragment與Activity通信
- Fragment依附於Activity存在,因此與Activity之間的通信可以歸納爲以下幾點:
- 如果你Activity中包含自己管理的Fragment的引用,可以通過引用直接訪問所有的Fragment的public方法
- 如果Activity中未保存任何Fragment的引用,那麼沒關係,每個Fragment都有一個唯一的TAG或者ID,可以通過getFragmentManager.findFragmentByTag()或者findFragmentById()獲得任何Fragment實例,然後進行操作
- Fragment中可以通過getActivity()得到當前綁定的Activity的實例,然後進行操作。不過不建議這樣獲取activity的實例,後面會提到的,容易報空指針異常問題。
06.Fragment與Activity通信的優化
- 因爲要考慮Fragment的重複使用,所以必須降低Fragment與Activity的耦合,而且Fragment更不應該直接操作別的Fragment,畢竟Fragment操作應該由它的管理者Activity來決定。
- 針對直接創建fragment,一般可以這樣寫
QrCodeLoginFragment fragment = new QrCodeLoginFragment(); Bundle bundle = new Bundle(); bundle.putString("unique_id", content); fragment.setArguments(bundle); FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.add(android.R.id.content, fragment, "qrCodeLoginFragment"); transaction.commit(); transaction.addToBackStack(null);
- 針對有些頻繁show或者hide的fragment,可以這樣處理
/** * 展示頁面 */ private void showPlayingFragment() { if (isPlayFragmentShow) { return; } FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.setCustomAnimations(R.anim.fragment_slide_up, 0); if (mPlayFragment == null) { mPlayFragment = PlayMusicFragment.newInstance("Main"); ft.replace(android.R.id.content, mPlayFragment); } else { ft.show(mPlayFragment); } ft.commitAllowingStateLoss(); isPlayFragmentShow = true; } /** * 隱藏頁面 */ private void hidePlayingFragment() { if(mPlayFragment!=null){ FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.setCustomAnimations(0, R.anim.fragment_slide_down); ft.hide(mPlayFragment); ft.commitAllowingStateLoss(); isPlayFragmentShow = false; } }
06.Fragment旋轉場景
- 在Activity的學習中都知道,當屏幕旋轉時,是對屏幕上的視圖進行了重新繪製。
- 因爲當屏幕發生旋轉,Activity發生重新啓動,默認的Activity中的Fragment也會跟着Activity重新創建,用腳趾頭都明白...橫屏和豎屏顯示的不一樣肯定是進行了重新繪製視圖的操作。所以,不斷的旋轉就不斷繪製,這是一種很耗費內存資源的操作,那麼如何來進行優化?
- 首先看看Fragment代碼
public class FragmentOne extends Fragment { private static final String TAG = "FragmentOne"; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.e(TAG, "onCreateView"); View view = inflater.inflate(R.layout.fragment_one, container, false); return view; } @Override public void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); Log.e(TAG, "onCreate"); } @Override public void onDestroyView() { // TODO Auto-generated method stub super.onDestroyView(); Log.e(TAG, "onDestroyView"); } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.e(TAG, "onDestroy"); } }
- 然後你多次翻轉屏幕都會打印如下log
07-20 08:18:46.651: E/FragmentOne(1633): onCreate 07-20 08:18:46.651: E/FragmentOne(1633): onCreate 07-20 08:18:46.651: E/FragmentOne(1633): onCreate 07-20 08:18:46.681: E/FragmentOne(1633): onCreateView 07-20 08:18:46.831: E/FragmentOne(1633): onCreateView 07-20 08:18:46.891: E/FragmentOne(1633): onCreateView
- 因爲當屏幕發生旋轉,Activity發生重新啓動,默認的Activity中的Fragment也會跟着Activity重新創建;這樣造成當旋轉的時候,本身存在的Fragment會重新啓動,然後當執行Activity的onCreate時,又會再次實例化一個新的Fragment,這就是出現的原因。
- 如何解決
- 通過檢查onCreate的參數Bundle savedInstanceState就可以判斷,當前是否發生Activity的重新創建
- 默認的savedInstanceState會存儲一些數據,包括Fragment的實例
- 簡單改一下代碼,判斷只有在savedInstanceState==null時,才進行創建Fragment實例
public class MainActivity extends Activity { private static final String TAG = "FragmentOne"; private FragmentOne mFOne; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); Log.e(TAG, savedInstanceState+""); if(savedInstanceState == null) { mFOne = new FragmentOne(); FragmentManager fm = getFragmentManager(); FragmentTransaction tx = fm.beginTransaction(); tx.add(R.id.id_content, mFOne, "ONE"); tx.commit(); } } }
- 現在無論進行多次旋轉都只會有一個Fragment實例在Activity中,現在還存在一個問題,就是重新繪製時,Fragment發生重建,原本的數據如何保持?
- 和Activity類似,Fragment也有onSaveInstanceState的方法,在此方法中進行保存數據,然後在onCreate或者onCreateView或者onActivityCreated進行恢復都可以。
07.Fragment使用建議
- 關於使用Fragment操作的使用建議
- 如果Fragment視圖被頻繁的使用,或者一會要再次使用,建議使用show/hide方法,這樣可以提升響應速度和性能。
- 如果Fragment佔用大量資源,使用完成後,可以使用replace方法,這樣可以及時的釋放資源。
- 傳遞參數建議
- Fragment的數據傳遞通過setArguments/getArguments進行,這樣在Activity重啓時,系統會幫你保存數據,這點和Activity很相似。
08.FragmentAdapter選擇
- FragmentPageAdapter和FragmentPageStateAdapter的區別?
- FragmnetPageAdapter在每次切換頁面時,只是將Fragment進行分離,適合頁面較少的Fragment使用以保存一些內存,對系統內存不會多大影響
- FragmentPageStateAdapter在每次切換頁面的時候,是將Fragment進行回收,適合頁面較多的Fragment使用,這樣就不會消耗更多的內存
- 如何使用FragmentPageStateAdapter?
public class BasePagerStateAdapter extends FragmentStatePagerAdapter { private List<?> mFragment; private List<String> mTitleList; /** * 接收首頁傳遞的標題 */ public BasePagerStateAdapter(FragmentManager fm, List<?> mFragment, List<String> mTitleList) { super(fm); this.mFragment = mFragment; this.mTitleList = mTitleList; } @Override public Fragment getItem(int position) { return (Fragment) mFragment.get(position); } @Override public int getCount() { return mFragment==null ? 0 : mFragment.size(); } /** * 首頁顯示title,每日推薦等.. * 若有問題,移到對應單獨頁面 */ @Override public CharSequence getPageTitle(int position) { if (mTitleList != null) { return mTitleList.get(position); } else { return ""; } } }
- 如何使用FragmentPagerAdapter?
public class BasePagerAdapter extends FragmentPagerAdapter { private List<?> mFragment; private List<String> mTitleList; /** * 普通,主頁使用 */ public BasePagerAdapter(FragmentManager fm, List<?> mFragment) { super(fm); this.mFragment = mFragment; } /** * 接收首頁傳遞的標題 */ public BasePagerAdapter(FragmentManager fm, List<?> mFragment, List<String> mTitleList) { super(fm); this.mFragment = mFragment; this.mTitleList = mTitleList; } @Override public Fragment getItem(int position) { return (Fragment) mFragment.get(position); } @Override public int getCount() { return mFragment==null ? 0 : mFragment.size(); } @Override public void destroyItem(ViewGroup container, int position, Object object) { super.destroyItem(container, position, object); } /** * 首頁顯示title,每日推薦等.. * 若有問題,移到對應單獨頁面 */ @Override public CharSequence getPageTitle(int position) { if (mTitleList != null) { return mTitleList.get(position); } else { return ""; } } public void addFragmentList(List<?> fragment) { this.mFragment.clear(); this.mFragment = null; this.mFragment = fragment; notifyDataSetChanged(); } } //具體使用 FragmentManager supportFragmentManager = getSupportFragmentManager(); BasePagerAdapter myAdapter = new BasePagerAdapter(supportFragmentManager, mFragments, mTitleList); vpContent.setAdapter(myAdapter); tabLayout.setupWithViewPager(vpContent);
關於Fragment的具體實現和應用,可移步fragment具體實現及應用。