Activity是Android四大组件中用来显示界面和操作互动的,一般来说,我们手机上都会打开多个App,每个App又有多个Activity,在用户看来,这些Activity的创建、回退、跳转、复用等,都是用户体验,甚至是业务逻辑的一部分,所以Android提供了完备的管理机制。
先来梳理一下基础知识。
Process、AMS、ActivityStack、ActivityTask
- Process,系统为App提供的进程(Process)资源,默认一个App一个Process,我们也可以要求为一个App提供多个Process。
- AMS,系统开机启动时,会启动ActivityManagerService(简称AMS)来管理四大组件以及Intent、pendingintent、apk进程、task和释放组件内存等。AMS是一个独立的进程(SystemServie、AMS、PMS、WMS等系统服务,实质都是一个个App,各自拥有自己的进程),系统只有一个AMS,而且因为AMS在独立的进程上,所以AMS和App之间,需要通过IPC机制进行跨进程通信。
- ActivityStack ,AMS为了管理Activity实例,会建立并管理一个ActivityStack,用ActivityStackSupervisor来更新这个栈,因为系统只有一个AMS,所以ActivityStack也只有一个。ActivityStack是一个管理者的角色,会管理协调ActivityRecord和TaskRecord,以及所有的任务栈。
- ActivityTask,系统中的所有Activity,在面向用户时可能分成多个组,这是按照任务栈(Task)来组织的,我们常说的Activity启动模式,其实是操作任务栈(Task),而不是操作Activity栈(Stack)。
组织形式大概是这样的:
(我们可以用gethashcode()获取Activity实例的唯一标识、用gettaskaffinity()获取Activity所在的任务栈,用adb shell dumpsys activity命令可以列出所有的Activity)
可见,Activity管理的核心在于AMS,AMS中分两个维度来管理Activity,一个是ActivityStack,只有一个,用ActivityRecord来记录对应的Activity信息,主要用来管理系统中有哪些Activity;另一个是ActivityTask,可能有多个,用TaskRecord来记录对应的Activity信息,主要用来把Activity组合成为多个Task栈,以便按照特定的业务逻辑展示给用户。
所以,要管理多个Activity之间的关系,主要依靠任务栈Task。
任务栈(Task)
长按Home键(有的设备是长按菜单键),那些最近打开的App记录,就是任务栈Task。Task任务栈也叫BackStack回退栈,是后进先出栈,在一个Task栈里一直按Back键,就会不断回退到上一个Activity(包括其他App的Activity),直到回退至Home,所以,用户感知到的Activity界面回退逻辑,是由Task栈决定的,管理Activity的回退跳转逻辑,其实就是管理Task任务栈。
Task不止一个,我们有可能会把现有的Task整体移动到后台,然后创建一个新的Task,点开Notification、点击Home键(或在Activity中调用moveTaskToBack(true),false值表示当前Activity必须在栈底)、App创建NewTask等行为,都可能把Task移动到后台,创建新的Task。
对Task任务栈的管理,主要包括Activity启动模式和IntentFlag创建方式两部分。
Activity的四种启动模式
启动Activity的重点在于,如何复用已经存在的Activity实例,所以四种启动模式实际上是四种复用模式,针对复用策略,Android中定义了四种启动模式(android:launchMode):
- "standard",默认,不复用,每次创建新实例。
- "singleTop",栈顶复用,检查栈顶,如果不是要启动的Activity,创建新实例;如果是,就复用。
- "singleTask",栈中复用,检查任务栈,如果没有要启动的Activity,创建新实例;如果有,就把该Activity上方的其他Activity全部推出任务栈,复用该Activity。
- "singleInstance",单例模式,独享一个任务栈,且只有一个任务栈,只有
一个Activity实例。
这四种启动模式可以在manifest文件中定义,也可以在intent中用代码动态赋值(例如添加FLAG_ACTIVITY_SINGLE_TOP标识,等同于设置singleTop属性)。
这四种模式中,standard和singleTop类型的Activity可能出现多个实例,singleTask和singleInstance类型则只能有一个实例。
如果要使用startActivityForResult,在5.0以前还有使用限制,只允许standard<->singleTop、singleTask->standard、singleTask->singleTop,其他跳转因为无法确保回到原Activity,可能会遇到RESULT_CANCELD问题导致无法接收返回值。
如果Activity被复用了,就会回调onNewIntent()函数。
正常启动的Activity生命周期是onCreate()-->onStart()-->onResume()...。
复用的Activity生命周期是onNewIntent()-->onResart()-->onStart()--> onResume()...
一些常见的IntentFlag
除了上一节的四种启动模式之外,我们还可以在代码中设置intentFlag,用更丰富的形式来定义Activity的创建和复用策略。
intentFlag有时候需要和taskAffinity属性(android:taskAffinity)配合使用,taskAffinity指的是Activity的任务亲和性,即Activity与哪个Task更亲和,默认值是应用的包名,taskAffinity属性在2种情况下起作用:启动 activity的Intent对象添加了FLAG_ACTIVITY_NEW_TASK标记,或activity的allowTaskReparenting属性为true。
一些常见的IntentFlag如下:
- FLAG_ACTIVITY_NEW_TASK (默认)
其实这种标识下,系统会根据Activity的taskAffinity去创建一个新的Task任务栈,并向任务栈中压入一个新创建的Activity实例。
不过,一般情况下App中的taskAffinity都是同一个默认值(包名),这个Task已经存在了,这样的话,运行效果就相当于standard启动模式,在已有的Task中压入一个新建的Activity。
还需要说明的是,如果要用startActivityForResult的形式来调起新的Activity并等待返回结果,就不能使用FLAG_ACTIVITY_NEW_TASK标记,否则调用方的Activity可能会立即收到onActivityResult,实际上就是回调失效了。这是因为使用startActivityForResult时,两个Activity需要在同一个Task里,Android认为不同Task之间的Activity是不能传递数据的。 - FLAG_ACTIVITY_SINGLE_TOP
相当于singleTop启动模式。 - FLAG_ACTIVITY_CLEAR_TOP
模式情况下,会把目标Activity顶部的所有Activity都销毁,同时连同目标Activity也销毁,然后重建目标Activity。
如果和FLAG_ACTIVITY_SINGLE_TOP一起使用,即:
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
就会复用目标Activity,这时相当于singleTask启动模式(CLEAR_TOP这个名字也揭示了singleTask的实质)。
FLAG_ACTIVITY_CLEAR_TOP往往和FLAG_ACTIVITY_NEW_TASK一起使用。用2个标记可以定位已存在的activity并让它处于可以响应intent的位置。
- FLAG_ACTIVITY_CLEAR_TASK
清空目标TASK,把新建的Activity作为栈底的Activity,必须与FLAG_ACTIVITY_NEW_TASK一起使用。 - FLAG_ACTIVITY_REORDER_TO_FRONT
如果Task中已经有实例,把该实例挪到栈顶。(优先级低于FLAG_ACTIVITY_CLEAR_TOP)。 - FLAG_ACTIVITY_BROUGHT_TO_FRONT
这个标志一般不是由程序代码设置的,而是在launchMode中设置singleTask模式时系统帮你设定。 - FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
任务重置,在Activity导致新建Task或挪动到Task顶部时使用。 - FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
为Task设置一个还原点,当Task恢复到前台时,找到有这个属性的Activity,把这个Activity顶部的其他Activity,连同这个Activity一起销毁。 - FLAG_ACTIVITY_MULTIPLE_TASK
Android系统为了节省资源,默认是复用Activity的,在打开Activity时,总是先搜索存在的task栈,去寻找匹配intent的一个activity。
但是,如果使用了FLAG_ACTIVITY_MULTIPLE_TASK标识(和FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK一起使用),就会跳过复用搜索步骤,直接创建一个新的task栈,并且在里面启动新的activity。 - FLAG_ACTIVITY_NO_USER_ACTION,用户行为标识,用来区分离开当前Activity是用户行为还是系统行为(比如呼入电话,会导致当前Activity退至后台,但这不是用户行为),如果不是用户行为,就不会执行生命周期中的onUserLeaveHint(),这样我们可以知道用户有没有做某些操作(比如是否看到了推送的通知)。
- FLAG_ACTIVITY_NO_ANIMATION
Activity切换时不展示动画。
其他一些与栈相关的Activity属性:
- android:taskAffinity
亲和性,在intentFlag标识中和FLAG_ACTIVITY_NEW_TASK一起作用;或者与android:allowTaskReparenting共同起作用。 - android:allowTaskReparenting
允许重排任务栈,就是说Activity可以更换所在的Task任务栈,Activity可能在TaskA中,此时系统把一个TaskB移动到前台,如果TaskA和TaskB的affinity相同,那么Activity就会被挪到TaskB。 - android:excludeFromRecents
Activity不添加到最近打开的应用列表中。 - android:alwaysRetainTaskState
保存Task栈中的Activity,当Task被移动到后台后,如果长期没有使用,Android可能会去回收内存,仅保留栈底的Activity,其他Activity全部回收掉,如果alwaysRetainTaskState属性为true,系统就不会进行回收。 - android:clearTaskOnLaunch
催促回收Task栈(与alwaysRetainTaskState相反),这个属性会被赋给栈底的Activity,一旦用户离开Task,系统就会立即回收栈中的Activity,仅保留栈底的Activity。 - android:finishOnTaskLaunch
催促回收Activity,被赋予这个属性的Activity,一旦用户退出Task,Activity就会立即被系统回收,Task中不再保留这个Activity,如果恰好是栈底Activity,那么Task也就清空了。 - android:noHistory
清空历史,离开Activity后,立即回收Activity,Task任务栈中不保存该Activity。noHistory和finishOnTaskLaunch的区别在于销毁Activity的时机,noHistory是在离开Activity时销毁,finishOnTaskLaunch是在切换Task时销毁。
Scheme跳转(页面内跳转)
我们知道,Activity跳转是通过Intent来实现的,在定义Intent时,有三种形式:
- 类名
定义intent时设置目标Activity的类名
intent.setClass(context,ActivityA.class);
setClass其实就是setComponent,相当于
intent.setComponent(new ComponentName(context, ActivityA.class)); - Action
需要在manifest中定义action
<intent-filter> <action android:name="your.action.name" />
定义intent时设置Action
intent.setAction(your.action.name); - Scheme
需要在manifest中定义scheme
定义intent时设置url
new Intent(Intent.ACTION_VIEW, Uri.parse(Url) )
在这三种形式中,Scheme是最灵活,适用最广的,因为只需要一个url,就可以实现跳转。
Scheme是一种页面内跳转协议,在Android和IOS都有使用,Activity向系统注册URL Scheme,就可以通过特定的URL,直接打开App中的某些页面,常用于支持以下几种场景:
- 服务器下发跳转路径,客户端根据服务器下发跳转路径,创建Intent,(new Intent(Intent.ACTION_VIEW, Uri.parse(Url) ) ) ,实现跳转;
- Web页面点击,根据具体跳转路径判断,如果(url.indexOf(H5Constant.SCHEME) != -1),就去创建Intent,(Intent intent = new Intent(Intent.ACTION_VIEW, uri) ) ,实现跳转;
- App端收到服务器端下发的通知,从消息中解析NotifeConstant.VALID_ACTION_URL得到URL,然后创建Intent,(Intent intent =H5Constant.buildSchemeFromUrl(Url)),实现跳转。
Scheme协议中,URL的格式为:
[scheme:][//domain][:port][path][?query params][#fragment]
其中,scheme、domain、path都需要在manifest中定义。
<activity android:name=".TargetActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<!--允许从浏览器打开App-->
<category android:name="android.intent.category.BROWSABLE"/>
<!--可以定义多组scheme,多个URL指向同一个Activity-->
<data android:scheme="biz1"
android:host="com.buy"
android:path="/path"/>
<data android:scheme="biz2"
android:host="com.earn"
android:path="/route"/>
</intent-filter>
</activity>
被启动的Activity可以从Intent中找到URL中的值
Intent intent = getIntent();
String scheme = intent.getScheme();
Uri uri = intent.getData();
String tab= uri.getQueryParameter("your param");
引用
Activity启动模式图文详解:standard, singleTop, singleTask 以及 singleInstance
Android中Activity四种启动模式和taskAffinity属性详解
android深入解析Activity的launchMode启动模式,Intent Flag,taskAffinity
一次搞定Process和Task