http://www.xuebuyuan.com/1624447.html
在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的区别与联系
<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1"
android:versionName="1"
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.myapp">
<application android:label="@string/app_name">
<activity android:name=".MyActivity" android:label="@string/app_nam">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".MyReceiver"/>
<provider android:name=".MyProvider"/>
<service android:name=".MyService"/>
</application>
</manifest>
由此可见,application是由四大组件组成的。在app安装时,系统会读取manifest的信息,将所有的组件解析出来,以便在运行时对组件进行实例化和调度。
Screen点击应用程序图标时,启动主Activity A,接着A开启B,B开启C,这时栈中有三个Activity,并且这三个Activity默认在同一个任务(task)中,当用户按返回时,弹出C,栈中只剩A和B,再按返回键,弹出B,栈中只剩A,再继续按返回键,弹出A,任务被移除。如下图所示:
A中,这样就确保了用户体验。
<activity android:name=".MyActivity" android:label="@string/app_nam"
android:process=":remote">
</activity>
这样的话这个activity会运行在一个独立的进程中。
二 Activity四种启动模式详解
<activity android:name=".app.InterstitialMessageActivity"
android:label="@string/interstitial_label"
android:theme="@style/Theme.Dialog"
android:launchMode="singleTask"
</activity>
standard
标准启动模式,也是activity的默认启动模式。在这种模式下启动的activity可以被多次实例化,即在同一个任务中可以存在多个activity的实例,每个实例都会处理一个Intent对象。如果Activity A的启动模式为standard,并且A已经启动,在A中再次启动Activity A,即调用startActivity(new Intent(this,A.class)),会在A的上面再次启动一个A的实例,即当前的桟中的状态为A-->A。
singleTop
如果一个以singleTop模式启动的activity的实例已经存在于任务桟的桟顶,那么再启动这个Activity时,不会创建新的实例,而是重用位于栈顶的那个实例,并且会调用该实例的onNewIntent()方法将Intent对象传递到这个实例中。举例来说,如果A的启动模式为singleTop,并且A的一个实例已经存在于栈顶中,那么再调用startActivity(new Intent(this,A.class))启动A时,不会再次创建A的实例,而是重用原来的实例,并且调用原来实例的onNewIntent()方法。这是任务桟中还是这有一个A的实例。
如果以singleTop模式启动的activity的一个实例已经存在与任务桟中,但是不在桟顶,那么它的行为和standard模式相同,也会创建多个实例。
singleTask
谷歌的官方文档上称,如果一个activity的启动模式为singleTask,那么系统总会在一个新任务的最底部(root)启动这个activity,并且被这个activity启动的其他activity会和该activity同时存在于这个新任务中。如果系统中已经存在这样的一个activity则会重用这个实例,并且调用他的onNewIntent()方法。即,这样的一个activity在系统中只会存在一个实例。其实官方文档中的这种说法并不准确,启动模式为singleTask的activity并不会总是开启一个新的任务。详情请参考 解开Android应用程序组件Activity的"singleTask"之谜,在本文后面也会通过示例来进行验证。
singleInstance
总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他activity会自动运行于另一个任务中。当再次启动该activity的实例时,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将Intent实例传递到该实例中。和singleTask相同,同一时刻在系统中只会存在一个这样的Activity实例。
三 实例验证singleTask启动模式
验证启动singleTask模式的activity时是否会创建新的任务
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jg.zhang.androidtasktest"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17" />
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name">
<activity android:label="@string/app_name"
android:name="com.jg.zhang.androidtasktest.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--android:taskAffinity="com.jg.zhang.androidtasktest.second"
android:alwaysRetainTaskState="true"
android:allowBackup="true" -->
<activity android:name="com.jg.zhang.androidtasktest.SecondActivity"
android:launchMode="singleTask">
<intent-filter >
<action android:name="com.jg.zhang.androidtasktest.SecondActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name="com.jg.zhang.androidtasktest.ThirdActivity"
android:label="@string/app_name" >
</activity>
</application>
</manifest>
由此可见,MainActivity和ThirdActivity都是标准的启动模式,而SecondActivity的启动模式为singleTask。
<pre name="code" class="java" style="margin-top: 0px; margin-bottom: 10px;">public class MainActivity extends Activity {
private static final String ACTIVITY_NAME = "MainActivity";
private static final String LOG_TAG = "xxxx";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
int taskId = getTaskId();
Log.i(LOG_TAG, ACTIVITY_NAME +"所在的任务的id为: " + taskId);
}
public class SecondActivity extends Activity {
private static final String ACTIVITY_NAME = "SecondActivity";
private static final String LOG_TAG = "xxxx";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
findViewById(R.id.button2).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
startActivity(intent);
}
});
int taskId = getTaskId();
Log.i(LOG_TAG, ACTIVITY_NAME +"所在的任务的id为: " + taskId);
}
ThirdActivity
public class ThirdActivity extends Activity {
private static final String ACTIVITY_NAME = "ThirdActivity";
private static final String LOG_TAG = "xxxx";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
int taskId = getTaskId();
Log.i(LOG_TAG, ACTIVITY_NAME +"所在的任务的id为: " + taskId);
}
以上三个activity只列出了onCreate()方法中的内容,实现的逻辑为在MainActivity中点击按钮启动SecondActivity,在SecondActivity中点击按钮启动ThirdActivity。并且在onCreate方法中会以log的形式打印出当前activity所属的任务(Task)的Id。
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}
<activity android:name="com.jg.zhang.androidtasktest.SecondActivity"
android:launchMode="singleTask"
android:taskAffinity="com.jg.zhang.androidtasktest.second">
<intent-filter >
<action android:name="com.jg.zhang.androidtasktest.SecondActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
重新运行该示例,执行相同的操作,即:点击MainActivity界面中的按钮,开启SecondActivity,并且点击SecondActivity中的按钮,启动ThirdActivity,log中输出的内容为:
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}
- taskAffinity表示当前activity具有亲和力的一个任务(翻译不是很准确,原句为The task that the activity has an affinity for.),大致可以这样理解,这个 taskAffinity表示一个任务,这个任务就是当前activity所在的任务。
- 在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务。
- 一个任务的affinity决定于这个任务的根activity(root activity)的taskAffinity。
- 这个属性决定两件事:当activity被re-parent时,它可以被re-paren哪个任务中;当activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被启动到哪个任务中。(这个比较 难以理解,请结合<activity>中的属性allowTaskReparenting和Intent中的标志 FLAG_ACTIVITY_NEW_TASK加以理解)
- 默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的 应用中的activity的taskAffinity设置成相同的值。
- 为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。
这就可以解释上面示例中的现象了,由第5条可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity为com.jg.zhang.androidtasktest,SecondActivity的taskAffinity为com.jg.zhang.androidtasktest.second,根据上面第4条,taskAffinity可以影响当activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被启动到哪个任务中。这句话的意思是,当新启动的activity(SecondActivity)是以FLAG_ACTIVITY_NEW_TASK标志启动时(可以认为FLAG_ACTIVITY_NEW_TASK和singleTask作用相同,当启动模式为singleTask时,framework会将它的启动标志设为FLAG_ACTIVITY_NEW_TASK),framework会检索是否已经存在了一个affinity为com.jg.zhang.androidtasktest.second的任务(即一个TaskRecord对象)
- 如果存在这样的一个任务,则检查在这个任务中是否已经有了一个SecondActivity的实例,
- 如果已经存在一个SecondActivity的实例,则会重用这个任务和任务中的SecondActivity实例,将这个任务调到前台,清除位于SecondActivity上面的所有Activity,显示SecondActivity,并调用SecondActivity的onNewIntent();
- 如果不存在一个SecondActivity的实例,会在这个任务中创建SecondActivity的实例,并调用onCreate()方法
- 如果不存在这样的一个任务,会创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将SecondActivity启动到这个新的任务中
上面讨论的是设置taskAffinity属性的情况,如果SecondActivity只设置启动模式为singleTask,而不设置taskAffinity,即三个Activity的taskAffinity相同,都为应用的包名,那么SecondActivity是不会开启一个新任务的,framework中的判定过程如下:
- 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK
- 然后获得SecondActivity的taskAffinity,即为包名com.jg.zhang.androidtasktest
- 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的
- 既然已经存在这个任务,就检索在这个任务中是否存在一个SecondActivity的实例,发现不存在
- 在这个已有的任务中启动一个SecondActivity的实例
为了作一个清楚的比较,列出SecondActivity启动模式设为singleTask,并且taskAffinity设为com.jg.zhang.androidtasktest.second时的启动过程
- 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK
- 然后获得SecondActivity的taskAffinity,即com.jg.zhang.androidtasktest.second
- 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest.second的任务,这个任务是不存在的
- 创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将SecondActivity启动到这个新的任务中
其实framework中对任务和activity‘的调度是很复杂的,尤其是把启动模式设为singleTask或者以FLAG_ACTIVITY_NEW_TASK标志启动时。所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK时,要仔细测试应用程序。这也是官方文档上的建议。
实例验证将两个不同app中的不同的singleTask模式的Activity的taskAffinity设成相同
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jg.zhang.androidtasktest1"
android:versionCode="1" android:versionName="1.0" >
<uses-sdk android: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>
<action android:name="android.intent.action.MAIN" />
<category android: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{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}
实例验证singleTask的另一意义:在同一个任务中具有唯一性
in task。即,在同一个任务中,只会有一个该activity的实例。现在让我们进行验证:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jg.zhang.androidtasktest"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17" />
<application android:allowBackup="true"
android:icon="@drawable/ic_launcher" android:label="androidtasktest">
<activity android:name="com.jg.zhang.androidtasktest.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.jg.zhang.androidtasktest.SecondActivity"
android:launchMode="singleTask"/>
<activity android:name="com.jg.zhang.androidtasktest.ThirdActivity"/>
<activity android:name="com.jg.zhang.androidtasktest.FourthActivity"/>
</application>
</manifest>
现在从MianActivity一直启动到FourthActivity,打印出的系统Log为:
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}
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}
四 实例验证singleInstance的行为
- 以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例
- 以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中(官方文档上的描述为,singleInstance模式的Activity不允许其他Activity和它共存在一个任务中)
- 被singleInstance模式的Activity开启的其他activity,能够开启一个新任务,但不一定开启新的任务,也可能在已有的一个任务中开启
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jg.zhang.androidtasktest"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17" />
<application android:allowBackup="true"
android:icon="@drawable/ic_launcher" android:label="androidtasktest">
<activity android:name="com.jg.zhang.androidtasktest.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.jg.zhang.androidtasktest.SecondActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="com.jg.zhang.androidtasktest.ACTION_MY"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name="com.jg.zhang.androidtasktest.ThirdActivity"/>
</application>
</manifest>
由上面的清单文件可以知道,该应用包括三个activity,分别为MianActivity,SecondActivity,ThirdActivity,其中SecondActivity启动模式设置为singleInstance。MianActivity可以开启SecondActivity,SecondActivity可以开启ThirdActivity。 并且为了可以在其他应用中开启SecondActivity,为SecondActivity设置了一个IntentFilter,这样就可以在其他应用中使用隐式Intent开启SecondActivity。
为了更好的验证singleInstance的全局唯一性,还需要其他一个应用,对上面的AndroidTaskTest1进行一些修改即可。AndroidTaskTest1只需要一个MianActivity,在MainActivity中点击按钮会开启AndroidTaskTest应用中的SecondActivity。开启AndroidTaskTest应用中的SecondActivity的代码如下:
/**
* 该方法在布局中按钮的android:onClick属性中指定
* android:onClick="launchOtherActivity"
* @param v
*/
public void launchOtherActivity(View v){
Intent intent = new Intent();
//以下Action为"com.jg.zhang.androidtasktest.ACTION_MY"
//即AndroidTaskTest应用中SecondActivity的action
intent.setAction("com.jg.zhang.androidtasktest.ACTION_MY");
startActivity(intent);
}
下面开始验证第一个特点:以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例
执行如下操作:安装AndroidTaskTest应用,点击MainActivity中的按钮,开启SecondActivity,可以看到如下log输出:
执行adb shell dumpsys activity命令,有以下输出:
TaskRecord{411189e0 #9 A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{4129af80 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{41305528 #8 A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{41296e60 com.jg.zhang.androidtasktest/.MainActivity}
以上可以说明,singleInstance模式的Activity总是会在新的任务中运行(前提是系统中还不存在这样的一个实例) 。
下面验证它的全局唯一性,执行以下操作:安装另一个应用AndroidTaskTest1,在开启的MainActivity中点击按钮开启AndroidTaskTest应用中的SecondActivity。看到打印出一条新的日志:
执行adb shell dumpsys activity命令,有以下输出:
TaskRecord{411189e0 #9 A com.jg.zhang.androidtasktest}
Run #3: ActivityRecord{4129af80 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{412dc788 #12 A com.jg.zhang.androidtasktest1}
Run #2: ActivityRecord{4121c628 com.jg.zhang.androidtasktest1/.MainActivity}
TaskRecord{41305528 #8 A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{41296e60 com.jg.zhang.androidtasktest/.MainActivity}
由红色字体可以得知,开启的SecondActivity就是上次创建的编号为4129af80的SecondActivity,并且Log中没有再次输出关于SecondActivity的信息,说明SecondActivity并没有重新创建。由此可以得出结论:以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
下面开始验证第二个特点:以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中
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}
下面开始验证第三个特点:被singleInstance模式的Activity开启的其他activity,能够在新的任务中启动,但不一定开启新的任务,也可能在已有的一个任务中开启
<activity android:name="com.jg.zhang.androidtasktest.ThirdActivity"
android:taskAffinity="com.jg.zhang.androidtasktest.second"/>
重新安装AndroidTaskTest应用,执行和上一步中同样的操作:点击MainActivity中的按钮,开启SecondActivity,在SecondActivity中点击按钮,开启ThirdActivity。可以看到有如下输出:
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}
由于ThirdActivity是被启动模式为singleInstance类型的Activity(即SecondActivity)启动的,framework会为它它加上FLAG_ACTIVITY_NEW_TASK标志,这时 framework会检索是否已经存在了一个affinity为com.jg.zhang.androidtasktest.second(即ThirdActivity的taskAffinity属性)的任务,
- 如果存在这样的一个任务,则检查在这个任务中是否已经有了一个ThirdActivity的实例,
- 如果已经存在一个ThirdActivity的实例,则会重用这个任务和任务中的ThirdActivity实例,将这个任务调到前台,清除位于ThirdActivity上面的所有Activity,显示ThirdActivity,并调用ThirdActivity的onNewIntent()。
- 如果不存在一个ThirdActivity的实例,会在这个任务中创建ThirdActivity的实例,并调用onCreate()方法
- 如果不存在这样的一个任务,会创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将ThirdActivity启动到这个新的任务中
如果ThirdActivity不设置taskAffinity,即ThirdActivity和MainActivity的taskAffinity相同,都为应用的包名,那么ThirdActivity是不会开启一个新任务的,framework中的判定过程如下:
- 在SecondActivity启动ThirdActivity时,因为SecondActivity是singleInstance的,所以设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK
- 然后获得ThirdActivity的taskAffinity,即为包名com.jg.zhang.androidtasktest
- 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的
- 既然已经存在这个任务,就检索在这个任务中是否存在一个ThirdActivity的实例,发现不存在
- 在这个已有的任务中启动一个SecondActivity的实例
为了作一个清楚的比较,列出ThirdActivity的taskAffinity属性设为com.jg.zhang.androidtasktest.second时的启动过程
- 在SecondActivity启动ThirdActivity时,因为SecondActivity是singleInstance的,那么设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK
- 然后获得ThirdActivity的taskAffinity,即为com.jg.zhang.androidtasktest.second
- 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest.second的任务,这个任务是不存在的
- 创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将ThirdActivity启动到这个新的任务
五 本文小结
由上述可知,Task是Android Framework中的一个概念,Task是由一系列相关的Activity组成的,是一组相关Activity的集合。Task是以栈的形式来管理的。
我们在操作软件的过程中,一定会涉及界面的跳转。其实在对界面进行跳转时,Android Framework既能在同一个任务中对Activity进行调度,也能以Task为单位进行整体调度。在启动模式为standard或singleTop时,一般是在同一个任务中对Activity进行调度,而在启动模式为singleTask或singleInstance是,一般会对Task进行整体调度。对Task进行整体调度包括以下操作:
- 按Home键,将之前的任务切换到后台
- 长按Home键,会显示出最近执行过的任务列表
- 在Launcher或HomeScreen点击app图标,开启一个新任务,或者是将已有的任务调度到前台
- 启动singleTask模式的Activity时,会在系统中搜寻是否已经存在一个合适的任务,若存在,则会将这个任务调度到前台以重用这个任务。如果这个任务中已经存在一个要启动的Activity的实例,则清除这个实例之上的所有Activity,将这个实例显示给用户。如果这个已存在的任务中不存在一个要启动的Activity的实例,则在这个任务的顶端启动一个实例。若这个任务不存在,则会启动一个新的任务,在这个新的任务中启动这个singleTask模式的Activity的一个实例。
- 启动singleInstance的Activity时,会在系统中搜寻是否已经存在一个这个Activity的实例,如果存在,会将这个实例所在的任务调度到前台,重用这个Activity的实例(该任务中只有这一个Activity),如果不存在,会开启一个新任务,并在这个新任务中启动这个singleInstance模式的Activity的一个实例。