[笔记]Activity的栈与跳转

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。
图片来自Activity启动模式图文详解

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容