1. 程式人生 > >Activity啟動模式圖文詳解:standard, singleTop, singleTask 以及 singleInstance

Activity啟動模式圖文詳解:standard, singleTop, singleTask 以及 singleInstance

英文原文:Understand Android Activity's launchMode: standard, singleTop, singleTask and singleInstance  另外關於啟動模式還有篇很好的文章:Android中Activity四種啟動模式和taskAffinity屬性詳解  

Activity是安卓上最聰明的設計之一,優秀的記憶體管理讓多工完美執行在最流行的作業系統之上。並不是讓Activity在螢幕上啟動就完事了,其啟動方式也是需要關注的。這個話題的內容很多,其中很重要的就是啟動模式(launchMode)。這也是我們這篇部落格要討論的內容。

因為不同的Activity有不同的目的。有些被設計成每傳送一個intent都單獨一個Activity工作,比如郵件客戶端中撰寫郵件的Activity,而有些則被設計成單例的,比如郵件收件箱的Activity。

這就是為什麼指明一個Activity是否需要新建還是使用現有Activity是很有必要的,否則可能導致糟糕的使用者體驗。多虧了安卓的核心工程師,讓launchMode可以幫助你專門應對這種情況。

設定一個launchMode

一般地,我們可以直接在AndroidManifest.xml <activity>標籤的一個屬性中設定launchMode,如下:

  1. <activity
  2.             android:name=".SingleTaskActivity"
  3.             android:label="singleTask launchMode"
  4.             
    android:launchMode="singleTask">

有4種類型的launchMode,我們一個一個的看。

standard

這是預設的模式。

這種模式下,當Intent傳送的時候,Activity總是被建立一個新的出來單獨工作。想象一下,如果有傳送10個撰寫郵件的Intent,那麼將有10個不同的Activity啟動。


Lollipop之前裝置上的表現


這種Activity將被建立並置於棧頂,和傳送intent的Activity處於同一個任務中。注:一般來講,安卓第三個虛擬鍵所列出的那些就是任務。

standardtopstandard


下面的圖片顯示了向標準啟動模式的Activity分享照片時的情況。雖然分別來自不同的應用,但仍然它會和傳送intent的Activity處於同一個任務中。

注:從圖中可以看出分享圖片的是Gallery應用。


standardgallery2


同時你會看到此時工作管理員是這樣的(有一點怪異)。



gallerystandard



如果我們切換到另外一個應用然後再切回到Gallery,你會發現standard launchMode啟動的Activity仍然在Gallery任務的上面,導致在操作Gallery之前,我們必須首先結束這個額外的Activity。


Lollipop裝置上的表現


如果Activity都是來自同一個應用,其表現和Lollipop之前的裝置一樣,在任務的頂端。


standardstandardl


但是如果intent來自其他應用,將建立一個新的任務,同時新建立的Activity會被作為一個根Activity,如下:


standardgalleryl

注:圖片中的Task#2和Task#3分別表示兩個任務,序號大的比序號小的後啟動。


下面是工作管理員中的樣子:


gallerystandardl1


發生這種情況的原因是Lollipop中任務管理系統做了修改,讓它看起來更合理了。因為它們在不同的任務中,你可以直接切回Gallery,你還可以觸發另一個Intent,建立新的與之前相同的任務。


gallerystandardl2


撰寫郵件的Activity或者釋出社交網路狀態的Activity都是採用這種Activity的例子。如果你希望Activity單獨服務於一個Intent,就可以考慮standard啟動模式。

singleTop

接下來就是singleTop模式。它的表現幾乎和standard模式一模一樣,一個singleTop Activity 的例項可以無限多,唯一的區別是如果在棧頂已經有一個相同型別的Activity例項,Intent不會再建立一個Activity,而是通過onNewIntent()被髮送到現有的Activity。


singletop


在singleTop模式下我們需要同時在onCreate() 和 onNewIntent()中處理髮來的intent,以滿足不同情況。

這種啟動模式的用例之一就是搜尋功能。假設我們建立了一個搜尋框,點選搜尋的時候將導航到一個顯示搜尋結果列表的SearchActivity中,為了更好的使用者體驗,這個搜尋框一般也會被放到SearchActivity中,這樣使用者想要再次搜尋就不需要按返回鍵。

想像一下,如果每次顯示搜尋結果的時候我們都啟動一個新的activity,10次搜尋10個activity,那樣當我們想返回最初的那個activity的時候需要按10次返回。

所以我們應該這樣,如果棧頂已經有一個SearchActivity,我們將Intent傳送給現有的activity,讓它來更新搜尋結果。這樣就只會有一個在棧頂的SearchActivity,只需點一次back就可以回到之前的activity。

不管怎樣,singleTop和它的呼叫者處在一個任務中。如果你想要讓intent傳送給另一個任務中處於棧頂的Activity,是不行的。

而當Intent來自於另外一個應用的時候,新的Activity的啟動方式和standard模式是一致的(pre-Lollipop:處於呼叫者任務的棧頂,Lollipop:會建立一個新的任務)。

singleTask

這種模式和standard以及singleTop有很大不同。singleTask模式的Activity只允許在系統中有一個例項。如果系統中已經有了一個例項,持有這個例項的任務將移動到頂部,同時intent將被通過onNewIntent()傳送。如果沒有,則會建立一個新的Activity並置放在合適的任務中。


在同一個應用中的情況


如果系統中還沒有singleTask的Activity,會新建立一個,並放在同一任務的棧頂。


singleTask1


但是如果已經存在,singleTask Activity上面的所有Activity將以合適的方式自動銷燬,讓我們想要顯示的Activity處於棧頂。同時Intent也會通過onNewIntent()方法傳送到這個singleTask Activity。


singleTaskD


在使用者體驗方面,可能不是很合理,但是它就是這樣設計的...

你可能注意到了 官方文件 中提到的一個問題:

 系統會建立一個新的任務,並將這個Activity例項化為新任務的根部(root)。

但從實驗結果來看,並不是這麼回事。singleTask Activity仍然在任務的Activity棧頂,我們可以從dumpsys activity 命令顯示上看出來:


 
  1. Task id #239
  2.   TaskRecord{428efe30 #239 A=com.thecheesefactory.lab.launchmode U=0 sz=2}
  3.   Intent
  4.  { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]
  5.  flg=0x10000000 
  6. cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
  7.     Hist #1: ActivityRecord{429a88d0 u0 com.thecheesefactory.lab.launchmode/.SingleTaskActivity t239}
  8.       Intent { cmp=com.thecheesefactory.lab.launchmode/.SingleTaskActivity }
  9.       ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}
  10.     Hist #0: ActivityRecord{425fec98 u0 com.thecheesefactory.lab.launchmode/.StandardActivity t239}
  11.       Intent
  12.  { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]
  13.  flg=0x10000000 
  14. cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
  15.       ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}


如果你希望singleTask Activity表現的和文件中描述的一致,你需要為singleTask Activity設定taskAffinity屬性。


 
  1. <activity
  2.             android:name=".SingleTaskActivity"
  3.             android:label="singleTask launchMode"
  4.             android:launchMode="singleTask"
  5.             android:taskAffinity="">


這裡是啟動SingleTaskActivity的的結果(使用了taskAffinity之後)。


singleTaskTaskAffinity

screenshot17


是否使用taskAffinity取決於你自己。


和其他應用一起工作的情況


一旦intent是從另外的應用傳送過來,並且系統中也沒有任何Activity的例項,則會建立一個新的任務,並且新的Activity被作為根Activity建立。


singleTaskAnotherApp1

singletaskfromapp2


除非擁有這個singleTask Activity 的應用已經存在,那樣的話,新建的Activity會置於這個任務的上面(而不是新建一個任務)。


singleTaskAnotherApp2

In case that there is an Activity instance existed in any Task, the whole Task would be moved to top and every single Activity placed above the singleTask Activity will be destroyed with lifecycle. If back button is pressed, user has to travel through the Activities in the stack before going back to the caller Task.

假設已經有了一個Activity的例項,不管它是在哪個任務中(包括上面的那種情況,在用於這個Activity的應用中),整個任務將被移到頂端,而singleTask  Activity上面的所有 Activity 都將被銷燬, 使用者需要按back鍵遍歷玩棧中的Activity才能回到呼叫者任務。


singleTaskAnotherApp3


這種模式的應用案例有。郵件客戶端的收件箱或者社交網路的時間軸。這些Activity一般不會設計成擁有多個例項,singleTask可以滿足。但是在使用這種模式的時候必須要明智,因為有些Activity會在使用者不知情的情況下被銷燬。

singleInstance

這個模式非常接近於singleTask,系統中只允許一個Activity的例項存在。區別在於持有這個Activity的任務中只能有一個Activity:即這個單例本身。If another Activity is called from this kind of Activity, a new Task would be automatically created to place that new Activity. Likewise, if singleInstance Activity is called, new Task would be created to place the Activity.

不過結果卻很怪異,從dumpsys提供的資訊來看,似乎系統中有兩個任務但工作管理員中只顯示一個,即最後被移到頂部的那個。導致雖然後臺有一個任務在執行,我們卻無法切換回去,這一點也不科學。

下面是當singleInstance Activity被呼叫的同時棧中已經有一些Activity的情況下所發生的事情:

singleInstance


本來有兩個任務,但是工作管理員中卻只顯示一個任務:


singleInstances

Since this Task could has only one Activity, we couldn't switch back to Task #1 anymore. Only way to do so is to relaunch the application from launcher but it appears that the singleInstance Task would be hidden in the background instead.

因為這個任務只有一個Activity,我們再也無法切回到任務#1了。唯一的辦法是重新在launcher中啟動這個應用。 but之後的沒有翻譯,因為我也不明白作者的意思。

不過這個問題也有解決方案,就像我們在singleTask Acvity中做的,只要為singleInstance Activity設定taskAffinity屬性就可以了。


 
  1. <activity
  2.             android:name=".SingleInstanceActivity"
  3.             android:label="singleInstance launchMode"
  4.             android:launchMode="singleInstance"
  5.             android:taskAffinity="">

現在科學多了。

screenshot18


這種模式很少被使用。實際使用的案例如Launcher的Activity或者100%確定只有一個Activity的應用。總之除非完全有必要,不然我不建議使用這種模式。

Intent Flags

除了在AndroidManifest.xml中直接設定launch mode,我們還可以通過叫做 Intent Flags的東西設定更多的行為,比如:


 
  1. Intent intent = new Intent(StandardActivity.this, StandardActivity.class);
  2. intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
  3. startActivity(intent);

這段程式碼將會啟動一個singleTop啟動模式的的StandardActivity

有許多種Flag可以使用,更多的請參考Intent

希望這篇文章對你有用。