《第一行代码》读书笔记-第二章04 活动的启动模式
task
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,任务被移除。
task是可以跨应用的,这正是task存在的一个重要原因。有的Activity,虽然不在同一个app中,但为了保持用户操作的连贯性,把他们放在同一个任务中。例如,在我们的应用中的一个Activity A中点击发送邮件,会启动邮件程序的一个Activity B来发送邮件,这两个activity是存在于不同app中的,但是被系统放在一个任务中,这样当发送完邮件后,用户按back键返回,可以返回到原来的Activity A中,这样就确保了用户体验。
process
process一般翻译成进程,进程是操作系统内核中的一个概念,表示直接受内核调度的执行单位。在应用程序的角度看,我们用java编写的应用程序,运行在dalvik虚拟机中,可以认为一个运行中的dalvik虚拟机实例占有一个进程,所以,在默认情况下,一个应用程序的所有组件运行在同一个进程中。但是这种情况也有例外,即,应用程序中的不同组件可以运行在不同的进程中。只需要在manifest中用process属性指定组件所运行的进程的名字。
<activity android:name=".MyActivity" android:label="@string/app_nam"
android:process=":remote">
</activity>
这样的话这个activity会运行在一个独立的进程中。
Activity四种启动模式详解
启动模式一共有四种,分别是standard
、singleTop
、singleTask
和singleInstance
, 可以在AndroidManifest.xml
中通过给<activity>
标签指定android:launchMode
属性来选择启动模式。
standard
standard
是活动默认的启动模式。对于使用standard
模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。如:在A Activity中启动A,会新建一个A 实例,在返回栈中存在两个A。
singleTop
在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。并且会调用该实例的 onNewIntent()方法将Intent对象传递到这个实例中。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Toast.makeText(FirstActivity.this, "调用了onNewIntent()方法" + intent.getStringExtra("name"), Toast.LENGTH_SHORT).show();
}
如果以singleTop模式启动的activity的一个实例已经存在与任务桟中,但是不在桟顶,那么它的行为和standard模式相同,也会创建多个实例。
singleTask
注意:此处讨论的均是以startActivity(intent)
的方式启动意图,如果以startActivityForResult(intent, 0)
的方式启动,在AndroidManifest.xml
配置taskAffinity
属性无效,不会开启一个新的task
。测试代码同下,日志打印出相同的ID。
谷歌的官方文档上称,如果一个activity的启动模式为singleTask
,那么系统总会在一个新任务的最底部(root)启动这个activity,并且被这个activity启动的其他activity会和该activity同时存在于这个新任务中。如果系统中已经存在这样的一个activity则会重用这个实例,并且调用他的onNewIntent()
方法。并把在这
个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。即,这样的一个activity在系统中只会存在一个实例。
其实官方文档中的这种说法并不准确,启动模式为singleTask的activity并不会总是开启一个新的任务。详情请参考 解开Android应用程序组件Activity的"singleTask"之谜。
验证启动singleTask模式的activity时是否会创建新的任务
转自:Android中Activity四种启动模式和taskAffinity属性详解
以下为验证示例AndroidTaskTest。这个实例中有三个Activity,分别为:MainActivity,SecondActivity和ThirdActivity。以下为这个示例的manifest文件。
<?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。
三个Activity的界面,很简单,在MainActivity中点击按钮启动SecondActivity,在SecondActivity中点击按钮启动ThirdActivity。
以下为这三个activity的主要代码:
MainActivity
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);
}
SecondActivity
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。
现在执行以下操作,运行该示例,并且点击MainActivity界面中的按钮,开启SecondActivity。在该示例中SecondActivity的启动模式为singleTask。按照官方文档的说法,SecondActivity会在一个新的任务中开启。但是查看打印出的log,发现MainActivity和SecondActivity所在的任务的Id相同。
在命令行中执行以下命令 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
属性,如下所示:
<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中输出的内容为:
xxxx MainActivity所在的任务id为:5
xxxx SecondActivity所在的任务id为:6
xxxx ThirdActivity所在的任务id为:6
在命令行中执行adb shell dumpsys activity
命令,有以下输出:
TaskRecord{411e6a88 #6 A com.jg.zhang.androidtasktest.second}
Run #3: ActivityRecord{411c8ea0 com.jg.zhang.androidtasktest/.ThirdActivity}
Run #2: ActivityRecord{412bc870 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{412ece18 #5 A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{412924c0 com.jg.zhang.androidtasktest/.MainActivity}
由此可见,MainActivity和SecondActivity运行在不同的任务中了,并且被SecondActivity启动的ThirdActivity和SecondActivity运行在同一个任务中。
taskAffinity
在这里便引出了manifest文件中
taskAffinity
表示当前activity具有亲和力的一个任务,大致可以这样理解,这个 taskAffinity表示一个任务,这个任务就是当前activity所在的任务。- 在概念上,具设置了相同
taskAffinity
属性的activity,属于同一个任务。 - 一个任务的affinity决定于这个任务的根activity(root activity)的taskAffinity。(如:上面,被SecondActivity启动的ThirdActivity和SecondActivity运行在同一个任务中)
- 这个属性决定两件事:①当activity被re-parent时,它可以被re-paren哪个任务中;②当activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被启动到哪个任务中。
- 默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的 应用中的activity的taskAffinity设置成相同的值。
- 为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。
在MainActivity启动SecondActivity时,发现启动模式为singleTask
,会设定他的启动标志为FLAG_ACTIVITY_NEW_TASK
其实framework中对任务和activity的调度是很复杂的,尤其是把启动模式设为singleTask或者以FLAG_ACTIVITY_NEW_TASK标志启动时。所以,在使用singleTask
和FLAG_ACTIVITY_NEW_TASK
时,要仔细测试应用程序。这也是官方文档上的建议,make sure to test the usability of the activity during launch。
不同的应用中的activity的taskAffinity设置成相同的值
可以把不同的应用中的activity的taskAffinity
设置成相同的值,这样的话这两个activity虽然不在同一应用中,却会在运行时分配到同一任务中
这样如果同时开启两个应用,应用中有的Activity设置相同的taskAffinity
。启动一个应用跳转到该Activity,按Home键退出,再启动另一个应用跳转到相应的Activity,点击Back键,将会退回到上一个应用中去。
总结
设置了"singleTask"启动模式的Activity的特点:
设置了
"singleTask"
启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity
等于它的属性值taskAffinity
的任务存在;如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了"singleTask"
启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity
属性值。如果设置了
"singleTask"
启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。
singleInstance
总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他activity会自动运行于另一个任务中。当再次启动该activity的实例时,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()
方法,将Intent实例传递到该实例中。和singleTask相同,同一时刻在系统中只会存在一个这样的Activity实例。
想象以下场景,假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢?使用前面三种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance 模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。
修改上面的例子,
android:name=".SecondActivity"
android:launchMode="singleInstance"
在 MainActivity 界面点击按钮进入到 SecondActivity , 然后在SecondActivity 界面点击按钮进入到 ThirdActivity 。
xxxx MainActivity所在的任务id为:10
xxxx SecondActivity所在的任务id为:11
xxxx ThirdActivity所在的任务id为:10
参考
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《第一行代码》读书笔记-第二章04 活动的启动模式
文章字数:3.4k
本文作者:Bin
发布时间:2016-05-14, 13:25:01
最后更新:2019-08-06, 00:49:54
原始链接:http://coolview.github.io/2016/05/14/Android/%E3%80%8A%E7%AC%AC%E4%B8%80%E8%A1%8C%E4%BB%A3%E7%A0%81%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E7%AC%AC%E4%BA%8C%E7%AB%A004-%E6%B4%BB%E5%8A%A8%E7%9A%84%E5%90%AF%E5%8A%A8%E6%A8%A1%E5%BC%8F/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。