美文网首页Android探索之路Android四大组建
探索Activity之启动Intent Flag和taskAff

探索Activity之启动Intent Flag和taskAff

作者: anly_jun | 来源:发表于2016-07-31 23:11 被阅读4016次

    引用上文生命周期launchMode介绍, Activity的生命周期实际上比我们想象的复杂得多.

    本文主要通过实例, 来探索下Activity的启动Intent Flag以及taskAffinity对生命周期和Task/Back Stack的影响. 算是对生命周期launchMode的一个补充, 以便我们在开发过程中灵活组合运用.

    照例, 我们先从一些官方解释开始:

    1, 相关概念

    • 对生命周期和Task/Back Stack有影响的Intent Flag主要有:

      • FLAG_ACTIVITY_NEW_TASK
      • FLAG_ACTIVITY_CLEAR_TOP
      • FLAG_ACTIVITY_SINGLE_TOP
    • FLAG_ACTIVITY_NEW_TASK

      • 会产生与 "singleTask" launchMode 值相同的行为.
      • 在新任务中启动Activity. 如果已有包含该Activity的任务,则该任务会转到前台并恢复其最后状态,同时该Activity会在onNewIntent()中收到新Intent.
    • FLAG_ACTIVITY_SINGLE_TOP

      • 会产生与 "singleTop" launchMode 值相同的行为.
      • 如果正在启动的Activity是当前Activity(位于返回栈的顶部), 则现有实例会接收对 onNewIntent()的调用,而不是创建 Activity 的新实例.
    • FLAG_ACTIVITY_CLEAR_TOP

      • 如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent() 将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例.
      • 如果指定 Activity 的启动模式为 "standard",则该 Activity 也会从堆栈中删除,并在其位置启动一个新实例,以便处理传入的 Intent。 这是因为当启动模式为 "standard" 时,将始终为新 Intent 创建新实例.

    以上为官方文档解释.

    探索Activity之launchMode一文中我们也提到了实际上文档由于"年久失修"没有跟上, 有些解释是不合理的.
    我们可以跟随实例一起看下.

    2, 开始探索

    借用上次探索生命周期的Demo程序.
    Github源码地址

    通过AActivity, BActivity, CActivity这三个Activity之间的跳转来进行intent flag的探索.

    如果没有特别之处, 默认A, B, C三个Activity的launchMode都是默认的standard模式.

    2.1, FLAG_ACTIVITY_NEW_TASK

    2.1.1, 执行B -> A, B启动A时加FLAG_ACTIVITY_NEW_TASK

    实验目的是看下, 在当前系统没有A实例时, 用FLAG_ACTIVITY_NEW_TASK来启动A会不会将A创建在单独的任务中.

    BActivity.java中:

    startActivity(new Intent(BActivity.this, AActivity.class)
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    

    生命周期Log:


    Task/Back Stack信息:

      Stack #1 mStackId=3:
        Task id #35
        * TaskRecord{42b60ae0 #35 A=com.anly.samples U=0 sz=3}
          numActivities=3 rootWasReset=false userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
          affinity=com.anly.samples
          intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
          realActivity=com.anly.samples/.MainActivity
          Activities=[ActivityRecord{4285c1b0 u0 com.anly.samples/.MainActivity t35}, 
          ActivityRecord{42decc00 u0 com.anly.samples/.activity.BActivity t35}, 
          ActivityRecord{4372b9e8 u0 com.anly.samples/.activity.AActivity t35}]
    

    可以看到:
    B以FLAG_ACTIVITY_NEW_TASK启动A, A仍然和B处在同一个Task中.

    2.1.2 执行A -> B -> A, B启动A时加FLAG_ACTIVITY_NEW_TASK

    实验目的是想验证下官方文档对FLAG_ACTIVITY_NEW_TASK的解释, 在A实例已经存在的情况下, 以FLAG_ACTIVITY_NEW_TASK启动A会发生什么.

    生命周期Log:


    Task/Back Stack信息:

      Stack #1 mStackId=2:
        Task id #34
        * TaskRecord{42bfb088 #34 A=com.anly.samples U=0 sz=4}
          numActivities=4 rootWasReset=false userId=0 mTaskType=0 numFullscreen=4 mOnTopOfHome=true
          affinity=com.anly.samples
          intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
          realActivity=com.anly.samples/.MainActivity
          Activities=[ActivityRecord{42568318 u0 com.anly.samples/.MainActivity t34}, 
          ActivityRecord{42725050 u0 com.anly.samples/.activity.AActivity t34}, 
          ActivityRecord{42dab240 u0 com.anly.samples/.activity.BActivity t34}, 
          ActivityRecord{42e293f8 u0 com.anly.samples/.activity.AActivity t34}]
    

    可以看到:
    1, 在B启动A之前, Task #34中本来就有A, 但是B加FLAG_ACTIVITY_NEW_TASK启动A时, A并未重用, 而是在本Task #34中在此创建了一个A的实例. (这点和文档描述不一致)
    2, 此时Task #34中的Activity实例为ABA.
    3, 但是如果A的lunchMode是singleTask的话, 如lunchMode一文2.2.3所示, 此时应该销毁A以上的实例, Task中只剩下A.
    4, 综上, FLAG_ACTIVITY_NEW_TASK并不等同与singleTask. 且FLAG_ACTIVITY_NEW_TASK感觉并为起作用(在A已经存在一个实例的情况下).

    2.2, FLAG_ACTIVITY_SINGLE_TOP

    2.2.1, 执行A -> B -> B, 其中B启动B时加FLAG_ACTIVITY_SINGLE_TOP

    BActivity启动B时加:

    startActivity(new Intent(BActivity.this, BActivity.class)
                            .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP));
    

    生命周期Log:


    Task/Back Stack信息:

      Stack #1 mStackId=6:
        Task id #38
        * TaskRecord{43665a30 #38 A=com.anly.samples U=0 sz=3}
          numActivities=3 rootWasReset=true userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
          affinity=com.anly.samples
          intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.anly.samples/.MainActivity}
          realActivity=com.anly.samples/.MainActivity
          Activities=[ActivityRecord{42bbfea0 u0 com.anly.samples/.MainActivity t38}, 
          ActivityRecord{433b6130 u0 com.anly.samples/.activity.AActivity t38}, 
          ActivityRecord{4324ef18 u0 com.anly.samples/.activity.BActivity t38}]
    

    可以看到:
    1, B复用了, 通过onNewIntent, 走onResume流程, 复用之前的B实例.
    2, 此时Task #38中的Activity实例为AB.

    2.3, FLAG_ACTIVITY_CLEAR_TOP

    2.3.1, 执行A -> B -> A, 启动B启动A时加FLAG_ACTIVITY_CLEAR_TOP

    实验目的是为了看下A会不会重用, 且B会不会被Clear.

    BActivity启动A的代码:

    startActivity(new Intent(BActivity.this, AActivity.class)
                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
    

    生命周期Log:


    Task/Back Stack信息:

     Stack #1 mStackId=7:
        Task id #39
        * TaskRecord{4274e020 #39 A=com.anly.samples U=0 sz=2}
          numActivities=2 rootWasReset=false userId=0 mTaskType=0 numFullscreen=2 mOnTopOfHome=true
          affinity=com.anly.samples
          intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
          realActivity=com.anly.samples/.MainActivity
          Activities=[ActivityRecord{42742598 u0 com.anly.samples/.MainActivity t39}, 
          ActivityRecord{4274eb28 u0 com.anly.samples/.activity.AActivity t39}]
    

    可以看到:
    1, A并没有重用, 而是新建了一个实例. 这个和文档是一致的, 参见FLAG_ACTIVITY_CLEAR_TOP概念第二点.
    2, B被销毁了(Clear Top).
    3, 此时Task #39中只有A(一个新的A).

    2.4, 组合使用

    以上是简单使用, 然后实际场景中会有很多组合使用Intent Flag以及Intent Flag与taskAffinity结合使用的情况. 其中官方文档就提到了:

    FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。

    下面我们来实验下这种组合:

    2.4.1, FLAG_ACTIVITY_CLEAR_TOP + FLAG_ACTIVITY_NEW_TASK

    执行A -> B -> A, 其中B启动A时, Intent flag加FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK

    B启动A的代码:

    startActivity(new Intent(BActivity.this, AActivity.class)
                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK));
    

    生命周期Log:


    Task/Back Stack信息:

      Stack #1 mStackId=8:
        Task id #40
        * TaskRecord{429c96b0 #40 A=com.anly.samples U=0 sz=2}
          numActivities=2 rootWasReset=false userId=0 mTaskType=0 numFullscreen=2 mOnTopOfHome=true
          affinity=com.anly.samples
          intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
          realActivity=com.anly.samples/.MainActivity
          Activities=[ActivityRecord{427907d0 u0 com.anly.samples/.MainActivity t40}, 
          ActivityRecord{42dd24b8 u0 com.anly.samples/.activity.AActivity t40}]
    

    可以看到:
    1, 结果和2.3.1单独使用Intent.FLAG_ACTIVITY_CLEAR_TOP是一样的.

    2.5, taskAffinity补充实验

    有2.1.1, 2.1.2以及2.4.1这三个包含Intent.FLAG_ACTIVITY_NEW_TASK的实验, 可以看到, 字面上Intent.FLAG_ACTIVITY_NEW_TASK的意思是在新的task启动Activity, 然而事实上, 新Activity还是在原来的task上创建的.

    这里有必要提出官网关于taskAffinity的解释了:

    taskAffinity指示Activity优先属于哪个task. 默认情况下, 同一应用中的所有 Activity 彼此关联. 因此, 默认情况下, 同一应用中的所有 Activity 优先位于相同任务中.
    taskAffinity在两种情况下会起作用:
    --- 启动 Activity 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志.
    --- Activity 将其 allowTaskReparenting 属性设置为 "true".

    让我们来结合taskAffinity做下实验:

    2.5.1, taskAffinity + FLAG_ACTIVITY_NEW_TASK

    设置A和B不同的taskAffinity, 执行Main -> A -> B -> A -> B -> A, 其中B启动A使用FLAG_ACTIVITY_NEW_TASK

    为什么要执行两次B -> A? 我们跟随实验结果, 稍后来看.

    设置A的taskAffinity为com.anly.aactivity, B默认(包名).

    <activity
      android:name=".activity.AActivity"
      android:label="A-Activity"
      android:taskAffinity="com.anly.aactivity"
      >
    </activity>
    
    <activity
      android:name=".activity.BActivity"
      android:label="B-Activity"
      >
    </activity>
    

    B启动A:

    startActivity(new Intent(BActivity.this, AActivity.class)
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    

    生命周期Log:


    Task/Back Stack信息:

    Stack #1 mStackId=10:
        Task id #44
        * TaskRecord{43085768 #44 A=com.anly.aactivity U=0 sz=2}
          numActivities=2 rootWasReset=false userId=0 mTaskType=0 numFullscreen=2 mOnTopOfHome=false
          affinity=com.anly.aactivity
          intent={flg=0x10000000 cmp=com.anly.samples/.activity.AActivity}
          realActivity=com.anly.samples/.activity.AActivity
          Activities=[ActivityRecord{4303fe00 u0 com.anly.samples/.activity.AActivity t44}, ActivityRecord{4324bb10 u0 com.anly.samples/.activity.BActivity t44}]
          
        Task id #43
        * TaskRecord{426d0a78 #43 A=com.anly.samples U=0 sz=3}
          numActivities=3 rootWasReset=true userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
          affinity=com.anly.samples
          intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.anly.samples/.MainActivity}
          realActivity=com.anly.samples/.MainActivity
          Activities=[ActivityRecord{4256ae80 u0 com.anly.samples/.MainActivity t43}, 
          ActivityRecord{4372db08 u0 com.anly.samples/.activity.AActivity t43}, 
          ActivityRecord{4273f478 u0 com.anly.samples/.activity.BActivity t43}]
    

    可以看到:
    1, Stack #1中有两个Task, #43(affinity=com.anly.samples)和#44(affinity=com.anly.aactivity).
    2, 第一轮Main -> A -> B时, 再Task #43中产生了Main,A,B三个Activity实例.
    3, 接着B -> A时, A在一个新的Task #44中创建了新的A实例.
    4, 然后A -> B, 因为B不加任何参数(启动模式, affinity, flag等), B会创建在启动他的Activity也就是A所在的Task.
    5, 此时Task #44中就有了A, B.
    6, 再次在B中点击启动A(携带Intent.FLAG_ACTIVITY_NEW_TASK)时. 并没有任何反应.

    为什么会出现第6点描述的这样的问题呢?
    我理解:
    此时B启动A, 因为携带Intent.FLAG_ACTIVITY_NEW_TASK, 且A的taskAffnity为"com.anly.aactivity". 系统会在affinity=com.anly.aactivity的Task中找有没有已经存在的A的实例, 发现Task #44中有. 于是乎, 想重用A. 然而并没有能销毁B, 让A弹出来接收新的Intent.
    所以说, 这种情况下, Intent.FLAG_ACTIVITY_NEW_TASK必须结合Intent.FLAG_ACTIVITY_CLEAR_TOP来一起用.

    让我们再做个实验验证下想法.

    2.5.1, taskAffinity + FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP

    设置A和B不同的taskAffinity, 执行Main -> A -> B -> A -> B -> A, 其中B启动A使用FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP

    A的affinity还是设置成com.anly.aactivity, B默认.

    B启动A的代码:

    startActivity(new Intent(BActivity.this, AActivity.class)
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP));
    

    生命周期Log:


    Task/Back Stack信息:

     Stack #1 mStackId=11:
        Task id #46
        * TaskRecord{4338bc08 #46 A=com.anly.aactivity U=0 sz=1}
          numActivities=1 rootWasReset=false userId=0 mTaskType=0 numFullscreen=1 mOnTopOfHome=false
          affinity=com.anly.aactivity
          intent={flg=0x14000000 cmp=com.anly.samples/.activity.AActivity}
          realActivity=com.anly.samples/.activity.AActivity
          Activities=[ActivityRecord{42d88890 u0 com.anly.samples/.activity.AActivity t46}]
    
        Task id #45
        * TaskRecord{42eee4d0 #45 A=com.anly.samples U=0 sz=3}
          numActivities=3 rootWasReset=false userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
          affinity=com.anly.samples
          intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
          realActivity=com.anly.samples/.MainActivity
          Activities=[ActivityRecord{42ed9690 u0 com.anly.samples/.MainActivity t45}, ActivityRecord{42e507b8 u0 com.anly.samples/.activity.AActivity t45}, ActivityRecord{42714cd0 u0 com.anly.samples/.activity.BActivity t45}]
    

    可以看到:
    果然, 此时第二次B -> A, 有效果了, 跳转到A了.
    然而, 我们发现, 虽然Task #46中只有一个A(B被clear top,销毁了). 然而A并不是重用的, 而是先销毁了然来的A实例, 重建了一个A实例.
    参见相关概念Intent.FLAG_ACTIVITY_CLEAR_TOP的第二点解释, 的确是这样, 因为A是默认的standard模式, 所以必须新创建实例.

    3, 结论

    至此, 我们关于Intent flag和taskAffinity的实验结束, 我们来看下相关结论:

    1. FLAG_ACTIVITY_NEW_TASK并不像官方文档所说的等同与singleTask.

    2. 在没有任何其他flag组合和taskAffinity设置的情况下, 同一应用内FLAG_ACTIVITY_NEW_TASK启动另外一个Activity, 不会在新的Task中创建实例, 也不会有实例复用.

    3. FLAG_ACTIVITY_SINGLE_TOP作用等同与singleTop, 当Task的top Activity是该Activity时, Activity复用.

    4. FLAG_ACTIVITY_CLEAR_TOP会clear top, 也就是说如果Task中有ABCD, 在D中启动B, 会clear掉B以上的CD. CD销毁.

    5. 注意, FLAG_ACTIVITY_CLEAR_TOP并不意味着重用, 默认Activity为standard模式的话, 只是会clear其top的其他Activity实例, 该Activity并不会重用, 而是也会销毁, 然后创建一个新的该Activity实例来响应此Intent.

    6. 在没有设置taskAffinity的情况下, 同一应用内FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP组合启动另外一个Activity, 作用和单独使用FLAG_ACTIVITY_CLEAR_TOP是一样.(此点类同与第二点)

    7. 如taskAffinity解释的一样, 在我们没有引入taskAffinity的2.1, 2.2, 2.3, 2.4的相关实验中, 同一个应用中, 使用各种Intent flag都并不会创建新的Task.

    8. taskAffinity需结合FLAG_ACTIVITY_NEW_TASK使用, 此时会再新的Task中寻找/创建待启动的Activity实例.

    9. 强烈建议FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP结合使用, 单独使用FLAG_ACTIVITY_NEW_TASK可能会遇到2.5.1那样的问题.

    10. Intent Flag并不能代替launchMode, 至少在想重用Activity的情况下, 你需要做的是考虑launchMode而非Intent Flag.

    11. 个人理解, Intent Flag更多是倾向于用来做Task中的Activity组织. 而launchMode兼顾Task组织和Activity实例的重用.

    相关文章

      网友评论

      • 真的放飞自我:FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP = singleTask
        jjlanbupt:楼上可能理解有误,FLAG_ACTIVITY_SINGLE_TOP + FLAG_ACTIVITY_CLEAR_TOP能相当于singTask的效果,FLAG_ACTIVITY_NEW_TASK+ FLAG_ACTIVITY_CLEAR_TOP 时onNewInntent方法并不会调用,不能等效于singleTask
      • 坚持编程_lyz:生命周期log怎么打印出来的
      • ecc5a1ef8b1b:clear_top和single_top两个flag联合使用可以重用activity啊
      • 4da100780a44:手动赞
        anly_jun:@KindJoker 谢谢捧场😊

      本文标题:探索Activity之启动Intent Flag和taskAff

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