【问题分析】界面点击无响应,非ANR【Android 14】

664 阅读5分钟

微信图片_2024031310500911.jpg

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,也就是说,会走这这段逻辑:

image.png

这里应该是允许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方法的参数列表可知:

image.png

callingUid就是这里的局部变量taskCallingUid,而realCallingUid为传参callingUid。

4.1 realCallingUid

上面分析realCallingUid就是ActivityTaskSupervisor.startActivityFromRecents方法的传参callingUid:

image.png

而ActivityTaskSupervisor.startActivityFromRecents是ActivityTaskManagerService.startActivityFromRecents调用的:

image.png

而该api最初是Launcher那边调用的,因此这里的callingUid就是Launcher的uid。

4.2 callingUid

如上面分析,callingUid就是ActivityTaskSupervisor.startActivityFromRecents方法中的局部变量taskCallingUid,是从Task的mCallingUid成员变量取值的:

taskCallingUid = task.mCallingUid;

而这里的局部变量task则是通过RootWindowContainer.anyTaskForId方法去取的。

image.png

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上也可以复现。