編輯:關於Android編程
在Android應用開發中,打造良好的用戶體驗是非常重要的。而在用戶體驗中,界面的引導和跳轉是值得深入研究的重要內容。在開發中,與界面跳轉聯系比較緊密的概念是Task(任務)和Back Stack(回退棧)。activity的啟動模式會影響Task和Back Stack的狀態,進而影響用戶體驗。除了啟動模式之外,Intent類中定義的一些標志(以FLAG_ACTIVITY_開頭)也會影響Task和Back Stack的狀態。在這篇文章中主要對四種啟動模式進行分析和驗證,其中涉及到activity的一個重要屬性taskAffinity和Intent中的標志之一FLAG_ACTIVITY_NEW_TASK。關於Intent中其他標志位的具體用法會在另一篇文章中介紹。
Task是一個存在於Framework層的概念,容易與它混淆的有Application(應用)和Process(進程)。在開始介紹Activity的啟動模式的使用之前,首先對這些概念做一個簡單的說明和區分。
一 Application,Task和Process的區別與聯系
application翻譯成中文時一般稱為“應用”或“應用程序”,在android中,總體來說一個應用就是一組組件的集合。眾所周知,android是在應用層組件化程度非常高的系統,android開發的第一課就是學習android的四大組件。當我們寫完了多個組件,並且在manifest文件中注冊了這些組件之後,把這些組件和組件使用到的資源打包成apk,我們就可以說完成了一個application。application和組件的關系可以在manifest文件中清晰地體現出來。如下所示:
android:versionName="1"
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.myapp">
由此可見,application是由四大組件組成的。在app安裝時,系統會讀取manifest的信息,將所有的組件解析出來,以便在運行時對組件進行實例化和調度。
而task是在程序運行時,只針對activity的概念。說白了,task是一組相互關聯的activity的集合,它是存在於framework層的一個概念,控制界面的跳轉和返回。這個task存在於一個稱為back stack的數據結構中,也就是說,framework是以棧的形式管理用戶開啟的activity。這個棧的基本行為是,當用戶在多個activity之間跳轉時,執行壓棧操作,當用戶按返回鍵時,執行出棧操作。舉例來說,如果應用程序中存在A,B,C三個activity,當用戶在Launcher或Home Screen點擊應用程序圖標時,啟動主Activity A,接著A開啟B,B開啟C,這時棧中有三個Activity,並且這三個Activity默認在同一個任務(task)中,當用戶按返回時,彈出C,棧中只剩A和B,再按返回鍵,彈出B,棧中只剩A,再繼續按返回鍵,彈出A,任務被移除。如下圖所示:
task是可以跨應用的,這正是task存在的一個重要原因。有的Activity,雖然不在同一個app中,但為了保持用戶操作的連貫性,把他們放在同一個任務中。例如,在我們的應用中的一個Activity A中點擊發送郵件,會啟動郵件程序的一個Activity B來發送郵件,這兩個activity是存在於不同app中的,但是被系統放在一個任務中,這樣當發送完郵件後,用戶按back鍵返回,可以返回到原來的Activity A中,這樣就確保了用戶體驗。
說完了application和task,最後介紹process。process一般翻譯成進程,進程是操作系統內核中的一個概念,表示直接受內核調度的執行單位。在應用程序的角度看,我們用java編寫的應用程序,運行在dalvik虛擬機中,可以認為一個運行中的dalvik虛擬機實例占有一個進程,所以,在默認情況下,一個應用程序的所有組件運行在同一個進程中。但是這種情況也有例外,即,應用程序中的不同組件可以運行在不同的進程中。只需要在manifest中用process屬性指定組件所運行的進程的名字。如下所示:
這樣的話這個activity會運行在一個獨立的進程中。
二 Activity四種啟動模式詳解
activity有四種啟動模式,分別為standard,singleTop,singleTask,singleInstance。如果要使用這四種啟動模式,必須在manifest文件中標簽中的launchMode屬性中配置,如:
android:label="@string/interstitial_label"
android:theme="@style/Theme.Dialog"
android:launchMode="singleTask"
同樣,在Intent類中定義了很多與Activity啟動或調度有關的標志,標簽中有一些屬性,這些標志,屬性和四種啟動模式聯合使用,會在很大程度上改變activity的行為,進而會改變task和back stask的狀態。關於Intent中的標志和標簽中有一些屬性會在本文後面介紹,在這一節中,先介紹activity的四種啟動模式。
standard
標准啟動模式,也是activity的默認啟動模式。在這種模式下啟動的activity可以被多次實例化,即在同一個任務中可以存在多個activity的實例,每個實例都會處理一個Intent對象。如果Activity A的啟動模式為standard,並且A已經啟動,在A中再次啟動Activity A,即調用startActivity(new Intent(this,A.class)),會在A的上面再次啟動一個A的實例,即當前的桟中的狀態為A-->A。
singleTop
如果一個以singleTop模式啟動的activity的實例已經存在於任務桟的桟頂,那麼再啟動這個Activity時,不會創建新的實例,而是重用位於棧頂的那個實例,並且會調用該實例的onNewIntent()方法將Intent對象傳遞到這個實例中。舉例來說,如果A的啟動模式為singleTop,並且A的一個實例已經存在於棧頂中,那麼再調用startActivity(new Intent(this,A.class))啟動A時,不會再次創建A的實例,而是重用原來的實例,並且調用原來實例的onNewIntent()方法。這是任務桟中還是這有一個A的實例。
如果以singleTop模式啟動的activity的一個實例已經存在與任務桟中,但是不在桟頂,那麼它的行為和standard模式相同,也會創建多個實例。
singleTask
谷歌的官方文檔上稱,如果一個activity的啟動模式為singleTask,那麼系統總會在一個新任務的最底部(root)啟動這個activity,並且被這個activity啟動的其他activity會和該activity同時存在於這個新任務中。如果系統中已經存在這樣的一個activity則會重用這個實例,並且調用他的onNewIntent()方法。即,這樣的一個activity在系統中只會存在一個實例。
其實官方文檔中的這種說法並不准確,啟動模式為singleTask的activity並不會總是開啟一個新的任務。詳情請參考解開Android應用程序組件Activity的"singleTask"之謎,在本文後面也會通過示例來進行驗證。
singleInstance
總是在新的任務中開啟,並且這個新的任務中有且只有這一個實例,也就是說被該實例啟動的其他activity會自動運行於另一個任務中。當再次啟動該activity的實例時,會重用已存在的任務和實例。並且會調用這個實例的onNewIntent()方法,將Intent實例傳遞到該實例中。和singleTask相同,同一時刻在系統中只會存在一個這樣的Activity實例。
三 實例驗證singleTask啟動模式
上面將activity的四種啟動模式就基本介紹完了。為了加深對啟動模式的了解,下面會通過一個簡單的例子進行驗證。由以上的介紹可知,standard和singleTop這兩種啟動模式行為比較簡單,所以在下面的例子中,會對singleTask和singleInstance著重介紹。
驗證啟動singleTask模式的activity時是否會創建新的任務
以下為驗證示例AndroidTaskTest。這個實例中有三個Activity,分別為:MainActivity,SecondActivity和ThirdActivity。以下為這個示例的manifest文件。
android:versionCode="1"
android:versionName="1.0">
android:launchMode="singleTask">
android:label="@string/app_name">
由此可見,MainActivity和ThirdActivity都是標准的啟動模式,而SecondActivity的啟動模式為singleTask。
以下為這三個Activity的界面,很簡單,在MainActivity中點擊按鈕啟動SecondActivity,在SecondActivity中點擊按鈕啟動ThirdActivity。
以下為這三個activity的主要代碼:
MainActivity
publicclassMainActivityextendsActivity{
privatestaticfinalStringACTIVITY_NAME="MainActivity";
privatestaticfinalStringLOG_TAG="xxxx";
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button1).setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
Intentintent=newIntent(MainActivity.this,SecondActivity.class);
startActivity(intent);
}
});
inttaskId=getTaskId();
Log.i(LOG_TAG,ACTIVITY_NAME+"所在的任務的id為:"+taskId);
}
SecondActivity
publicclassSecondActivityextendsActivity{
privatestaticfinalStringACTIVITY_NAME="SecondActivity";
privatestaticfinalStringLOG_TAG="xxxx";
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
findViewById(R.id.button2).setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
Intentintent=newIntent(SecondActivity.this,ThirdActivity.class);
startActivity(intent);
}
});
inttaskId=getTaskId();
Log.i(LOG_TAG,ACTIVITY_NAME+"所在的任務的id為:"+taskId);
}
ThirdActivity
publicclassThirdActivityextendsActivity{
privatestaticfinalStringACTIVITY_NAME="ThirdActivity";
privatestaticfinalStringLOG_TAG="xxxx";
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
inttaskId=getTaskId();
Log.i(LOG_TAG,ACTIVITY_NAME+"所在的任務的id為:"+taskId);
}
以上三個activity只列出了onCreate()方法中的內容,實現的邏輯為在MainActivity中點擊按鈕啟動SecondActivity,在SecondActivity中點擊按鈕啟動ThirdActivity。並且在onCreate方法中會以log的形式打印出當前activity所屬的任務(Task)的Id。
現在執行以下操作,運行該示例,並且點擊MainActivity界面中的按鈕,開啟SecondActivity。在該示例中SecondActivity的啟動模式為singleTask。按照官方文檔的說法,SecondActivity會在一個新的任務中開啟。但是查看打印出的log,發現MainActivity和SecondActivity所在的任務的Id相同。
在命令行中執行以下命令 adb shell dumpsys activity , 有以下輸出:
TaskRecord{412ded08 #8 A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{412c91e8 com.jg.zhang.androidtasktest/.SecondActivity}
Run #1: ActivityRecord{412c08a0 com.jg.zhang.androidtasktest/.MainActivity}
所以,和官方文檔表述的不同,MainActivity和SecondActivity是啟動在同一個任務中的。其實,把啟動模式設置為singleTask,framework在啟動該activity時只會把它標示為可在一個新任務中啟動,至於是否在一個新任務中啟動,還要受其他條件的限制。現在在SecondActivity增加一個taskAffinity屬性,如下所示:
android:launchMode="singleTask"
android:taskAffinity="com.jg.zhang.androidtasktest.second">
lt;/activity>
重新運行該示例,執行相同的操作,即:點擊MainActivity界面中的按鈕,開啟SecondActivity,並且點擊SecondActivity中的按鈕,啟動ThirdActivity,log中輸出的內容為:
在命令行中執行adb shell dumpsys activity命令,有以下輸出:
TaskRecord{411e6a88 #6 A com.jg.zhang.androidtasktest.second}
Run #3: ActivityRecord{411c8ea0 com.jg.zhang.androidtasktest/.ThirdActivity}
Run #2: ActivityRecord{412bc870 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{412ece18 #5 A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{412924c0 com.jg.zhang.androidtasktest/.MainActivity}
由此可見,MainActivity和SecondActivity運行在不同的任務中了,並且被SecondActivity啟動的ThirdActivity和SecondActivity運行在同一個任務中
在這裡便引出了manifest文件中的一個重要屬性,taskAffinity。在官方文檔中可以得到關於taskAffinity的以下信息
taskAffinity表示當前activity具有親和力的一個任務(翻譯不是很准確,原句為The task that the activity has an affinity for.),大致可以這樣理解,這個taskAffinity表示一個任務,這個任務就是當前activity所在的任務。在概念上,具有相同的affinity的activity(即設置了相同taskAffinity屬性的activity)屬於同一個任務。一個任務的affinity決定於這個任務的根activity(root activity)的taskAffinity。這個屬性決定兩件事:當activity被re-parent時,它可以被re-paren哪個任務中;當activity以FLAG_ACTIVITY_NEW_TASK標志啟動時,它會被啟動到哪個任務中。(這個比較 難以理解,請結合中的屬性allowTaskReparenting和Intent中的標志 FLAG_ACTIVITY_NEW_TASK加以理解)默認情況下,一個應用中的所有activity具有相同的taskAffinity,即應用程序的包名。我們可以通過設置不同的taskAffinity屬性給應用中的activity分組,也可以把不同的 應用中的activity的taskAffinity設置成相同的值。為一個activity的taskAffinity設置一個空字符串,表明這個activity不屬於任何task。
這就可以解釋上面示例中的現象了,由第5條可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity為com.jg.zhang.androidtasktest,SecondActivity的taskAffinity為com.jg.zhang.androidtasktest.second,根據上面第4條,taskAffinity可以影響當activity以FLAG_ACTIVITY_NEW_TASK標志啟動時,它會被啟動到哪個任務中。這句話的意思是,當新啟動的activity(SecondActivity)是以FLAG_ACTIVITY_NEW_TASK標志啟動時(可以認為FLAG_ACTIVITY_NEW_TASK和singleTask作用相同,當啟動模式為singleTask時,framework會將它的啟動標志設為FLAG_ACTIVITY_NEW_TASK),framework會檢索是否已經存在了一個affinity為com.jg.zhang.androidtasktest.second的任務(即一個TaskRecord對象)
如果存在這樣的一個任務,則檢查在這個任務中是否已經有了一個SecondActivity的實例, 如果已經存在一個SecondActivity的實例,則會重用這個任務和任務中的SecondActivity實例,將這個任務調到前台,清除位於SecondActivity上面的所有Activity,顯示SecondActivity,並調用SecondActivity的onNewIntent();如果不存在一個SecondActivity的實例,會在這個任務中創建SecondActivity的實例,並調用onCreate()方法 如果不存在這樣的一個任務,會創建一個新的affinity為com.jg.zhang.androidtasktest.second的任務,並且將SecondActivity啟動到這個新的任務中
上面討論的是設置taskAffinity屬性的情況,如果SecondActivity只設置啟動模式為singleTask,而不設置taskAffinity,即三個Activity的taskAffinity相同,都為應用的包名,那麼SecondActivity是不會開啟一個新任務的,framework中的判定過程如下:
在MainActivity啟動SecondActivity時,發現啟動模式為singleTask,那麼設定他的啟動標志為FLAG_ACTIVITY_NEW_TASK然後獲得SecondActivity的taskAffinity,即為包名com.jg.zhang.androidtasktest檢查是否已經存在一個affinity為com.jg.zhang.androidtasktest的任務,這個任務是存在的,就是MainActivity所在的任務,這個任務是在啟動MainActivity時開啟的既然已經存在這個任務,就檢索在這個任務中是否存在一個SecondActivity的實例,發現不存在在這個已有的任務中啟動一個SecondActivity的實例
為了作一個清楚的比較,列出SecondActivity啟動模式設為singleTask,並且taskAffinity設為com.jg.zhang.androidtasktest.second時的啟動過程
在MainActivity啟動SecondActivity時,發現啟動模式為singleTask,那麼設定他的啟動標志為FLAG_ACTIVITY_NEW_TASK然後獲得SecondActivity的taskAffinity,即com.jg.zhang.androidtasktest.second檢查是否已經存在一個affinity為com.jg.zhang.androidtasktest.second的任務,這個任務是不存在的創建一個新的affinity為com.jg.zhang.androidtasktest.second的任務,並且將SecondActivity啟動到這個新的任務中
其實framework中對任務和activity‘的調度是很復雜的,尤其是把啟動模式設為singleTask或者以FLAG_ACTIVITY_NEW_TASK標志啟動時。所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK時,要仔細測試應用程序。這也是官方文檔上的建議。
實例驗證將兩個不同app中的不同的singleTask模式的Activity的taskAffinity設成相同
官方文檔中提到,可以把不同的 應用中的activity的taskAffinity設置成相同的值,這樣的話這兩個activity雖然不在同一應用中,卻會在運行時分配到同一任務中,下面對此進行驗證,在這裡,會使用上面的示例AndroidTaskTest,並創建一個新的示例AndroidTaskTest1。AndroidTaskTest1由兩個activity組成,分別為MianActivity和OtherActivity,在MianActivity中點擊按鈕會啟動OtherActivity,該程序的界面和上一個類似,代碼也類似,再此僅列出清單文件。
android:versionCode="1"android:versionName="1.0">
android:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/app_name"
android:theme="@style/AppTheme">
android:name="com.jg.zhang.androidtasktest1.MainActivity"
android:label="com.jg.zhang.androidtasktest1.MainActivity">
android:name="com.jg.zhang.androidtasktest1.OtherActivity"
android:label="com.jg.zhang.androidtasktest1.OtherActivity"
android:taskAffinity="com.jg.zhang.androidtasktest.second"
android:launchMode="singleTask">
可以看到OtherActivity的啟動模式被設置為singleTask,並且taskAffinity屬性被設置為com.jg.zhang.androidtasktest.second,這和AndroidTaskTest應用中的SecondActivity相同。現在將這兩個應用安裝在設備上。執行以下操作:
啟動AndroidTaskTest應用,在它的MianActivity中點擊按鈕開啟SecondActivity,由上面的介紹可知secondActivity是運行在一個新任務中的,這個任務就是com.jg.zhang.androidtasktest.second。
然後按Home鍵回到Launcher,啟動AndroidTaskTest1,在啟動AndroidTaskTest1的入口Activity(MianActivity)時,會自動啟動新的任務,那麼現在一共有三個任務,AndroidTaskTest的MianActivity和SecondActivity分別占用一個任務,AndroidTaskTest1的MianActivity也占用一個任務。
在AndroidTaskTest1的MianActivity中點擊按鈕啟動OtherActivity,那麼這個OtherActivity是在哪個任務中呢?
下面執行adb shell dumpsys activity命令,發現有以下輸出:
TaskRecord{412370c0 #4 Acom.jg.zhang.androidtasktest.second}
Intent { cmp=com.jg.zhang.androidtasktest/.SecondActivity }
Hist #4: ActivityRecord{412f5ba0com.jg.zhang.androidtasktest1/.OtherActivity}
Intent { flg=0x400000 cmp=com.jg.zhang.androidtasktest1/.OtherActivity }
ProcessRecord{412adb28 479:com.jg.zhang.androidtasktest1/10044}
Hist #3: ActivityRecord{4125c880com.jg.zhang.androidtasktest/.SecondActivity}
Intent { cmp=com.jg.zhang.androidtasktest/.SecondActivity }
ProcessRecord{41218e48 463:com.jg.zhang.androidtasktest/10043}
TaskRecord{412f0f60 #5 Acom.jg.zhang.androidtasktest1}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.jg.zhang.androidtasktest1/.MainActivity }
Hist #2: ActivityRecord{413045a8com.jg.zhang.androidtasktest1/.MainActivity}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.jg.zhang.androidtasktest1/.MainActivity }
ProcessRecord{412adb28 479:com.jg.zhang.androidtasktest1/10044}
TaskRecord{412c5928 #3 Acom.jg.zhang.androidtasktest}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.jg.zhang.androidtasktest/.MainActivity }
Hist #0: ActivityRecord{41250850com.jg.zhang.androidtasktest/.MainActivity}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.jg.zhang.androidtasktest/.MainActivity }
ProcessRecord{41218e48 463:com.jg.zhang.androidtasktest/10043}
在執行上述操作時,打印出的Log為:
所以由此可見,AndroidTaskTest的SecondActivity和AndroidTaskTest1的OtherActivity是在同一任務中的。由上面adb shell dumpsys activity命令的輸出結果(藍色字體)還可以看出,AndroidTaskTest和AndroidTaskTest1這兩個應用程序會開啟兩個進程,他們的所有組件分別運行在獨立的進程中,其中AndroidTaskTest所在進程的進程號為10043,AndroidTaskTest1所在進程的進程號為10044。com.jg.zhang.androidtasktest.second任務中的兩個activity屬於不同的應用,並且運行在不同的進程中,這也說明了一個問題:任務(Task)不僅可以跨應用(Application),還可以跨進程(Process)。
實例驗證singleTask的另一意義:在同一個任務中具有唯一性
谷歌官方文檔中提到,singleTask模式的activity總會在一個新的任務中開啟。上面已經驗證了這種說法不確切,singleTask模式只意味著“可以在一個新的任務中開啟”,至於是不是真的會在新任務中開啟,在framework中還有其他條件的限制。由上面的介紹可知,這個條件為:是否已經存在了一個由他的taskAffinity屬性指定的任務。這一點具有迷惑性,我們在看到singleTask這個單詞的時候,會直觀的想到它的本意:single in task。即,在同一個任務中,只會有一個該activity的實例。現在讓我們進行驗證:
為了驗證這種情況,需要修改一下上面用到的AndroidTaskTest示例。增加一個FourthActivity,並且MianActivity,SecondActivity,ThirdActivity和FourthActivity這四個activity都不設置taskAffinity屬性,並且將SecondActivity啟動模式設為singleTask,這樣這四個activity會在同一個任務中開啟。他們的開啟流程是這樣的:MianActivity開啟SecondActivity,SecondActivity開啟ThirdActivity,ThirdActivity開啟FourthActivity,FourthActivity開啟SecondActivity。代碼和軟件界面就不列出了,只列出清單文件
android:versionCode="1"
android:versionName="1.0">
android:icon="@drawable/ic_launcher"android:label="androidtasktest">
android:launchMode="singleTask"/>
現在從MianActivity一直啟動到FourthActivity,打印出的系統Log為:
由此可見這四個activity都是在同一個任務中的。再次執行adb shell dumpsys activity命令加以驗證:
TaskRecord{412e9458 #6 A com.jg.zhang.androidtasktest}
Run #4: ActivityRecord{412e12e8 com.jg.zhang.androidtasktest/.FourthActivity}
Run #3: ActivityRecord{412a9e30 com.jg.zhang.androidtasktest/.ThirdActivity}
Run #2: ActivityRecord{412a4dd8 com.jg.zhang.androidtasktest/.SecondActivity}
Run #1: ActivityRecord{4122fae0 com.jg.zhang.androidtasktest/.MainActivity}
同樣可以說明目前這四個activity都運行在affinity為com.jg.zhang.androidtasktest的任務中,即棧中的狀態為MainActivity --> SecondActivity -->ThirdActivity -->FourthActivity。
下面執行在FourthActivity中點擊按鈕啟動SecondActivity的操作,注意,SecondActivity的啟動模式為singleTask,那麼現在棧中的情況如何呢?再次執行adb shell dumpsys activity命令,有以下輸出:
TaskRecord{412e9458 #6 A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{412a4dd8 com.jg.zhang.androidtasktest/.SecondActivity}
Run #1: ActivityRecord{4122fae0 com.jg.zhang.androidtasktest/.MainActivity}
這時棧中的狀態為MainActivity --> SecondActivity。確實確保了在任務中是唯一的,並且清除了同一任務中它上面的所有Activity。那麼這個SecondActivity的實例是重用的上次已有的實例還是重新啟動了一個實例呢?可以觀察系統Log,發現系統Log沒有改變,還是上面的四條Log。打印Log的語句是在各個Activity中的onCreate方法中執行的,沒有打印出新的Log,說明SecondActivity的onCreate的方法沒有重新執行,也就是說是重用的上次已經啟動的實例,而不是銷毀重建。
經過上面的驗證,可以得出如下的結論:在啟動一個singleTask的Activity實例時,如果系統中已經存在這樣一個實例,就會將這個實例調度到任務棧的棧頂,並清除它當前所在任務中位於它上面的所有的activity。
四 實例驗證singleInstance的行為
根據上面的講解,並且參考谷歌官方文檔,singleInstance的特點可以歸結為以下三條:
以singleInstance模式啟動的Activity具有全局唯一性,即整個系統中只會存在一個這樣的實例以singleInstance模式啟動的Activity具有獨占性,即它會獨自占用一個任務,被他開啟的任何activity都會運行在其他任務中(官方文檔上的描述為,singleInstance模式的Activity不允許其他Activity和它共存在一個任務中)被singleInstance模式的Activity開啟的其他activity,能夠開啟一個新任務,但不一定開啟新的任務,也可能在已有的一個任務中開啟
下面對這三個特點分別驗證,所使用的示例同樣為AndroidTaskTest,只不過會進行一些修改,下面列出它的清單文件:
android:versionCode="1"
android:versionName="1.0">
android:icon="@drawable/ic_launcher"android:label="androidtasktest">
android:launchMode="singleInstance">
由上面的清單文件可以知道,該應用包括三個activity,分別為MianActivity,SecondActivity,ThirdActivity,其中SecondActivity啟動模式設置為singleInstance。MianActivity可以開啟SecondActivity,SecondActivity可以開啟ThirdActivity。並且為了可以在其他應用中開啟SecondActivity,為SecondActivity設置了一個IntentFilter,這樣就可以在其他應用中使用隱式Intent開啟SecondActivity。
為了更好的驗證singleInstance的全局唯一性,還需要其他一個應用,對上面的AndroidTaskTest1進行一些修改即可。AndroidTaskTest1只需要一個MianActivity,在MainActivity中點擊按鈕會開啟AndroidTaskTest應用中的SecondActivity。開啟AndroidTaskTest應用中的SecondActivity的代碼如下:
/**
*該方法在布局中按鈕的android:onClick屬性中指定
*android:onClick="launchOtherActivity"
*@paramv
*/
publicvoidlaunchOtherActivity(Viewv){
Intentintent=newIntent();
//以下Action為"com.jg.zhang.androidtasktest.ACTION_MY"
//即AndroidTaskTest應用中SecondActivity的action
intent.setAction("com.jg.zhang.androidtasktest.ACTION_MY");
startActivity(intent);
}
下面開始驗證第一個特點:以singleInstance模式啟動的Activity具有全局唯一性,即整個系統中只會存在一個這樣的實例
執行如下操作:安裝AndroidTaskTest應用,點擊MainActivity中的按鈕,開啟SecondActivity,可以看到如下log輸出:
執行adb shell dumpsys activity命令,有以下輸出:
TaskRecord{411189e0 #9 A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{4129af80 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{41305528 #8 A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{41296e60 com.jg.zhang.androidtasktest/.MainActivity}
以上可以說明,singleInstance模式的Activity總是會在新的任務中運行(前提是系統中還不存在這樣的一個實例) 。
下面驗證它的全局唯一性,執行以下操作:安裝另一個應用AndroidTaskTest1,在開啟的MainActivity中點擊按鈕開啟AndroidTaskTest應用中的SecondActivity。看到打印出一條新的日志:
執行adb shell dumpsys activity命令,有以下輸出:
TaskRecord{411189e0 #9 A com.jg.zhang.androidtasktest}
Run #3: ActivityRecord{4129af80 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{412dc788 #12 A com.jg.zhang.androidtasktest1}
Run #2: ActivityRecord{4121c628 com.jg.zhang.androidtasktest1/.MainActivity}
TaskRecord{41305528 #8 A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{41296e60 com.jg.zhang.androidtasktest/.MainActivity}
由紅色字體可以得知,開啟的SecondActivity就是上次創建的編號為4129af80的SecondActivity,並且Log中沒有再次輸出關於SecondActivity的信息,說明SecondActivity並沒有重新創建。由此可以得出結論:以singleInstance模式啟動的Activity在整個系統中是單例的,如果在啟動這樣的Activiyt時,已經存在了一個實例,那麼會把它所在的任務調度到前台,重用這個實例。
下面開始驗證第二個特點:以singleInstance模式啟動的Activity具有獨占性,即它會獨自占用一個任務,被他開啟的任何activity都會運行在其他任務中
重新安裝AndroidTaskTest應用,點擊MainActivity中的按鈕,開啟SecondActivity,在SecondActivity中點擊按鈕,開啟ThirdActivity。可以看到有如下Log輸出:
執行adb shell dumpsys activity命令,有以下輸出:
TaskRecord{412a95b8 #15 A com.jg.zhang.androidtasktest}
Run #3: ActivityRecord{411f9318 com.jg.zhang.androidtasktest/.ThirdActivity}
TaskRecord{41353a68 #16 A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{413537c8 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{412a95b8 #15 A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{4123a0c8 com.jg.zhang.androidtasktest/.MainActivity}
SecondActivity所在的任務為16,被SecondActivity啟動的ThirdActivity所在的任務為15,這就說明以singleInstance模式啟動的Activity具有獨占性,即它會獨自占用一個任務,被他開啟的任何activity都會運行在其他任務中
下面開始驗證第三個特點:被singleInstance模式的Activity開啟的其他activity,能夠在新的任務中啟動,但不一定開啟新的任務,也可能在已有的一個任務中開啟
有上面對第二個特點的驗證可以看到,被SecondActivity啟動的ThirdActivity並沒有運行在一個新開啟的任務中,而是和MainActivity運行在了同一個已有的任務中,那麼在什麼情況下ThirdActivity才會啟動一個新的任務呢?
現在對程序的清單文件做以下修改,為ThirdActivity增加一個屬性taskAffinity:
android:taskAffinity="com.jg.zhang.androidtasktest.second"/>
重新安裝AndroidTaskTest應用,執行和上一步中同樣的操作:點擊MainActivity中的按鈕,開啟SecondActivity,在SecondActivity中點擊按鈕,開啟ThirdActivity。可以看到有如下輸出:
執行adb shell dumpsys activity命令,有以下輸出:
TaskRecord{413551b0#20A com.jg.zhang.androidtasktest.second}
Run #3: ActivityRecord{412de9c0 com.jg.zhang.androidtasktest/.ThirdActivity}
TaskRecord{4134b268#19A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{412a36a0 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{413131e8#18A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{41271e10 com.jg.zhang.androidtasktest/.MainActivity}
可見,被SecondActivity啟動的ThirdActivity啟動在了一個新的任務中,即在啟動ThirdActivity時創建了一個新任務。這就說明被singleInstance模式的Activity A在開啟另一activity B時,能夠開啟一個新任務,但是是不是真的開啟新任務,還要受其他條件的限制,這個條件是:當前系統中是不是已經有了一個activity B的taskAffinity屬性指定的任務。
其實這種行為和singleTask啟動時的情況相同。在Activity的啟動模式設置為singleTask時,啟動時系統會為它加上FLAG_ACTIVITY_NEW_TASK標志,而被singleInstance模式的Activity開啟的activity,啟動時系統也會為它加上FLAG_ACTIVITY_NEW_TASK標志,所以他們啟動時的情況是相同的,上面再驗證singleTask時已經闡述過,現在重新說明一下:
由於ThirdActivity是被啟動模式為singleInstance類型的Activity(即SecondActivity)啟動的,framework會為它它加上FLAG_ACTIVITY_NEW_TASK標志,這時framework會檢索是否已經存在了一個affinity為com.jg.zhang.androidtasktest.second(即ThirdActivity的taskAffinity屬性)的任務,
如果存在這樣的一個任務,則檢查在這個任務中是否已經有了一個ThirdActivity的實例, 如果已經存在一個ThirdActivity的實例,則會重用這個任務和任務中的ThirdActivity實例,將這個任務調到前台,清除位於ThirdActivity上面的所有Activity,顯示ThirdActivity,並調用ThirdActivity的onNewIntent()。如果不存在一個ThirdActivity的實例,會在這個任務中創建ThirdActivity的實例,並調用onCreate()方法 如果不存在這樣的一個任務,會創建一個新的affinity為com.jg.zhang.androidtasktest.second的任務,並且將ThirdActivity啟動到這個新的任務中
如果ThirdActivity不設置taskAffinity,即ThirdActivity和MainActivity的taskAffinity相同,都為應用的包名,那麼ThirdActivity是不會開啟一個新任務的,framework中的判定過程如下:
在SecondActivity啟動ThirdActivity時,因為SecondActivity是singleInstance的,所以設定ThirdActivity的啟動標志為FLAG_ACTIVITY_NEW_TASK然後獲得ThirdActivity的taskAffinity,即為包名com.jg.zhang.androidtasktest檢查是否已經存在一個affinity為com.jg.zhang.androidtasktest的任務,這個任務是存在的,就是MainActivity所在的任務,這個任務是在啟動MainActivity時開啟的既然已經存在這個任務,就檢索在這個任務中是否存在一個ThirdActivity的實例,發現不存在在這個已有的任務中啟動一個SecondActivity的實例
為了作一個清楚的比較,列出ThirdActivity的taskAffinity屬性設為com.jg.zhang.androidtasktest.second時的啟動過程
在SecondActivity啟動ThirdActivity時,因為SecondActivity是singleInstance的,那麼設定ThirdActivity的啟動標志為FLAG_ACTIVITY_NEW_TASK然後獲得ThirdActivity的taskAffinity,即為com.jg.zhang.androidtasktest.second檢查是否已經存在一個affinity為com.jg.zhang.androidtasktest.second的任務,這個任務是不存在的創建一個新的affinity為com.jg.zhang.androidtasktest.second的任務,並且將ThirdActivity啟動到這個新的任務
到此singleInstance也介紹完了。
五 本文小結
由上述可知,Task是Android Framework中的一個概念,Task是由一系列相關的Activity組成的,是一組相關Activity的集合。Task是以棧的形式來管理的。
我們在操作軟件的過程中,一定會涉及界面的跳轉。其實在對界面進行跳轉時,Android Framework既能在同一個任務中對Activity進行調度,也能以Task為單位進行整體調度。在啟動模式為standard或singleTop時,一般是在同一個任務中對Activity進行調度,而在啟動模式為singleTask或singleInstance是,一般會對Task進行整體調度。
對Task進行整體調度包括以下操作:
按Home鍵,將之前的任務切換到後台長按Home鍵,會顯示出最近執行過的任務列表在Launcher或HomeScreen點擊app圖標,開啟一個新任務,或者是將已有的任務調度到前台啟動singleTask模式的Activity時,會在系統中搜尋是否已經存在一個合適的任務,若存在,則會將這個任務調度到前台以重用這個任務。如果這個任務中已經存在一個要啟動的Activity的實例,則清除這個實例之上的所有Activity,將這個實例顯示給用戶。如果這個已存在的任務中不存在一個要啟動的Activity的實例,則在這個任務的頂端啟動一個實例。若這個任務不存在,則會啟動一個新的任務,在這個新的任務中啟動這個singleTask模式的Activity的一個實例。啟動singleInstance的Activity時,會在系統中搜尋是否已經存在一個這個Activity的實例,如果存在,會將這個實例所在的任務調度到前台,重用這個Activity的實例(該任務中只有這一個Activity),如果不存在,會開啟一個新任務,並在這個新任務中啟動這個singleInstance模式的Activity的一個實例。
先貼出本文程序運行結果的截圖,上面是播放/停止音頻,可用SeekBar來調進度,下面是播放/停止視頻,也是用SeekBar來調進度: main.xml的源碼:
首先介紹功能,我要實現動態加載布局的效果,之前是采用的new組件的辦法來實現,但是android內存有限,new的對象會達到500多個,為了減少new的對象,我決定使用x
前言 Android有自己的默認字體,但是有時候我們並不想使用它的默認字體,我們想使用諸如楷體,隸書等字體,那麼該怎麼去做呢?本文就是說明該如何使用
基於Android 6.0的源碼剖析, 分析Binder IPC通信的權限控制方法clearCallingIdentity和restoreCallingId