在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文件中清晰地体现出来。如下所示:
<?xmlversion="1.0" encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testlaunchermode"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.testlaunchermode.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<actionandroid:name="android.intent.action.MAIN" />
<categoryandroid:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<serviceandroid:name=""></service>
<receiverandroid:name=""></receiver>
<providerandroid:name=""></provider>
</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=".MainActivity"
android:process=":remote"
</activity>
这样的话,这个activity就会运行在一个单独的进程中。
二 Activity四中启动模型详解
activity有四种启动模式,分别是:standard、singleTop、singleTask、singleIntance,如何要使用这四种启动方式,就必须在manifest文件中进行注册,如下所示:
<activity android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/mainapp_style"
android:launchMode="singleTask">
</activity>
同样,在Intent类中定义了许多与Activity启动或者调度有关的标志,<activity>标签中也有一些属性,这些标志和属性与四种启动模式联合使用,会在很大程度上改变activity的行为,进而会改变task和back state的状态。关于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的一个实例。
SingleTask
虽然官方文档说:如果一个activity的启动模式为singleTask,那么系统总会在一个新任务的最底部(root)启动这个activity并且被这个activity启动的其他activity会和该activity同时存在于这个新任务栈中,诶过系统中已经存在这样的一个activity则会重用这个实例,并且调用它的onNewIntent()方法。即这样的一个activity在系统中只会存在一个实例。但是官方文档中的这种说法是不正确的,启动模式为singleTask的activity并不会总是开启一个新的任务。稍后进行验证详解。
SingleIntance
总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他activity会自动运行于另一个任务中。当再次启动该activity的实例看,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将Intent实例传递到该实例中。和singleTask相同,同一时刻在系统中只会存在一个这样的activity实例。
三.实例验证singleTask启动模式
上面的四中启动模式都已经介绍完毕,为了加强理解,下面进行验证,由于singleTop和standard比较简单,所以就不再介绍,下面主要讲解singleTask和singleInstance。
验证启动singleTask模式的Activity时是否会创建新任务
创建android工程:TestLaunchMode,里面有三个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.testlaunchermode"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="10"
android:targetSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.testlaunchermode.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="com.example.testlaunchermode.SecondActivity"
android:launchMode="singleTask"
>
<intent-filter >
<action android:name="com.example.testlaunchermode.SecondActivity"/>
&