引言

对Android的启动模式还有些印象,现在项目的一个需求是:登录页是一个登录选择页,包含了帐号密码登录、手机号登录、微信第三方登录几种选择,选择某种登录方式意味着进入某个登录页面,点击“返回键”能返回到登录选择页,某个注册/登录页面注册/登录成功后当前页面需要销毁,登录选择页面也需要销毁。

带着这样的需求,我们进入主题。


Activity的四种启动模式:

Android的启动模式(android:launchMode,我们在AndroidMainfest中,给Activity添加这个属性,就可以设置启动模式):

  • standard(默认启动模式):在默认启动模式下,无论我们之前创建过这个Activity,还是没有创建过,他都会重新创建一个。

  • singleInstance(全局单一模式):每一个Activity都存放到一个新的任务栈(Task)中,所以他们都是位于栈顶。

  • singleTop(栈顶复用模式)

  • singleTask(栈内复用模式)


Task
  • 一个后进先出的栈结构,用来保存调度activity
standard (默认的启动模式)
  • 默认只在当前task启动
  • 启动时会新建一个activity实例放到栈顶
singleTop(栈顶复用模式)
  • 默认只在当前task启动

  • 当 当前task的栈顶已经有一个相同的Activity,则不会创建新的activity,而是会调用栈顶activity的onNewIntent()方法

  • 栈顶不存在相同Activity时,会新建一个activity实例放到栈顶

singleTask
  • 默认可以在其他task启动,但是到底会不会在其他task启动需要依赖taskAffinity具体分析

  • 当 当前task中已经存在一个相同的Activity时,会将task中位于该Activity实例上方的activity出栈直到该Activity实例位于栈顶(类似Intent中添加FLAG_ACTIVITY_CLEAR_TOP的效果)

  • 对一个singleTask的Activity类,系统中只会存在一个实例

singleInstance(独享task)
  • 默认新建task,并独占该task

  • singleInstance的Activity中启动的task会在其他task

  • 系统中同样只会存在一个实例


絮叨一下基本概念

一个应用程序当中通常都会包含很多个Activity,每个Activity都是一个具有特定的功能,并且可以让用户进行操作的组件。

另外,Activity之间可以相互启动,当前应用Activity甚至可以去启动其他应用Activity。比如你的应用希望去发送一封邮件,你就可以定义一个具有"send"动作的Intent,并且传入一些数据,如对方邮箱地址、邮件内容等。这样,如果另外一个应用程序中的某个Activity声明自己是可以响应这种Intent的,那么这个Activity就会被打开。当邮件发送之后,按下返回键仍然还是会回到你的应用程序当中,这让用户看起来好像刚才那个编写邮件的Activity就是你的应用程序当中的一部分。所以说,即使有很多个Activity分别都是来自于不同应用程序的,Android系统仍然可以将它们无缝地结合到一起。那这一切是怎么实现的呢?这就要讲到Activity任务栈以及Activity启动模式了。

任务栈是什么

任务栈Task,是一种用来放置Activity实例容器,它是以的形式进行盛放,也就是所谓的先进后出,主要有2个基本操作:压栈出栈,其所存放的Activity是不支持重新排序的,只能根据压栈和出栈操作更改Activity的顺序。

启动一个Application的时候,系统会为它默认创建一个对应的Task,用来放置根Activity。默认启动Activity会放在同一个Task中,新启动的Activity会被压入启动它的那个Activity的栈中,并且显示它。当用户按下回退键时,这个Activity就会被弹出栈,按下Home键回到桌面,再启动另一个应用,这时候之前那个Task就被移到后台,成为后台任务栈,而刚启动的那个Task就被调到前台,成为前台任务栈Android系统显示的就是前台任务栈中的Top实例Activity。

任务栈的作用

以往基于应用(application)的程序开发中,程序具有明确的边界,一个程序就是一个应用,一个应用为了实现功能可以采用开辟新线程甚至新进程来辅助,但是应用与应用之间不能复用资源和功能。而Android引入了基于组件开发软件架构,虽然我们开发android程序,仍然使用一个apk工程一个Application的开发形式,但是对于Aplication的开发就用到了Activityservice等四大组件,其中的每一个组件,都是可以被跨应用复用的,这就是android的神奇之处。虽然组件可以跨应用被调用,但是一个组件所在的进程必须是在组件所在的Aplication进程中。由于android强化了组件概念,弱化了Application的概念,所以在android程序开发中,A应用的A组件想要使用拍照或录像的功能就可以不用去针对Camera类进行开发,直接调用系统自带的摄像头应用(称其B应用)中的组件(称其B组件)就可以了,但是这就引发了一个新问题,A组件运行在A应用中,B组件运行在B应用中,自然都不在同一个进程中,那么从B组件中返回的时候,如何实现正确返回到A组件呢?Task就是来负责实现这个功能的,它是从用户角度来理解应用而建立的一个抽象概念。因为用户所能看到的组件就是Activity,所以Task可以理解为实现一个功能而负责管理所有用到的Activity实例的栈。

是一个先进后出线性表,根据Activity在当前栈结构中的位置,来决定该Activity的状态。正常情况下,当一个Activity启动了另一个Activity的时候,新启动的Activity就会置于任务栈的顶端,并处于活动状态,而启动它的Activity虽然成功身退,但依然保留在任务栈中,处于停止状态,当用户按下返回键或者调用finish()方法时,系统会移除顶部Activity,让后面的Activity恢复活动状态。当然,世界不可能一直这么“和谐”,可以给Activity设置一些“特权”,来打破这种“和谐”的模式,这种特权,就是通过在AndroidManifest文件中的属性andorid:launchMode来设置或者通过Intentflag来设置的。

下面就先介绍下Activity的几种启动模式。


图解Activity的启动模式

standard

默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。应用场景:绝大多数Activity。

standard模式

如果以这种方式启动的Activity被跨进程调用,在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管它们属于不同的程序,这似乎有点费解,看起来也不是那么合理,所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中,这样就合理的多了。

singleTop

栈顶复用模式,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用 onNewIntent() 方法。避免栈顶的activity被重复的创建。应用场景:在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用singleTop,否则每次点击都会新建一个Activity。当然实际的开发过程中,测试妹纸没准给你提过这样的bug:某个场景下连续快速点击,启动了两个Activity。如果这个时候待启动的Activity使用 singleTop模式也是可以避免这个Bug的。

singleTop

standard模式,如果是外部程序启动singleTop的Activity,在Android 5.0之前新创建的Activity会位于调用者的Task中,5.0及以后会放入新的Task中。

singleTask

栈内复用模式, activity只会在任务栈里面存在一个实例。如果要激活的activity,在任务栈里面已经存在,就不会创建新的activity,而是复用这个已经存在的activity,调用 onNewIntent()方法,并且清空这个activity任务栈上面所有的activity。应用场景:大多数App的主页。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例,通过这种方式能够保证退出应用时所有的Activity都能销毁

在跨应用Intent传递时,如果系统中不存在singleTask Activity的实例,那么将创建一个新的Task,然后创建SingleTask Activity的实例,将其放入新的Task中。

1:假如目前有个任务栈T1中的情况是ABC,这个时候Activity D以singleTask模式请求启动,其所需要的任务栈正是T1,则系统会直接创建D的实例并将其入栈到T1中。

singleTask1

2:假如D Activity启动所需要的任务栈为T2,由于T2和D的实例均不存在,那么系统会先创建任务栈T2,然后再创建D的实例并将其入栈到T2中。我们可以通过设置Activity的taskAffinity属性来模拟这一场景。

3:如果D所需的任务栈为T3,并且当前任务栈T3的情况为ADBC,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent()方法,同时由于singleTask默认具有ClearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终T3的情况为AD。

4:假如目前有两个任务栈,前台任务栈T4的情况为AB,后台任务栈t4里存有CD,假设CD的启动模式均为singleTask,现在由B去启动D,那么整个后台任务都会被切换到前台,这个时候整个栈就变成了ABCD。

5:假如上面的其他条件不变,B启动的是C而不是D,那么整个栈的情况就变成了ABC,因为D在C上面,会被清理出栈。

singleInstance

单一实例模式,整个手机操作系统里面只有一个实例存在。不同的应用去打开这个activity 共享公用的同一个activity。它会运行在自己单独独立任务栈里面,并且任务栈里面只有它一个实例存在。应用场景:呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。建议谨慎使用。

设置Intent的Flag

系统提供了两种方式来设置一个Activity的启动模式,除了在AndroidManifest文件中设置以外,还可以通过IntentFlag来设置一个Activity的启动模式,下面我们简单介绍一些Flag

  • FLAG_ACTIVITY_NEW_TASK

使用一个新的Task来启动一个Activity,但启动的每个Activity都将在一个新的Task中。该Flag通常使用在从Service中启动Activity的场景,由于Service中并不存在Activity栈,所以使用该Flag来创建一个新的Activity栈,并创建新的Activity实例。

  • FLAG_ACTIVITY_SINGLE_TOP

使用singletop模式启动一个Activity,与指定android:launchMode=“singleTop”效果相同。

  • FLAG_ACTIVITY_CLEAR_TOP

使用SingleTask模式来启动一个Activity,与指定android:launchMode=“singleTask”效果相同。

  • FLAG_ACTIVITY_NO_HISTORY

Activity使用这种模式启动Activity,当该Activity启动其他Activity后,该Activity就消失了,不会保留在Activity栈中。

LaunchMode与StartActivityForResult

我们在开发过程中经常会用到StartActivityForResult方法启动一个Activity,然后在onActivityResult()方法中可以接收到上个页面的回传值,但你有可能遇到过拿不到返回值的情况,那有可能是因为Activity的LaunchMode设置为了singleTask。5.0之后,android的LaunchModeStartActivityForResult的关系发生了一些改变。两个Activity,A和B,现在由A页面跳转到B页面,看一下LaunchMode与StartActivityForResult之间的关系:

这是为什么呢?

这是因为ActivityStackSupervisor类中的startActivityUncheckedLocked方法在5.0中进行了修改。在5.0之前,当启动一个Activity时,系统将首先检查Activity的launchMode,如果为A页面设置为SingleInstance或者B页面设置为singleTask或者singleInstance,则会在LaunchFlags中加入FLAG_ACTIVITY_NEW_TASK标志,而如果含有FLAG_ACTIVITY_NEW_TASK标志的话,onActivityResult将会立即接收到一个cancel的信息,而5.0之后这个方法做了修改,修改之后即便启动的页面设置launchMode为singleTask或singleInstance,onActivityResult依旧可以正常工作,也就是说无论设置哪种启动方式,StartActivityForResultonActivityResult()这一组合都是有效的。

所以如果你目前正好基于5.0做相关开发,不要忘了向下兼容,这里有个坑请注意避让。

实际需求示例

现在有个这样的需求,登录页是个选择登录页,里面含有帐号密码登录方式B、手机号登录/注册方式C、微信第三方登录D等几种选择,视为activity A。点击A中的某个button实现activity的跳转,比如B,在B中返回能跳到登录选择页。当B/C/D成功登录时,应跳转到主页面MainAcitivity E,此时点返回键应该是直接跳出应用,而不是又跳转会A/B/C/D页面去。单单是想让B/C/D页面消失,其实很容易,在做activity跳转时,finish当前页面即可。但页面A是没有办法清楚的,解决方案如下:

Intent intent = new Intent(A.this,B.class);intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);

且看看FLAG_ACTIVITY_CLEAR_TASK 的注释:

/**     * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},     * this flag will cause any existing task that would be associated with the     * activity to be cleared before the activity is started.  That is, the activity     * becomes the new root of an otherwise empty task, and any old activities     * are finished.  This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}.     */    public static final int FLAG_ACTIVITY_CLEAR_TASK = 0X00008000;

源码中明确说明如果在startActivity的时候传递FLAG_ACTIVITY_CLEAR_TASK这个标志,那么这个标志将会清除之前所有已经打开的activity.然后将会变成另外一个空栈的root,然后其他的Activitys就都被关闭了.这个方法必须跟着{@link #FLAG_ACTIVITY_NEW_TASK}一起使用.

总结

实际开发过程中如果采用比较合理的Activity启动模式来做好任务栈的管理,可以事半功倍。在launchMode的选择上首先要搞清楚当前的Activity的作用,以及实际使用场景来做出合理选择。

更多相关文章

  1. Android之Handler:实现计时器实例
  2. Android系统的启动时间
  3. Android(安卓)ProGuard实例教程
  4. 采用CakePHP框架为Android应用快速搭建Web Service服务器及API接
  5. 什么时候会启动多个进程
  6. Android(安卓)P zygote 原理分析之app_process
  7. android 6.0 新特性
  8. Delphi/C++ Builder 开发 Android(安卓)程序启动画面简单完美解
  9. Android(安卓)MVVM:Google 为 Android(安卓)又开了一扇窗

随机推荐

  1. guest mode 无法record audio
  2. Android之ComponentCallbacks2
  3. Unable to cleanly close USB accessory
  4. 解决Android(安卓)SDK Manager下载太慢问
  5. Android(安卓)背景虚化实现
  6. Android(安卓)3.1 r1 中文API文档 (121)
  7. Android用于提示等待的ProgressDialog
  8. Android(安卓)简述touch事件中的MotionEv
  9. java android HTTP应用程序设计
  10. android 设置全屏方法1