深入理解Handler(四) --- 主线程的Looper为什么不会导致应用的ANR

    科技2025-01-06  21

    一、概述

    是否了解ANR的产生条件是否对Android App的进程运行机制有深入了解是否对Looper的消息机制有深刻的理解

    在面试中经常也时常会遇到类似的问题,其实这两个八杆子打不着, 通过这篇文章我们好好捋一捋这两者的概念。

    ANR类型

    Service Timeout

    前台服务 20s后台服务 200s

    BroadcaseQueue Timeout

    前台广播 10s后台广播 60s

    ContentProvider Timeout

    10s

    InputDispatching Timeout

    5s

    ANR产生源码

    这里以Service启动为例,AMS在调用startService后最终会执行到 ActiveServices的 scheduleServiceTimeoutLocked,这里就挑关键代码讲了

    ActiveServices::scheduleServiceTimeoutLocked

    // How long we wait for a service to finish executing. static final int SERVICE_TIMEOUT = 20*1000; // How long we wait for a service to finish executing. static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10; 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::serviceDoneExecutingLocked

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

    这个时候就会把消息remove掉

    ActiveServices::realStartServiceLocked

    private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ... bumpServiceExecutingLocked(r, execInFg, "create"); ... app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); ... serviceDoneExecutingLocked(r, inDestroying, inDestroying); }

    这里我们看下完整的调用链路,首先先发送一个定时消息,在调用Service的onCreate,如果onCreate在规定时间执行完,就会调用serviceDoneExecutingLocked将定时消息移除。

    这里我们看看如果触发了ANR会走向哪里,ActivityManagerService.SERVICE_TIMEOUT_MSG我们看下这个消息

    ActivityManagerService::MainHandler::handleMessage

    final class MainHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { ... case SERVICE_FOREGROUND_TIMEOUT_MSG: { mServices.serviceForegroundTimeout((ServiceRecord)msg.obj); } break; ... }} }

    这个mServices 其实是ActiveServices

    ActiveServices::serviceForegroundTimeout

    void serviceForegroundTimeout(ServiceRecord r) { ProcessRecord app; synchronized (mAm) { if (!r.fgRequired || r.destroying) { return; } app = r.app; if (app != null && app.debugging) { // The app's being debugged; let it ride return; } if (DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "Service foreground-required timeout for " + r); } r.fgWaiting = false; stopServiceLocked(r); } if (app != null) { mAm.mAppErrors.appNotResponding(app, null, null, false, "Context.startForegroundService() did not then call Service.startForeground(): " + r); } }

    AppErrors::appNotResponding

    final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) { ... // Set the app's notResponding state, and look up the errorReportReceiver makeAppNotRespondingLocked(app, activity != null ? activity.shortComponentName : null, annotation != null ? "ANR " + annotation : "ANR", info.toString()); // Bring up the infamous App Not Responding dialog Message msg = Message.obtain(); msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem); mService.mUiHandler.sendMessage(msg); }

    以上是整个ANR产生的源码部分,那我们继续看下Looper又究竟在干什么

    主线程究竟在干什么

    ActivityThread::main

    public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); ... Looper.prepareMainLooper(); ... ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }

    这个在之前的文章介绍过了很多遍了,开启Looper等待消息过来并处理。

    Looper和ANR的关系

    理清了两者的概念我们就知道Looper是线程中处理消息的机制,而ANR是执行到某一个环节的时候为开发者在开发时对主线程进行监控。所以Looper不会产生ANR。

    那接下来引出下一个问题,为什么Looper死循环不会导致CPU占用率过高呢?

    Looper死循环为什么不会导致CPU占用率过高

    - Looper.cpp struct epoll_event eventItems[EPOLL_MAX_EVENTS]; //等待事件发生或者超时,在nativeWake()方法,向管道写端写入字符,则该方法会返回; int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis); // Allocate the new epoll instance and register the wake pipe. mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC)); int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);

    在消息队列空的时候,会调用 epoll_wait,会等待文件的消息,文件有消息后,才会唤醒。阻塞是不会消耗CPU的时间片,不会导致CPU占用率高。这一点大家要理清楚。

    这里留一个问题给大家思考

    直接在Activity Sleep 5000ms,再Post一个runable会不会ANR?

    深入理解Handler(一) — Android中为什么非UI线程不能更新UI 深入理解Handler(二) — 从源码角度来理解Android线程间消息传递机制 深入理解Handler(三) — Handler发送延时消息实现 深入理解Handler(四) — 主线程的Looper为什么不会导致应用的ANR

    Processed: 0.010, SQL: 8