美文网首页
[笔记]Activity的栈与跳转

[笔记]Activity的栈与跳转

作者: 蓝灰_q | 来源:发表于2017-08-29 22:13 被阅读842次

    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

    相关文章

      网友评论

          本文标题:[笔记]Activity的栈与跳转

          本文链接:https://www.haomeiwen.com/subject/xajedxtx.html