Activity異常狀态時的生命周期
當Activity在異常情況下需要重新建立時,系統會預設儲存目前的Activity視圖結構,并且在Activity重新開機後為我們恢複這些資料,那麼針對某一個特定的View,系統能回複哪些資料呢?
通過View的源碼可以看到。
關于儲存和回複View層次結構,系統的工作流程是這樣的:首先Activity被意外終止時,Activity會調用
onSaveInstanceState
去儲存資料,然後Activity會委托Window去儲存資料,接着Window再委托它上面的頂級容器去儲存資料。頂層容器是一個ViewGroup,一般來說它很可能是DecorView。最後頂層容器再去一一通知它的子元素來儲存資料,這樣整個資料儲存過程就完成了。可以發現,這是一種典型的委托思想,上層委托下層。父容器委托子元素去處理一件事情,這種思想在Android中有很多應用,比如View的繪制過程、事件分發等都是類似的思想。至于資料恢複過程也是類似。
onSaveInstanceState隻會在Activity即将被銷毀并且有機會重新顯示的情況下才會調用它。
系統隻有在異常終止的時候才會調用onSaveInstanceState和onRestoreInstanceState來存儲和恢複資料,其它情況不會觸發這個過程。

Activity關于SingleTask啟動模式
SingleTask啟動模式:棧内複用模式,單執行個體模式,隻要Activity在一個棧中存在,那麼多次啟動此Activity都不會重新建立執行個體,回調onNewIntent方法。具體是當一個singaleTask模式的Activity A請求啟動時,系統會現尋找存在它想要的任務棧,如果不存在,就重新建立一個任務棧,然後建立A的執行個體後把A放入棧中。如果存在它所需的任務棧,那麼看棧中是否存在A,如果存在則清空A上面的任務棧,另A到棧頂,否則建立A并入棧。如果不存在想要的任務棧,但是A存在于不想要的任務棧中,那麼仍然通過TaskAffinity制定想要的任務棧啟動時,仍然會建立想要的任務棧,然後建立A并入棧。
什麼是Activity所需的任務棧呢?取決于TaskAffinity:翻譯為任務相關性。
這個參數辨別了Activity所需任務棧的名字,預設情況下,所有Activity的TaskAffinity的名字為應用的packageName。TaskAffinity主要和singleTask後者allowTaskReparenting屬性配對使用,在其他情況下沒有意義。任務棧分為前台任務棧和背景任務棧。
當TaskAffinity和allowTaskReparenting結合的時候,當一個應用A啟動了應用B的某個Activity後,如果這個Activity的allowTaskReparenting屬性為true的話,那麼當應用B被啟動後,此Activity會直接從應用A的任務棧轉移到應用B的任務棧中。
根據實際測試,當要從B應用啟動某個Activity C,并且它設定了allowTaskReparenting屬性為true時,launchMode不設定時,那麼Home按鍵回到桌面,點選B應用,打開的不是B的首界面而是Activity C.
Common是A應用,OneSDK是B應用,藍色是Activity C,紅色是B應用的首界面
當要從B應用啟動某個Activity C,并且它設定了allowTaskReparenting屬性為true時,launchMode設定為singleTask時,那麼Home按鍵回到桌面,點選B應用,打開的不是B的首界面而是Activity C.
如果不設定B應用的Activity C,那麼相同的操作打開的是B應用的首界面,此時檢視任務棧,A應用的任務棧最上層是Activity C,B應用的最上層是首界面。
Common是A應用,OneSDK是B應用,藍色是Activity C,紅色是B應用的首界面
當即通過AndroidManifest設定launchMode,同時在Intent中指定了啟動模式時,代碼中的啟動模式優先級更高,也就是Intent方式為準。
但是Intent無法為Activity設定為singelInstance模式,在AndroidManifest無法設定Activity為FLAG_ACTIGITY_CLEAR_TOp辨別。
測試執行個體
定義以下Activity
<activity
android:name="com.derek.demo.MainActivity"
android:configChanges="keyboard|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="sensor" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.derek.demo.MainActivity.SecondActivity"
android:configChanges="keyboard|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="sensor"
android:taskAffinity="com.derek.task1" >
</activity>
<activity
android:name="com.derek.demo.MainActivity.ThirdActivity"
android:configChanges="keyboard|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="sensor"
android:taskAffinity="com.derek.task1" >
</activity>
将SecondActivity和ThirdActivity都設成singleTask并指定它們的taskAffinity屬性為
com.derek.task1
,注意這個taskAffinity屬性的值為字元串,且中間必須包含包名分隔符”.”。然後進行如下操作:在MainAcitiviy中點選按鈕啟動SecondActivity,在SecondActiviy中點選按鈕啟動ThridActivity,在ThridActivity中單擊按鈕又啟動MainActivity,最後再在MainActivity中單擊按鈕啟動SecondActivity,現在按下back鍵,然後看到那個Activity?
答案是回到桌面。
Activity的Flags
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有這個标記的Activity不會出現在曆史Activity的清單中,當某些情況下我們不希望使用者通過曆史清單回到我們的Activity的時候這個标記比較有用。它等同于XML中指定Activity的屬性
android:excludeFromRecents= "true"
IntentFilter的比對規則
判斷隐式啟動Activity的時候,可以做一下判斷,檢視是否有Activity能夠比對我們的Intent。
PackageManager提供了兩個方法如下:
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,int flags);
public abstract ResolveInfo resolveActivity(Intent intetn,int flags);
第二個參數flag需要使用MATCH_DEFAULT_ONLY這個标記為,來僅比對那些在intent-filter中聲明了
第一個方法傳回所有比對的Activity,第二個方法隻傳回最佳比對的一個Activity