# Android中Activity四种启动模式和taskAffinity属性详解 #(3)

# Android中Activity四种启动模式和taskAffinity属性详解 #(3)

----------影子侠开发者社区Rong


## 三 实例验证singleTask启动模式 ##

 

 

上面将activity的四种启动模式就基本介绍完了。为了加深对启动模式的了解,下面会通过一个简单的例子进行验证。由以上的介绍可知,standard和singleTop这两种启动模式行为比较简单,所以在下面的例子中,会对singleTask和singleInstance着重介绍。

 

### 验证启动singleTask模式的activity时是否会创建新的任务###

 

以下为验证示例AndroidTaskTest。这个实例中有三个Activity,分别为:MainActivity,SecondActivity和ThirdActivity。以下为这个示例的manifest文件。

 

         <?xmlversion="1.0" encoding="utf-8"?>

         <manifestxmlns:android="http://schemas.android.com/apk/res/android"

             package="com.jg.zhang.androidtasktest"

             android:versionCode="1"

             android:versionName="1.0">

 

               <uses-sdkandroid:minSdkVersion="10" android:targetSdkVersion="17"/>

 

             <applicationandroid:icon="@drawable/ic_launcher" android:label="@string/   app_name">

                <activity  android:label="@string/app_name"

                    android:name="com.jg.zhang.androidtasktest.MainActivity">

                    <intent-filter>

                         <actionandroid:name="android.intent.action.MAIN" />

                         <categoryandroid:name="android.intent.category.LAUNCHER" />

                     </intent-filter>

                 </activity>

                

                <!--android:taskAffinity="com.jg.zhang.androidtasktest.second"

                          android:alwaysRetainTaskState="true"

                          android:allowBackup="true"-->

                         

                  <activityandroid:name="com.jg.zhang.androidtasktest.SecondActivity"

                     android:launchMode="singleTask">

                     <intent-filter >

                         <actionandroid:name="com.jg.zhang.androidtasktest.SecondActivity"/>

                         <categoryandroid:name="android.intent.category.DEFAULT"/>

                     </intent-filter>

                 </activity>

                

                  <activityandroid:name="com.jg.zhang.androidtasktest.ThirdActivity"

                    android:label="@string/app_name" >

                 </activity>

             </application>

            

         </manifest>

 

由此可见,MainActivity和ThirdActivity都是标准的启动模式,而SecondActivity的启动模式为singleTask。

 

以下为这三个Activity的界面,很简单,在MainActivity中点击按钮启动SecondActivity,在SecondActivity中点击按钮启动ThirdActivity。

 

![](https://img-blog.csdn.net/20130903213316203?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

以下为这三个activity的主要代码:

 

**MainActivity**

 

   public class MainActivity extends Activity {

 

                   privatestatic final String ACTIVITY_NAME = "MainActivity";

                   privatestatic final String LOG_TAG = "xxxx";

        

                   @Override

                   protectedvoid onCreate(Bundle savedInstanceState) {

                            super.onCreate(savedInstanceState);

                            setContentView(R.layout.activity_main);

                           

                            findViewById(R.id.button1).setOnClickListener(newView.OnClickListener() {

                                     @Override

                                     publicvoid onClick(View v) {

                                               Intentintent = new Intent(MainActivity.this, SecondActivity.class);

        

                                               startActivity(intent);

                                     }

                            });

                           

                            inttaskId = getTaskId();

                            Log.i(LOG_TAG,ACTIVITY_NAME +"所在的任务的id为: " +  taskId);

                   }

 

**SecondActivity**

 

   public class SecondActivity extends Activity {

                   privatestatic final String ACTIVITY_NAME = "SecondActivity";

                   privatestatic final String LOG_TAG = "xxxx";

                   @Override

                   protectedvoid onCreate(Bundle savedInstanceState) {

                            super.onCreate(savedInstanceState);

                            setContentView(R.layout.activity_second);

                           

                            findViewById(R.id.button2).setOnClickListener(newOnClickListener() {

                                     @Override

                                     publicvoid onClick(View v) {

                                                        Intentintent = new Intent(SecondActivity.this, ThirdActivity.class);

                                                        startActivity(intent);

                                     }

                            });

                           

                            inttaskId = getTaskId();

                            Log.i(LOG_TAG,ACTIVITY_NAME +"所在的任务的id为: " +  taskId);

                           

                   }

 

 

**ThirdActivity**

 

   public class ThirdActivity extends Activity {

        

                   privatestatic final String ACTIVITY_NAME = "ThirdActivity";

                   privatestatic final String LOG_TAG = "xxxx";

                   @Override

                   protectedvoid onCreate(Bundle savedInstanceState) {

                            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相同。

 

![](https://img-blog.csdn.net/20130903215224890?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

在命令行中执行以下命令 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属性,如下所示:

 

      <activityandroid:name="com.jg.zhang.androidtasktest.SecondActivity"

            android:launchMode="singleTask"

            android:taskAffinity="com.jg.zhang.androidtasktest.second">

           <intent-filter >

                <actionandroid:name="com.jg.zhang.androidtasktest.SecondActivity"/>

                <categoryandroid:name="android.intent.category.DEFAULT"/>

           </intent-filter>

       </activity>

 

重新运行该示例,执行相同的操作,即:点击MainActivity界面中的按钮,开启SecondActivity,并且点击SecondActivity中的按钮,启动ThirdActivity,log中输出的内容为:

 

![](https://img-blog.csdn.net/20130903222218765?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

在命令行中执行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{412924c0com.jg.zhang.androidtasktest/.MainActivity}

 

由此可见,MainActivity和SecondActivity运行在不同的任务中了,并且被SecondActivity启动的ThirdActivity和SecondActivity运行在同一个任务中。这种现象的具体解释可以[参考解开Android应用程序组件Activity的"singleTask"之谜](http://blog.csdn.net/luoshengyang/article/details/6714543 "参考解开Android应用程序组件Activity的"singleTask"之谜")。

 

在这里便引出了manifest文件中<activity>的一个重要属性,taskAffinity。在官方文档中可以得到关于taskAffinity的以下信息

 

1. taskAffinity表示当前activity具有亲和力的一个任务(翻译不是很准确,原句为The task that the activity has an affinity for.),大致可以这样理解,这个taskAffinity表示一个任务,这个任务就是当前activity所在的任务。

 

1. 在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务。

 

1. 一个任务的affinity决定于这个任务的根activity(rootactivity)的taskAffinity。

 

1. 这个属性决定两件事:当activity被re-parent时,它可以被re-paren哪个任务中;当activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被启动到哪个任务中。(这个比较    难以理解,请结合<activity>中的属性allowTaskReparenting和Intent中的标志       FLAG_ACTIVITY_NEW_TASK加以理解)

 

1. 默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的       应用中的activity的taskAffinity设置成相同的值。

 

1. 为一个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对象)

 

1 如果存在这样的一个任务,则检查在这个任务中是否已经有了一个SecondActivity的实例,

 

- 如果已经存在一个SecondActivity的实例,则会重用这个任务和任务中的SecondActivity实例,将这个任务调到前台,清除位于SecondActivity上面的所有Activity,显示SecondActivity,并调用SecondActivity的onNewIntent();

- 如果不存在一个SecondActivity的实例,会在这个任务中创建SecondActivity的实例,并调用onCreate()方法

 

2 如果不存在这样的一个任务,会创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将SecondActivity启动到这个新的任务中

 

上面讨论的是设置taskAffinity属性的情况,如果SecondActivity只设置启动模式为singleTask,而不设置taskAffinity,即三个Activity的taskAffinity相同,都为应用的包名,那么SecondActivity是不会开启一个新任务的,framework中的判定过程如下:

 

1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK

 

1. 然后获得SecondActivity的taskAffinity,即为包名com.jg.zhang.androidtasktest

 

1. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的

 

1. 既然已经存在这个任务,就检索在这个任务中是否存在一个SecondActivity的实例,发现不存在

 

1. 在这个已有的任务中启动一个SecondActivity的实例

 

为了作一个清楚的比较,列出SecondActivity启动模式设为singleTask,并且taskAffinity设为com.jg.zhang.androidtasktest.second时的启动过程

 

1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK

 

1. 然后获得SecondActivity的taskAffinity,即com.jg.zhang.androidtasktest.second

 

1. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest.second的任务,这个任务是不存在的

 

1. 创建一个新的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,该程序的界面和上一个类似,代码也类似,再此仅列出清单文件。

 

   <?xml version="1.0" encoding="utf-8"?>

         <manifestxmlns:android="http://schemas.android.com/apk/res/android"

             package="com.jg.zhang.androidtasktest1"

             android:versionCode="1"  android:versionName="1.0" >

        

             <uses-sdkandroid:minSdkVersion="9" android:targetSdkVersion="17" />

        

             <application

                 android:allowBackup="true"android:icon="@drawable/ic_launcher" android:label="@string/app_name"

                android:theme="@style/AppTheme" >

                 <activity

                    android:name="com.jg.zhang.androidtasktest1.MainActivity"

                    android:label="com.jg.zhang.androidtasktest1.MainActivity">

                     <intent-filter>

                         <actionandroid:name="android.intent.action.MAIN" />

                         <categoryandroid:name="android.intent.category.LAUNCHER" />

                     </intent-filter>

                 </activity>

                

                 <activity

                     android:name="com.jg.zhang.androidtasktest1.OtherActivity"

                    android:label="com.jg.zhang.androidtasktest1.OtherActivity"

                    android:taskAffinity="com.jg.zhang.androidtasktest.second"

                    android:launchMode="singleTask">

                 </activity>

             </application>

        

         </manifest>

 

可以看到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 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{412adb28479:com.jg.zhang.androidtasktest1/10044}

                   Hist#3: ActivityRecord{4125c880 com.jg.zhang.androidtasktest/.SecondActivity}

                            Intent{ cmp=com.jg.zhang.androidtasktest/.SecondActivity }

                            ProcessRecord{41218e48463: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{412adb28479: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{41218e48463:com.jg.zhang.androidtasktest/10043}

 

在执行上述操作时,打印出的Log为:

 

![](https://img-blog.csdn.net/20130905214152937?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

所以由此可见,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。代码和软件界面就不列出了,只列出清单文件

 

   <?xml version="1.0" encoding="utf-8"?>

         <manifestxmlns:android="http://schemas.android.com/apk/res/android"

            package="com.jg.zhang.androidtasktest"

             android:versionCode="1"

             android:versionName="1.0" >

        

             <uses-sdkandroid:minSdkVersion="10" android:targetSdkVersion="17"/>

        

             <applicationandroid:allowBackup="true"

                 android:icon="@drawable/ic_launcher"android:label="androidtasktest">

                

                 <activity android:name="com.jg.zhang.androidtasktest.MainActivity">

                     <intent-filter>

                         <actionandroid:name="android.intent.action.MAIN" />

                         <categoryandroid:name="android.intent.category.LAUNCHER" />

                     </intent-filter>

                 </activity>

                         

                  <activityandroid:name="com.jg.zhang.androidtasktest.SecondActivity"

                      android:launchMode="singleTask"/>

                

                  <activityandroid:name="com.jg.zhang.androidtasktest.ThirdActivity"/>

                 

                  <activityandroid:name="com.jg.zhang.androidtasktest.FourthActivity"/>

                 

             </application>

            

         </manifest>

 

现在从MianActivity一直启动到FourthActivity,打印出的系统Log为:

 

![](https://img-blog.csdn.net/20130905221853187?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

由此可见这四个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。

此文章系原创,如需转载,请注明出处影子侠开发者社区www.yingzixia.com

 



## 三 实例验证singleTask启动模式 ##

 

 

上面将activity的四种启动模式就基本介绍完了。为了加深对启动模式的了解,下面会通过一个简单的例子进行验证。由以上的介绍可知,standard和singleTop这两种启动模式行为比较简单,所以在下面的例子中,会对singleTask和singleInstance着重介绍。

 

### 验证启动singleTask模式的activity时是否会创建新的任务###

 

以下为验证示例AndroidTaskTest。这个实例中有三个Activity,分别为:MainActivity,SecondActivity和ThirdActivity。以下为这个示例的manifest文件。

 

         <?xmlversion="1.0" encoding="utf-8"?>

         <manifestxmlns:android="http://schemas.android.com/apk/res/android"

             package="com.jg.zhang.androidtasktest"

             android:versionCode="1"

             android:versionName="1.0">

 

               <uses-sdkandroid:minSdkVersion="10" android:targetSdkVersion="17"/>

 

             <applicationandroid:icon="@drawable/ic_launcher" android:label="@string/   app_name">

                <activity  android:label="@string/app_name"

                    android:name="com.jg.zhang.androidtasktest.MainActivity">

                    <intent-filter>

                         <actionandroid:name="android.intent.action.MAIN" />

                         <categoryandroid:name="android.intent.category.LAUNCHER" />

                     </intent-filter>

                 </activity>

                

                <!--android:taskAffinity="com.jg.zhang.androidtasktest.second"

                          android:alwaysRetainTaskState="true"

                          android:allowBackup="true"-->

                         

                  <activityandroid:name="com.jg.zhang.androidtasktest.SecondActivity"

                     android:launchMode="singleTask">

                     <intent-filter >

                         <actionandroid:name="com.jg.zhang.androidtasktest.SecondActivity"/>

                         <categoryandroid:name="android.intent.category.DEFAULT"/>

                     </intent-filter>

                 </activity>

                

                  <activityandroid:name="com.jg.zhang.androidtasktest.ThirdActivity"

                    android:label="@string/app_name" >

                 </activity>

             </application>

            

         </manifest>

 

由此可见,MainActivity和ThirdActivity都是标准的启动模式,而SecondActivity的启动模式为singleTask。

 

以下为这三个Activity的界面,很简单,在MainActivity中点击按钮启动SecondActivity,在SecondActivity中点击按钮启动ThirdActivity。

 

![](https://img-blog.csdn.net/20130903213316203?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

以下为这三个activity的主要代码:

 

**MainActivity**

 

   public class MainActivity extends Activity {

 

                   privatestatic final String ACTIVITY_NAME = "MainActivity";

                   privatestatic final String LOG_TAG = "xxxx";

        

                   @Override

                   protectedvoid onCreate(Bundle savedInstanceState) {

                            super.onCreate(savedInstanceState);

                            setContentView(R.layout.activity_main);

                           

                            findViewById(R.id.button1).setOnClickListener(newView.OnClickListener() {

                                     @Override

                                     publicvoid onClick(View v) {

                                               Intentintent = new Intent(MainActivity.this, SecondActivity.class);

        

                                               startActivity(intent);

                                     }

                            });

                           

                            inttaskId = getTaskId();

                            Log.i(LOG_TAG,ACTIVITY_NAME +"所在的任务的id为: " +  taskId);

                   }

 

**SecondActivity**

 

   public class SecondActivity extends Activity {

                   privatestatic final String ACTIVITY_NAME = "SecondActivity";

                   privatestatic final String LOG_TAG = "xxxx";

                   @Override

                   protectedvoid onCreate(Bundle savedInstanceState) {

                            super.onCreate(savedInstanceState);

                            setContentView(R.layout.activity_second);

                           

                            findViewById(R.id.button2).setOnClickListener(newOnClickListener() {

                                     @Override

                                     publicvoid onClick(View v) {

                                                        Intentintent = new Intent(SecondActivity.this, ThirdActivity.class);

                                                        startActivity(intent);

                                     }

                            });

                           

                            inttaskId = getTaskId();

                            Log.i(LOG_TAG,ACTIVITY_NAME +"所在的任务的id为: " +  taskId);

                           

                   }

 

 

**ThirdActivity**

 

   public class ThirdActivity extends Activity {

        

                   privatestatic final String ACTIVITY_NAME = "ThirdActivity";

                   privatestatic final String LOG_TAG = "xxxx";

                   @Override

                   protectedvoid onCreate(Bundle savedInstanceState) {

                            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相同。

 

![](https://img-blog.csdn.net/20130903215224890?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

在命令行中执行以下命令 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属性,如下所示:

 

      <activityandroid:name="com.jg.zhang.androidtasktest.SecondActivity"

            android:launchMode="singleTask"

            android:taskAffinity="com.jg.zhang.androidtasktest.second">

           <intent-filter >

                <actionandroid:name="com.jg.zhang.androidtasktest.SecondActivity"/>

                <categoryandroid:name="android.intent.category.DEFAULT"/>

           </intent-filter>

       </activity>

 

重新运行该示例,执行相同的操作,即:点击MainActivity界面中的按钮,开启SecondActivity,并且点击SecondActivity中的按钮,启动ThirdActivity,log中输出的内容为:

 

![](https://img-blog.csdn.net/20130903222218765?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

在命令行中执行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{412924c0com.jg.zhang.androidtasktest/.MainActivity}

 

由此可见,MainActivity和SecondActivity运行在不同的任务中了,并且被SecondActivity启动的ThirdActivity和SecondActivity运行在同一个任务中。这种现象的具体解释可以[参考解开Android应用程序组件Activity的"singleTask"之谜](http://blog.csdn.net/luoshengyang/article/details/6714543 "参考解开Android应用程序组件Activity的"singleTask"之谜")。

 

在这里便引出了manifest文件中<activity>的一个重要属性,taskAffinity。在官方文档中可以得到关于taskAffinity的以下信息

 

1. taskAffinity表示当前activity具有亲和力的一个任务(翻译不是很准确,原句为The task that the activity has an affinity for.),大致可以这样理解,这个taskAffinity表示一个任务,这个任务就是当前activity所在的任务。

 

1. 在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务。

 

1. 一个任务的affinity决定于这个任务的根activity(rootactivity)的taskAffinity。

 

1. 这个属性决定两件事:当activity被re-parent时,它可以被re-paren哪个任务中;当activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被启动到哪个任务中。(这个比较    难以理解,请结合<activity>中的属性allowTaskReparenting和Intent中的标志       FLAG_ACTIVITY_NEW_TASK加以理解)

 

1. 默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的       应用中的activity的taskAffinity设置成相同的值。

 

1. 为一个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对象)

 

1 如果存在这样的一个任务,则检查在这个任务中是否已经有了一个SecondActivity的实例,

 

- 如果已经存在一个SecondActivity的实例,则会重用这个任务和任务中的SecondActivity实例,将这个任务调到前台,清除位于SecondActivity上面的所有Activity,显示SecondActivity,并调用SecondActivity的onNewIntent();

- 如果不存在一个SecondActivity的实例,会在这个任务中创建SecondActivity的实例,并调用onCreate()方法

 

2 如果不存在这样的一个任务,会创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将SecondActivity启动到这个新的任务中

 

上面讨论的是设置taskAffinity属性的情况,如果SecondActivity只设置启动模式为singleTask,而不设置taskAffinity,即三个Activity的taskAffinity相同,都为应用的包名,那么SecondActivity是不会开启一个新任务的,framework中的判定过程如下:

 

1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK

 

1. 然后获得SecondActivity的taskAffinity,即为包名com.jg.zhang.androidtasktest

 

1. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的

 

1. 既然已经存在这个任务,就检索在这个任务中是否存在一个SecondActivity的实例,发现不存在

 

1. 在这个已有的任务中启动一个SecondActivity的实例

 

为了作一个清楚的比较,列出SecondActivity启动模式设为singleTask,并且taskAffinity设为com.jg.zhang.androidtasktest.second时的启动过程

 

1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK

 

1. 然后获得SecondActivity的taskAffinity,即com.jg.zhang.androidtasktest.second

 

1. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest.second的任务,这个任务是不存在的

 

1. 创建一个新的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,该程序的界面和上一个类似,代码也类似,再此仅列出清单文件。

 

   <?xml version="1.0" encoding="utf-8"?>

         <manifestxmlns:android="http://schemas.android.com/apk/res/android"

             package="com.jg.zhang.androidtasktest1"

             android:versionCode="1"  android:versionName="1.0" >

        

             <uses-sdkandroid:minSdkVersion="9" android:targetSdkVersion="17" />

        

             <application

                 android:allowBackup="true"android:icon="@drawable/ic_launcher" android:label="@string/app_name"

                android:theme="@style/AppTheme" >

                 <activity

                    android:name="com.jg.zhang.androidtasktest1.MainActivity"

                    android:label="com.jg.zhang.androidtasktest1.MainActivity">

                     <intent-filter>

                         <actionandroid:name="android.intent.action.MAIN" />

                         <categoryandroid:name="android.intent.category.LAUNCHER" />

                     </intent-filter>

                 </activity>

                

                 <activity

                     android:name="com.jg.zhang.androidtasktest1.OtherActivity"

                    android:label="com.jg.zhang.androidtasktest1.OtherActivity"

                    android:taskAffinity="com.jg.zhang.androidtasktest.second"

                    android:launchMode="singleTask">

                 </activity>

             </application>

        

         </manifest>

 

可以看到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 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{412adb28479:com.jg.zhang.androidtasktest1/10044}

                   Hist#3: ActivityRecord{4125c880 com.jg.zhang.androidtasktest/.SecondActivity}

                            Intent{ cmp=com.jg.zhang.androidtasktest/.SecondActivity }

                            ProcessRecord{41218e48463: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{412adb28479: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{41218e48463:com.jg.zhang.androidtasktest/10043}

 

在执行上述操作时,打印出的Log为:

 

![](https://img-blog.csdn.net/20130905214152937?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

所以由此可见,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。代码和软件界面就不列出了,只列出清单文件

 

   <?xml version="1.0" encoding="utf-8"?>

         <manifestxmlns:android="http://schemas.android.com/apk/res/android"

            package="com.jg.zhang.androidtasktest"

             android:versionCode="1"

             android:versionName="1.0" >

        

             <uses-sdkandroid:minSdkVersion="10" android:targetSdkVersion="17"/>

        

             <applicationandroid:allowBackup="true"

                 android:icon="@drawable/ic_launcher"android:label="androidtasktest">

                

                 <activity android:name="com.jg.zhang.androidtasktest.MainActivity">

                     <intent-filter>

                         <actionandroid:name="android.intent.action.MAIN" />

                         <categoryandroid:name="android.intent.category.LAUNCHER" />

                     </intent-filter>

                 </activity>

                         

                  <activityandroid:name="com.jg.zhang.androidtasktest.SecondActivity"

                      android:launchMode="singleTask"/>

                

                  <activityandroid:name="com.jg.zhang.androidtasktest.ThirdActivity"/>

                 

                  <activityandroid:name="com.jg.zhang.androidtasktest.FourthActivity"/>

                 

             </application>

            

         </manifest>

 

现在从MianActivity一直启动到FourthActivity,打印出的系统Log为:

 

![](https://img-blog.csdn.net/20130905221853187?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

由此可见这四个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。

此文章系原创,如需转载,请注明出处影子侠开发者社区www.yingzixia.com

 


## 三 实例验证singleTask启动模式 ##

 

 

上面将activity的四种启动模式就基本介绍完了。为了加深对启动模式的了解,下面会通过一个简单的例子进行验证。由以上的介绍可知,standard和singleTop这两种启动模式行为比较简单,所以在下面的例子中,会对singleTask和singleInstance着重介绍。

 

### 验证启动singleTask模式的activity时是否会创建新的任务###

 

以下为验证示例AndroidTaskTest。这个实例中有三个Activity,分别为:MainActivity,SecondActivity和ThirdActivity。以下为这个示例的manifest文件。

 

         <?xmlversion="1.0" encoding="utf-8"?>

         <manifestxmlns:android="http://schemas.android.com/apk/res/android"

             package="com.jg.zhang.androidtasktest"

             android:versionCode="1"

             android:versionName="1.0">

 

               <uses-sdkandroid:minSdkVersion="10" android:targetSdkVersion="17"/>

 

             <applicationandroid:icon="@drawable/ic_launcher" android:label="@string/   app_name">

                <activity  android:label="@string/app_name"

                    android:name="com.jg.zhang.androidtasktest.MainActivity">

                    <intent-filter>

                         <actionandroid:name="android.intent.action.MAIN" />

                         <categoryandroid:name="android.intent.category.LAUNCHER" />

                     </intent-filter>

                 </activity>

                

                <!--android:taskAffinity="com.jg.zhang.androidtasktest.second"

                          android:alwaysRetainTaskState="true"

                          android:allowBackup="true"-->

                         

                  <activityandroid:name="com.jg.zhang.androidtasktest.SecondActivity"

                     android:launchMode="singleTask">

                     <intent-filter >

                         <actionandroid:name="com.jg.zhang.androidtasktest.SecondActivity"/>

                         <categoryandroid:name="android.intent.category.DEFAULT"/>

                     </intent-filter>

                 </activity>

                

                  <activityandroid:name="com.jg.zhang.androidtasktest.ThirdActivity"

                    android:label="@string/app_name" >

                 </activity>

             </application>

            

         </manifest>

 

由此可见,MainActivity和ThirdActivity都是标准的启动模式,而SecondActivity的启动模式为singleTask。

 

以下为这三个Activity的界面,很简单,在MainActivity中点击按钮启动SecondActivity,在SecondActivity中点击按钮启动ThirdActivity。

 

![](https://img-blog.csdn.net/20130903213316203?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

以下为这三个activity的主要代码:

 

**MainActivity**

 

   public class MainActivity extends Activity {

 

                   privatestatic final String ACTIVITY_NAME = "MainActivity";

                   privatestatic final String LOG_TAG = "xxxx";

        

                   @Override

                   protectedvoid onCreate(Bundle savedInstanceState) {

                            super.onCreate(savedInstanceState);

                            setContentView(R.layout.activity_main);

                           

                            findViewById(R.id.button1).setOnClickListener(newView.OnClickListener() {

                                     @Override

                                     publicvoid onClick(View v) {

                                               Intentintent = new Intent(MainActivity.this, SecondActivity.class);

        

                                               startActivity(intent);

                                     }

                            });

                           

                            inttaskId = getTaskId();

                            Log.i(LOG_TAG,ACTIVITY_NAME +"所在的任务的id为: " +  taskId);

                   }

 

**SecondActivity**

 

   public class SecondActivity extends Activity {

                   privatestatic final String ACTIVITY_NAME = "SecondActivity";

                   privatestatic final String LOG_TAG = "xxxx";

                   @Override

                   protectedvoid onCreate(Bundle savedInstanceState) {

                            super.onCreate(savedInstanceState);

                            setContentView(R.layout.activity_second);

                           

                            findViewById(R.id.button2).setOnClickListener(newOnClickListener() {

                                     @Override

                                     publicvoid onClick(View v) {

                                                        Intentintent = new Intent(SecondActivity.this, ThirdActivity.class);

                                                        startActivity(intent);

                                     }

                            });

                           

                            inttaskId = getTaskId();

                            Log.i(LOG_TAG,ACTIVITY_NAME +"所在的任务的id为: " +  taskId);

                           

                   }

 

 

**ThirdActivity**

 

   public class ThirdActivity extends Activity {

        

                   privatestatic final String ACTIVITY_NAME = "ThirdActivity";

                   privatestatic final String LOG_TAG = "xxxx";

                   @Override

                   protectedvoid onCreate(Bundle savedInstanceState) {

                            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相同。

 

![](https://img-blog.csdn.net/20130903215224890?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

在命令行中执行以下命令 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属性,如下所示:

 

      <activityandroid:name="com.jg.zhang.androidtasktest.SecondActivity"

            android:launchMode="singleTask"

            android:taskAffinity="com.jg.zhang.androidtasktest.second">

           <intent-filter >

                <actionandroid:name="com.jg.zhang.androidtasktest.SecondActivity"/>

                <categoryandroid:name="android.intent.category.DEFAULT"/>

           </intent-filter>

       </activity>

 

重新运行该示例,执行相同的操作,即:点击MainActivity界面中的按钮,开启SecondActivity,并且点击SecondActivity中的按钮,启动ThirdActivity,log中输出的内容为:

 

![](https://img-blog.csdn.net/20130903222218765?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

在命令行中执行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{412924c0com.jg.zhang.androidtasktest/.MainActivity}

 

由此可见,MainActivity和SecondActivity运行在不同的任务中了,并且被SecondActivity启动的ThirdActivity和SecondActivity运行在同一个任务中。这种现象的具体解释可以[参考解开Android应用程序组件Activity的"singleTask"之谜](http://blog.csdn.net/luoshengyang/article/details/6714543 "参考解开Android应用程序组件Activity的"singleTask"之谜")。

 

在这里便引出了manifest文件中<activity>的一个重要属性,taskAffinity。在官方文档中可以得到关于taskAffinity的以下信息

 

1. taskAffinity表示当前activity具有亲和力的一个任务(翻译不是很准确,原句为The task that the activity has an affinity for.),大致可以这样理解,这个taskAffinity表示一个任务,这个任务就是当前activity所在的任务。

 

1. 在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务。

 

1. 一个任务的affinity决定于这个任务的根activity(rootactivity)的taskAffinity。

 

1. 这个属性决定两件事:当activity被re-parent时,它可以被re-paren哪个任务中;当activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被启动到哪个任务中。(这个比较    难以理解,请结合<activity>中的属性allowTaskReparenting和Intent中的标志       FLAG_ACTIVITY_NEW_TASK加以理解)

 

1. 默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的       应用中的activity的taskAffinity设置成相同的值。

 

1. 为一个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对象)

 

1 如果存在这样的一个任务,则检查在这个任务中是否已经有了一个SecondActivity的实例,

 

- 如果已经存在一个SecondActivity的实例,则会重用这个任务和任务中的SecondActivity实例,将这个任务调到前台,清除位于SecondActivity上面的所有Activity,显示SecondActivity,并调用SecondActivity的onNewIntent();

- 如果不存在一个SecondActivity的实例,会在这个任务中创建SecondActivity的实例,并调用onCreate()方法

 

2 如果不存在这样的一个任务,会创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将SecondActivity启动到这个新的任务中

 

上面讨论的是设置taskAffinity属性的情况,如果SecondActivity只设置启动模式为singleTask,而不设置taskAffinity,即三个Activity的taskAffinity相同,都为应用的包名,那么SecondActivity是不会开启一个新任务的,framework中的判定过程如下:

 

1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK

 

1. 然后获得SecondActivity的taskAffinity,即为包名com.jg.zhang.androidtasktest

 

1. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的

 

1. 既然已经存在这个任务,就检索在这个任务中是否存在一个SecondActivity的实例,发现不存在

 

1. 在这个已有的任务中启动一个SecondActivity的实例

 

为了作一个清楚的比较,列出SecondActivity启动模式设为singleTask,并且taskAffinity设为com.jg.zhang.androidtasktest.second时的启动过程

 

1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK

 

1. 然后获得SecondActivity的taskAffinity,即com.jg.zhang.androidtasktest.second

 

1. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest.second的任务,这个任务是不存在的

 

1. 创建一个新的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,该程序的界面和上一个类似,代码也类似,再此仅列出清单文件。

 

   <?xml version="1.0" encoding="utf-8"?>

         <manifestxmlns:android="http://schemas.android.com/apk/res/android"

             package="com.jg.zhang.androidtasktest1"

             android:versionCode="1"  android:versionName="1.0" >

        

             <uses-sdkandroid:minSdkVersion="9" android:targetSdkVersion="17" />

        

             <application

                 android:allowBackup="true"android:icon="@drawable/ic_launcher" android:label="@string/app_name"

                android:theme="@style/AppTheme" >

                 <activity

                    android:name="com.jg.zhang.androidtasktest1.MainActivity"

                    android:label="com.jg.zhang.androidtasktest1.MainActivity">

                     <intent-filter>

                         <actionandroid:name="android.intent.action.MAIN" />

                         <categoryandroid:name="android.intent.category.LAUNCHER" />

                     </intent-filter>

                 </activity>

                

                 <activity

                     android:name="com.jg.zhang.androidtasktest1.OtherActivity"

                    android:label="com.jg.zhang.androidtasktest1.OtherActivity"

                    android:taskAffinity="com.jg.zhang.androidtasktest.second"

                    android:launchMode="singleTask">

                 </activity>

             </application>

        

         </manifest>

 

可以看到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 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{412adb28479:com.jg.zhang.androidtasktest1/10044}

                   Hist#3: ActivityRecord{4125c880 com.jg.zhang.androidtasktest/.SecondActivity}

                            Intent{ cmp=com.jg.zhang.androidtasktest/.SecondActivity }

                            ProcessRecord{41218e48463: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{412adb28479: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{41218e48463:com.jg.zhang.androidtasktest/10043}

 

在执行上述操作时,打印出的Log为:

 

![](https://img-blog.csdn.net/20130905214152937?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

所以由此可见,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。代码和软件界面就不列出了,只列出清单文件

 

   <?xml version="1.0" encoding="utf-8"?>

         <manifestxmlns:android="http://schemas.android.com/apk/res/android"

            package="com.jg.zhang.androidtasktest"

             android:versionCode="1"

             android:versionName="1.0" >

        

             <uses-sdkandroid:minSdkVersion="10" android:targetSdkVersion="17"/>

        

             <applicationandroid:allowBackup="true"

                 android:icon="@drawable/ic_launcher"android:label="androidtasktest">

                

                 <activity android:name="com.jg.zhang.androidtasktest.MainActivity">

                     <intent-filter>

                         <actionandroid:name="android.intent.action.MAIN" />

                         <categoryandroid:name="android.intent.category.LAUNCHER" />

                     </intent-filter>

                 </activity>

                         

                  <activityandroid:name="com.jg.zhang.androidtasktest.SecondActivity"

                      android:launchMode="singleTask"/>

                

                  <activityandroid:name="com.jg.zhang.androidtasktest.ThirdActivity"/>

                 

                  <activityandroid:name="com.jg.zhang.androidtasktest.FourthActivity"/>

                 

             </application>

            

         </manifest>

 

现在从MianActivity一直启动到FourthActivity,打印出的系统Log为:

 

![](https://img-blog.csdn.net/20130905221853187?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdqZ19ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

 

由此可见这四个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。

此文章系原创,如需转载,请注明出处影子侠开发者社区www.yingzixia.com 开发者Q群:188168040

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值