引子
在安卓的服务中这样启动活动:
Intent intent = new Intent(this,XXXActivity.class);
startActivity(intent);
你会得到这样的错误:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
你知道我对安卓的什么地方最为痴迷?是它应用之间的协作。怎么协作?依靠activity之间的协作。
task
这种协作是可以跨越应用的。我们从task谈起。
task最直观的是安卓第三个虚拟键所列出的那些就是任务。
An activity can even start activities that exist in other applications on the device. …… Eventhough the activities may be from different applications, Android maintains this seamless userexperience by keeping both activities in the same task.
以上这种功能的实现,要从task谈起,developer上,这样定义task:
A task is a collection of activities that users interact withwhen performing a certain job. The activities are arranged in a stack (the back stack), inthe order in which each activity is opened. When apps are running simultaneously in amulti-windowed environment, supported inAndroid 7.0 (API level 24) and higher, the system manages tasks separately for each window;each window may have multiple tasks. The same holds true for Android apps running on Chromebooks: the system manages tasks, or groups of tasks, on aper-window basis.
翻译过来,大体意识就是task是一个具有栈结构的容器,用以执行一定特定的工作,它可以放置多个Activity实例。
启动一个应用,系统就会为之创建一个task,来放置根Activity。默认情况下,一个Activity启动另一个Activity时,两个Activity是放置在同一个task中的,后者被压入前者所在的task栈,当用户按下后退键,后者从task被弹出,前者又显示在幕前,特别是启动其他应用中的Activity时,两个Activity对用户来说就好像是属于同一个应用。
task可以分为系统task和task。这两者之间是互相独立的。
当我们运行一个应用时,按下Home键回到主屏,启动另一个应用,这个过程中,之前的task被转移到后台,新的task被转移到前台,其根Activity也会显示到幕前,过了一会之后,在此按下Home键回到主屏,再选择之前的应用,之前的task会被转移到前台,系统仍然保留着task内的所有Activity实例,而那个新的task会被转移到后台,如果这时用户再做后退等动作,就是针对该task内部进行操作了。
更近一步,Activity的归属
一个Activity当然要表面自己身在哪个task,所以每个Activity都有taskAffinity属性。这个属性指出了它希望进入的Task。
如果一个Activity指明自己的taskaffinity,那么它的这个属性就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。
而Task也有自己的affinity属性,它的值等于它的根Activity的taskAffinity的值。
显示的声明activiy的属性
这很简单,在AndroidManifest.xml中声明
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".TaskTestActivity"
android:taskAffinity="com.tasktest.task"
android:label="@string/app_name">
</activity>
</application>
有了上面的基础,请记住这两种文档说明的情况:
1. 如果该Activity的allowTaskReparenting设置为true,它进入后台,当一个和它有相同affinity的Task进入前台时,它会重新宿主,进入到该前台的task中。
**2. 如果加载某个Activity的intent,Flag被设置成FLAG_ACTIVITY_NEW_TASK时,它会首先检查是否存在与自己taskAffinity相同的Task,如果存在,那么它会直接宿主到该Task中,如果不存在则重新创建Task。 **
再来看看Activity四种启动模式详解
activity有四种启动模式,分别为standard,singleTop,singleTask,singleInstance。
这四种模式我不细说,我们只从名字上分析分析。
第一种,标准模式,想想就知道是平常的模式,这里的标准意思是每生成一个activity的实例,就当一个实例的放在栈里。
第二种,singleTop,在于那个top。要是activity不在栈顶,它和standard模式没什么区别,要是在top,就不创建一个新的,用栈顶原来那个。
第三种,不那么容易,尤其是官网的说法好像有问题。
singleTask模式的Activity只允许在系统中有一个实例。如果系统中已经有了一个实例,持有这个实例的任务将移动到顶部,同时intent将被通过onNewIntent()发送。如果没有,则会创建一个新的Activity并置放在合适的任务中。
这话的意思,我们分两种情况讨论,一是在同一个应用,二是不同。
在同一个应用中,如果系统中还没有singleTask的Activity,会新创建一个,并放在同一任务的栈顶。但是如果已经存在,singleTask Activity上面的所有Activity将以合适的方式自动销毁,让我们想要显示的Activity处于栈顶。
在非同一个应用中,intent是从另外的应用发送过来。系统中没有任何Activity的实例的化,会创建一个新的任务,并且新的Activity被作为根Activity创建;如果系统中拥有这个singleTask的应用存在,新建的Activity会置于这个任务的上面。
第四种,和第三种很像,关键在于singleInstance,就是只能有这一个单例存在在栈中。
Intent Flags
回到开头,我们还没解决开始的错误。最简单粗暴的方法是:
Intent intent = new Intent(this,XXXActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
这可以解决问题,不过也还存在一个小问题,留待读者发现:)
Flag的字面意思很好理解,如果你读懂了上述的任务与活动启动模式的化,再提供几个intent Flags:
**FLAG_ACTIVITY_CLEAR_TOP**
**FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET**
**FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS**
**FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY**
**FLAG_ACTIVITY_MULTIPLE_TASK **
**FLAG_ACTIVITY_NEW_TASK**
**FLAG_ACTIVITY_NO_ANIMATION**
**FLAG_ACTIVITY_NO_HISTORY**
**FLAG_ACTIVITY_NO_USER_ACTION**
**FLAG_ACTIVITY_PREVIOUS_IS_TOP **
**FLAG_ACTIVITY_REORDER_TO_FRONT**
**FLAG_ACTIVITY_RESET_TASK_IF_NEEDED **
**FLAG_ACTIVITY_SINGLE_TOP**
至于意思,还是字面理解就好了。