Android中Activity四种启动模式(LaunchMode)和taskAffinity属性

.

   在android应用开发中,界面的引导和跳转是值得深入研究的重要内容。在开发中,与界面跳转联系比较紧密的概念是Task(任务)和Back Stack(回退栈)。activity的启动模式会影响Task和Back Stack的状态,进而影响用户体验。除了启动模式(LaunchMode)之外,Intent类中定义的一些标志(以FLAG_ACTIVITY_开头)也会影响Task和Back Stack的状态。其中也涉及到activity的一个重要属性taskAffinity和Intent中的标志(setFlag)之一FLAG_ACTIVITY_NEW_TASK。关于Intent中其他标志位的具体用法会在另一篇文章中介绍。

   Task是一个存在于Framework层的概念,容易与它混淆的有Application(应用)和Process(进程)。在开始介绍Activity的启动模式的使用之前,首先对这些概念做一个简单的说明和区分。

   一、Application,Task和Process的区别与联系

    application翻译成中文时一般称为“应用”或“应用 程序”,在android中,总体来说一个应用就是一组组件的集合。众所周知,android是在应用层组件化程度非常高的系统,android开发的第一课就是学习android的四大组件。当我们写完了多个组件,并且在manifest文件中注册了这些组件之后,把这些组件和组件使用到的资源打包成apk,我们就可以说完成了一个application。application和组件的关系可以在manifest文件中清晰地体现出来。如下所示:
<pre name="code" class="html"><?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的数据结构中,这个栈的基本行为是,当用户在多个activity之间跳转时,执行压栈操作,当用户按返回键时,执行出栈操作。举例来说,如果应用程序中存在A,B,C三个activity,当用户在Launcher或HomeScreen点击应用程序图标时,启动主Activity A,接着A开启B,B开启C,这时栈中有三个Activity,并且这三个Activity默认在同一个任务(task)中,当用户按返回时,弹出C,栈中只剩A和B,再按返回键,弹出B,栈中只剩A,再继续按返回键,弹出A,任务被移除。如下图所示:
Android中Activity四种启动模式(LaunchMode)和taskAffinity属性_第1张图片
    task是可以跨应用的,这正是task存在的一个重要原因。有的Activity,虽然不在同一个app中,但为了保持用户操作的连贯性,把他们放在同一个任务中。例如,在我们的应用中的一个Activity A中点击发送邮件,会启动邮件程序的一个Activity B来发送邮件,这两个activity是存在于不同app中的,但是被系统放在一个任务中,这样当发送完邮件后,用户按back键返回,可以返回到原来的Activity A中,这样就确保了用户体验。
    process一般翻译成进程,进程是操作系统内核中的一个概念,表示直接受内核调度的执行单位。在应用程序的角度看,我们用java编写的应用程序,运行在dalvik虚拟机中,可以认为一个运行中的dalvik虚拟机实例占有一个进程,所以,在默认情况下,一个应用程序的所有组件运行在同一个进程中。但是这种情况也有例外,即,应用程序中的不同组件可以运行在不同的进程中。只需要在manifest中用process属性指定组件所运行的进程的名字。如下所示:
<pre name="code" class="html"> <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"
                  <span style="color:#FF0000;">android:launchMode="singleTask</span>"
        </activity>
   同样,在Intent类中定义了很多与Activity启动或调度有关的标志,<activity>标签中有一些属性,这些标志,属性和四种启动模式联合使用,会在很大程度上改变activity的行为,进而会改变task和back stask的状态。在此介绍activity的四种启动模式。

standard

标准启动模式,也是activity的默认启动模式。在这种模式下启动的activity可以被多次实例化,即在同一个任务中可以存在多个activity的实例,每个实例都会处理一个Intent对象。如果Activity A的启动模式为standard,并且A已经启动,在A中再次启动Activity A,即调用startActivity(new Intent(this,A.class)),会在A的上面再次启动一个A的实例,即当前的桟中的状态为A-->A。

stangleTop

如果一个以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和singeInstance进行验证。
1.验证启动singleTask模式的activity时在什么情况下会创建新的任务(task):

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity 
            android:name="com.jin.cai.wei.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name="com.jin.cai.wei.OneActivity"
            <span style="color:#FF6666;">android:launchMode="singleTask"</span>></activity>
        <activity
            android:name="com.jin.cai.wei.SecondActivity"></activity>
    </application>

MainActivity--------->oneActivity--------->SecondActivity--------->oneActivity
getTaskId()              getTaskId()             getTaskId()                   getTaskId()



结论:由以上的执行顺序和Log结果可知,MainActivity、OneActivity和ScondActivity都是启动在同一个任务中的。 1.当重新启动oneActivity时,Log还是原来的结果,由此可知,在启动一个singleTask的Activity实例时,如果系统中已经存在这样一个实例,就会将这个实例调度到任务栈的栈顶,并清除它当前所在任务中位于它上面的所有的activity。 2. 其实,把启动模式设置为singleTask,framework在启动该activity时只会把它标示为可以在一个新任务中启动,至于是否真的在一个新任务中启动,还要受其他条件的限制。
现在在OneActivity增加一个taskAffinity属性,如下所示:

<activity
            android:name="com.jin.cai.wei.OneActivity"
            android:launchMode="singleTask"
           <span style="color:#FF6666;"> android:taskAffinity="<span style="color:#FF6666;">com.jin.cai.wei.one</span>"</span>></activity>



结论:由以上的执行Log结果可知,当OneActivity添加taskAffinity属性之后,OneActivity和ScondActivit则启动在另外一个任务中。其实, taskAffinity表示一个任务,这个任务就是当前activity所在的任务;在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务。 默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的应用中的activity的taskAffinity设置成相同的值,或者为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。
注:FLAG_ACTIVITY_NEW_TASK和singleTask作用相同,当启动模式为singleTask时,framework会将它的启动标志设为FLAG_ACTIVITY_NEW_TASK,framework会检索是否已经存在了一个affinity为 com.jin.cai.wei.one的任务(即一个TaskRecord对象)。

framework中的判定过程如下:

  1. 在MainActivity启动OneActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK
  2.  然后获得OneActivity的taskAffinity,即为包名com.jin.cai.wei.one的任务
  3. 检查是否已经存在一个taskAffinity为com.jin.cai.wei.one的任务,如果已经存在,则检索在这个任务中是否存在一个OneActivity的实例,如果存在OneActivity实例,直接重用这个任务中的OneActivity实例,将这个任务调到前台,清除位于OneActivity上面的所有Activity,显示OneActivity,并调用OneActivity的onNewIntent();如果发现不存在,创建一个新的taskAffinity为com.jin.cai.wei.one的任务,并且将OneActivity启动到这个新的任务中

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


2.实例验证两个不同app,分别设置两个Activity为singleTask模式,并且将taskAffinity设成相同,测试taskAffinity相同的Activity是否在同一个task中:

我们在上面的测试项目“Test1”的基础上,再新建一个项目“Test2”,在Test2的OtherActivity上添加与Test1的taskAffinity相同,xml清单如下:
<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity 
            android:name="com.jin.cai.wei.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity 
            android:name="com.jin.cai.wei.OtherActivity"
            android:launchMode="singleTask"
            <span style="color:#FF6666;">android:taskAffinity="com.jin.cai.wei.one<span style="color:#000000;">"</span></span>>
        </activity>
    </application>
执行以下操作:启动Test1,在它的MianActivity中点击按钮开启OneActivity,由上面的介绍可知OneActivity是运行在一个新任务中的,这个任务就是 com.jin.cai.wei.one。然后按Home键回到桌面,再启动Test2,进入OtherActivity,会自动启动新的任务。那么现在一共有三个任务,Test1的MianActivity和OneActivity分别占用一个任务Test2的MainActivity也占用一个任务。那么这个OtherActivity是在哪个任务中呢?



由此可见,Test1的OneActivity和Test2的OtherActivity是在同一任务中的。而 com.jin.cai.wei.one任务中的两个activity属于不同的应用,并且运行在不同的进程中,这也说明了一个问题: 任务(Task)不仅可以跨应用(Application),还可以跨进程(Process)。

3.singleInstance的行为:

根据上面的讲解,并且参考谷歌官方文档,singleInstance的特点可以归结为以下三条:
  • 以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例。(与singletask的差异)。
  • 以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中。(官方文档上的描述为,singleInstance模式的Activity不允许其他Activity和它共存在一个任务中。与singletask的差异)
  • 被singleInstance模式的Activity开启的其他activity,能够开启一个新任务,但不一定开启新的任务,也可能在已有的一个任务中开启。(与singletask的共同点)


三、本文小结

由上述可知,Task是Android Framework中的一个概念,Task是由一系列相关的Activity组成的,是一组相关Activity的集合。Task是以栈的形式来管理的。

我们在操作应用时,会涉及界面的跳转。其实在对界面进行跳转时,Android Framework既能在同一个任务中对Activity进行调度,也能以Task为单位进行整体调度。在启动模式为standard或singleTop时,一般是在同一个任务中对Activity进行调度,而在启动模式为singleTask或singleInstance是,一般会对Task进行整体调度。

对Task进行整体调度包括以下操作:

  • 按Home键,将之前的任务切换到后台。
  • 长按Home键,会显示出最近执行过的任务列表。
  • 在Launcher或HomeScreen点击图标打开应用时,开启一个新任务,或者是将已有的任务调度到前台。
  • 当启动singleTask模式的Activity时,会在系统中搜寻是否已经存在一个合适的任务(有已建taskAffinity的任务),1.若存在,则会将这个任务调度到前台以重用这个任务。a.如果这个任务中已经存在一个要启动的Activity的实例,则清除这个实例之上的所有Activity,将这个实例显示给用户;b.如果这个已存在的任务中不存在一个要启动的Activity的实例,则在这个任务的顶端启动一个实例。2.若这个任务不存在,则会启动一个新的任务,在这个新的任务中启动这个singleTask模式的Activity的一个实例。
  • 启动singleInstance的Activity时,会在系统中搜寻是否已经存在一个这个Activity的实例,如果存在,会将这个实例所在的任务调度到前台,重用这个Activity的实例(该任务中只有这一个Activity),如果不存在,会开启一个新任务,并在这个新任务中启动这个singleInstance模式的Activity的一个实例。




你可能感兴趣的:(android,launchMode,taskAffinity)