天天看點

Activity四種啟動模式和task、process、Application之間的差別

          在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檔案中清晰地展現出來。如下所示:

<?xmlversion="1.0" encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.testlaunchermode"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18"/>
 
    <application
        android:allowBackup="true"
       android:icon="@drawable/ic_launcher"
       android:label="@string/app_name"
       android:theme="@style/AppTheme" >
        <activity
           android:name="com.example.testlaunchermode.MainActivity"
           android:label="@string/app_name" >
            <intent-filter>
                <actionandroid:name="android.intent.action.MAIN" />
 
                <categoryandroid:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <serviceandroid:name=""></service>
        <receiverandroid:name=""></receiver>
        <providerandroid:name=""></provider>
    </application>
 
</manifest>
           

        由此可見,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,任務被移除。如下圖所示:

Activity四種啟動模式和task、process、Application之間的差別

        Task是可以跨應用的,這正是task存在的一個重要原因。有的Activity,雖然不在一個app中,但是為了保持使用者操作的連貫性,把他們放在同一個任務中。例如,在我們的應用中的一個Activity A中點選發送郵件,會啟動郵件程式的一個Activity B來發送郵件,這兩個activity是存在于不同app中,但是被系統放到了一個程式中,這樣當發送完郵件後,使用者按back鍵傳回,可以傳回原來的Activity A中,這樣就確定了使用者體驗。

       說完了application和task,最後介紹process。Process一般翻譯成程序,程序是作業系統核心中的一個概念,表示隻接受核心排程的執行機關。在應用程式的角度看,我們用java編寫的應用程式,運作在dalvik虛拟機中,可以認為一個運作中的dalvik虛拟機執行個體占有一個程序,是以,在預設情況下,一個應用程式的所有元件運作在同一個程序中。但是這種情況也有例外,即應用程式中的不同元件可以運作在不同的程序中,隻需要在manifest中用process屬性指定元件所運作的程序的名字,如下所示:

<activity android:name=".MainActivity"
               android:process=":remote"
</activity>
           

這樣的話,這個activity就會運作在一個單獨的程序中。

二 Activity四中啟動模型詳解

     activity有四種啟動模式,分别是:standard、singleTop、singleTask、singleIntance,如何要使用這四種啟動方式,就必須在manifest檔案中進行注冊,如下所示:

<activity android:name=".MainActivity"
            android:label="@string/app_name"
             android:theme="@style/mainapp_style"
             android:launchMode="singleTask">
</activity>
           

       同樣,在Intent類中定義了許多與Activity啟動或者排程有關的标志,<activity>标簽中也有一些屬性,這些标志和屬性與四種啟動模式聯合使用,會在很大程度上改變activity的行為,進而會改變task和back state的狀态。關于Intent中的标志和<activity>中的一些屬性會在本文後面介紹,在這一節中,先介紹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的一個執行個體。

SingleTask

      雖然官方文檔說:如果一個activity的啟動模式為singleTask,那麼系統總會在一個新任務的最底部(root)啟動這個activity并且被這個activity啟動的其他activity會和該activity同時存在于這個新任務棧中,诶過系統中已經存在這樣的一個activity則會重用這個執行個體,并且調用它的onNewIntent()方法。即這樣的一個activity在系統中隻會存在一個執行個體。但是官方文檔中的這種說法是不正确的,啟動模式為singleTask的activity并不會總是開啟一個新的任務。稍後進行驗證詳解。

SingleIntance

      總是在新的任務中開啟,并且這個新的任務中有且隻有這一個執行個體,也就是說被該執行個體啟動的其他activity會自動運作于另一個任務中。當再次啟動該activity的執行個體看,會重用已存在的任務和執行個體。并且會調用這個執行個體的onNewIntent()方法,将Intent執行個體傳遞到該執行個體中。和singleTask相同,同一時刻在系統中隻會存在一個這樣的activity執行個體。

三.執行個體驗證singleTask啟動模式

     上面的四中啟動模式都已經介紹完畢,為了加強了解,下面進行驗證,由于singleTop和standard比較簡單,是以就不再介紹,下面主要講解singleTask和singleInstance。

驗證啟動singleTask模式的Activity時是否會建立新任務

       建立android工程:TestLaunchMode,裡面有三個Activity,MainActivity、SecondActivity、ThirdActivity,下面是Manifest檔案:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testlaunchermode"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.testlaunchermode.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>         
        <activity android:name="com.example.testlaunchermode.SecondActivity"
            android:launchMode="singleTask"
            >
            <intent-filter >
                <action android:name="com.example.testlaunchermode.SecondActivity"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        <activity android:name="com.example.testlaunchermode.ThirdActivity"></activity>
    </application>

</manifest>
           

從上面可以看出:MainActivity和ThirdActivity的啟動方式是standard模式,但是SecondActivity的啟動方式是SingleTask模式。三個界面之間就是主界面啟動第二個界面,第二個界面啟動第三個界面,很簡單。

Activity四種啟動模式和task、process、Application之間的差別

      以下是三個Activity的主要代碼:

      MainActivity:

package com.example.testlaunchermode;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

	private static final String ACTIVITY_NAME="MainActivity";
	private static final String LOG_TAG="****";
	private Button btn_next;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		btn_next=(Button) findViewById(R.id.btn_next);
		btn_next.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
                Intent intent=new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
			}
		});
		int id=getTaskId();
		Log.i(LOG_TAG, ACTIVITY_NAME+"所在的任務id為:"+id);
	}

}
           

SecondActivity:

package com.example.testlaunchermode;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class SecondActivity extends Activity {
	
	private static final String ACTIVITY_NAME="SecondActivity";
	private static final String LOG_TAG="****";
	private Button btn_next;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        btn_next=(Button) findViewById(R.id.btn_next);
        btn_next.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
              Intent intent=new Intent(SecondActivity.this, ThirdActivity.class);
              startActivity(intent);
			}
		});
		int id=getTaskId();
		Log.i(LOG_TAG, ACTIVITY_NAME+"所在的任務id為:"+id);
	}
}
           

ThirdActivity:

package com.example.testlaunchermode;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class ThirdActivity extends Activity {

	private static final String ACTIVITY_NAME="ThirdActivity";
	private static final String LOG_TAG="****";
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);
		int id=getTaskId();
		Log.i(LOG_TAG, ACTIVITY_NAME+"所在的任務id為:"+id);
	}
}
           

以上是運作界面和主要代碼:實作很簡單,就是每一個界面都有一個按鈕,啟動另一個界面。下面是輸出Log:

Activity四種啟動模式和task、process、Application之間的差別

按照官方文檔的說法,SecondActivity應該在一個新的任務棧中,但是從上面的Log資訊可以看出他們兩個的TaskId是一樣的,接下來我們繼續在指令行執行adb shell dumpsys activity,發現:

Activity四種啟動模式和task、process、Application之間的差別

由此可以看出它跟官方文檔描述的是不一樣,MainActivity和SecondActivity啟動是在同一個任務中,其實将啟動模式設定成singleTask,framework在啟動該activity時隻是會把它标記為可以在一個新的任務中啟動,但是是否在一個新的任務中啟動還要受到其他條件的制約。現在在SecondActivity配置中添加TaskAffinity屬性,如下所示:

<activity android:name="com.example.testlaunchermode.SecondActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.example.testlaunchermode.second"
            >
            <intent-filter >
                <action android:name="com.example.testlaunchermode.SecondActivity"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
           

繼續運作一次,将會看到:

Activity四種啟動模式和task、process、Application之間的差別

可以看到SecondActivity和ThirdActivity處在一個新的任務棧中。繼續執行adb shell dumpsys activity,結果如下:

Activity四種啟動模式和task、process、Application之間的差別

是以我們可以知道隻有在singleTask模式的啟動中添加屬性TsakAffinity才會在新的任務棧中啟動singleTask模式的activity。這時就引出了一個屬性TsakAffinity, 官方文檔表示:

       1.taskAffinity表示目前Activity具有親和力的一個任務,可以這樣了解,這個taskAffinity表示一個任務,這個任務就是目前的activity所在的任務。

       2.在概念上,具有相同的affinity的activity(即設定了相同taskAffinity屬性的activity)屬于同一個任務。

       3.一個任務的affinity決定了這個任務的根activity的taskAffinity。

       4.這個屬性決定兩件事:當activity被reparent時,它可以被reparent哪個任務中;當activity以FLAG_ACTIVITY_NEW_TASK标志啟動時,它會被啟動到哪個任           務中。(這個可以結合<activity>中的屬性allowTaskReparenting和Intent中的标志FLAG_ACTIVITY_NEW+TASK一起加以了解)。

       5.預設情況下,一個應用中的所有activity具有相同的taskAffinity,即應用程式的包名,我們可以通過設定不同的taskAffinity屬性給應用中的activity分組,也可           以把不同的應用中的activity的taskAffinity設定成相同的值。

       6.為一個activity的taskAffinity設定一個空字元串,表明這個activity不屬于任何task。

      這就可以解釋上面示例中的現象了,由第五條可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity為“com.example.testlaunchermode”而SecondActivity的taskAffinity為“com.example.testlaunchermode.second”。根據上面的第四條,taskAffinity可以影響activity以FLAG_ACTIVITY_NEW_TASK啟動時,它會被啟動到哪一個任務中,這句話的意思是,當新啟動的activity(SecondActivity)是以FLAG_ACTIVITY_NEW_TASK标志啟動時(可以認為FLAG_ACTIVITY_NEW_TASK和singleTask作用相同,當啟動模式是singleTask時,framework會将它的啟動标志設為FLAG_ACTIVITY_NEW_TASK),framwork會檢索是否已經存在了一個affinity為com.example.testlaunchermode.second的任務(即TaskRecord對象)

       如果存在這樣的一個任務,則檢查在這個任務中是否已經有了一個SecondActivity的執行個體

             如果已經存在一個SecondActivity的執行個體,則會重用這個任務和任務中的SecondActivity執行個體,将這個任務調到前台,清除位于SecondActivity上面的所有              的Activity,顯示SecondActivity,并調用SecondActivity的onNewIntent();

             如果不存在一個SecondActivity的執行個體,會在這個任務中建立SecondActivity的執行個體,并調用onCreate()方法;

      如果不存在這樣的一個任務,會建立一個新的affinity為“com.example.testlaunchermode.second”的任務,并且将SecondActivity啟動到這個新的任務中。

     上面讨論了設定taskAffinity屬性的情況,如果SecondActivity隻設定了啟動模式為SingTask,而不設定taskAffinity,即三個Activity的taskAffinity相同,都為應用的包名,那麼SecondActivity是不會開啟一個新的任務的,frameWork的判定如下:

     1.MainActivity啟動SecondActivity時,發現啟動模式為SingleTask,那麼設定它的啟動标志為FLAG_ACTIVITY_NEW_TASK

     2.然後獲得SecondActivity的taskAffinity,即包名com.example.testlaunchermode

     3.檢查是否已經存在一個affinity為com.example.testlaunchermode的任務,這個任務是存在的,就是MainActivity所在的任務,這個任務是在啟動MainActivity的時候開啟的

     4.既然已經存在這樣的任務,就檢索在這個任務中是否存在一個SecondActivity的執行個體,發現不存在

     5.在這個已經存在的任務中建立SecondActivity的執行個體

    為了做一個清除的比較,列出SecondActivity'啟動模式設成singleTask,并且taskAffinity設為“com.example.testlaunchermode.second”時的啟動過程

      1.MainActivity啟動SecondActivity時,發現啟動模式為SingleTask,那麼設定它的啟動标志為FLAG_ACTIVITY_NEW_TASK

      2.然後擷取SecondActivity的taskActivity,即com.example.testlaunchermode.second

      3.檢查是否已經存在一個affinity為com.example.testlaunchermode.second的任務,這個任務不存在

      4.建立一個新的affinity為com.example.testlaunchermode.second的任務,并将SecondActivity啟動到這個新的任務中

      其實framework中對activity和任務的排程是非常複雜的,尤其是把啟動模式設定成singleTask或者以FLAG_ACTIVITY_NEW_TASK标志啟動時。是以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK時,要仔細的測試應用程式。

      執行個體驗證将兩個不同app中的不同的singleTask模式的Activity的taskAffinity設成相同

     官方文檔中提到,可以将不同應用的中的activity的taskAffinity設定成相同的值,這樣的話這兩個activity雖然不在同一個應用中,卻會在運作時配置設定到同一個任務棧中,下面對此進行驗證,建立一個新工程,TestLaunchermode2,裡面有兩個Activity,一個是MainActivity,另一個是OtherActivity,在MainActivity中點選按鈕會啟動OtherActivity,改程式的界面和上一個類似,代碼也類似,在此列出清單檔案

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testlaunchermode2"
    android:versionCode="1"
    android:versionName="1.0" >


    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />


    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.testlaunchermode2.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />


                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity 
            android:name="com.example.testlaunchermode2.OtherActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.example.testlaunchermode.second"
            ></activity>
    </application>


</manifest>
           

可以看到OtherActivity的啟動模式被設定成了singleTask,并且taskAffinity屬性被設定成為"com.example.testlaunchermode.second"這和 TestLaunchermode中的SecondActivity相同。現在将這兩個應用安裝到裝置上,執行以下操作:啟動TestLaunchermode應用,在他的MainActivity中點選按鈕開啟SecondActivity,由上面的介紹可知SecondActivity是運作在一個新的任務中的,這個任務就是com.example.testlaunchermode.second,然後按住Home鍵回到Launcher,啟動TestLaunchermode2,在啟動TestLaunchermode2的入口Activity時,會自動啟動新的任務,那麼現在就有三個任務了,TestLaunchermode的MainActivity和SecondActivity分别占用一個任務,TestLaunchermode2的MainActivity也占用一個任務,現在在TestLaunchermode2的MainActivity點選按鈕啟動OtherActivity,那麼這個OtherActivity是在哪個任務中呢?通過 adb shell dumpsys activity指令,發現有以下輸出:

Activity四種啟動模式和task、process、Application之間的差別
Activity四種啟動模式和task、process、Application之間的差別
Activity四種啟動模式和task、process、Application之間的差別
Activity四種啟動模式和task、process、Application之間的差別

從上圖可以看出任務有三個,TestLaunchermode的SecondActivity和TestLaunchermode2的OtherActivity處于同一個任務中,從上面還可以看出TestLaunchermode和TestLaunchermode2分别開啟了兩個程序,但是com.example.testlaunchermode.second任務中的兩個activity:SecondActivity和OtherActivity屬于不同的應用,并且處在兩個不同的程序中,這就說明,task不僅可以跨應用而且還可以跨程序

        執行個體驗證singleTask在同一個應用中具有唯一性

        修改上面的工程TestLaunchermode,添加activity,FourthActivity,其中所有的activity都不設定taskAffinity屬性,啟動流程為:MainActivity啟動SecondActivity,SecondActivity啟動ThirdActivity,ThirdActivity啟動FourthActivity,FourthActivity啟動SecondActivity,SecondActivity啟動方式為SingleTask。清單檔案如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testlaunchermode"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.testlaunchermode.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>  
        <!-- android:taskAffinity="com.example.testlaunchermode.second" -->       
        <activity android:name="com.example.testlaunchermode.SecondActivity"
            android:launchMode="singleTask"
            >
            <intent-filter >
                <action android:name="com.example.testlaunchermode.SecondActivity"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        <activity android:name="com.example.testlaunchermode.ThirdActivity"></activity>
        <activity android:name="com.example.testlaunchermode.FourthActivity"></activity>
    </application>

</manifest>
           
Activity四種啟動模式和task、process、Application之間的差別

        由此可見四個activity均在同一個任務中,輸入指令:adb shell dumpsys activity得到如下結果:

Activity四種啟動模式和task、process、Application之間的差別

       此時當點選FourthActivity中的按鈕再次啟動SecondActivity,注意,此時由于SecondActivity的啟動方式是singleTask,則出現結果如下圖所示:

Activity四種啟動模式和task、process、Application之間的差別

       此時棧中的狀态為MainActivity-->SecondActivity。确實確定了在任務中是唯一的,并且清楚了同一任務中它上面的所有的Activity.那麼這個SecondActivity的執行個體是重用上次已經存在的還是重新啟動的呢?可以檢視系統Log發現Log并沒有改變,這是因為列印Log的語句實在OnCreate()方法中執行的,沒有Log列印說明沒有執行OnCreate()方法,也就說明是使用的上次的執行個體,而并不是銷毀重建。

     經過上面的驗證可以得出:在啟動一個singleTask的Activity執行個體時,如果系統中已經存在這樣的一個執行個體,就會将這個執行個體調到任務棧的棧頂,并清除它目前躲在任務中位于它上面的suoyoudeactivity。

四 執行個體驗證singleInstance啟動模式

     根據官方文檔singleInstance啟動模式有以下特點:

      1.以singleInstance模式啟動的Activity具有全局唯一性,即整個系統中隻會存在一個這樣的執行個體

      2.以singleInstance模式啟動的Activity具有獨占性,即它會獨自占用一個任務,被他開啟的任何activity都會運作在其他任務中(官方文檔上的描述為,singleInstance模式的Activity不允             許其他Activity和它共存在一個任務中)

      3.被singleInstance模式的Activity開啟的其他activity,能夠開啟一個新任務,但不一定開啟新的任務,也可能在已有的一個任務中開啟

      下面開始驗證上面的三個特點,先對之前的項目TestLaunchermode進行修改,清單檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testlaunchermode"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.testlaunchermode.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>  
        <!-- android:taskAffinity="com.example.testlaunchermode.second" -->       
        <activity android:name="com.example.testlaunchermode.SecondActivity"
            android:launchMode="singleInstance"
            >
            <intent-filter >
                <action android:name="com.example.testlaunchermode.ACTION_MY"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        <activity android:name="com.example.testlaunchermode.ThirdActivity"></activity>
    </application>

</manifest>
           

        在上面主要有三個activity,MainActivity啟動SecondActivity,SecondActivity啟動ThirdActivity,其中SecondActivity的啟動方式是SingleInstance,為了友善通路,設定intentFilter屬性:com.example.testlaunchermode.ACTION_MY。并且對 TestLaunchermode2進行相應的修改,使得它的MainActivity能夠啟動SecondActivity,主要代碼如下:

btn_next=(Button) findViewById(R.id.btn_next);
		btn_next.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
                Intent intent=new Intent("com.example.testlaunchermode.ACTION_MY");
                startActivity(intent);
			}
		});
           

        下面開始驗證第一個特點:以singleInstance模式啟動的Activity具有全局唯一性,即整個系統中隻會存在一個這樣的執行個體

        運作TestLaunchermode,點選MainActivity中的按鈕啟動SecondActivity,将會發現:

Activity四種啟動模式和task、process、Application之間的差別

        輸入指令:adb shell dumpsys activity,将會看到:

Activity四種啟動模式和task、process、Application之間的差別

        這說明singleInstance模式的activity總會在一個新的任務中運作(前提是還沒有存在一個這樣的執行個體)

        為了驗證它的全局唯一性,運作TestLaunchermode2,點選MainActivity中的按鈕啟動SecondActivity。Log輸入如下所示:

Activity四種啟動模式和task、process、Application之間的差別
Activity四種啟動模式和task、process、Application之間的差別

         Log沒有輸出新的關于SecondActivity的資訊說明是重用了已經存在的執行個體,是以可以得出結論:以singleInstance模式啟動的activity在整個系統中都是單例的,如果在啟動這樣的activity時,已經存在了一個執行個體,那麼會把它所在的任務排程到前台,重用這個執行個體。

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

         重新運作TestLaunchermode,在MainActivity中點選按鈕啟動SecondActivity,然後點選SecondActivity中的按鈕啟動ThirdActivity,Log資訊如下:

Activity四種啟動模式和task、process、Application之間的差別
Activity四種啟動模式和task、process、Application之間的差別

         從上面的輸出可以得出以下結論:以singleInstance模式啟動的Activity具有獨占性,即它會獨自占用一個任務,被他開啟的任何activity都會運作在其他任務中

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

          從第二個特點的驗證可以看出,啟動ThirdActivity并沒有在新的任務中啟動而是還在在原來的任務中啟動,對上面的SecondActivity添加taskAffinity屬性如下所示:

<activity android:name="com.example.testlaunchermode.SecondActivity"
            android:launchMode="singleInstance"
            android:taskAffinity="com.example.testlaunchermode.second"
            >
            <intent-filter >
                <action android:name="com.example.testlaunchermode.ACTION_MY"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
           

           此時Log的列印消息為:

Activity四種啟動模式和task、process、Application之間的差別
Activity四種啟動模式和task、process、Application之間的差別

          這就說明被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.example.testlaunchermode.second(即ThirdActivity的taskAffinity屬性)的任務

         如果存在這樣的一個任務,則檢查在這個任務中是否已經有了一個ThirdActivity的執行個體,

                 如果已經存在一個ThirdActivity的執行個體,則會重用這個任務和任務中的ThirdActivity執行個體,将這個任務調到前台,清除位于ThirdActivity上面的所有Activity,顯示ThirdActivity,并調用ThirdActivity的                  onNewIntent()。

                 如果不存在一個ThirdActivity的執行個體,會在這個任務中建立ThirdActivity的執行個體,并調用onCreate()方法

         如果不存在這樣的一個任務,會建立一個新的affinity為com.example.testlaunchermode.second的任務,并且将ThirdActivity啟動到這個新的任務中

        如果ThirdActivity不設定taskAffinity,即ThirdActivity和MainActivity的taskAffinity相同,都為應用的包名,那麼ThirdActivity是不會開啟一個新任務的,framework中的判定過程如下:

                1.在SecondActivity啟動ThirdActivity時,因為SecondActivity是singleInstance的,是以設定ThirdActivity的啟動标志為FLAG_ACTIVITY_NEW_TASK

                2.然後獲得ThirdActivity的taskAffinity,即為包名com.example.testlaunchermode.second

                3.檢查是否已經存在一個affinity為com.example.testlaunchermode的任務,這個任務是存在的,就是MainActivity所在的任務,這個任務是在啟動MainActivity時開啟的

                4.既然已經存在這個任務,就檢索在這個任務中是否存在一個ThirdActivity的執行個體,發現不存在

                5.在這個已有的任務中啟動一個SecondActivity的執行個體

        為了作一個清楚的比較,列出ThirdActivity的taskAffinity屬性設為com.example.testlaunchermode.second時的啟動過程

               1.在SecondActivity啟動ThirdActivity時,因為SecondActivity是singleInstance的,那麼設定ThirdActivity的啟動标志為FLAG_ACTIVITY_NEW_TASK

               2.然後獲得ThirdActivity的taskAffinity,即為com.example.testlaunchermode.second

               3.檢查是否已經存在一個affinity為com.example.testlaunchermode.second的任務,這個任務是不存在的

               4.建立一個新的affinity為com.example.testlaunchermode.second的任務,并且将ThirdActivity啟動到這個新的任務

         到此singleInstance也介紹完了。

五 本文總結

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

         我們在操作軟體的過程中,一定會涉及界面的跳轉。其實在對界面進行跳轉時,Android Framework既能在同一個任務中對Activity進行排程,也能以Task為機關進行整體排程。在啟動模式為standard或singleTop時,一般是在同一個任務中對Activity進行排程,而在啟動模式為singleTask或singleInstance是,一般會對Task進行整體排程。對Task進行整體排程包括以下操作:

        1.按Home鍵,将之前的任務切換到背景

        2.長按Home鍵,會顯示出最近執行過的任務清單

        3.在Launcher或HomeScreen點選app圖示,開啟一個新任務,或者是将已有的任務排程到前台

        4.啟動singleTask模式的Activity時,會在系統中搜尋是否已經存在一個合适的任務,若存在,則會将這個任務排程到前台以重用這個任務。如果這個任務中已經存在一個要啟動的Activity的執行個體,則清除這個執行個體之上的所有Activity,将這個執行個體顯示給使用者。如果這個已存在的任務中不存在一個要啟動的Activity的執行個體,則在這個任務的頂端啟動一個執行個體。若這個任務不存在,則會啟動一個新的任務,在這個新的任務中啟動這個singleTask模式的Activity的一個執行個體。

        5.啟動singleInstance的Activity時,會在系統中搜尋是否已經存在一個這個Activity的執行個體,如果存在,會将這個執行個體所在的任務排程到前台,重用這個Activity的執行個體(該任務中隻有這一個Activity),如果不存在,會開啟一個新任務,并在這個新任務中啟動這個singleInstance模式的Activity的一個執行個體。