明修"栈"道——越过Android启动栈陷阱( 三 )

明修"栈"道——越过Android启动栈陷阱

文章插图
 
预想的结果是:与场景0一致 , 会跳到B-2所在Task的已有顶层B-3 。
实际的结果是:在已有的Task2中 , 产生了一个新的B-2实例 。
仅仅是改变了一下重新跳转B-2的方式 , 效果就完全不一样了!这与官方文档中提到该flag与"singleTask" launchMode值产生的行为并不一致!
【场景3】把场景1再做修改:这次C-4不跳栈底的B-2 , 改为跳转B-3 , 且还是通过action方式 。
// C-4跳转B-3public static void jumpTo_B_3_ByAction_New(Context context) {Intent intent = new Intent();intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3");intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(intent);}如下图 , A-1设置NEW_TASK跳转B-2 , 再跳转B-3 , 再设置NEW_TASK跳转C-4 , 最终设置NEW_TASK想跳转B-3 。
 
明修"栈"道——越过Android启动栈陷阱

文章插图
 
预想的结果是:与场景0一致 , 会跳到B-2所在Task的顶层B-3 。
实际的结果是:在已有的Task2中 , 产生了一个新的B-3实例 。
不是说好的 , Activity已经存在时 , 展示其所在Task的最新状态吗?明明Task2中已经有了B-3 , 并没有直接展示它 , 而是生成了新的B-3实例 。
【场景4】既然Activity没有被复用 , 那Task一定会被复用吗?把场景3稍做修改 , 直接给B-3指定一个单独的affinity 。
<activityandroid:name=".Activity3"android:exported="true"android:taskAffinity="b3.task"><!--指定了亲和性标识--><intent-filter><action android:name="com.zkp.task.ACTION_TO_B_PAGE3" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></activity>【明修"栈"道——越过Android启动栈陷阱】如下图 , A-1设置NEW_TASK跳转B-2 , 再跳转B-3 , 再设置NEW_TASK跳转C-4 , 最终设置NEW_TASK想跳转B-3 。
 
明修&quot;栈&quot;道——越过Android启动栈陷阱

文章插图
 
——这次 , 连Task也不会再被复用了……Activity3在一个新的栈中被实例化了 。
再回看官方的注释 , 就会显得非常不准确 , 甚至会让开发者对该部分的认知产生严重错误!稍微改变过程中的某个毫无关联的属性(如跳转目标、跳转方式……) , 就会产生很大差异 。
在看flag相关注释时 , 我们要树立一个意识:Task和Activity跳转的实际效果 , 是launchMode、taskAffinity、跳转方式、Activity在Task中的层级等属性综合作用的结果 , 不要相信“一面之词” 。
回到问题本身 , 究竟是哪些原因造就了上面的不同效果呢?只有源码最值得信赖了 。
三、场景分析与源码探索本文以Android 12.0源码为基础 , 进行探究 。上述场景在不同Android版本上的表现是一致的 。
3.1 源码调试注意事项源码的调试方法 , 许多文章已经有了详细的教学 , 本文不再赘述 。此处只简单总结其中需要注意的事项:
  1. 下载模拟器时 , 不要使用google Play版本 , 该版本类似user版本 , 无法选择system_process进程进行断点 。
  2. 即使是Google官方模拟器和源码 , 在断点时 , 也会有行数严重不对应的情况(比如:模拟器实际会运行到方法A , 但在源码中打断点时 , 实际不能定位到方法A的对应行数) , 该问题并没有很好的处理方法 , 只能尽量规避 , 如使模拟器版本与源码版本保持一致、多打一些断点增加关键行数被定位到的几率 。
3.2 初步断点 , 明确启动结果以【场景0】为例 , 我们初步确认一下 , 为什么B-3跳转B-2会无反应 , 系统是否告知了原因 。
3.2.1 明确启动结果及其来源
在Android源码的断点调试中 , 常见的有两类进程:应用进程和system_process进程 。
在应用进程中 , 我们能获取到应用启动结果的状态码result , 这个result用来告诉我们启动是否成功 。涉及堆栈如下图(标记1)所示:


推荐阅读