android的啟動模式是在我們日常開發中經常用使用到,這個也是在面試用經常問到的一個問題。雖然我們對他很熟悉,但也會有些地方瞭解的太全面,因此寫篇文章來來總結這方面的知識。文章主要內容來自《android開發藝術探討》這本書,在文章的最後這本書的網頁版本可供檢視。
專案原始碼
目錄
- 四中啟動模式
- 什麼是任務棧
- Activity如何指定需要的任務棧
- TaskAffinity使用場景
- Activity 的Flags
- IntentFilter的匹配規則
- 如何判斷隱式啟動是否成功
1. 四中啟動模式
standard: 標準啟動模式
- 每啟動一個Activity都會重新建立,不管這個例項是否存在。
<!--系統預設啟動方式,不需要指定launchMode值-->
<activity
android:name=".StandardActivity"/>
複製程式碼
下面內容摘自《android開發藝術探討》第一章16頁底部
在standard模式下,誰啟動了這個Activity那麼這個Activity就執行在它的任務棧中。例如:ActivityA啟動了ActivityB(B為標準模式),那麼ActivityB就會進入ActivityA的任務棧中。
啟動Activity的時候傳入的Context不要是ApplicationContext。如果一定要傳,那麼一定要設定
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
否則回報下面異常:
singleTop: 棧頂複用模式
- 新啟動Activity棧頂已經存在,不會重新建立,同時會呼叫onNewIntent方法
- Activity如果存在,但是不再棧頂,則會重新建立,並將新的Activity壓入棧頂。
<activity
android:name=".SingleTopActivity"
android:launchMode="singleTop"/>
複製程式碼
singleTask: 棧內複用模式
- 只要Activity在棧記憶體在,多次啟動此Activity也不會建立新的例項,並且系統會呼叫onNewIntent方法
- 如果Activity在棧記憶體在,但是沒有在棧頂,系統會將該Activity之上的Activity全部擠出棧頂,使該Activity位於棧頂。
<activity
android:name=".SingleTaskActivity"
android:launchMode="singleTask" />
複製程式碼
singleInstance: 單例項模式
- 該Activity只能單獨位於一個任務棧中
<activity
android:name=".SingleInstanceActivity"
android:launchMode="singleInstance" >
複製程式碼
生命週期執行:
- singleTask、singleInstance、singleInstance模式下,如果啟動該Activity正好在頂部。那麼他的生命週期執行為:
onPause-->onNewIntent-->onResume
複製程式碼
- singleTask、singleInstance模式下,如果棧內有Activity例項,但不在棧頂。那麼生命週期執行如下
onNewIntent-->onRestart-->onStart
複製程式碼
2. 什麼是任務棧
檢視activity在棧中的情況,可在控制檯輸入:adb shell dumpsys activity activities 通過搜尋關鍵字 most recent first 快速定位 留意包名
任務棧(Task):
Task特點:
- android的任務棧主要用於存放Activity,遵循先進後出的原則。
- android的任務棧是一個包含了Activity的集合,我們每次開啟新的Activity或者關閉一個Activity任務棧中就會增加或減少一個Activity元件。
- 任務棧在沒有Activity或者App退出的時候都會被銷燬。
- 一個App不止有一個任務棧,任務棧的Activity可以來自不同的App,同一個App的Activity也可以不再一個任務棧中。
3. Activity如何指定需要的任務棧
Activity指定需要啟動的任務棧可以用過在配置檔案中新增taskAffinity屬性來實現。
TaskAffinity特點:
- 預設情況下,Activity啟動的任務棧名稱為應用包名。
- 如果自己指定該屬性值,不能與包名相同,否則相當於沒指定。
- 該屬於一般配合singleTask或者allowTaskReparenting屬性結合使用,在其他情況下沒有實際意義。
<activity android:name=".TestActivity"
android:launchMode="singleTask"
android:taskAffinity="com.test.singleTask.affinity"/>
<activity android:name=".Test2ActivityC"
android:exported="true"
android:allowTaskReparenting="true"/>
複製程式碼
4. TaskAffinity使用場景
下面這段內容摘自Activity啟動模式與任務棧(Task)全面深入記錄(下)這篇文章。
TaskAffinity與singleTask應用場景
假如現在有這麼一個需求,我們的客戶端app正處於後臺執行,此時我們因為某些需要,讓微信呼叫自己客戶端app的某個頁面,使用者完成相關操作後,我們不做任何處理,按下回退或者當前Activity.finish(),頁面都會停留在自己的客戶端(此時我們的app回退棧不為空),這顯然不符合邏輯的,使用者體驗也是相當出問題的。我們要求是,回退必須回到微信客戶端,而且要保證不殺死自己的app.這時候我們的處理方案就是,設定當前被調起Activity的屬性為:
LaunchMode=""SingleTask" taskAffinity="com.tencent.mm"
其中com.tencent.mm是藉助於工具找到的微信包名,就是把自己的Activity放到微信預設的Task棧裡面,這樣回退時就會遵循“Task只要有Activity一定從本Task剩餘Activity回退”的原則,不會回到自己的客戶端;而且也不會影響自己客戶端本來的Activity和Task邏輯。
TaskAffinity與allowTaskReparenting應用場景
一個e-mail應用訊息包含一個網頁連結,點選這個連結將出發一個activity來顯示這個頁面,雖然這個activity是瀏覽器應用定義的,但是activity由於e-mail應用程式載入的,所以在這個時候該activity也屬於e-mail這個task。如果e-mail應用切換到後臺,瀏覽器在下次開啟時由於allowTaskReparenting值為true,此時瀏覽器就會顯示該activity而不顯示瀏覽器主介面,同時actvity也將從e-mail的任務棧遷移到瀏覽器的任務棧,下次開啟e-買了時並不會再顯示該activity。
Taskffinity與singleTask例項:
注: 如果使用我在GitHub上建立的專案測這個功能的時候,請將TestTaskffinityOrAllowTaskRep.zip這個壓縮包解壓,並匯入AS中。這個壓縮包是我在測試的時候寫的用於跳轉androidreview應用的testTask應用。
testTask應用(簡稱T應用)MainActivity中有一個按鈕A,點選按鈕會呼叫androidreview應用(簡稱A應用)的SingleTaskActivity。下面是兩個應用的主要程式碼。
testTask應用程式碼:
switch (v.getId()) {
case R.id.mBnt_ForSingleTask:
//跳轉androidreview應用SingleToak頁面的按鈕方法
ComponentName cnForSingleTask = new ComponentName(
"com.hdd.androidreview",
"com.hdd.androidreview.Patterm.SingleTaskActivity");
intent.setComponent(cnForSingleTask);
startActivity(intent);
break;
}
複製程式碼
androidreview應用程式碼:
<activity
android:name=".Patterm.SingleTaskActivity"
android:exported="true"
android:launchMode="singleTask"
android:taskAffinity="cmom.han.testbt.testTask">
</activity>
複製程式碼
從上面的A應用程式碼配置資訊中可以看到taskAffinity屬性配置的是T應用的包名。因此SingleTaskActivity會在T的任務中被建立。假如T應用MainActivity的按鈕A點選事件中啟動了SingleTaskActivity。那麼cmom.han.testbt.testTask任務棧中會存在SingleTaskActivity和MainActivity兩個Activity。下面是Activity在棧中的資訊。
這時如果按了hone鍵T應用返回桌面,SingleTaskActivity進入後臺;然後點選A應用的圖示啟動的卻是A應用的MainActivity。出現這種情況是因為SingleTaskActivity的taskAffinity屬性指定的是T應用包名。在T應用的MainActivity啟動的SingleTaskActivy是在T應用的任務棧中。TaskAffinity與allowTaskReparenting例項 T應用的B按鈕點選事件程式碼:
case R.id.mBnt_ForAllowTaskRep:
//allowTaskReparenting模式跳轉PattermActivity
intent.setAction("com.hdd.androidreview.PattermActivity");
ComponentName cnForAllowTaskRep = new ComponentName(
"com.hdd.androidreview",
"com.hdd.androidreview.Patterm.PattermActivity");
intent.setComponent(cnForAllowTaskRep);
break;
複製程式碼
A應用的PattermActivity(簡稱PActivity)的配置資訊:
<activity
android:name=".Patterm.PattermActivity"
android:allowTaskReparenting="true"
android:theme="@style/AppTheme.NoActionBar" >
<intent-filter>
<action android:name="com.hdd.androidreview.PattermActivity"/>
</intent-filter>
</activity>
複製程式碼
點選T用於的B按鈕,會啟動A用的PActivity。該Activity的allowTaskReparenting屬性為true,那麼Activity會被移動到T應用的任務中站建立。當T應用按home返回桌面,再點選A應用;PActivity會被移回A的任務棧中。
T應用呼叫A應用的PActivity棧內資訊
T應用按home返回桌面,在啟動A應用棧內資訊:
allowTaskReparenting僅限於以standard 和singleTop啟動的activity使用
5. Activity 的Flags
指定Activity的啟動模式有兩種,一種是在AndroidMenifest.xml中指定
<activity
android:name=".Patterm.SingleInstanceActivity"
android:launchMode="singleInstance">
複製程式碼
另外一種是通過Intent來指定
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setClass(context, CycleActivity.class);
context.startActivity(intent);
複製程式碼
注:intent指定的優先順序大於xml中指定的優先順序;如果兩個方式都指定了啟動方式,那麼系統會以intent指定的啟動方式為準。
FLAG_ACTIVITY_NEW_TASK
等同於在xml中配置了singleTask啟動碼模式
FLAG_ACTIVITY_SINGLE_TOP
等同於在xml中配置了singleTop啟動碼模式
FLAG_ACTIVITY_CLEAR_TOP
singTask自帶該標記。這個標記會清除同一個任務棧中目標Activity之上的Activity。 如果目標Activity採用了standard啟動模式,但是任務棧中已經存在了Activity的例項。那麼系統會清除任務中該例項以及它上面的Activity,並且會重新建立一個目標Activity例項放入棧頂。
6. IntentFilter的匹配規則
啟動Activity有兩種,一種為顯示呼叫
Intent intent = new Intent(MainActivity.this, TestActivity.class);
startActivity(intent);
複製程式碼
另一種為隱式呼叫要配置清單檔案
<!--隱式呼叫-->
<activity
android:name=".Patterm.PattermActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="com.hdd.androidreview.asdf" />
<category android:name="com.hdd.123456" />
<!--比就加上,否則會報錯-->
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
複製程式碼
Activity跳轉程式碼
Intent intent = new Intent();
intent.setAction("com.hdd.androidreview.asdf");
//category非必須指定。如果要指定,一點要和清單檔案中填寫的一至
// intent.addCategory("com.hdd.123456");
context.startActivity(intent);
複製程式碼
1. action
- 可以再清單檔案中配置多個
- intent指定的action值只要和清單檔案中的其中一個字串值一樣,即可匹配成功。
- 如果清單檔案中配置了action,那麼在intent跳轉中必須至少指定且配對成功一個。
<activity
android:name=".Patterm.PattermActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="com.hdd.androidreview.asdf" />
<action android:name="com.hdd.androidreview.qwer" />
<action android:name="12345678" />
<!--必須加上,否則會報錯-->
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
//java程式碼
Intent intent = new Intent();
intent.setAction("12345678");
context.startActivity(intent);
複製程式碼
2. category
- category可以再清單檔案中新增多個
- intent的addCategory()字串有一個相同就可以匹配成功。
- 他和action區別是,action是要在intent必須要指定的,切至少和一個匹配成功。category可以不用指定。如果指定也是至少和一個匹配成功。
Intent intent = new Intent();
//必須指定一個並匹配成功
intent.setAction("com.hdd.androidreview.asdf");
//category非必須指定。如果要指定,一點要和清單檔案中填寫的一至
// intent.addCategory("com.hdd.123456");
context.startActivity(intent);
複製程式碼
3. data
-
data的匹配規則和action類似,如果過濾規則中定義了data,那麼intent中必須要匹配data。
-
data有mimeType和URL兩部分組成
mimeType為媒體型別
: image/jpeg、audio/mpeg4-generic以及video/*等。
URL資料結構為:
<scheme>://<host>:<prot>/[<path>|<pathPrefix>|<pathPattern>]
複製程式碼
例如:
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info
複製程式碼
Scheme:URL的模式,比如http、file、content等;如果URL沒有指定scheme,這個URL是無效的。
Host:URL主機名,比如www.baidu.com,如果未指定,URL無效。
Port:RUL埠號,比如80,只有scheme和host指定了port才有意義。
Path:表示完整的路徑資訊。 PathPattern:表示完整路徑資訊,裡面可以包含萬用字元。 PathPrefix:表示路徑的字首資訊
data在定義的時候有兩種情況需要注意
- 第一種情況
非完整寫法,及只指定了mimeType或者只指定了URL。
注:如果只指定了URL,系統會預設設定mimeType的值為content和file
<!-- 隱式呼叫,只配置URL -->
<activity android:name=".RegulationActivity">
<intent-filter>
<action android:name="12345678" />
<!-- 必須加上,否則會報錯 -->
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="www.baidu.com"
android:scheme="http" />
</intent-filter>
</activity>
<!-- 隱式呼叫,只配置mimeType· -->
<activity android:name=".RegulationActivity">
<intent-filter>
<action android:name="12345678" />
<!-- 必須加上,否則會報錯 -->
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="audio/mpeg" />
</intent-filter>
</activity>
複製程式碼
java程式碼:
//intent指定只配置了URL的data
intent.setAction("12345678");
intent.setData(Uri.parse("http://www.baidu.com"));
context.startActivity(intent);
//intent指定只配置了mimeType的data
intent.setAction("12345678");
intent.setType("audio/mpeg");
context.startActivity(intent);
複製程式碼
- 第二種情況
完整寫法
注:如果intent指定的為完整的data,必須要使用setDataAndType(),因為setData()和setType()會彼此清楚對方的值。
<!-- 隱式呼叫 -->
<activity android:name=".RegulationActivity">
<intent-filter>
<action android:name="12345678" />
<!-- 必須加上,否則會報錯 -->
<category android:name="android.intent.category.DEFAULT" />
<data
android:mimeType="audio/mpeg"
android:host="www.baidu.com"
android:scheme="http" />
</intent-filter>
</activity>
複製程式碼
java程式碼:
//intent完整的data
intent.setAction("12345678");
intent.setDataAndType(Uri.parse("http://www.baidu.com"), "audio/mpeg");
context.startActivity(intent);
複製程式碼
還有一種特殊寫法:
//寫法1
<intent-filter>
<data
android:mimeType="audio/mpeg"
android:host="www.baidu.com"
android:scheme="http" />
</intent-filter>
//寫法2
<intent-filter>
<data android:mimeType="audio/mpeg" />
<data android:scheme="http" />
<data android:host="www.baidu.com" />
</intent-filter>
複製程式碼
上面的兩個寫法上在使用效果上是一樣的。
7. 如何判斷隱式啟動是否成功
第一種,使用intent的resolveActivity
ComponentName componentName = intent.resolveActivity(context.getPackageManager());
if (componentName != null)
context.startActivity(intent);
else
Toast.makeText(context, "匹配不成功", Toast.LENGTH_SHORT).show();
複製程式碼
第二種,使用PackageManager的resolveActivity
PackageManager packageManager=context.getPackageManager();
ResolveInfo resolveInfo = packageManager.resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY);
//判斷是否匹配成功
if (resolveInfo != null)
context.startActivity(intent);
else
Toast.makeText(context, "匹配不成功", Toast.LENGTH_SHORT).show();
複製程式碼
上面的程式碼中返回ResolveInfo不是最佳的Activity資訊,而是所有匹配成功的Activity資訊。resolveActivity()填寫的第二個引數必須是MATCH_DEFAULT_ONLY。具體原因可以檢視《android開發藝術探索》第一章34頁,這裡就不解釋了。
終結:
用了3天終於寫完了!其實寫這篇文章是為了複習android的基礎知識,在複習過程中增加了對Activity啟動模式以及Activity任務棧中的狀態的瞭解。文章中四個啟動模式中最麻煩的模式個人認為是singTask,最有意思的為Taskffinerty這個標籤的使用,自己可以寫個demo或者使用我在GitHub上的專案測試。