Android ANR触发机制之Service ANR

一、前言

    在Service组件StartService()方式启动流程分析文章中,针对Context#startService()启动Service流程分析了源码,其实关于Service启动还有一个比较重要的点是Service启动的ANR,因为因为线上出现了上百例的"executing service " + service.shortName的异常。

二、Service-ANR原理

2.1 Service启动ANR原理简述

    Service的ANR触发原理,是在启动Service前使用Handler发送一个延时的Message(埋炸弹过程),然后在Service启动完成后remove掉这个Message(拆炸弹过程)。如果在指定的延迟时间内没有remove掉这个Message,那么就会触发ANR(没有在炸弹爆炸前拆掉就会爆炸),弹出AppNotResponding的弹窗。
Android-应用程序无响应
    其实这个机制跟Windows/MacOS的应用程序无响应,是类似的交互设计。
Windows-应用程序无响应
Mac-应用程序无响应

2.2 前台Service VS 后台Service的区别

2.2.1 前台Service

    前台Service是一种在通知栏中显示持续通知的服务,它通常用于执行用户明确知晓的任务,比如音乐播放器、定位服务等。前台Service在系统内部被视为用户正在主动使用的组件,因此它具有更高的优先级和较低的系统资源限制。在使用前台Service时,必须在通知栏中显示一个通知,以告知用户有一个正在运行的Service,并且通常还应该提供一些与该Service相关的有用信息。

2.2.3 后台Service

    后台Service是一种不会在通知栏中显示通知的服务。它用于执行一些不需要用户直接交互或注意的任务,例如数据同步、网络请求等。后台Service具有较低的系统优先级,系统可能会在资源紧张的情况下终止这些服务,以释放资源。

2.3 Service启动ANR源码执行过程

    ps: 还是基于Android SDK28源码分析
    基于文章:Service组件StartService()方式启动流程分析的总结,我们已经很清楚,通过startService的方式启动Service的源码过程。因此,本文直接从com.android.server.am.ActiveServices#bringUpServiceLocked方法的源码开始分析,如有不清楚前置的启动流程的同学,可以参考我之前的文章,然后打开AS对照看下这部分的代码。

2.3.1 ActiveServices#bringUpServiceLocked

    private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,boolean whileRestarting, boolean permissionsReviewRequired)throws TransactionTooLargeException {if (r.app != null && r.app.thread != null) {sendServiceArgsLocked(r, execInFg, false);return null;}realStartServiceLocked(r, app, execInFg);}

2.3.2 ActiveServices#realStartServiceLocked

private final void realStartServiceLocked(ServiceRecord r,ProcessRecord app, boolean execInFg) throws RemoteException {// ...// 会转调到scheduleServiceTimeoutLocked(r.app)方法进行埋炸弹操作bumpServiceExecutingLocked(r, execInFg, "create");try {app.thread.scheduleCreateService(r, r.serviceInfo,mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),app.repProcState);} catch (DeadObjectException e) {mAm.appDiedLocked(app);throw e;} finally {// serviceDoneExecutingLocked方法内会拆除炸弹serviceDoneExecutingLocked(r, inDestroying, inDestroying);// ...}
}

2.3.3 埋炸弹过程:ActiveServices#bumpServiceExecutingLocked

private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {// ...scheduleServiceTimeoutLocked(r.app);// ...
}
  • com.android.server.am.ActiveServices#scheduleServiceTimeoutLocked
void scheduleServiceTimeoutLocked(ProcessRecord proc) {if (proc.executingServices.size() == 0 || proc.thread == null) {return;}Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);msg.obj = proc;mAm.mHandler.sendMessageDelayed(msg,proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
  • 这里需要知道,炸弹的爆炸时间在ActiveServices中定义了三个:
// 前台Service的超时时间是20s
static final int SERVICE_TIMEOUT = 20*1000;
// 后台Service的超时时间是200s,是前台超时时间的10倍
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
// How long the startForegroundService() grace period is to get around to
// calling startForeground() before we ANR + stop it.
static final int SERVICE_START_FOREGROUND_TIMEOUT = 10*1000;

2.3.4 拆炸弹过程:ActiveServices#serviceDoneExecutingLocked

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,boolean finishing) {// ...mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); // ...       
}

2.3.5 炸弹爆炸出发ANR弹窗过程

    如上文分析的,ANR弹窗其实就是一个sendMessageDelayed()方式发送的一个Message,想要了解ANR炸弹这么爆炸的,其实检索这个what值为ActivityManagerService.SERVICE_TIMEOUT_MSG的消息处理过程即可。
这个消息的Handler对应的handleMessage方法实现代码在AMS.java中。

  • com.android.server.am.ActivityManagerService.MainHandler#handleMessage
final class MainHandler extends Handler {public MainHandler(Looper looper) {super(looper, null, true);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {// ...case SERVICE_TIMEOUT_MSG: {mServices.serviceTimeout((ProcessRecord)msg.obj);} break;case SERVICE_FOREGROUND_TIMEOUT_MSG: {mServices.serviceForegroundTimeout((ServiceRecord)msg.obj);} break;case SERVICE_FOREGROUND_CRASH_MSG: {mServices.serviceForegroundCrash((ProcessRecord) msg.obj, msg.getData().getCharSequence(SERVICE_RECORD_KEY));} break;// ...}}
  • com.android.server.am.ActiveServices#serviceTimeout
void serviceTimeout(ProcessRecord proc) {String anrMessage = null;synchronized(mAm) {if (proc.executingServices.size() == 0 || proc.thread == null) {return;}final long now = SystemClock.uptimeMillis();final long maxTime =  now -(proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);ServiceRecord timeout = null;long nextTime = 0;for (int i=proc.executingServices.size()-1; i>=0; i--) {ServiceRecord sr = proc.executingServices.valueAt(i);if (sr.executingStart < maxTime) {timeout = sr;break;}if (sr.executingStart > nextTime) {nextTime = sr.executingStart;}}if (timeout != null && mAm.mLruProcesses.contains(proc)) {Slog.w(TAG, "Timeout executing service: " + timeout);StringWriter sw = new StringWriter();PrintWriter pw = new FastPrintWriter(sw, false, 1024);pw.println(timeout);timeout.dump(pw, "    ");pw.close();mLastAnrDump = sw.toString();mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);// 这一句对于我们分析问题比较关键,如果有Service的ANR,// 就会在log中有这样的前缀打印:executing service Service.shortNameanrMessage = "executing service " + timeout.shortName;} else {Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);msg.obj = proc;mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));}}// 真正触发ANR弹窗的位置if (anrMessage != null) {mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);}
}

这里记住anrMessage的格式是:executing service Service.shortName,代表的是Service的启动超时。

  • com.android.server.am.AppErrors#appNotResponding
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) {if (mService.mController != null) {try {// 0 == continue, -1 = kill process immediately// !!关键:mService.mController的实现类是:// com.android.server.am.ActivityManagerShellCommand.MyActivityControllerint res = mService.mController.appEarlyNotResponding(app.processName, app.pid, annotation);if (res < 0 && app.pid != MY_PID) {app.kill("anr", true);}} catch (RemoteException e) {mService.mController = null;Watchdog.getInstance().setActivityController(null);}long anrTime = SystemClock.uptimeMillis();if (ActivityManagerService.MONITOR_CPU_USAGE) {mService.updateCpuStatsNow();}// Unless configured otherwise, swallow ANRs in background processes // & kill the process.// 读取开发者选项中的“显示后台ANR”开关boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;boolean isSilentANR;// Don't dump other PIDs if it's a background ANRisSilentANR = !showBackground && !isInterestingForBackgroundTraces(app);// Log the ANR to the main log.StringBuilder info = new StringBuilder();info.setLength(0);// 这里可以看出写入到日志文件中的格式是:ANR in processName,// 比如:我们应用包名是com.techmix.myapp,// 那可在搜索时,直接输入ANR in com.techminx.myapp搜索,可更高效定位到ANR的trace位置info.append("ANR in ").append(app.processName);if (activity != null && activity.shortComponentName != null) {info.append(" (").append(activity.shortComponentName).append(")");}info.append("\n");info.append("PID: ").append(app.pid).append("\n");if (annotation != null) {// 这里的reason还是serviceTimeout中定义的Service启动的anrMessage字符串:// "executing service Service.shortName",没有多余的更明细的分类了,具体是哪一步ANR了。info.append("Reason: ").append(annotation).append("\n");}if (parent != null && parent != activity) {info.append("Parent: ").append(parent.shortComponentName).append("\n");}ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);// For background ANRs, don't pass the ProcessCpuTracker to// avoid spending 1/2 second collecting stats to rank lastPids.File tracesFile = ActivityManagerService.dumpStackTraces(true, firstPids,(isSilentANR) ? null : processCpuTracker, (isSilentANR) ? null : lastPids, nativePids);// 写入cpu占用信息到anr log中String cpuInfo = null;if (ActivityManagerService.MONITOR_CPU_USAGE) {mService.updateCpuStatsNow();synchronized (mService.mProcessCpuTracker) {cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);}info.append(processCpuTracker.printCurrentLoad());info.append(cpuInfo);}info.append(processCpuTracker.printCurrentState(anrTime));// ANR log写入到dropbox文件夹中,annotation变量就是// com.android.server.am.ActiveServices#serviceTimeout中传入的anrMessage变量mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,cpuInfo, tracesFile, null);synchronized (mService) {// 静默ANR的定义?if (isSilentANR) {app.kill("bg anr", true);return;}// 通过Handler发送ANR弹窗的dialog,这里直接跟进// ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG这个消息的handleMessage处理逻辑即可Message msg = Message.obtain();msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);// 注意这里的mService是AMSmService.mUiHandler.sendMessage(msg);}
}
  • 静默ANR的定义?
  • 弹出ANR弹窗的逻辑代码:com.android.server.am.ActivityManagerService.UiHandler#handleMessage
case SHOW_NOT_RESPONDING_UI_MSG: {// 还是转调到AppErrors中的方法去实现了,所以这里只是用AMS中的UiHandler切换了一下线程而已// 最开始的startService方法,从应用主线程// ContextImpl#startService->AMS#startService(),后者其实是执行在binder线程池的线程里// 面的,是子线程。所以这里通过消息的方式切到主线程mAppErrors.handleShowAnrUi(msg);ensureBootCompleted();} break;
  • com.android.server.am.AppErrors#handleShowAnrUi
	// 跟ANR相关的变量直接存储在了ProcessRecord.java类中,每个进程单独维护一个boolean notResponding;      // does the app have a not responding dialog?Dialog anrDialog;           // dialog being displayed due to app not resp.void handleShowAnrUi(Message msg) {Dialog dialogToShow = null;synchronized (mService) {AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;final ProcessRecord proc = data.proc;if (proc == null) {Slog.e(TAG, "handleShowAnrUi: proc is null");return;}if (proc.anrDialog != null) {Slog.e(TAG, "App already has anr dialog: " + proc);MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,AppNotRespondingDialog.ALREADY_SHOWING);return;}// 这个ANR的广播,应用进程如果注册了能接收到吗?Intent intent = new Intent("android.intent.action.ANR");if (!mService.mProcessesReady) {intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY| Intent.FLAG_RECEIVER_FOREGROUND);}mService.broadcastIntentLocked(null, null, intent,null, null, 0, null, null, null, AppOpsManager.OP_NONE,null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;if (mService.canShowErrorDialogs() || showBackground) {dialogToShow = new AppNotRespondingDialog(mService, mContext, data);proc.anrDialog = dialogToShow;} else {MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,AppNotRespondingDialog.CANT_SHOW);// 如果ANR弹窗是关闭状态下,直接kill当前应用进程了// 跟ANR弹窗中点关闭应用一样,多是调用AMS#killAppAtUsersRequest方法// 关闭当前进程mService.killAppAtUsersRequest(proc, null);}}// If we've created a crash dialog, show it without the lock heldif (dialogToShow != null) {dialogToShow.show();}}
  • com.android.server.am.AppErrors#killAppAtUserRequestLocked
  void killAppAtUserRequestLocked(ProcessRecord app, Dialog fromDialog) {app.crashing = false;app.crashingReport = null;app.notResponding = false;app.notRespondingReport = null;if (app.anrDialog == fromDialog) {app.anrDialog = null;}if (app.waitDialog == fromDialog) {app.waitDialog = null;}// 这里的MY_PID是定义在AMS中的:static final int MY_PID = myPid();// 所以这里只要是有效的应用pid,都是能进入if逻辑分支中的if (app.pid > 0 && app.pid != MY_PID) {handleAppCrashLocked(app, "user-terminated" /*reason*/,null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);app.kill("user request after error", true);}}

app.kill()调用的是ProcessRecord#kill(),最终转调到AMS#killProcessGroup()方法了。这里AMS方式kill掉进程的,在Android的logcat中其实都能搜索到,Activity Manager killing的字样的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/11919.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

R-并行计算

本文介绍在计算机多核上通过parallel包进行并行计算。 并行计算运算步骤&#xff1a; 加载并行计算包&#xff0c;如library(parallel)。创建几个“workers”,通常一个workers一个核&#xff08;core&#xff09;&#xff1b;这些workers什么都不知道&#xff0c;它们的全局环…

c++学习(位图)[22]

位图 位图&#xff08;Bitmap&#xff09;是一种数据结构&#xff0c;用于表示一个固定范围的布尔值&#xff08;通常是0或1&#xff09;。它使用一个二进制位来表示一个布尔值&#xff0c;其中每个位的值表示对应位置的元素是否存在或满足某种条件。 位图可以用于解决一些特…

利用MATLAB制作DEM山体阴影

在地理绘图中&#xff0c;我们使用的DEM数据添加山体阴影使得绘制的图件显得更加的美观。 GIS中使用ArcGIS软件就可以达到这一目的&#xff0c;或者使用GMT&#xff0c;同样可以得到山体阴影的效果。 本文提供了一个MATLAB的函数&#xff0c;可以得到山体阴影。 clear all;c…

《面试1v1》如何能从Kafka得到准确的信息

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

安防视频管理平台GB设备接入EasyCVR, 如何获取RTMP与RTSP视频流

安防视频监控平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力&#xff0c;比…

基于粒子群优化算法的分布式电源选址与定容【多目标优化】【IEEE33节点】(Matlab代码实现)

目录 &#x1f4a5;1 概述 1.1 目标函数 2.2 约束条件 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码实现 &#x1f4a5;1 概述 分布式电源接入配电网&#xff0c;实现就地消纳&#xff0c;可以提高新能源的利用率、提高电能质量和降低系统网损…

出海周报|Temu在美状告shein、ChatGPT安卓版上线、小红书回应闪退

工程机械产业“出海”成绩喜人&#xff0c;山东相关企业全国最多Temu在美状告shein&#xff0c;跨境电商战事升级TikTok将在美国推出电子商务计划&#xff0c;售卖中国商品高德即将上线国际图服务&#xff0c;初期即可覆盖全球超200个国家和地区ChatGPT安卓版正式上线&#xff…

echarts遇到的问题

文章目录 折线图-区域面积图 areaStyley轴只有整数y轴不从0开始y轴数值不确定&#xff0c;有大有小&#xff0c;需要动态处理折线-显示label标线legend的格式化和默认选中状态x轴的lable超长处理x轴的相关设置 echarts各个场景遇到的问题 折线图-区域面积图 areaStyle areaStyl…

node.js的优点

提示&#xff1a;node.js的优点 文章目录 一、什么是node.js二、node.js的特性 一、什么是node.js 提示&#xff1a;什么是node.js? Node.js发布于2009年5月&#xff0c;由Ryan Dahl开发&#xff0c;是一个基于ChromeV8引擎的JavaScript运行环境&#xff0c;使用了一个事件驱…

【c语言进阶】字符函数和字符串函数知识总结

字符函数和字符串函数 前期背景求字符串长度函数strlen函数strlen函数三种模拟实现 长度不受限制的字符串函数strcpy函数strcpy函数模拟实现strcat函数strcat函数模拟实现strcmp函数strcmp函数模拟实现 长度受限制的字符串函数strncpy函数strncpy函数模拟实现strncat函数strnca…

粘包处理的方式

为什么出现粘包&#xff1a; 发送端在发送的时候由于 Nagel 算法的存在会将字节数较小的数据整合到一起发送&#xff0c;导致粘包&#xff1b;接收端不知道发送端数据的长度&#xff0c;导致接收时无法区分数据&#xff1b; 粘包处理的方式&#xff1a; 通过在数据前面加上报…

最新版本docker 设置国内镜像源 加速办法

解决问题:加速 docker 设置国内镜像源 目录: 国内加速地址 修改方法 国内加速地址 1.Docker中国区官方镜像 https://registry.docker-cn.com 2.网易 http://hub-mirror.c.163.com 3.ustc https://docker.mirrors.ustc.edu.cn 4.中国科技大学 https://docker.mirrors…

windows11打不开任务管理器,

目录 第一章、win11系统任务管理器打不开&#xff1f;第二章、解决方式修改注册表 友情提醒&#xff1a; 先看文章目录&#xff0c;大致了解文章知识点结构&#xff0c;点击文章目录可直接跳转到文章指定位置。 第一章、win11系统任务管理器打不开&#xff1f; Win11任务管理…

FPGA+EMMC 8通道存储小板

FPGA 采用XILINX公司A7100作为主芯片 AD采用AD7606及一款陀螺仪传感器&#xff0c;可以实时存储到EMMC&#xff0c;系统分为采集模式及回放模式 通过232接口对工作模式进行配置&#xff0c;采样率可以动态配置 回放采用W5100S通过TCP协议进行回放数据

【C语言进阶篇】回调函数都学了吧!那么用冒泡排序实现qsort函数你会嘛?

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言&#x1f4ac; qsort 和 冒泡排序的区别&#x1f4d1; qsort 的特点&#x1f4d1; 冒泡排序 …

JS逆向之猿人学爬虫第20题-wasm

文章目录 题目地址sign参数分析python算法还原往期逆向文章推荐题目地址 https://match.yuanrenxue.cn/match/20第20题被置顶到了第1页,题目难度 写的是中等 算法很简单,就一个标准的md5算法,主要是盐值不确定, 而盐值就在wasm里面,可以说难点就在于wasm分析 sign参数分…

[Linux]进程间通信

[Linux]进程间通信 文章目录 [Linux]进程间通信进程间通信什么是进程间通信进程间通信的目的进程间通信的本质为什么存在进程间通信进程间通信的分类 管道什么是管道匿名管道本质pipepipe的使用匿名管道读写情况匿名管道的特征 命名管道本质命令行创建命名管道创建和删除命名管…

如何在电脑上查看连接过的wifi信息?

忘记wifi密码&#xff1f;想要看看wifi信息&#xff1f; 我想这篇文章可以帮到你O(∩_∩)O哈哈~。 通过网络连接中心查看 电脑上找到“网络和共享中心” 点击连接的wifi名称 点击无线属性 在安全选项中就有密码 通过电脑命令行工具查看推荐 通过winr快捷键打开电脑运…

手动搭建gateway,项目集成gateway实现Token效果

目录 背景步骤1、首先创建springboot项目2、引入依赖3、配置文件&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff08;超级重要&#xff01;&#xff01;&#xff01;根据自己的需要进行配置&#xff09;4、相关类我们在服务中进行的白名单中接口的操作如…

信驰达推出RTL8720DN系列2.4G和5G双频Wi-Fi+蓝牙二合一模块

近日&#xff0c;领先的无线物联网通信模块厂商深圳信驰达科技RF-star推出了基于RTL8720DN SoC的2.4 GHz和5 GHz双频Wi-Fi蓝牙二合一模块—RF-WM-20DNB1。 图 1信驰达RF-WM-20DNB1 Wi-Fi模块 RF-WM-20DNB1是一款低功耗单芯片无线蓝牙和Wi-Fi组合模块&#xff0c;支持双频(2.4 G…