Activity的四种启动模式

本文整体框架转载自http://blog.csdn.net/zhangjg_blog/article/details/10923643

当时看到这篇文章的时候就发现很多东西是自己以前没有注意到的,有一种“原来是这样的感觉”,所以打算对部分测试内容自己亲自测试做个记录,方便自己回过头在回顾,同时对一些文章中一些别人有歧义的地方在做进一步测试,毕竟有时候自己亲自做过的东西,印象才会更深刻,理解更深。

在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文件中清晰地体现出来。如下所示:
<?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的信息,将所有的组件解析出来,以便在运行时对组件进行实例化和调度。

而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中,这样就确保了用户体验。

说完了application和task,最后介绍process。process一般翻译成进程,进程是操作系统内核中的一个概念,表示直接受内核调度的执行单位。在应用程序的角度看,我们用java编写的应用程序,运行在dalvik虚拟机中,可以认为一个运行中的dalvik虚拟机实例占有一个进程,所以, 在默认情况下,一个应用程序的所有组件运行在同一个进程中。但是这种情况也有例外,即,应用程序中的不同组件可以运行在不同的进程中。只需要在manifest中用process属性指定组件所运行的进程的名字。如下所示:
<activity android:name=".MyActivity" android:label="@string/app_nam"  
    android:process=":remote">  
</activity>  
这样的话这个activity会运行在一个独立的进程中。

二 Activity四种启动模式详解


activity有四种启动模式,分别为standard,singleTop,singleTask,singleInstance。如果要使用这四种启动模式,必须在manifest文件中<activity>标签中的launchMode属性中配置,如:
<activity android:name=".app.InterstitialMessageActivity"  
          android:label="@string/interstitial_label"  
          android:theme="@style/Theme.Dialog"  
          android:launchMode="singleTask"  
</activity>  
同样,在Intent类中定义了很多与Activity启动或调度有关的标志,<activity>标签中有一些属性,这些标志,属性和四种启动模式联合使用,会在很大程度上改变activity的行为,进而会改变task和back stask的状态。关于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的实例。
如果以singleTop模式启动的activity的一个实例已经存在与任务桟中,但是不在桟顶,那么它的行为和standard模式相同,也会创建多个实例。

也就是说在A中启动A,会重用原来的A不会调用新的实例,并且调用A的onNewIntent()

这里自己做2个小测试:一共3个activity,MainActivity(standard)、SecondActivity(singleTop)、thirdActivity(standard)

第一次测试:栈顶有实例

首先程序启动,MainActivity创建,单击按钮,创建SecondActivity,单击按钮再次创建SecondActivity

第一次创建 SecondActivity

adb shell dumpsys activity

在SecondActivity中第二次创建 SecondActivity,单击按钮

adb shell dumpsys activity

对比上面可以看出两个SecondActivity是同一个实例,只是调用了onNewIntent重用了原来的实例

第二次测试:栈顶无实例

首先程序启动,MainActivity创建,单击按钮,创建SecondActivity,单击按钮,创建thirdActivity,单击按钮创建SecondActivity。

第一次创建 SecondActivity

adb shell dumpsys activity

在SecondActivity中创建 thirdActivity,单击按钮

adb shell dumpsys activity

在thirdActivity中创建 SecondActivity,单击按钮

adb shell dumpsys activity

通过两次对比,就可以清楚的发现singleTop模式下,activity只有在栈顶有该实例的时候会重用调用onNewIntent方法,而在即使task中有该实例,但是非栈顶时,还是会创建一个新的实例

    

singleTask

谷歌的官方文档上称,如果一个activity的启动模式为singleTask,那么系统总会在一个新任务的最底部(root)启动这个activity,并且被这个activity启动的其他activity会和该activity同时存在于这个新任务中。如果系统中已经存在这样的一个activity则会重用这个实例,并且调用他的onNewIntent()方法。即,这样的一个activity在系统中只会存在一个实例。

其实官方文档中的这种说法并不准确,启动模式为singleTask的activity并不会总是开启一个新的任务。详情请参考  解开Android应用程序组件Activity的"singleTask"之谜,在本文后面也会通过示例来进行验证。

singleInstance

总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他activity会自动运行于另一个任务中。当再次启动该activity的实例时,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将Intent实例传递到该实例中。和singleTask相同,同一时刻在系统中只会存在一个这样的Activity实例。


三 实例验证singleTask启动模式


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


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


以下为验证示例View。这个实例中有三个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.q.view">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".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.q.view.second"-->
        <activity android:name=".SecondActivity"
            android:exported="true"

            android:launchMode="singleTask"
            android:label="secondActivity">

        </activity>
        <!--android:theme="@style/translucent"-->
        <activity android:name=".thirdActivity"
            android:label="thirdActivity">

        </activity>
        <activity android:name=".WebActivity"
            android:label="webview">

        </activity>
    </application>

</manifest>

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

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



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

MainActivity
public class MainActivity extends AppCompatActivity {
    private  static  final String TAG="MainActivity";
    Button btn_change;
    int taskid;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        taskid=getTaskId();
        Log.e(TAG, "onCreate:  taskid="+taskid);
        btn_change=(Button) findViewById(R.id.btn_change);
        btn_change.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent();
                intent.setClass(MainActivity.this,SecondActivity.class);
                startActivity(intent);
                //change
            }
        });
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        taskid=getTaskId();
        Log.e(TAG, "onNewIntent: taskid="+taskid);
    }
}



SecondActivity
public class SecondActivity extends AppCompatActivity {
    private  static  final String TAG="secondActivity";
    Button btn_change1;
    int taskid;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main1);
        taskid=getTaskId();
        Log.e(TAG, "onCreate:  taskid="+taskid);
        btn_change1=(Button) findViewById(R.id.btn_change1);
        btn_change1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent();
                intent.setClass(SecondActivity.this,thirdActivity.class);
                startActivity(intent);
            }
        });
    }
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        taskid=getTaskId();
        Log.e(TAG, "onNewIntent: taskid="+taskid);
    }
}


ThirdActivity

public class thirdActivity extends AppCompatActivity {
    private  static  final String TAG="thirdActivity";
    Button btn_change2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Log.e(TAG, "onCreate: ");
        btn_change2=(Button) findViewById(R.id.btn_change2);
        btn_change2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent();
                intent.setClass(thirdActivity.this,SecondActivity.class);
                startActivity(intent);

            }
        });
    }

}



以上三个activity只列出了onCreate()方法中的内容,实现的逻辑为在MainActivity中点击按钮启动SecondActivity,在SecondActivity中点击按钮启动ThirdActivity。并且在onCreate方法中会以log的形式打印出当前activity所属的任务(Task)的Id。

现在执行以下操作,运行该示例,并且点击MainActivity界面中的按钮,开启SecondActivity。在该示例中SecondActivity的启动模式为singleTask。按照官方文档的说法,SecondActivity会在一个新的任务中开启。但是查看打印出的log,发现MainActivity和SecondActivity所在的任务的Id相同。


在命令行中执行以下命令 adb shell dumpsys activity , 有以下输出:


所以,和官方文档表述的不同,MainActivity和SecondActivity是启动在同一个任务中的。 其实,把启动模式设置为singleTask,framework在启动该activity时只会把它标示为可在一个新任务中启动,至于是否在一个新任务中启动,还要受其他条件的限制。现在在SecondActivity增加一个taskAffinity属性,如下所示:
<activity android:name=".SecondActivity"
            android:exported="true"
            android:taskAffinity="com.example.q.view.second"
            android:launchMode="singleTask"
            android:label="secondActivity">
        </activity>
重新运行该示例,执行相同的操作,即:点击MainActivity界面中的按钮,开启SecondActivity,并且点击SecondActivity中的按钮,启动ThirdActivity,log中输出的内容为:

在命令行中执行adb shell dumpsys activity命令,有以下输出:

点击SecondActivity中的按钮,启动ThirdActivity

 


由此可见,MainActivity和SecondActivity运行在不同的任务中了,并且被SecondActivity启动的ThirdActivity和SecondActivity运行在同一个任务中。这种现象的具体解释可以参考 解开Android应用程序组件Activity的"singleTask"之谜

在这里便引出了manifest文件中<activity>的一个重要属性,taskAffinity。在官方文档中可以得到关于taskAffinity的以下信息
  1. taskAffinity表示当前activity具有亲和力的一个任务(翻译不是很准确,原句为The task that the activity has an affinity for.),大致可以这样理解,这个 taskAffinity表示一个任务,这个任务就是当前activity所在的任务。
  2. 在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务。
  3.  一个任务的affinity决定于这个任务的根activity(root activity)的taskAffinity。
  4.  这个属性决定两件事:当activity被re-parent时,它可以被re-paren哪个任务中;当activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被启动到哪个任务中。(这个比较    难以理解,请结合<activity>中的属性allowTaskReparenting和Intent中的标志       FLAG_ACTIVITY_NEW_TASK加以理解)
  5.  默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的       应用中的activity的taskAffinity设置成相同的值。
  6.  为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。

这就可以解释上面示例中的现象了,由第5条可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity为com.example.q.view,SecondActivity的taskAffinity为com.example.q.view.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.example.q.view.second的任务(即一个TaskRecord对象)

注:这里如果仅仅是设置了FLAG_ACTIVITY_NEW_TASK和taskAffinity而不设置启动模式为singleTask,是不能保证Activity的栈内唯一性的。所以上面“可以认为FLAG_ACTIVITY_NEW_TASK和singleTask作用相同“的表述可能会有点小问题

  • 如果存在这样的一个任务,则检查在这个任务中是否已经有了一个SecondActivity的实例,
    • 如果已经存在一个SecondActivity的实例,则会重用这个任务和任务中的SecondActivity实例,将这个任务调到前台,清除位于SecondActivity上面的所有Activity,显示SecondActivity,并调用SecondActivity的onNewIntent();
    • 如果不存在一个SecondActivity的实例,会在这个任务中创建SecondActivity的实例,并调用onCreate()方法
  • 如果不存在这样的一个任务,会创建一个新的affinity为com.example.q.view.second的任务,并且将SecondActivity启动到这个新的任务中

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

  1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK
  2.  然后获得SecondActivity的taskAffinity,即为com.example.q.view
  3. 检查是否已经存在一个affinity为com.example.q.view的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的
  4.  既然已经存在这个任务,就检索在这个任务中是否存在一个SecondActivity的实例,发现不存在
  5.  在这个已有的任务中启动一个SecondActivity的实例

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

  1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK
  2. 然后获得SecondActivity的taskAffinity,即com.example.q.view.second
  3. 检查是否已经存在一个affinity为com.example.q.view.second的任务,这个任务是不存在的
  4. 创建一个新的affinity为com.example.q.view.second的任务,并且将SecondActivity启动到这个新的任务中

其实framework中对任务和activity的调度是很复杂的,尤其是把启动模式设为singleTask或者以FLAG_ACTIVITY_NEW_TASK标志启动时。所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK时,要仔细测试应用程序。这也是官方文档上的建议。


实例验证将两个不同app中的不同的singleTask模式的Activity的taskAffinity设成相同


官方文档中提到,可以把不同的 应用中的activity的taskAffinity设置成相同的值,这样的话这两个activity虽然不在同一应用中,却会在运行时分配到同一任务中,下面对此进行验证,在这里,会使用上面的示例 View,并创建一个新的示例AndroidTaskTest。AndroidTaskTest由两个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.q.androidtasktest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        </activity>
    </application>

</manifest>

可以看到OtherActivity的启动模式被设置为singleTask,并且taskAffinity属性被设置为com.example.q.view.second
,这和View应用中的SecondActivity相同。现在将这两个应用安装在设备上。执行以下操作:

启动View应用,在它的MainActivity中点击按钮开启SecondActivity,由上面的介绍可知secondActivity是运行在一个新任务中的,这个任务就是 com.example.q.view.second

然后按Home键回到Launcher,启动AndroidTaskTest,在启动AndroidTaskTest的入口Activity(MainActivity)时,会自动启动新的任务,那么现在一共有三个任务,View的MainActivity和SecondActivity分别占用一个任务,AndroidTaskTest的MainActivity也占用一个任务。

在AndroidTaskTest的MainActivity中点击按钮启动OtherActivity,那么这个OtherActivity是在哪个任务中呢?

下面执行adb shell dumpsys activity命令,发现有以下输出:



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

AndroidTaskTest的log

好吧这里忘记给OtherActivity打印log了,不过从上面的adb shell里也可以看出来


view的secondactivity和androidtasktest的otheractivity是属于同一个task id=46的。

所以由此可见, view的SecondActivity和 androidtasktest的OtherActivity是在同一任务中的。由上面adb shell dumpsys activity命令的输出结果还可以看出, viewandroidtasktest这两个应用程序会开启两个进程,他们的所有组件分别运行在独立的进程中,其中 view所在进程的进程号为10652, androidtasktest所在进程的进程号为10791。 com.example.q.view.second任务中的两个activity属于不同的应用,并且运行在不同的进程中,这也说明了一个问题: 任务(Task)不仅可以跨应用(Application),还可以跨进程(Process)


实例验证singleTask的另一意义:在同一个任务中具有唯一性

注:这里是原博主在13年写的,谷歌官网文档可能还没写的那么清楚,不过现在的官方文档是写明了,不一定会开启一个新任务,官方文档: when starting the activity, there is already a task running that starts with this activity, then instead of starting a new instance the current task is brought to the front. (大概意思是, 如果在启动活动时已经有一个以此活动开始的任务运行,那么不是启动新实例,而是将当前任务带到最前面。
谷歌官方文档中提到,singleTask模式的activity总会在一个新的任务中开启。上面已经验证了这种说法不确切,singleTask模式只意味着“可以在一个新的任务中开启”,至于是不是真的会在新任务中开启,在framework中还有其他条件的限制。由上面的介绍可知,这个条件为:是否已经存在了一个由他的taskAffinity属性指定的任务。这一点具有迷惑性,我们在看到singleTask这个单词的时候,会直观的想到它的本意:single in task。即,在同一个任务中,只会有一个该activity的实例。现在让我们进行 验证:


为了验证这种情况,需要修改一下上面用到的view示例。增加一个FourthActivity,并且MainActivity,SecondActivity,ThirdActivity和FourthActivity这四个activity都不设置taskAffinity属性,并且将SecondActivity启动模式设为singleTask,这样这四个activity会在同一个任务中开启。他们的开启流程是这样的:MianActivity开启SecondActivity,SecondActivity开启ThirdActivity,ThirdActivity开启FourthActivity,FourthActivity开启SecondActivity。代码和软件界面就不列出了,只列出清单文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.q.view">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".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=".SecondActivity"
            android:exported="true"
            android:label="secondActivity"
            android:launchMode="singleTask"
            >

        </activity>
        <!-- android:theme="@style/translucent" -->
        <activity
            android:name=".thirdActivity"
            android:label="thirdActivity"
            >

        </activity>
        <activity android:name=".FourthActivity"
            android:label="fourthActivity"
           >
        </activity>
    </application>

</manifest>
现在从MainActivity一直启动到FourthActivity,打印出的系统Log为:


由此可见这四个activity都是在同一个任务中的。再次执行adb shell dumpsys activity命令加以验证:


同样可以说明目前这四个activity都运行在affinity为 com.example.q.view的任务中,即栈中的状态为MainActivity -->  SecondActivity --> ThirdActivity --> FourthActivity。

下面执行在FourthActivity中点击按钮启动SecondActivity的操作,注意,SecondActivity的启动模式为singleTask,那么现在栈中的情况如何呢?

log打印

这里我没有给fourthactivity的除onCreate外的方法打印log,不然应该是fourthactivity也会被stop和destroy,从后面的adb shell 也能看出来 task id=49的任务里在Second之上的活动全部被清除了。

再次执行adb shell dumpsys activity命令,有以下输出:

这时栈中的状态为MainActivity -->  SecondActivity。确实确保了在任务中是唯一的,并且清除了同一任务中它上面的所有Activity。 那么这个SecondActivity的实例是重用的上次已有的实例还是重新启动了一个实例呢?可以观察系统Log, 发现系统Log没有改变,还是上面的四条Log。打印Log的语句是在各个Activity中的onCreate方法中执行的,没有打印出新的Log,说明SecondActivity的onCreate的方法没有重新执行,也就是说是重用的上次已经启动的实例,而不是销毁重建。

经过上面的验证,可以得出如下的结论: 在启动一个singleTask的Activity实例时,如果系统中已经存在这样一个实例,就会将这个实例调度到任务栈的栈顶,并清除它当前所在任务中位于它上面的所有的activity。



四 实例验证singleInstance的行为


根据上面的讲解,并且参考谷歌官方文档,singleInstance的特点可以归结为以下三条:
  1. 以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例
  2. 以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中(官方文档上的描述为,singleInstance模式的Activity不允许其他Activity和它共存在一个任务中)
  3. 被singleInstance模式的Activity开启的其他activity,能够开启一个新任务,但不一定开启新的任务,也可能在已有的一个任务中开启
下面对这三个特点分别验证,所使用的示例同样为View,只不过会进行一些修改,下面列出它的清单文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.q.view">

 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".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=".SecondActivity"
            android:exported="true"
            android:label="secondActivity"
            android:launchMode="singleInstance"
            >
            <intent-filter>
                <action android:name="com.example.q.view.ACTION_MY"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        <!-- android:theme="@style/translucent" -->
        <activity
            android:name=".thirdActivity"
            android:label="thirdActivity"
            >
        </activity>
        <activity android:name=".FourthActivity"
            android:label="fourthActivity"
           >
        </activity>

    </application>

</manifest>

由上面的清单文件可以知道,该应用包括三个activity,分别为MainActivity,SecondActivity,ThirdActivity,其中SecondActivity启动模式设置为singleInstance。MainActivity可以开启SecondActivity,SecondActivity可以开启ThirdActivity。 并且为了可以在其他应用中开启SecondActivity,为SecondActivity设置了一个IntentFilter,这样就可以在其他应用中使用隐式Intent开启SecondActivity。


为了更好的验证singleInstance的全局唯一性,还需要其他一个应用,对上面的AndroidTaskTest进行一些修改即可。AndroidTaskTest只需要一个MainActivity,在MainActivity中点击按钮会开启AndroidTaskTest应用中的SecondActivity。开启AndroidTaskTest应用中的SecondActivity的代码如下:

[java]   view plain  copy
  1. /** 
  2.  * 该方法在布局中按钮的android:onClick属性中指定 
  3.  * android:onClick="launchOtherActivity" 
  4.  * @param v 
  5.  */  
  6. public void launchOtherActivity(View v){  
  7.     Intent intent = new Intent();  
  8.       
  9.     //以下Action为"com.jg.zhang.androidtasktest.ACTION_MY"  
  10.     //即AndroidTaskTest应用中SecondActivity的action  
  11.     intent.setAction("com.jg.zhang.androidtasktest.ACTION_MY");  
  12.       
  13.     startActivity(intent);  
  14. }  


下面开始验证第一个特点:以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例


执行如下操作:安装AndroidTaskTest应用,点击MainActivity中的按钮,开启SecondActivity,可以看到如下log输出:

执行adb shell dumpsys activity命令,有以下输出:


以上可以说明,singleInstance模式的Activity总是会在新的任务中运行(前提是系统中还不存在这样的一个实例) 。


下面验证它的全局唯一性,执行以下操作:安装另一个应用AndroidTaskTest1,在开启的MainActivity中点击按钮开启AndroidTaskTest应用中的SecondActivity。看到打印出一条新的日志:


执行adb shell dumpsys activity命令,有以下输出:


由此可以得知,开启的SecondActivity就是上次创建的编号为task id=51的SecondActivity,并且Log中没有再次输出关于SecondActivity的信息,说明SecondActivity并没有重新创建。由此可以得出结论:以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。



下面开始验证第二个特点:以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中


重新安装AndroidTaskTest应用,点击MainActivity中的按钮,开启SecondActivity,在SecondActivity中点击按钮,开启ThirdActivity。可以看到有如下Log输出:


执行adb shell dumpsys activity命令,有以下输出:


SecondActivity所在的任务为55,被SecondActivity启动的ThirdActivity所在的任务为54,这就说明 以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中



下面开始验证第三个特点:被singleInstance模式的Activity开启的其他activity,能够在新的任务中启动,但不一定开启新的任务,也可能在已有的一个任务中开启


有上面对第二个特点的验证可以看到,被SecondActivity启动的ThirdActivity并没有运行在一个新开启的任务中,而是和MainActivity运行在了同一个已有的任务中,那么在什么情况下ThirdActivity才会启动一个新的任务呢?

现在对程序的清单文件做以下修改,为ThirdActivity增加一个属性taskAffinity:
<activity
            android:name=".thirdActivity"
            android:label="thirdActivity"
            android:taskAffinity="com.example.q.view.second"
            >
        </activity>
重新安装view应用,执行和上一步中同样的操作:点击MainActivity中的按钮,开启SecondActivity,在SecondActivity中点击按钮,开启ThirdActivity。可以看到有如下输出:

执行adb shell dumpsys activity命令,有以下输出:


可见,被SecondActivity启动的ThirdActivity启动在了一个新的任务中,即在启动ThirdActivity时创建了一个新任务。这就说明 被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.q.view.second

(即ThirdActivity的taskAffinity属性)的任务,

  • 如果存在这样的一个任务,则检查在这个任务中是否已经有了一个ThirdActivity的实例,
  1. 如果已经存在一个ThirdActivity的实例,则会重用这个任务和任务中的ThirdActivity实例,将这个任务调到前台,清除位于ThirdActivity上面的所有Activity,显示ThirdActivity,并调用ThirdActivity的onNewIntent()。
  2. 如果不存在一个ThirdActivity的实例,会在这个任务中创建ThirdActivity的实例,并调用onCreate()方法
  • 如果不存在这样的一个任务,会创建一个新的affinity为com.example.q.view.second的任务,并且将ThirdActivity启动到这个新的任务中

如果ThirdActivity不设置taskAffinity,即ThirdActivity和MainActivity的taskAffinity相同,都为应用的包名,那么ThirdActivity是不会开启一个新任务的,framework中的判定过程如下:

  1. 在SecondActivity启动ThirdActivity时,因为SecondActivity是singleInstance的,所以设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK
  2. 然后获得ThirdActivity的taskAffinity,即为包名com.example.q.view
  3. 检查是否已经存在一个affinity为com.example.q.view的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的
  4.  既然已经存在这个任务,就检索在这个任务中是否存在一个ThirdActivity的实例,发现不存在
  5.  在这个已有的任务中启动一个SecondActivity的实例

为了作一个清楚的比较,列出ThirdActivity的taskAffinity属性设为com.jg.zhang.androidtasktest.second时的启动过程

  1. 在SecondActivity启动ThirdActivity时,因为SecondActivity是singleInstance的,那么设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK
  2. 然后获得ThirdActivity的taskAffinity,即为com.example.q.view.second
  3. 检查是否已经存在一个affinity为com.example.q.view.second的任务,这个任务是不存在的
  4.  创建一个新的affinity为com.example.q.view.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的一个实例。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值