广告
返回顶部
首页 > 资讯 > 移动开发 >Android ANR原理分析
  • 732
分享到

Android ANR原理分析

2024-04-02 19:04:59 732人浏览 薄情痞子
摘要

目录卡顿原理 卡顿监控 ANR原理 卡顿原理 主线程有耗时操作会导致卡顿,卡顿超过阀值,触发ANR。 应用进程启动时候,ZyGote会反射调用ActivityThread的main

卡顿原理

线程有耗时操作会导致卡顿,卡顿超过阀值,触发ANR。 应用进程启动时候,ZyGote会反射调用ActivityThread的main方法,启动loop循环。 ActivityThread(api29)


    public static void main(String[] args) {
        Looper.prepareMainLooper();
        ...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

Looper的loop方法:


// 在线程中运行消息队列。一定要调用
public static void loop() {
        for (;;) {
            // 1、取消息
            Message msg = queue.next(); // might block
            ...
            // This must be in a local variable, in case a UI event sets the logger
            // 2、消息处理前回调
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            ...
            // 3、消息开始处理
            msg.target.dispatchMessage(msg);
            ...
            // 4、消息处理完回调
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
        }
}

loop中for循环存在,主线程可以长时间运行。在主线程执行任务,可以通过Handler post一个任务到消息队列去,loop循环拿到msg,交给msg的target(Handler)处理。

可能导致卡顿两个地方:

  • 注释1 queue.next()
  • 注释3 dispatchMessage耗时

MessageQueue.next 耗时代码(api29)


    @UnsupportedAppUsage
    Message next() {
        for (;;) {
            // 1、nextPollTimeoutMillis不为0则阻塞
            nativePollOnce(ptr, nextPollTimeoutMillis);
            // 2、先判断当前第一条消息是不是同步屏障消息,
            if (msg != null && msg.target == null) {
                    // 3、遇到同步屏障消息,就跳过去取后面的异步消息来处理,同步消息相当于被设立了屏障
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
             }
             // 4、正常消息处理,判断是否延时
             if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 5、如果没有取到异步消息,下次循环到注视1,nativePollOnce为-1,会一直阻塞
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
        }
    }
  1. MessageQueue是链表数据结构,判断MessageQueue头部(第一个消息)是不是同步屏障消息(给同步消息加一层屏障,让同步消息不被处理,只会处理异步消息);
  2. 如果遇到同步屏障消息,就会跳过MessageQueue中同步消息,只会处理里面的异步消息来处理。如果没有异步消息则到注释5,nextPollTimeoutMillis为-1,下次循环调用注释1的nativePollOnce就会阻塞;
  3. 如果looper能正常获取消息,不论异步/同步消息,处理流程一样,在注释4,判断是否延时,如果是,nextPollTimeoutMillis被赋值,下次调用注释1的nativePollOnce就会阻塞一段时间。如果不是delay消息,直接返回msg,给handler处理。

next方法不断从MessageQueue取消息,有消息就处理,没有消息就调用nativePollOnce阻塞,底层是linux的epoll机制,Linux IO多路复用。

Linux IO多路复用方案有select、poll、epoll。其中epoll性能最优,支持并发量最大。

  • select: 是操作系统提供的系统调用函数,可以把文件描述符的数组发给操作系统,操作系统去遍历,确定哪个描述符可以读写,告诉我们去处理。
  • poll:和select主要区别,去掉了select只能监听1024个文件描述符的限制。
  • epoll:针对select的三个可优化点进行改进。

1、内核中保持一份文件描述符集合,无需用户每次重新传入,只需要告诉内核修改部分。
2、内核不再通过轮询方式找到就绪的文件描述符,通过异步IO事件唤醒。
3、内核仅会将有IO的文件描述符返回给用户,用户无需遍历整个文件描述符集合。

同步屏障消息

Android App是无法直接调用同步消息屏障的,MessageQueue(api29)代码


    @TestApi
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        ...
    }

系统高优先级的操作使用到同步屏障消息,例如:View绘制的时候ViewRootImpl的scheduleTraversals方法,插入同步屏障消息,绘制完成后移除同步屏障消息。ViewRootImpl api29


    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    
    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

为了保证View的绘制过程不被主线程其他任务影响,View在绘制之前会先往MessageQueue插入同步屏障消息,然后再注册Vsync信号监听,Choreographer$FrameDisplayEventReceiver监听接收vsync信号回调的。


private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
            @Override
            public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
                Message msg = Message.obtain(mHandler, this);
                // 1、发送异步消息
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
              }
              
                      @Override
            public void run() {
                // 2、doFrame优先执行
                doFrame(mTimestampNanos, mFrame);
              }
            }

收到Vsync信号回调,注释1往主线程MessageQueue post一个异步消息,保证注释2的doFrame优先执行。

doFrame才是View真正开始绘制的地方,会调用ViewRootIml的doTraversal、perfORMTraversals,而performTraversals里面会调用View的onMeasure、onLayout、onDraw。

虽然app无法发送同步屏障消息,但是使用异步消息是允许的。

异步消息 SDK中限制了App不能post异步消息到MessageQueue中,Message类


    @UnsupportedAppUsage
     int flags;

谨慎使用异步消息,使用不当,可能出现主线程假死。

Handler#dispatchMessage


    
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
  1. Handler#post(Runnable r)
  2. 构造方法传CallBack
  3. Handler重写handlerMessage方法

应用卡顿,一般都是Handler处理消息太耗时导致的(方法本身、算法效率、cpu被抢占、内存不足、IPC超时等)

卡顿监控

卡顿监控方案一 Looper#loop


// 在线程中运行消息队列。一定要调用
public static void loop() {
        for (;;) {
            // 1、取消息
            Message msg = queue.next(); // might block
            ...
            // This must be in a local variable, in case a UI event sets the logger
            // 2、消息处理前回调
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            ...
            // 3、消息开始处理
            msg.target.dispatchMessage(msg);
            ...
            // 4、消息处理完回调
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
        }
}

注释2和4的logging.println是api提供接口,可监听Handler耗时,通过Looper.getMainLooper().setMessageLogging(printer),拿到消息前后的时间。监听到卡顿后,dispatchMessage早已调用结束,堆栈不包含卡顿代码。

定时获取主线程堆栈,时间为key,堆栈信息为value,保存map中,发生卡顿,取出卡顿时间内的堆栈可行。适合线下使用。

  1. logging.println存在字符串拼接,频繁调用,创建大量对象,内存抖动。
  2. 后台频繁获取主线程堆栈,对性能影响,获取主线程堆栈,暂停主线程的运行。

卡顿监控方案二

对于线上卡顿监控,需要字节码插桩技术。

通过Gradle Plugin+ASM,编译期在每个方法开始和结束位置分别插入一行代码,统计耗时。例如微信Matrix使用的卡顿监控方案。注意问题:

  1. 避免方法数暴增:分配独立ID作为参数
  2. 过滤简单函数:添加黑明单降低非必要函数统计

微信Matrix做大量优化,包体积增加1%~2%,帧率下降2帧以内,灰度包使用。

ANR原理

  • Service Timeout:前台服务20s内未执行完成,后台服务是10s
  • BroadcastQueue Timeout:前台广播10s内执行完成,后台60s
  • ContentProvider Timeout:publish超时10s
  • InputDispatching Timeout:输入事件分发超过5s,包括按键和触摸事件。

ActivityManagerService api29


    // How long we allow a receiver to run before giving up on it.
    static final int BROADCAST_FG_TIMEOUT = 10*1000;
    static final int BROADCAST_BG_TIMEOUT = 60*1000;

ANR触发流程

埋炸弹

后台sevice调用:Context.startService--> AMS.startService--> ActiveService.startService--> ActiveService.realStartServiceLocked


    private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
                // 1、发送delay消息(SERVICE_TIMEOUT_MSG)
                bumpServiceExecutingLocked(r, execInFg, "create");
                try {
                    //  2、通知AMS创建服务
                    app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                    app.getReportedProcState());
                }
            }

注释1内部调用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;
        // 发送delay消息,前台服务是20s,后台服务是200s
        mAm.mHandler.sendMessageDelayed(msg,
                proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    }

注释2通知AMS启动服务前,注释1发送handler延迟消息,20s内(前台服务)没有处理完,则ActiveServices#serviceTimeout被调用。

拆炸弹

启动一个Service,先要经过AMS管理,然后AMS通知应用执行Service的生命周期,ActivityThread的handlerCreateService方法被调用。


    @UnsupportedAppUsage
    private void handleCreateService(CreateServiceData data) {
        try {
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
            // 1、service onCreate调用
            service.onCreate();
            mServices.put(data.token, service);
            try {
                // 2、拆炸弹
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

注释1,Service的onCreate方法被调用 注释2,调用AMS的serviceDoneExecuting方法,最终会调用ActiveServices.serviceDoneExecutingLocked


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

onCreate调用后,就会移除delay消息,炸弹拆除。

引爆炸弹,假设Service的onCreate执行超过10s,那么炸弹就会引爆,也就是ActiveServices#serviceTimeout方法会被调用。api29


void serviceTimeout(ProcessRecord proc) {
        if (anrMessage != null) {
            proc.appNotResponding(null, null, null, null, false, anrMessage);
        }
}

所有ANR,最终带调用ProcessRecord的appNotResponding方法。api29


void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
            String parentShortComponentName, WindowProcessController parentProcess,
            boolean aboveSystem, String annotation) {
        // 1、写入event log
        // Log the ANR to the event log.
        EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags,
                    annotation);
        // 2、收集需要的log、anr、cpu等,放到StringBuilder中。
        // Log the ANR to the main log.
        StringBuilder info = new StringBuilder();
        info.setLength(0);
        info.append("ANR in ").append(processName);
        if (activityShortComponentName != null) {
            info.append(" (").append(activityShortComponentName).append(")");
        }
        info.append("\n");
        info.append("PID: ").append(pid).append("\n");
        if (annotation != null) {
            info.append("Reason: ").append(annotation).append("\n");
        }
        if (parentShortComponentName != null
                && parentShortComponentName.equals(activityShortComponentName)) {
            info.append("Parent: ").append(parentShortComponentName).append("\n");
        }

        ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
        // 3、dump堆栈信息,包括java堆栈和native堆栈,保存到文件中
        // For background ANRs, don't pass the ProcessCpuTracker to
        // avoid spending 1/2 second collecting stats to rank lastPids.
        File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
                (isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids,
                nativePids);
        String cpuInfo = null;
        // 4、输出ANR日志
        Slog.e(TAG, info.toString());
        if (tracesFile == null) {
            // 5、没有抓到tracesFile,发一个SIGNAL_QUIT信号
            // There is no trace file, so dump (only) the alleged culprit's threads to the log
            Process.sendSignal(pid, Process.SIGNAL_QUIT);
        }
        // 6、输出到drapbox
        mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
                parentShortComponentName, parentPr, annotation, cpuInfo, tracesFile, null);
        synchronized (mService) {
            // 7、后台ANR,直接杀进程
            if (isSilentAnr() && !isDebugging()) {
                kill("bg anr", true);
                return;
            }
            // 8、错误报告
            // Set the app's notResponding state, and look up the errorReportReceiver
            makeAppNotRespondingLocked(activityShortComponentName,
                    annotation != null ? "ANR " + annotation : "ANR", info.toString());
            // 9、弹出ANR dialog,会调用handleShowAnrUi方法
            Message msg = Message.obtain();
            msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
            msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem);

            mService.mUiHandler.sendMessage(msg);
        }
  }
  1. 写入event log
  2. 写入main log
  3. 生成tracesFile
  4. 输出ANR loGCat(控制台可以看到)
  5. 如果没有获取tracesFile,会发SIGNAL_QUIT信号,触发收集线程堆栈信息流程,写入traceFile
  6. 输出到drapbox
  7. 后台ANR,直接杀进程
  8. 错误报告
  9. 弹出ANR dialog 调用AppErrors#handleShowAnrUi方法。

ANR触发流程,埋炸弹--》拆炸弹的过程
启动Service,onCreate方法调用之前会使用Handler延时10s的消息,Service的onCreate方法执行完,会把延迟消息移除掉。
假如Service的onCreate方法耗时超过10s,延时消息就会被正常处理,触发ANR,收集cpu、堆栈消息,弹ANR dialog

抓取系统的data/anr/trace.txt文件,但是高版本系统需要root权限才能读取这个目录。

ANRWatchDog GitHub.com/SalomonBrys…

自动检测ANR开源

以上就是Android ANR原理分析的详细内容,更多关于Android ANR原理的资料请关注编程网其它相关文章!

--结束END--

本文标题: Android ANR原理分析

本文链接: https://www.lsjlt.com/news/128882.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Android ANR原理分析
    目录卡顿原理 卡顿监控 ANR原理 卡顿原理 主线程有耗时操作会导致卡顿,卡顿超过阀值,触发ANR。 应用进程启动时候,Zygote会反射调用ActivityThread的main...
    99+
    2022-11-12
  • Android之ANR分析
    目录 一、ANR是什么?二、ANR触发原因和场景ANR类型ANR原因ANR常见的场景 三、ANR触发时系统做了啥四、看源码小结ANR输出重要进程的traces信息,这些进程包含: 重...
    99+
    2023-10-23
    android java
  • Android anr问题分析
    前言 本文主要介绍anr问题一手分析、分类判断,归类后提case给平台处理。 不是针对应用开发的anr分析和优化处理。 anr问题分类 anr问题主要分为 1、input 无焦点anr Reason:...
    99+
    2023-09-01
    android
  • 深入学习Android ANR 的原理分析及解决办法
    目录一、ANR说明和原因1.1 简介1.2 原因1.3 避免二、ANR分析办法2.1 ANR重现2.2 ANR分析办法一:Log2.3 ANR分析办法二:traces.txt2...
    99+
    2022-06-07
    anr Android
  • Android中ANR的示例分析
    这篇文章将为大家详细讲解有关Android中ANR的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Android ANR(Application Not Responding)的分析ANR (Ap...
    99+
    2023-05-30
    android anr
  • Android ANR的原理是什么
    本篇内容介绍了“Android ANR的原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、ANR说明和原因1.1 简介A...
    99+
    2023-06-21
  • Android中ANR的分析和解决
    一、认识ANR ANR的定义 ANR,是“Application Not Responding”的缩写,即“应用程序无响应”。如果你应用程序在UI线程被阻塞太长时间,就会出现ANR,通常出现ANR,...
    99+
    2023-09-26
    Framework Android
  • 解析Android ANR问题
    目录一、ANR介绍1.1、ANR类型1.1.1、KeyDispatchTimeout1.1.2、BroadcastTimeout1.1.3、ServiceTimeout&nb...
    99+
    2022-11-12
  • Android进阶宝典 —如何通过ANR日志分析问题原因
    1 ANR再了解 当系统发生ANR时,会主动dump trace日志并保存在data/anr/trace.txt文件夹下,我们在拿到anr日志之后,就可以着手分析日志;或者可以通过bugreport命...
    99+
    2023-10-05
    android 性能优化
  • Android广播事件流程与广播ANR原理深入刨析
    目录序言一.基本流程和概念二.无序广播流程注册广播接收者流程广播通知流程三.有序广播流程四.广播ANR流程五.总结六.扩展问题序言 本想写广播流程中ANR是如何触发的,但是如果想讲清...
    99+
    2022-11-13
    Android广播事件流程 Android广播ANR
  • Android性能优化之ANR问题定位分析
    目录前言1 ANR原因总结1.1 KeyDispatchTimeout1.2 BroadCastTimeout1.3 ServiceTimeout1.4 ContentProvide...
    99+
    2022-11-13
  • Android ANR分析trace文件的产生流程详情
    目录前言接着分析最后一步向收集到的进程发送信号前言 首先收集需要dump trace的进程并给对应进程发送dump trace的信号 1.当一些带有超时机制的系统消息(如:Servi...
    99+
    2022-11-13
  • 全面解析Android之ANR日志
    目录一、概述二、ANR产生机制2.1 输入事件超时(5s)2.2 广播类型超时(前台15s,后台60s)2.3 服务超时(前台20s,后台200s)2.4 ContentProvid...
    99+
    2022-11-12
  • 通过Android trace文件分析死锁ANR实例过程
    对于从事Android开发的人来说,遇到ANR(Application Not Responding)是比较常见的问题。一般情况下,如果有ANR发生,系统都会在/data/an...
    99+
    2022-06-06
    anr Android
  • SharedPreference引发ANR原理详解
    目录正文SharedPreference问题总结正文 日常开发中,使用过SharedPreference的同学,肯定在监控平台上看到过和SharedPreference相关的ANR...
    99+
    2023-02-21
    SharedPreference引发ANR SharedPreference ANR
  • 怎么使用Android ANR分析trace文件的产生流程
    这篇文章主要介绍“怎么使用Android ANR分析trace文件的产生流程”,在日常操作中,相信很多人在怎么使用Android ANR分析trace文件的产生流程问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作...
    99+
    2023-07-02
  • Android APK 签名打包原理分析(二)【Android签名原理】
    说到签名,从这个词来理解,正常个人需要签名的时候,一般是用来证明这是某个人的特属认证。 大家是否有印象?还记得我们之前在学习、总结网络相关知识的时候,说到过,客户端和服务端虽然通信数据上,可以采用对称加密和非对称加密组合去进行数据的加密...
    99+
    2023-08-16
    android 签名 打包 消息摘要
  • SharedPreference引发ANR原理是什么
    这篇文章主要介绍“SharedPreference引发ANR原理是什么”,在日常操作中,相信很多人在SharedPreference引发ANR原理是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Share...
    99+
    2023-07-05
  • Android Handler 机制实现原理分析
    handler在安卓开发中是必须掌握的技术,但是很多人都是停留在使用阶段。使用起来很简单,就两个步骤,在主线程重写handler的handleMessage( )方法,在工作线...
    99+
    2022-06-06
    handler Android
  • Android Broadcast原理分析之registerReceiver详解
    目录1. BroadcastReceiver概述2. BroadcastReceiver分类3. registerReceiver流程图4. 源码解析4.1 ContextImpl....
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作