子曰:溫故而知新,可以為師矣。 《論語》-- 孔子
一、 Activity
生命周期
Activity
先來放一張最經典的圖
1.1 典型情況下生命周期分析
-
:onCreate
生命周期的第一步。此方法中,常見操作有Activity
加載布局資源;業務需求需要的資料做初始化操作,例如setContView()
,setText()
等等。setImage()
-
:表示某一個背景onRestart()
重新切換到了前台,即從背景不可見狀态變成前台可見狀态。Activity
-
:onStart()
正在被啟動,背景可見,但并未出現在前台,即相對使用者不可見,使用者無法與此Activity
界面互動。Activity
-
:onResume()
可見且出現在前台,使用者可互動。Activity
-
:onPause()
正在停止,正常情況下,緊接着就會調用Activity
方法。此方法中,可以做一些存儲資料,停止動畫等操作,但是不能太耗時。onStop()
-
:onStop()
處于停滞狀态,可以做一些稍微重量級的操作,但是也不能太耗時。Activity
-
:onDestroy()
即将被銷毀,可以做一些回收工作和最終資源的釋放。Activity
不同場景下的生命周期
1) 第一次啟動某一個
Activity
:
onCreate --> onStart --> onResume
2)
Activity
A 跳轉到
Activity
B(不是透明的
Activity
) :
onPause(Activity A)--> onCreate(Activity B)--> onStart(Activity B)--> onStop(Activity A)--> onResume(B)
3)
Activity
A 跳轉到
Activity
B(透明的
Activity
)
onPause(Activity A)--> onCreate(Activity B)--> onStart(Activity B)--> onResume(B)
4)背景
Activity
進入 前台(背景
Activity
并未因為系統記憶體不足被殺死):
onRestart --> onStart --> onResume
5)背景
Activity
因為系統記憶體不足被殺死,重新啟動該
Activity
:
onCreate --> onStart --> onResume
6)鎖屏,開屏 :
鎖屏 onPause --> onStop
開屏 onStart --> onResume
7)使用者點選
back
鍵 :
onPause --> onStop --> onDestroy
上面列舉了一些常見的場景下生命周期的變化,在此做一個小結:
- 從整個生命周期來看,
和onCreate
是配對的,分别辨別着onDestroy
的建立和銷毀,并且隻可能有一次調用;Activity
- 從
是否可見,Activity
和onStart
是配對的,随着使用者操作或者裝置點亮熄滅,可能被調用多次;onStop
- 從
是否在前台來說,Activity
和onResume
是配對的,随着使用者操作或者裝置點亮熄滅,可能被調用多次。onPause
1.2 異常情況下生命周期分析:
1)資源相關的系統配置發生改變導緻
Activity
被殺死并重新建立(常見舉例 :
橫豎屏切換
)。
// onPause 和 onSaveInstanceState 的執行順序,誰都可能在前
// 官方建議在 onRestoreInstanceState 方法進行資料恢複。
onPause --> onSaveInstanceState --> onStop --> onDestroy --> onCreate --> onRestoreInstanceState
2)資源記憶體不足導緻優先級低的
Activity
被殺死。
- 前台
:正在和使用者互動的Activity
,優先級最高。Activity
- 可見但非前台
:比如Activity
中彈出了一個對話框,導緻Activity
可見但是位于背景無法和使用者直接互動。Activity
- 背景
:已經被暫停的Activity
,比如執行了 onStop,優先級最低。Activity
1.3 ConfigChanges
ConfigChanges
如果不想要在系統配置發生改變時,重新建立
Activity
,可以給
Activity
指定
configChanges
屬性。
下面列舉幾個常見的
configChangs
的屬性:
項目 | 含義 |
---|---|
locale | 裝置的本地位置發生了改變,一般指切換了語言系統 |
keyboardHidden | 鍵盤的可通路性發生了改變,比如使用者調用出了鍵盤 |
orientation | 螢幕方向發生了改變,比如旋轉手機螢幕。 |
screenSize | 當螢幕尺寸資訊發生了變化,這個選項比較特殊,它和編譯選項有關,當編譯選項中的 minSdkVersion 和 targetSdkVersion 均低于13時,此選項不會導緻 Activity 重新開機,否則會導緻 Activity 重新開機 |
具體做法,在
AndroidMinfest.xml
檔案中,給不想要因為系統配置發生改變而導緻
Activity
重建的
Activity
添加如下代碼:
// 如果 MiniSdkVersion 和 TargetSdkVersion 屬性大于等于13的情況下,需要同時設定 screenSize 和 orientation
android:configChanges="screenSize|orientation"
二、 Activity 啟動模式
在預設情況下,當我們多次啟動同一個 Activity 時,系統會建立多個執行個體,并把它們一一放入任務棧中,那麼多次啟動同一個 Activity,系統就會重複建立多個執行個體,這是一個很傻的行為,是以提供了啟動模式來修改系統的預設行為。
目前有4種啟動模式:
standard
、
singleTop
、
singTask
、
singleInstance
。
2.1 standard 模式
- 标準模式,也是系統預設模式。
舉例說明:
- 建立
基類。BaseActivity
-
繼承基類,MainActivity
類繼承基類,同時設定StandardActivity
為lanchmode
。standard
-
跳轉到MainActivity
,在StandardActivity
類中,點選三次按鈕,做跳轉自身操作。StandardActivity
- 檢視
日志。Log
BaseActivity
代碼如下:
public class BaseActivity extends AppCompatActivity {
public static final String TAG = "TAG";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "===============onCreate()方法==");
Log.i(TAG, "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
dumpTaskAffinity();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i("TAG", "===============onNewIntent()方法==");
Log.i("TAG", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
dumpTaskAffinity();
}
private void dumpTaskAffinity() {
try {
ActivityInfo info = this.getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Log.e(TAG, "taskAffinity:" + info.taskAffinity);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
Log
日志輸出 :
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 28 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:183570408
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:78785677
E/TAG: taskAffinity:com.example.activitylanuchmode
總結:
-
做跳轉自身三次操作,日志顯示了三個不同的StandardActivity
的值, 推出結論: 每次啟動一個hasCode
都會重新建立一個新的執行個體,不管這個執行個體是否存在。Activity
- 日志輸出了三次
方法,推出結論:被建立的執行個體的生命周期符合典型情況下的生命周期,它的onCreate
、onCreate
、onStart
都會調用。onResume
-
和MainActivity
的StandardAcitivty
值一樣,推出結論: 在這種模式下,誰啟動了這個taskAffinity
,那麼這個Activity
就運作在啟動它的那個Activity
所在的棧中。Activity
2.2 singleTop
- 棧頂複用模式。
通俗易懂的說一下此模式:
目前棧内情況為 ABCD 四個
Activity
,A 位于棧底,D 位于棧頂,假設需要再次啟動 D,如果 D 的啟動模式是
singleTop
,那麼棧内的情況還是 ABCD,如果 D 的啟動模式
standard
,那麼棧内的情況是 ABCDD。
舉例說明:
- 建立
(SingleTopActivity
為lanchmode
),singleTop
(OtherActivity
為lanchmode
)。standard
-
跳轉到MainActivity
,SingleTopActivity
跳轉到SingleTopActivity
跳轉到OhterActivity
,SingleTopActivity
自身跳轉三次。SingleTopActivity
- 檢視
日志。Log
Log 日志輸出:
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 29 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTopActivity TaskId: 29 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTopActivity TaskId: 29 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
總結:
- 第一次建立
時,回調了SingleTopActivity
方法,onCreate
值一樣,推出結論: 目前棧中不存在該taskId
的執行個體時,其行為同Activity
啟動模式。standard
-
傳回到OhterTopActivity
時,從日志看出SingleTopActivity
方法 被調用,且onCreate
值 與hasCode
第一次建立時 hasCode 值不一樣,推出結論: 目前棧中已有該SingleTopActivity
的執行個體但是該執行個體不在棧頂時,其行為和Activity
啟動模式一樣,依然會建立一個新的執行個體。standard
-
做跳轉自身三次操作的 hasCode 值都一樣,推出結論: 目前棧中已有該SingleTopActivity
的執行個體并且該執行個體位于棧頂時,不會建立執行個體,而是複用棧頂的執行個體,并且會将Activity
對象傳入,回調Intent
方法。onNewIntent
2.3 singleTask
- 棧内複用模式。
在講解此模式前,先說一下什麼是
Activity
的任務棧 ?
前文提及過
taskAffinity
這個參數,可以翻譯為
任務相關性
。 這個參數辨別了一個
Activity
所需要的任務棧的名字。
預設情況下,所有的
Activity
所需要的任務棧的名字為應用的包名。
當然,我們也可以為每一個
Activity
單獨指定該屬性。
設定了相同
taskAffinity
屬性的
Activity
在同一個任務棧中。如果你為某一個
Activity
的
taskAffinity
屬性設定為空字元,那麼表示該
Activity
不屬于任何task。
standard
和
singleTop
啟動模式都是在原任務棧中建立
Activity
執行個體,不會啟動新的Task,即使你指定了
taskAffinity
屬性。
通俗易懂的說一下此模式:
當一個具有
singleTask
模式的
Activity
請求啟動後,例如
Activity A
,系統首先會尋找是否存在 A 想要的任務棧,如果不存在,就重新建立一個任務棧。然後建立 A 的執行個體,把 A 放入棧中。如果存在 A 所需的任務棧,這時要看 A 是否在棧中有執行個體存在,如果有執行個體存在,系統會把A調到棧頂并調用
onNewIntent()
方法,如果執行個體不存在,就建立 A 的執行個體并把 A 壓入棧中。
舉例說明:
- 目前任務棧 S1 的情況是 ABC,這個時候
以Activity D
模式請求啟動,所需要的任務棧為 S2,由于 S2 和 D 的執行個體都不存在,是以系統會先建立任務棧 S2,然後在建立singleTask
的執行個體并将其入棧 S2。Activity D
- 假設
所需要的任務棧為 S1,其它如1所示,那麼由于 S1 已經存在,那麼系統會直接建立 D 的執行個體并将其壓入棧 S1。Activity D
- 如果 D 需要的任務棧是 S1,且目前 S1 的情況是 ADBC,A棧底,C棧頂,根據棧内複用原則,此時 D 不會被重新建立,系統會把D切換到棧頂并調用
方法,同時由于onNewIntent
預設具有singleTask
方法,會導緻棧内所有的在 D 上面的clearTop
全部出棧。Activity
代碼驗證:
- 建立
,啟動模式為SingleTaskActivity
,同時建立singleTask
。OtherTaskActivity
- 做以下操作:
跳轉到MainActivity
,SingleTaskActivity
跳轉到SingleTaskActivity
,OtherTaskActivity
回跳到OtherTaskActivity
,SingleTaskActivity
點選跳轉自身按鈕兩次。SingleTaskActivity
- 檢視
日志。Log
Log
日志輸出:
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 31 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTaskActivity TaskId: 31 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
上面的操作都是在未給
SingleTaskActivity
指定
taskAffinity
值的情況下,根據日志:
OtherActivity
跳回
SingleTaskActivity
,
SingleTaskActivity
并未建立,複用了該執行個體,由
hasCode
值可看出,且調用了
onNewIntent
方法。同時,
OhterActivity
出棧了,可以通過 指令
adb shell dumpsys activity
,在顯示的内容中檢視
Running activities
這一塊顯示區域。
下面,我們給
SingleTaskActivity
指定一下
taskAffinity
的值。注意,這個屬性的值時字元串,且中間必須包含分隔符
.
。
<activity
android:name=".SingleTaskActivity"
android:launchMode="singleTask"
android:taskAffinity="com.example.activitylanuchmode.singletask"
/>
重複之前的操作,再來看一下日志:
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 33 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTaskActivity TaskId: 34 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
從日志中可以看到差別就是
taskAffinity
的值不同了,說明
SingleTaskActivity
所屬的任務棧不同于
MainActivity
了。
總結
-
模式的singleTask
啟動時,首先根據Activity
屬性查找目前是否存在一個對應名字的任務棧。taskAffinity
- 若不存在該屬性的任務棧,則會建立一個新的Task,并建立新的
執行個體入棧到新建立的Activity
中去。Task
- 若存在該屬性的任務棧,則查找該任務棧中是否存在該
執行個體。Activity
- 若存在該執行個體,則将它上面的所有
執行個體都出棧,然後回調啟動的Activity
執行個體的Activity
方法onNewIntent
- 若不存在該執行個體,則建立
入棧。Activity
2.4 singleInstance
- 單執行個體模式,加強版
。singleTask
簡單說明一下此模式
具備
singleTask
模式的所有特性,還加強了一點,就是具有此模式的 Activity 隻能單獨地位于一個任務棧中。
舉例說明
比如
Activity A
是
singleInstance
模式,那當 A 啟動後,系統會為它建立一個新的任務棧,然後 A 獨自在這個任務棧中,由于棧内複用特性,後續的請求都不會建立新的
Activity
,除非這個獨特的任務棧被系統銷毀了。
2.5 Flags
标記位
Flags
Activity
的标記位有很多,這裡說一些常用的。
-
: 為 Activity 指定 “singleTask” 啟動模式,其效果和在xml中指定該啟動模式相同。FLAG_ACTIVITY_NEW_TASK
-
: 為 Activity 指定 “singleTop” 啟動模式, 其效果和在xml中指定該啟動模式相同。FLAG_ACTIVITY_SINGLE_TOP
-
: 啟動有此辨別的FLAG_ACTIVITY_CLEAR_TOP
,在同一個任務棧中,所有位于它上面的Activity
都要出棧。Activity
-
: 有此辨別的FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
不會出現在曆史Activity
清單中,等同于在XML中指定Activity
的屬性Activity
。android:excludeFromRecents="true"
至于
Activity
的
flag
如何使用呢?
前面介紹了四種啟動模式,我們給
Activity
指定啟動模式都是在
AndroidMenifest.xml
檔案中設定的,其實還有一種方法,通過在
Intent
中設定,舉例設定
singleTask
,代碼如下:
Intent intent = new Intent();
intent.setClass(this,SingleTaskActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
三、 IntentFilter
比對規則
IntentFilter
隐式調用需要
Intent
能夠比對目标元件的
IntentFilter
中所設定的過濾資訊,是以我們需要對
IntentFilter
的過濾資訊有所了解。
IntentFilter
的過濾資訊有 action
、 category
、 data
。
IntentFilter
action
category
data
幾個要點說明:
- 一個
隻要能比對任意一組Intent
即可成功啟動對應的intent-filter
。Activity
- 一個過濾清單中的
、action
、category
可以有多個。例如下面:data
<activity android:name=".MainActivity"
android:configChanges="screenSize|orientation">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- 一個
可以有多個Activity
(過濾清單)。例如下面:intent-filter
<activity android:name=".MainActivity"
android:configChanges="screenSize|orientation" >
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPL>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
好了,大概的說了一下
IntentFilter
的幾個小的注意點,下面來具體說一說那三個過濾資訊。
3.1 Action
的比對規則
Action
-
是一個字元串。action
-
區分大小寫。action
-
中必須存在一個Intent
。action
-
中的Intent
和action
中的 任意一個intent-filter
的字元串值完全相同,則action
比對成功。action
3.2 category
的比對規則
category
-
是一個字元串。category
-
中的Intnet
必須和category
中的intent-filter
相同。category
- 如果
沒有Intent
,那麼為了category
能夠被隐式接收,就需要在activity
中添加intent-filter
。"android.intent.category.DEFAULT"
3.3 `data 的比對規則
首先看一下
data
的結構:
<data android:scheme="String"
android:host="String"
android:path="String"
android:pathPattern="String"
android:pathPrefix="String"
android:mimeType="String"
data
由兩個部分組成:
mimeType
和
URL
。
mimeType
: 媒體類型,例如
img
、
jpeg
等。
URL
:
<Scheme>://<host>:<pot>/[<path>|<pathPrefix>|<pathPattern>]
// 解釋說明:
Scheme: URL 模式。例如 http、file、content
Host: URL 主機名。例如 www.baidu.com
Port: URL 端口号。例如 80
Path、pathPrefix、pathPattern: 表示路徑資訊。
data
的比對規則與
action
的比對規則類似。
-
中必須有一個Intent
。data
-
中的Intent
必須完全比對data
中的intent-filter
。data
舉例說明
<intent-fileter
<data android:mimeType="image/*"/>
...
</intent-filter>
上述規則制定了媒體類型為所有類型的圖檔,沒有指定
URL
,但是
URL
有預設值:
content
和
file
。
如何比對上述過濾資訊,如下:
intent.setDataAndType(Uri.parse("file://abc","image/png"));
有一類
action
和
category
比較重要:
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
如上的二者缺一不可,表明這是一個入口
Activity
,并且會出現在系統的應用清單中。
好了,關于
IntentFilter
的過濾規則就說完了,下面給出一個完整的舉例說明:
過濾規則:
<activity android:name=".DemoActivity"
android:configChanges="screenSize|orientation">
<intent-filter>
<action android:name="com.example.intentfilter.demo"/>
<category android:name="android.intent.category.demo"/>
<data android:mimtType="text/plain">
</intent-filter>
</activity>
比對:
Intent intent = new Intent("com.example.intentfilter.demo");
intent.addCategory("com.example.category.demo");
intent.setDataAndType(Uri.parse("file://abc"),"text/plain");
startActivity(intent);
寫在文末
紙上得來終覺淺,絕知此事要躬行。 《冬夜讀書示子聿》-- 陸遊
至此,
Activity
的基礎知識點算是梳理了一遍,各位看官食用愉快。