乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Android中Activity四種啟動模式和taskAffinity屬性詳解

       華府九五二七 2019-07-19

      在android應(yīng)用開發(fā)中,打造良好的用戶體驗是非常重要的。而在用戶體驗中,界面的引導(dǎo)和跳轉(zhuǎn)是值得深入研究的重要內(nèi)容。在開發(fā)中,與界面跳轉(zhuǎn)聯(lián)系比較緊密的概念是Task(任務(wù))和Back Stack(回退棧)。activity的啟動模式會影響Task和Back Stack的狀態(tài),進而影響用戶體驗。除了啟動模式之外,Intent類中定義的一些標(biāo)志(以FLAG_ACTIVITY_開頭)也會影響Task和Back Stack的狀態(tài)。在這篇文章中主要對四種啟動模式進行分析和驗證,其中涉及到activity的一個重要屬性taskAffinity和Intent中的標(biāo)志之一FLAG_ACTIVITY_NEW_TASK。關(guān)于Intent中其他標(biāo)志位的具體用法會在另一篇文章中介紹。

      Task是一個存在于Framework層的概念,容易與它混淆的有Application(應(yīng)用)和Process(進程)。在開始介紹Activity的啟動模式的使用之前,首先對這些概念做一個簡單的說明和區(qū)分。

      一 Application,Task和Process的區(qū)別與聯(lián)系


      application翻譯成中文時一般稱為“應(yīng)用”或“應(yīng)用程序”,在android中,總體來說一個應(yīng)用就是一組組件的集合。眾所周知,android是在應(yīng)用層組件化程度非常高的系統(tǒng),android開發(fā)的第一課就是學(xué)習(xí)android的四大組件。當(dāng)我們寫完了多個組件,并且在manifest文件中注冊了這些組件之后,把這些組件和組件使用到的資源打包成apk,我們就可以說完成了一個application。application和組件的關(guān)系可以在manifest文件中清晰地體現(xiàn)出來。如下所示:

      1. <?xml version='1.0' encoding='utf-8'?>
      2. <manifest android:versionCode='1'
      3. android:versionName='1'
      4. xmlns:android='http://schemas./apk/res/android'
      5. package='com.example.android.myapp'>
      6. <application android:label='@string/app_name'>
      7. <activity android:name='.MyActivity' android:label='@string/app_nam'>
      8. <intent-filter>
      9. <action android:name='android.intent.action.MAIN' />
      10. <category android:name='android.intent.category.LAUNCHER' />
      11. </intent-filter>
      12. </activity>
      13. <receiver android:name='.MyReceiver'/>
      14. <provider android:name='.MyProvider'/>
      15. <service android:name='.MyService'/>
      16. </application>
      17. </manifest>
      由此可見,application是由四大組件組成的。在app安裝時,系統(tǒng)會讀取manifest的信息,將所有的組件解析出來,以便在運行時對組件進行實例化和調(diào)度。

      而task是在程序運行時,只針對activity的概念。說白了,task是一組相互關(guān)聯(lián)的activity的集合,它是存在于framework層的一個概念,控制界面的跳轉(zhuǎn)和返回。這個task存在于一個稱為back stack的數(shù)據(jù)結(jié)構(gòu)中,也就是說,framework是以棧的形式管理用戶開啟的activity。這個棧的基本行為是,當(dāng)用戶在多個activity之間跳轉(zhuǎn)時,執(zhí)行壓棧操作,當(dāng)用戶按返回鍵時,執(zhí)行出棧操作。舉例來說,如果應(yīng)用程序中存在A,B,C三個activity,當(dāng)用戶在Launcher或Home Screen點擊應(yīng)用程序圖標(biāo)時,啟動主Activity A,接著A開啟B,B開啟C,這時棧中有三個Activity,并且這三個Activity默認(rèn)在同一個任務(wù)(task)中,當(dāng)用戶按返回時,彈出C,棧中只剩A和B,再按返回鍵,彈出B,棧中只剩A,再繼續(xù)按返回鍵,彈出A,任務(wù)被移除。如下圖所示:


       task是可以跨應(yīng)用的,這正是task存在的一個重要原因。有的Activity,雖然不在同一個app中,但為了保持用戶操作的連貫性,把他們放在同一個任務(wù)中。例如,在我們的應(yīng)用中的一個Activity A中點擊發(fā)送郵件,會啟動郵件程序的一個Activity B來發(fā)送郵件,這兩個activity是存在于不同app中的,但是被系統(tǒng)放在一個任務(wù)中,這樣當(dāng)發(fā)送完郵件后,用戶按back鍵返回,可以返回到原來的Activity A中,這樣就確保了用戶體驗。

      說完了application和task,最后介紹process。process一般翻譯成進程,進程是操作系統(tǒng)內(nèi)核中的一個概念,表示直接受內(nèi)核調(diào)度的執(zhí)行單位。在應(yīng)用程序的角度看,我們用java編寫的應(yīng)用程序,運行在dalvik虛擬機中,可以認(rèn)為一個運行中的dalvik虛擬機實例占有一個進程,所以,在默認(rèn)情況下,一個應(yīng)用程序的所有組件運行在同一個進程中。但是這種情況也有例外,即,應(yīng)用程序中的不同組件可以運行在不同的進程中。只需要在manifest中用process屬性指定組件所運行的進程的名字。如下所示:

      1. <activity android:name='.MyActivity' android:label='@string/app_nam'
      2. android:process=':remote'>
      3. </activity>

      這樣的話這個activity會運行在一個獨立的進程中。


      二 Activity四種啟動模式詳解


      activity有四種啟動模式,分別為standard,singleTop,singleTask,singleInstance。如果要使用這四種啟動模式,必須在manifest文件中<activity>標(biāo)簽中的launchMode屬性中配置,如:

      1. <activity android:name='.app.InterstitialMessageActivity'
      2. android:label='@string/interstitial_label'
      3. android:theme='@style/Theme.Dialog'
      4. android:launchMode='singleTask'
      5. </activity>

      同樣,在Intent類中定義了很多與Activity啟動或調(diào)度有關(guān)的標(biāo)志,<activity>標(biāo)簽中有一些屬性,這些標(biāo)志,屬性和四種啟動模式聯(lián)合使用,會在很大程度上改變activity的行為,進而會改變task和back stask的狀態(tài)。關(guān)于Intent中的標(biāo)志和<activity>標(biāo)簽中有一些屬性會在本文后面介紹,在這一節(jié)中,先介紹activity的四種啟動模式。

      standard

      標(biāo)準(zhǔn)啟動模式,也是activity的默認(rèn)啟動模式。在這種模式下啟動的activity可以被多次實例化,即在同一個任務(wù)中可以存在多個activity的實例,每個實例都會處理一個Intent對象。如果Activity A的啟動模式為standard,并且A已經(jīng)啟動,在A中再次啟動Activity A,即調(diào)用startActivity(new Intent(this,A.class)),會在A的上面再次啟動一個A的實例,即當(dāng)前的桟中的狀態(tài)為A-->A。

      singleTop

      如果一個以singleTop模式啟動的activity的實例已經(jīng)存在于任務(wù)桟的桟頂,那么再啟動這個Activity時,不會創(chuàng)建新的實例,而是重用位于棧頂?shù)哪莻€實例,并且會調(diào)用該實例的onNewIntent()方法將Intent對象傳遞到這個實例中。舉例來說,如果A的啟動模式為singleTop,并且A的一個實例已經(jīng)存在于棧頂中,那么再調(diào)用startActivity(new Intent(this,A.class))啟動A時,不會再次創(chuàng)建A的實例,而是重用原來的實例,并且調(diào)用原來實例的onNewIntent()方法。這是任務(wù)桟中還是這有一個A的實例。
      如果以singleTop模式啟動的activity的一個實例已經(jīng)存在與任務(wù)桟中,但是不在桟頂,那么它的行為和standard模式相同,也會創(chuàng)建多個實例。

      singleTask

      谷歌的官方文檔上稱,如果一個activity的啟動模式為singleTask,那么系統(tǒng)總會在一個新任務(wù)的最底部(root)啟動這個activity,并且被這個activity啟動的其他activity會和該activity同時存在于這個新任務(wù)中。如果系統(tǒng)中已經(jīng)存在這樣的一個activity則會重用這個實例,并且調(diào)用他的onNewIntent()方法。即,這樣的一個activity在系統(tǒng)中只會存在一個實例。

      其實官方文檔中的這種說法并不準(zhǔn)確,啟動模式為singleTask的activity并不會總是開啟一個新的任務(wù)。詳情請參考 解開Android應(yīng)用程序組件Activity的'singleTask'之謎,在本文后面也會通過示例來進行驗證。

      singleInstance

      總是在新的任務(wù)中開啟,并且這個新的任務(wù)中有且只有這一個實例,也就是說被該實例啟動的其他activity會自動運行于另一個任務(wù)中。當(dāng)再次啟動該activity的實例時,會重用已存在的任務(wù)和實例。并且會調(diào)用這個實例的onNewIntent()方法,將Intent實例傳遞到該實例中。和singleTask相同,同一時刻在系統(tǒng)中只會存在一個這樣的Activity實例。


      三 實例驗證singleTask啟動模式


      上面將activity的四種啟動模式就基本介紹完了。為了加深對啟動模式的了解,下面會通過一個簡單的例子進行驗證。由以上的介紹可知,standard和singleTop這兩種啟動模式行為比較簡單,所以在下面的例子中,會對singleTask和singleInstance著重介紹。


      驗證啟動singleTask模式的activity時是否會創(chuàng)建新的任務(wù)


      以下為驗證示例AndroidTaskTest。這個實例中有三個Activity,分別為:MainActivity,SecondActivity和ThirdActivity。以下為這個示例的manifest文件。

      1. <?xml version='1.0' encoding='utf-8'?>
      2. <manifest xmlns:android='http://schemas./apk/res/android'
      3. package='com.jg.zhang.androidtasktest'
      4. android:versionCode='1'
      5. android:versionName='1.0' >
      6. <uses-sdk android:minSdkVersion='10' android:targetSdkVersion='17' />
      7. <application android:icon='@drawable/ic_launcher' android:label='@string/app_name'>
      8. <activity android:label='@string/app_name'
      9. android:name='com.jg.zhang.androidtasktest.MainActivity'>
      10. <intent-filter>
      11. <action android:name='android.intent.action.MAIN' />
      12. <category android:name='android.intent.category.LAUNCHER' />
      13. </intent-filter>
      14. </activity>
      15. <!--android:taskAffinity='com.jg.zhang.androidtasktest.second'
      16. android:alwaysRetainTaskState='true'
      17. android:allowBackup='true' -->
      18. <activity android:name='com.jg.zhang.androidtasktest.SecondActivity'
      19. android:launchMode='singleTask'>
      20. <intent-filter >
      21. <action android:name='com.jg.zhang.androidtasktest.SecondActivity'/>
      22. <category android:name='android.intent.category.DEFAULT'/>
      23. </intent-filter>
      24. </activity>
      25. <activity android:name='com.jg.zhang.androidtasktest.ThirdActivity'
      26. android:label='@string/app_name' >
      27. </activity>
      28. </application>
      29. </manifest>

      由此可見,MainActivity和ThirdActivity都是標(biāo)準(zhǔn)的啟動模式,而SecondActivity的啟動模式為singleTask。

      以下為這三個Activity的界面,很簡單,在MainActivity中點擊按鈕啟動SecondActivity,在SecondActivity中點擊按鈕啟動ThirdActivity。



      以下為這三個activity的主要代碼:

      MainActivity
      1. public class MainActivity extends Activity {
      2. private static final String ACTIVITY_NAME = 'MainActivity';
      3. private static final String LOG_TAG = 'xxxx';
      4. @Override
      5. protected void onCreate(Bundle savedInstanceState) {
      6. super.onCreate(savedInstanceState);
      7. setContentView(R.layout.activity_main);
      8. findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
      9. @Override
      10. public void onClick(View v) {
      11. Intent intent = new Intent(MainActivity.this, SecondActivity.class);
      12. startActivity(intent);
      13. }
      14. });
      15. int taskId = getTaskId();
      16. Log.i(LOG_TAG, ACTIVITY_NAME +'所在的任務(wù)的id為: ' + taskId);
      17. }



      SecondActivity
      1. public class SecondActivity extends Activity {
      2. private static final String ACTIVITY_NAME = 'SecondActivity';
      3. private static final String LOG_TAG = 'xxxx';
      4. @Override
      5. protected void onCreate(Bundle savedInstanceState) {
      6. super.onCreate(savedInstanceState);
      7. setContentView(R.layout.activity_second);
      8. findViewById(R.id.button2).setOnClickListener(new OnClickListener() {
      9. @Override
      10. public void onClick(View v) {
      11. Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
      12. startActivity(intent);
      13. }
      14. });
      15. int taskId = getTaskId();
      16. Log.i(LOG_TAG, ACTIVITY_NAME +'所在的任務(wù)的id為: ' + taskId);
      17. }

      ThirdActivity
      1. public class ThirdActivity extends Activity {
      2. private static final String ACTIVITY_NAME = 'ThirdActivity';
      3. private static final String LOG_TAG = 'xxxx';
      4. @Override
      5. protected void onCreate(Bundle savedInstanceState) {
      6. super.onCreate(savedInstanceState);
      7. setContentView(R.layout.activity_third);
      8. int taskId = getTaskId();
      9. Log.i(LOG_TAG, ACTIVITY_NAME +'所在的任務(wù)的id為: ' + taskId);
      10. }

      以上三個activity只列出了onCreate()方法中的內(nèi)容,實現(xiàn)的邏輯為在MainActivity中點擊按鈕啟動SecondActivity,在SecondActivity中點擊按鈕啟動ThirdActivity。并且在onCreate方法中會以log的形式打印出當(dāng)前activity所屬的任務(wù)(Task)的Id。

      現(xiàn)在執(zhí)行以下操作,運行該示例,并且點擊MainActivity界面中的按鈕,開啟SecondActivity。在該示例中SecondActivity的啟動模式為singleTask。按照官方文檔的說法,SecondActivity會在一個新的任務(wù)中開啟。但是查看打印出的log,發(fā)現(xiàn)MainActivity和SecondActivity所在的任務(wù)的Id相同。


      在命令行中執(zhí)行以下命令 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是啟動在同一個任務(wù)中的。其實,把啟動模式設(shè)置為singleTask,framework在啟動該activity時只會把它標(biāo)示為可在一個新任務(wù)中啟動,至于是否在一個新任務(wù)中啟動,還要受其他條件的限制。現(xiàn)在在SecondActivity增加一個taskAffinity屬性,如下所示:

      1. <activity android:name='com.jg.zhang.androidtasktest.SecondActivity'
      2. android:launchMode='singleTask'
      3. android:taskAffinity='com.jg.zhang.androidtasktest.second'>
      4. <intent-filter >
      5. <action android:name='com.jg.zhang.androidtasktest.SecondActivity'/>
      6. <category android:name='android.intent.category.DEFAULT'/>
      7. </intent-filter>
      8. </activity>

      重新運行該示例,執(zhí)行相同的操作,即:點擊MainActivity界面中的按鈕,開啟SecondActivity,并且點擊SecondActivity中的按鈕,啟動ThirdActivity,log中輸出的內(nèi)容為:


      在命令行中執(zhí)行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運行在不同的任務(wù)中了,并且被SecondActivity啟動的ThirdActivity和SecondActivity運行在同一個任務(wù)中。這種現(xiàn)象的具體解釋可以參考解開Android應(yīng)用程序組件Activity的'singleTask'之謎。

      在這里便引出了manifest文件中<activity>的一個重要屬性,taskAffinity。在官方文檔中可以得到關(guān)于taskAffinity的以下信息
      1. taskAffinity表示當(dāng)前activity具有親和力的一個任務(wù)(翻譯不是很準(zhǔn)確,原句為The task that the activity has an affinity for.),大致可以這樣理解,這個 taskAffinity表示一個任務(wù),這個任務(wù)就是當(dāng)前activity所在的任務(wù)。
      2. 在概念上,具有相同的affinity的activity(即設(shè)置了相同taskAffinity屬性的activity)屬于同一個任務(wù)。
      3.  一個任務(wù)的affinity決定于這個任務(wù)的根activity(root activity)的taskAffinity。
      4.  這個屬性決定兩件事:當(dāng)activity被re-parent時,它可以被re-paren哪個任務(wù)中;當(dāng)activity以FLAG_ACTIVITY_NEW_TASK標(biāo)志啟動時,它會被啟動到哪個任務(wù)中。(這個比較    難以理解,請結(jié)合<activity>中的屬性allowTaskReparenting和Intent中的標(biāo)志       FLAG_ACTIVITY_NEW_TASK加以理解)
      5.  默認(rèn)情況下,一個應(yīng)用中的所有activity具有相同的taskAffinity,即應(yīng)用程序的包名。我們可以通過設(shè)置不同的taskAffinity屬性給應(yīng)用中的activity分組,也可以把不同的       應(yīng)用中的activity的taskAffinity設(shè)置成相同的值。
      6.  為一個activity的taskAffinity設(shè)置一個空字符串,表明這個activity不屬于任何task。

      這就可以解釋上面示例中的現(xiàn)象了,由第5條可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity為com.jg.zhang.androidtasktest,SecondActivity的taskAffinity為com.jg.zhang.androidtasktest.second,根據(jù)上面第4條,taskAffinity可以影響當(dāng)activity以FLAG_ACTIVITY_NEW_TASK標(biāo)志啟動時,它會被啟動到哪個任務(wù)中。這句話的意思是,當(dāng)新啟動的activity(SecondActivity)是以FLAG_ACTIVITY_NEW_TASK標(biāo)志啟動時(可以認(rèn)為FLAG_ACTIVITY_NEW_TASK和singleTask作用相同,當(dāng)啟動模式為singleTask時,framework會將它的啟動標(biāo)志設(shè)為FLAG_ACTIVITY_NEW_TASK),framework會檢索是否已經(jīng)存在了一個affinity為com.jg.zhang.androidtasktest.second的任務(wù)(即一個TaskRecord對象)

      • 如果存在這樣的一個任務(wù),則檢查在這個任務(wù)中是否已經(jīng)有了一個SecondActivity的實例,
        • 如果已經(jīng)存在一個SecondActivity的實例,則會重用這個任務(wù)和任務(wù)中的SecondActivity實例,將這個任務(wù)調(diào)到前臺,清除位于SecondActivity上面的所有Activity,顯示SecondActivity,并調(diào)用SecondActivity的onNewIntent();
        • 如果不存在一個SecondActivity的實例,會在這個任務(wù)中創(chuàng)建SecondActivity的實例,并調(diào)用onCreate()方法
      • 如果不存在這樣的一個任務(wù),會創(chuàng)建一個新的affinity為com.jg.zhang.androidtasktest.second的任務(wù),并且將SecondActivity啟動到這個新的任務(wù)中

      上面討論的是設(shè)置taskAffinity屬性的情況,如果SecondActivity只設(shè)置啟動模式為singleTask,而不設(shè)置taskAffinity,即三個Activity的taskAffinity相同,都為應(yīng)用的包名,那么SecondActivity是不會開啟一個新任務(wù)的,framework中的判定過程如下:

      1. 在MainActivity啟動SecondActivity時,發(fā)現(xiàn)啟動模式為singleTask,那么設(shè)定他的啟動標(biāo)志為FLAG_ACTIVITY_NEW_TASK
      2.  然后獲得SecondActivity的taskAffinity,即為包名com.jg.zhang.androidtasktest
      3. 檢查是否已經(jīng)存在一個affinity為com.jg.zhang.androidtasktest的任務(wù),這個任務(wù)是存在的,就是MainActivity所在的任務(wù),這個任務(wù)是在啟動MainActivity時開啟的
      4.  既然已經(jīng)存在這個任務(wù),就檢索在這個任務(wù)中是否存在一個SecondActivity的實例,發(fā)現(xiàn)不存在
      5.  在這個已有的任務(wù)中啟動一個SecondActivity的實例

      為了作一個清楚的比較,列出SecondActivity啟動模式設(shè)為singleTask,并且taskAffinity設(shè)為com.jg.zhang.androidtasktest.second時的啟動過程

      1. 在MainActivity啟動SecondActivity時,發(fā)現(xiàn)啟動模式為singleTask,那么設(shè)定他的啟動標(biāo)志為FLAG_ACTIVITY_NEW_TASK
      2. 然后獲得SecondActivity的taskAffinity,即com.jg.zhang.androidtasktest.second
      3. 檢查是否已經(jīng)存在一個affinity為com.jg.zhang.androidtasktest.second的任務(wù),這個任務(wù)是不存在的
      4. 創(chuàng)建一個新的affinity為com.jg.zhang.androidtasktest.second的任務(wù),并且將SecondActivity啟動到這個新的任務(wù)中

      其實framework中對任務(wù)和activity‘的調(diào)度是很復(fù)雜的,尤其是把啟動模式設(shè)為singleTask或者以FLAG_ACTIVITY_NEW_TASK標(biāo)志啟動時。所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK時,要仔細(xì)測試應(yīng)用程序。這也是官方文檔上的建議。

      實例驗證將兩個不同app中的不同的singleTask模式的Activity的taskAffinity設(shè)成相同


      官方文檔中提到,可以把不同的 應(yīng)用中的activity的taskAffinity設(shè)置成相同的值,這樣的話這兩個activity雖然不在同一應(yīng)用中,卻會在運行時分配到同一任務(wù)中,下面對此進行驗證,在這里,會使用上面的示例AndroidTaskTest,并創(chuàng)建一個新的示例AndroidTaskTest1。AndroidTaskTest1由兩個activity組成,分別為MianActivity和OtherActivity,在MianActivity中點擊按鈕會啟動OtherActivity,該程序的界面和上一個類似,代碼也類似,再此僅列出清單文件。

      1. <?xml version='1.0' encoding='utf-8'?>
      2. <manifest xmlns:android='http://schemas./apk/res/android'
      3. package='com.jg.zhang.androidtasktest1'
      4. android:versionCode='1' android:versionName='1.0' >
      5. <uses-sdk android:minSdkVersion='9' android:targetSdkVersion='17' />
      6. <application
      7. android:allowBackup='true' android:icon='@drawable/ic_launcher' android:label='@string/app_name'
      8. android:theme='@style/AppTheme' >
      9. <activity
      10. android:name='com.jg.zhang.androidtasktest1.MainActivity'
      11. android:label='com.jg.zhang.androidtasktest1.MainActivity' >
      12. <intent-filter>
      13. <action android:name='android.intent.action.MAIN' />
      14. <category android:name='android.intent.category.LAUNCHER' />
      15. </intent-filter>
      16. </activity>
      17. <activity
      18. android:name='com.jg.zhang.androidtasktest1.OtherActivity'
      19. android:label='com.jg.zhang.androidtasktest1.OtherActivity'
      20. android:taskAffinity='com.jg.zhang.androidtasktest.second'
      21. android:launchMode='singleTask'>
      22. </activity>
      23. </application>
      24. </manifest>

      可以看到OtherActivity的啟動模式被設(shè)置為singleTask,并且taskAffinity屬性被設(shè)置為com.jg.zhang.androidtasktest.second,這和AndroidTaskTest應(yīng)用中的SecondActivity相同?,F(xiàn)在將這兩個應(yīng)用安裝在設(shè)備上。執(zhí)行以下操作:

      啟動AndroidTaskTest應(yīng)用,在它的MianActivity中點擊按鈕開啟SecondActivity,由上面的介紹可知secondActivity是運行在一個新任務(wù)中的,這個任務(wù)就是com.jg.zhang.androidtasktest.second。

      然后按Home鍵回到Launcher,啟動AndroidTaskTest1,在啟動AndroidTaskTest1的入口Activity(MianActivity)時,會自動啟動新的任務(wù),那么現(xiàn)在一共有三個任務(wù),AndroidTaskTest的MianActivity和SecondActivity分別占用一個任務(wù),AndroidTaskTest1的MianActivity也占用一個任務(wù)。

      在AndroidTaskTest1的MianActivity中點擊按鈕啟動OtherActivity,那么這個OtherActivity是在哪個任務(wù)中呢?

      下面執(zhí)行adb shell dumpsys activity命令,發(fā)現(xiàn)有以下輸出:

      TaskRecord{412370c0 #4 A com.jg.zhang.androidtasktest.second
      Intent { cmp=com.jg.zhang.androidtasktest/.SecondActivity }
      Hist #4: ActivityRecord{412f5ba0 com.jg.zhang.androidtasktest1/.OtherActivity}
      Intent { flg=0x400000 cmp=com.jg.zhang.androidtasktest1/.OtherActivity }
      ProcessRecord{412adb28 479:com.jg.zhang.androidtasktest1/10044}
      Hist #3: ActivityRecord{4125c880 com.jg.zhang.androidtasktest/.SecondActivity}
      Intent { cmp=com.jg.zhang.androidtasktest/.SecondActivity }
      ProcessRecord{41218e48 463:com.jg.zhang.androidtasktest/10043}

      TaskRecord{412f0f60 #5 A com.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{413045a8 com.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 A com.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{41250850 com.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}

      在執(zhí)行上述操作時,打印出的Log為:


      所以由此可見,AndroidTaskTest的SecondActivity和AndroidTaskTest1的OtherActivity是在同一任務(wù)中的。由上面adb shell dumpsys activity命令的輸出結(jié)果(藍色字體)還可以看出,AndroidTaskTest和AndroidTaskTest1這兩個應(yīng)用程序會開啟兩個進程,他們的所有組件分別運行在獨立的進程中,其中AndroidTaskTest所在進程的進程號為10043,AndroidTaskTest1所在進程的進程號為10044。com.jg.zhang.androidtasktest.second任務(wù)中的兩個activity屬于不同的應(yīng)用,并且運行在不同的進程中,這也說明了一個問題:任務(wù)(Task)不僅可以跨應(yīng)用(Application),還可以跨進程(Process)。


      實例驗證singleTask的另一意義:在同一個任務(wù)中具有唯一性


      谷歌官方文檔中提到,singleTask模式的activity總會在一個新的任務(wù)中開啟。上面已經(jīng)驗證了這種說法不確切,singleTask模式只意味著“可以在一個新的任務(wù)中開啟”,至于是不是真的會在新任務(wù)中開啟,在framework中還有其他條件的限制。由上面的介紹可知,這個條件為:是否已經(jīng)存在了一個由他的taskAffinity屬性指定的任務(wù)。這一點具有迷惑性,我們在看到singleTask這個單詞的時候,會直觀的想到它的本意:single in task。即,在同一個任務(wù)中,只會有一個該activity的實例?,F(xiàn)在讓我們進行驗證:

      為了驗證這種情況,需要修改一下上面用到的AndroidTaskTest示例。增加一個FourthActivity,并且MianActivity,SecondActivity,ThirdActivity和FourthActivity這四個activity都不設(shè)置taskAffinity屬性,并且將SecondActivity啟動模式設(shè)為singleTask,這樣這四個activity會在同一個任務(wù)中開啟。他們的開啟流程是這樣的:MianActivity開啟SecondActivity,SecondActivity開啟ThirdActivity,ThirdActivity開啟FourthActivity,F(xiàn)ourthActivity開啟SecondActivity。代碼和軟件界面就不列出了,只列出清單文件

      1. <?xml version='1.0' encoding='utf-8'?>
      2. <manifest xmlns:android='http://schemas./apk/res/android'
      3. package='com.jg.zhang.androidtasktest'
      4. android:versionCode='1'
      5. android:versionName='1.0' >
      6. <uses-sdk android:minSdkVersion='10' android:targetSdkVersion='17' />
      7. <application android:allowBackup='true'
      8. android:icon='@drawable/ic_launcher' android:label='androidtasktest'>
      9. <activity android:name='com.jg.zhang.androidtasktest.MainActivity'>
      10. <intent-filter>
      11. <action android:name='android.intent.action.MAIN' />
      12. <category android:name='android.intent.category.LAUNCHER' />
      13. </intent-filter>
      14. </activity>
      15. <activity android:name='com.jg.zhang.androidtasktest.SecondActivity'
      16. android:launchMode='singleTask'/>
      17. <activity android:name='com.jg.zhang.androidtasktest.ThirdActivity'/>
      18. <activity android:name='com.jg.zhang.androidtasktest.FourthActivity'/>
      19. </application>
      20. </manifest>

      現(xiàn)在從MianActivity一直啟動到FourthActivity,打印出的系統(tǒng)Log為:


      由此可見這四個activity都是在同一個任務(wù)中的。再次執(zhí)行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的任務(wù)中,即棧中的狀態(tài)為MainActivity -->  SecondActivity --> ThirdActivity --> FourthActivity。

      下面執(zhí)行在FourthActivity中點擊按鈕啟動SecondActivity的操作,注意,SecondActivity的啟動模式為singleTask,那么現(xiàn)在棧中的情況如何呢?再次執(zhí)行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}

      這時棧中的狀態(tài)為MainActivity -->  SecondActivity。確實確保了在任務(wù)中是唯一的,并且清除了同一任務(wù)中它上面的所有Activity。 那么這個SecondActivity的實例是重用的上次已有的實例還是重新啟動了一個實例呢?可以觀察系統(tǒng)Log, 發(fā)現(xiàn)系統(tǒng)Log沒有改變,還是上面的四條Log。打印Log的語句是在各個Activity中的onCreate方法中執(zhí)行的,沒有打印出新的Log,說明SecondActivity的onCreate的方法沒有重新執(zhí)行,也就是說是重用的上次已經(jīng)啟動的實例,而不是銷毀重建。

      經(jīng)過上面的驗證,可以得出如下的結(jié)論:在啟動一個singleTask的Activity實例時,如果系統(tǒng)中已經(jīng)存在這樣一個實例,就會將這個實例調(diào)度到任務(wù)棧的棧頂,并清除它當(dāng)前所在任務(wù)中位于它上面的所有的activity。



      四 實例驗證singleInstance的行為


      根據(jù)上面的講解,并且參考谷歌官方文檔,singleInstance的特點可以歸結(jié)為以下三條:
      1. 以singleInstance模式啟動的Activity具有全局唯一性,即整個系統(tǒng)中只會存在一個這樣的實例
      2. 以singleInstance模式啟動的Activity具有獨占性,即它會獨自占用一個任務(wù),被他開啟的任何activity都會運行在其他任務(wù)中(官方文檔上的描述為,singleInstance模式的Activity不允許其他Activity和它共存在一個任務(wù)中)
      3. 被singleInstance模式的Activity開啟的其他activity,能夠開啟一個新任務(wù),但不一定開啟新的任務(wù),也可能在已有的一個任務(wù)中開啟
      下面對這三個特點分別驗證,所使用的示例同樣為AndroidTaskTest,只不過會進行一些修改,下面列出它的清單文件:

      1. <?xml version='1.0' encoding='utf-8'?>
      2. <manifest xmlns:android='http://schemas./apk/res/android'
      3. package='com.jg.zhang.androidtasktest'
      4. android:versionCode='1'
      5. android:versionName='1.0' >
      6. <uses-sdk android:minSdkVersion='10' android:targetSdkVersion='17' />
      7. <application android:allowBackup='true'
      8. android:icon='@drawable/ic_launcher' android:label='androidtasktest'>
      9. <activity android:name='com.jg.zhang.androidtasktest.MainActivity'>
      10. <intent-filter>
      11. <action android:name='android.intent.action.MAIN' />
      12. <category android:name='android.intent.category.LAUNCHER' />
      13. </intent-filter>
      14. </activity>
      15. <activity android:name='com.jg.zhang.androidtasktest.SecondActivity'
      16. android:launchMode='singleInstance'>
      17. <intent-filter>
      18. <action android:name='com.jg.zhang.androidtasktest.ACTION_MY'/>
      19. <category android:name='android.intent.category.DEFAULT'/>
      20. </intent-filter>
      21. </activity>
      22. <activity android:name='com.jg.zhang.androidtasktest.ThirdActivity'/>
      23. </application>
      24. </manifest>

      由上面的清單文件可以知道,該應(yīng)用包括三個activity,分別為MianActivity,SecondActivity,ThirdActivity,其中SecondActivity啟動模式設(shè)置為singleInstance。MianActivity可以開啟SecondActivity,SecondActivity可以開啟ThirdActivity。 并且為了可以在其他應(yīng)用中開啟SecondActivity,為SecondActivity設(shè)置了一個IntentFilter,這樣就可以在其他應(yīng)用中使用隱式Intent開啟SecondActivity。

      為了更好的驗證singleInstance的全局唯一性,還需要其他一個應(yīng)用,對上面的AndroidTaskTest1進行一些修改即可。AndroidTaskTest1只需要一個MianActivity,在MainActivity中點擊按鈕會開啟AndroidTaskTest應(yīng)用中的SecondActivity。開啟AndroidTaskTest應(yīng)用中的SecondActivity的代碼如下:

      1. /**
      2. * 該方法在布局中按鈕的android:onClick屬性中指定
      3. * android:onClick='launchOtherActivity'
      4. * @param v
      5. */
      6. public void launchOtherActivity(View v){
      7. Intent intent = new Intent();
      8. //以下Action為'com.jg.zhang.androidtasktest.ACTION_MY'
      9. //即AndroidTaskTest應(yīng)用中SecondActivity的action
      10. intent.setAction('com.jg.zhang.androidtasktest.ACTION_MY');
      11. startActivity(intent);
      12. }

      下面開始驗證第一個特點:以singleInstance模式啟動的Activity具有全局唯一性,即整個系統(tǒng)中只會存在一個這樣的實例


      執(zhí)行如下操作:安裝AndroidTaskTest應(yīng)用,點擊MainActivity中的按鈕,開啟SecondActivity,可以看到如下log輸出:

      執(zhí)行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總是會在新的任務(wù)中運行(前提是系統(tǒng)中還不存在這樣的一個實例) 。

      下面驗證它的全局唯一性,執(zhí)行以下操作:安裝另一個應(yīng)用AndroidTaskTest1,在開啟的MainActivity中點擊按鈕開啟AndroidTaskTest應(yīng)用中的SecondActivity??吹酱蛴〕鲆粭l新的日志:


      執(zhí)行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就是上次創(chuàng)建的編號為4129af80的SecondActivity,并且Log中沒有再次輸出關(guān)于SecondActivity的信息,說明SecondActivity并沒有重新創(chuàng)建。由此可以得出結(jié)論:以singleInstance模式啟動的Activity在整個系統(tǒng)中是單例的,如果在啟動這樣的Activiyt時,已經(jīng)存在了一個實例,那么會把它所在的任務(wù)調(diào)度到前臺,重用這個實例。

      下面開始驗證第二個特點:以singleInstance模式啟動的Activity具有獨占性,即它會獨自占用一個任務(wù),被他開啟的任何activity都會運行在其他任務(wù)中


      重新安裝AndroidTaskTest應(yīng)用,點擊MainActivity中的按鈕,開啟SecondActivity,在SecondActivity中點擊按鈕,開啟ThirdActivity??梢钥吹接腥缦翷og輸出:


      執(zhí)行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所在的任務(wù)為16,被SecondActivity啟動的ThirdActivity所在的任務(wù)為15,這就說明以singleInstance模式啟動的Activity具有獨占性,即它會獨自占用一個任務(wù),被他開啟的任何activity都會運行在其他任務(wù)中



      下面開始驗證第三個特點:被singleInstance模式的Activity開啟的其他activity,能夠在新的任務(wù)中啟動,但不一定開啟新的任務(wù),也可能在已有的一個任務(wù)中開啟


      有上面對第二個特點的驗證可以看到,被SecondActivity啟動的ThirdActivity并沒有運行在一個新開啟的任務(wù)中,而是和MainActivity運行在了同一個已有的任務(wù)中,那么在什么情況下ThirdActivity才會啟動一個新的任務(wù)呢?

      現(xiàn)在對程序的清單文件做以下修改,為ThirdActivity增加一個屬性taskAffinity:

      1. <activity android:name='com.jg.zhang.androidtasktest.ThirdActivity'
      2. android:taskAffinity='com.jg.zhang.androidtasktest.second'/>

      重新安裝AndroidTaskTest應(yīng)用,執(zhí)行和上一步中同樣的操作:點擊MainActivity中的按鈕,開啟SecondActivity,在SecondActivity中點擊按鈕,開啟ThirdActivity。可以看到有如下輸出:



      執(zhí)行adb shell dumpsys activity命令,有以下輸出:

      TaskRecord{413551b0 #20 A com.jg.zhang.androidtasktest.second}
       Run #3: ActivityRecord{412de9c0 com.jg.zhang.androidtasktest/.ThirdActivity}
      TaskRecord{4134b268 #19 A com.jg.zhang.androidtasktest}
      Run #2: ActivityRecord{412a36a0 com.jg.zhang.androidtasktest/.SecondActivity}
      TaskRecord{413131e8 #18 A com.jg.zhang.androidtasktest}
      Run #1: ActivityRecord{41271e10 com.jg.zhang.androidtasktest/.MainActivity}

      可見,被SecondActivity啟動的ThirdActivity啟動在了一個新的任務(wù)中,即在啟動ThirdActivity時創(chuàng)建了一個新任務(wù)。這就說明被singleInstance模式的Activity A在開啟另一activity B時,能夠開啟一個新任務(wù),但是是不是真的開啟新任務(wù),還要受其他條件的限制,這個條件是:當(dāng)前系統(tǒng)中是不是已經(jīng)有了一個activity B的taskAffinity屬性指定的任務(wù)。

      其實這種行為和singleTask啟動時的情況相同。在Activity的啟動模式設(shè)置為singleTask時,啟動時系統(tǒng)會為它加上FLAG_ACTIVITY_NEW_TASK標(biāo)志,而被singleInstance模式的Activity開啟的activity,啟動時系統(tǒng)也會為它加上FLAG_ACTIVITY_NEW_TASK標(biāo)志,所以他們啟動時的情況是相同的,上面再驗證singleTask時已經(jīng)闡述過,現(xiàn)在重新說明一下:

      由于ThirdActivity是被啟動模式為singleInstance類型的Activity(即SecondActivity)啟動的,framework會為它它加上FLAG_ACTIVITY_NEW_TASK標(biāo)志,這時  framework會檢索是否已經(jīng)存在了一個affinity為com.jg.zhang.androidtasktest.second(即ThirdActivity的taskAffinity屬性)的任務(wù),

      • 如果存在這樣的一個任務(wù),則檢查在這個任務(wù)中是否已經(jīng)有了一個ThirdActivity的實例,
        1. 如果已經(jīng)存在一個ThirdActivity的實例,則會重用這個任務(wù)和任務(wù)中的ThirdActivity實例,將這個任務(wù)調(diào)到前臺,清除位于ThirdActivity上面的所有Activity,顯示ThirdActivity,并調(diào)用ThirdActivity的onNewIntent()。
        2. 如果不存在一個ThirdActivity的實例,會在這個任務(wù)中創(chuàng)建ThirdActivity的實例,并調(diào)用onCreate()方法
      • 如果不存在這樣的一個任務(wù),會創(chuàng)建一個新的affinity為com.jg.zhang.androidtasktest.second的任務(wù),并且將ThirdActivity啟動到這個新的任務(wù)中

      如果ThirdActivity不設(shè)置taskAffinity,即ThirdActivity和MainActivity的taskAffinity相同,都為應(yīng)用的包名,那么ThirdActivity是不會開啟一個新任務(wù)的,framework中的判定過程如下:

      1. 在SecondActivity啟動ThirdActivity時,因為SecondActivity是singleInstance的,所以設(shè)定ThirdActivity的啟動標(biāo)志為FLAG_ACTIVITY_NEW_TASK
      2. 然后獲得ThirdActivity的taskAffinity,即為包名com.jg.zhang.androidtasktest
      3. 檢查是否已經(jīng)存在一個affinity為com.jg.zhang.androidtasktest的任務(wù),這個任務(wù)是存在的,就是MainActivity所在的任務(wù),這個任務(wù)是在啟動MainActivity時開啟的
      4.  既然已經(jīng)存在這個任務(wù),就檢索在這個任務(wù)中是否存在一個ThirdActivity的實例,發(fā)現(xiàn)不存在
      5.  在這個已有的任務(wù)中啟動一個SecondActivity的實例

      為了作一個清楚的比較,列出ThirdActivity的taskAffinity屬性設(shè)為com.jg.zhang.androidtasktest.second時的啟動過程

      1. 在SecondActivity啟動ThirdActivity時,因為SecondActivity是singleInstance的,那么設(shè)定ThirdActivity的啟動標(biāo)志為FLAG_ACTIVITY_NEW_TASK
      2. 然后獲得ThirdActivity的taskAffinity,即為com.jg.zhang.androidtasktest.second
      3. 檢查是否已經(jīng)存在一個affinity為com.jg.zhang.androidtasktest.second的任務(wù),這個任務(wù)是不存在的
      4.  創(chuàng)建一個新的affinity為com.jg.zhang.androidtasktest.second的任務(wù),并且將ThirdActivity啟動到這個新的任務(wù)
      到此singleInstance也介紹完了。


      五 本文小結(jié)


      由上述可知,Task是Android Framework中的一個概念,Task是由一系列相關(guān)的Activity組成的,是一組相關(guān)Activity的集合。Task是以棧的形式來管理的。

      我們在操作軟件的過程中,一定會涉及界面的跳轉(zhuǎn)。其實在對界面進行跳轉(zhuǎn)時,Android Framework既能在同一個任務(wù)中對Activity進行調(diào)度,也能以Task為單位進行整體調(diào)度。在啟動模式為standard或singleTop時,一般是在同一個任務(wù)中對Activity進行調(diào)度,而在啟動模式為singleTask或singleInstance是,一般會對Task進行整體調(diào)度。

      對Task進行整體調(diào)度包括以下操作:

      1. 按Home鍵,將之前的任務(wù)切換到后臺
      2. 長按Home鍵,會顯示出最近執(zhí)行過的任務(wù)列表
      3. 在Launcher或HomeScreen點擊app圖標(biāo),開啟一個新任務(wù),或者是將已有的任務(wù)調(diào)度到前臺
      4. 啟動singleTask模式的Activity時,會在系統(tǒng)中搜尋是否已經(jīng)存在一個合適的任務(wù),若存在,則會將這個任務(wù)調(diào)度到前臺以重用這個任務(wù)。如果這個任務(wù)中已經(jīng)存在一個要啟動的Activity的實例,則清除這個實例之上的所有Activity,將這個實例顯示給用戶。如果這個已存在的任務(wù)中不存在一個要啟動的Activity的實例,則在這個任務(wù)的頂端啟動一個實例。若這個任務(wù)不存在,則會啟動一個新的任務(wù),在這個新的任務(wù)中啟動這個singleTask模式的Activity的一個實例。
      5. 啟動singleInstance的Activity時,會在系統(tǒng)中搜尋是否已經(jīng)存在一個這個Activity的實例,如果存在,會將這個實例所在的任務(wù)調(diào)度到前臺,重用這個Activity的實例(該任務(wù)中只有這一個Activity),如果不存在,會開啟一個新任務(wù),并在這個新任務(wù)中啟動這個singleInstance模式的Activity的一個實例。

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多