1. 问题描述
用户操作出的点击Message界面无任何响应的问题,但是也不发生ANR。
2. log分析
与Launcher的同事沟通后,继续分析startActivityFromRecents为什么没有把Message启动起来。
查看SystemLog,发现Launcher调用startActivityFromRecents方法启动Message后,紧接着就是以下log:
是后台Activitiy启动的策略限制了本次启动:"BAL_BLOCK",但是不太清楚的是为什么,这是第一个疑问点。
另外这一次启动Message用的Intent为:
intent: Intent { flg=0x1010c000 cmp=com.google.android.apps.messaging/.main.MainActivity (has extras) };
原因是之前启动MainActivity用的就是这个Intent,这次是直接复用了。
而上一次启动MainActivity是在:
01-25 00:08:08.497936 1454 1636 I ActivityTaskManager: START u0 {flg=0x1010c000 cmp=com.google.android.apps.messaging/.main.MainActivity (has extras)} with LAUNCH_MULTIPLE from uid 10149 (realCallingUid=10081) (BAL_ALLOW_PENDING_INTENT) result code=0
这次应该是在Launcher的Recents启动的,因此Intent也是复用的。
继续往前排查,能从log中找到的三次启动Message的情况都是已经出现问题了:
因此从目前的log中无法得知首次是如何启动"com.google.android.apps.messaging/.main.MainActivity"的,我们本地在Launcher界面点击Message图标,启动的是"com.google.android.apps.messaging.ui.ConversationListActivity",这是第二个疑问点。
总结一下,问题中总共有两个疑问点,如果要复现这个问题,需要弄清这两个疑问点:
1、后台BAL策略为何限制了Message的启动,返回了BAL_BLOCK。
2、"com.google.android.apps.messaging/.main.MainActivity"是如何启动的,我们本地在Launcher界面点击Message图标,启动的是"com.google.android.apps.messaging.ui.ConversationListActivity"。
3. BAL_BLOCK分析
还是先看log:
这些log是在BackgroundActivityStartController.checkBackgroundActivityStart方法中被打印的。
看BackgroundActivityStartController.checkBackgroundActivityStart方法代码:
从log上知道,这里传入的originatingPendingIntent为null,那么局部变量useCallingUidState为true,也就是说,会走这这段逻辑:
这里应该是允许Home App能够启动Activity的,但是实际上我们却没有在这里return,而是在该方法的最后return了BAL_BLOCK,原因则可能是因为callingPackage(callingUid)和realCallingPackage(realCallingUid)不一致:
虽然真正的启动Message的是"com.tcl.android.launcher",但是这里的calingPackage用的是" com.google.android.apps.messaging",导致没有办法在这里return。
4. callingPackage和realCallingPackage
再看下callingPackage和realCallingPackage都是在哪里赋值的。
由于此流程是通过Launcher调用startActivityFromRecents开始的,因此我们可以知道具体的调用堆栈为:
ActivityTaskManagerService.startActivityFromRecents
-> ActivityTaskSupervisor.startActivityFromRecents
-> ActivityStarterController.startActivityInPackage
-> ActivityStarter.execute
-> ActivityStarter.executeRequest
-> BackgroundActivityStartController.checkBackgroundActivityStart
最后发现关键点就在ActivityTaskManagerService.startActivityFromRecents:
这里分析callingUid和realCallingUid可能要更简单一点,和callingPackage和realCallingPackage是一样的。
根据这里最后调用的ActivityStartController.startActivityInPackage方法的参数列表可知:
callingUid就是这里的局部变量taskCallingUid,而realCallingUid为传参callingUid。
4.1 realCallingUid
上面分析realCallingUid就是ActivityTaskSupervisor.startActivityFromRecents方法的传参callingUid:
而ActivityTaskSupervisor.startActivityFromRecents是ActivityTaskManagerService.startActivityFromRecents调用的:
而该api最初是Launcher那边调用的,因此这里的callingUid就是Launcher的uid。
4.2 callingUid
如上面分析,callingUid就是ActivityTaskSupervisor.startActivityFromRecents方法中的局部变量taskCallingUid,是从Task的mCallingUid成员变量取值的:
taskCallingUid = task.mCallingUid;
而这里的局部变量task则是通过RootWindowContainer.anyTaskForId方法去取的。
RootWindowContainer.anyTaskForId方法根据传入的taskId,从两个地方去取符合该taskId的Task:
1)、如果能从现有的Task中找到一个符合条件的,就返回这个Task。
2)、如果现有的Task都不符合条件,则从历史Task,即RecentTasks中去找。
即这个Task是之前启动过的(不管现在是否还存在),因此如果我们想要让取得的这个Task的mCallingUid是"com.google.android.apps.messaging",那么就需要让这个Task创建的时候是由Message自身启动的。
如果从Launcher点击Message图标启动Message,那taskCallingUid就是"com.tcl.android.launcher":
这边尝试了多种启动Message的方式,发现通过接收到短信后点击Notification的方式启动Message,可以实现Message的Task.mCallingUid对应为Message:
那么实现callingPackage(callingUid)和realCallingPackage(realCallingUid)不一致的方法可以是:
1)、通过点击Notification的方式启动Message,Task.mCallingUid对应为Message。
2)、点击Back键将Message销毁,然后通过Launcher启动Message,并且需要让Launcher以复用intent的方式去启动Message,比如从Recents界面启动。
如此一来,就可以实现callingPackage(callingUid)和realCallingPackage(realCallingUid)不一致,从而在启动Message的流程中就可以返回BAL_BLOCK了。
这里解决了我们在第一节提出的一疑问1:"后台BAL策略为何限制了Message的启动,返回了BAL_BLOCK。"
不过这种方式启动的是"com.google.android.apps.messaging.ui.ConversationListActivity",不是我们想要的"com.google.android.apps.messaging/.main.MainActivity",也即疑问二:"com.google.android.apps.messaging/.main.MainActivity"是如何启动的。
这里直接放结论吧,通过本地各种实验,发现如果通过Phone向联系人发送短信的方式,跳转到Message,可以将"com.google.android.apps.messaging/.main.MainActivity"启动起来。
最后还有一点要注意:
调用RootWindowContainer.anyTaskForId根据传入的taskId寻找Task的时候,不能从RootWindowContainer中找到一个现有的Task,而要从RecentTasks中找历史Task(曾经被创建,后面从RootWindowContainer中被remove了)。如果该Task有一个RootActivity,那么就不会在最后调用ActivityStarterController.startActivityInPackage去走startActivity的流程:
而是直接把该Task移动到前台,然后返回ActivityManager.START_TASK_TO_FRONT:
因此为了复现问题的场景,我们需要保证走到这里的时候,Message对应的Task是已经被移除了,也就是说是从RecentTasks中拿到了Message的Task。所以即使为了实现这一步,我们也需要启动"com.google.android.apps.messaging/.main.MainActivity",该Activity会在接收到Back事件的时候去finish,然后整个Task才会被remove掉,后续我们就可以从RecentTasks中找回来。而如果启动的是"com.google.android.apps.messaging.ui.ConversationListActivity",那么由于该Activity接收到Back事件不会走finish,因此这里就是从现有的Task中拿Message的Task了,无法复现到问题。
5. 复现问题
那么最终复现该问题的稳定步骤为(导航模式为手势):
1)、将Message对应的Task从Recents清除(清除之前的影响)。
2)、接受到短信,然后点击Notification跳转到短信(Message自身启动自己的Task)。
3)、上滑回到Launcher,然后进入Phone,通过点击"Send a message"的方式跳转到Message(启动"com.google.android.apps.messaging/.main.MainActivity")。
4)、回到Message后通过侧滑再次回到Phone(类似于点击Back,回到Phone的同时,Message的Activity和Task也被移除)。
5)、通过在底部左滑的方式切换到Message ------ KO,发现界面无法点击(laucnher调用startActivityFromRecents的api,并且复用之前Message启动自身的时候的Intent)。
经过以上分析可知该问题为google原生问题,同样在pixel上也可以复现。