2024年Android最新卡顿、死锁、ANR原理,线上监控方案分析,字节跳动三面四面

结尾

最后,针对上面谈的内容,给大家推荐一个Android资料,应该对大家有用。

首先是一个知识清单:(对于现在的Android及移动互联网来说,我们需要掌握的技术)

泛型原理丶反射原理丶Java虚拟机原理丶线程池原理丶
注解原理丶注解原理丶序列化
Activity知识体系(Activity的生命周期丶Activity的任务栈丶Activity的启动模式丶View源码丶Fragment内核相关丶service原理等)
代码框架结构优化(数据结构丶排序算法丶设计模式)
APP性能优化(用户体验优化丶适配丶代码调优)
热修复丶热升级丶Hook技术丶IOC架构设计
NDK(c编程丶C++丶JNI丶LINUX)
如何提高开发效率?
MVC丶MVP丶MVVM
微信小程序
Hybrid
Flutter

接下来是资料清单:(敲黑板!!!


1.数据结构和算法

2.设计模式

3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)

不论遇到什么困难,都不应该成为我们放弃的理由!共勉~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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;

}

}

next方法的大致流程是这样的:

  1. MessageQueue是一个链表数据结构,判断MessageQueue的头部(第一个消息)是不是一个同步屏障消息,所谓同步屏障消息,就是给同步消息加一层屏障,让同步消息不被处理,只会处理异步消息;

  2. 如果遇到同步屏障消息,就会跳过MessageQueue中的同步消息,只获取里面的异步消息来处理。如果里面没有异步消息,那就会走到注释5,nextPollTimeoutMillis设置为-1,下次循环调用注释1的nativePollOnce就会阻塞;

  3. 如果looper能正常获取到消息,不管是异步消息或者同步消息,处理流程都是一样的,在注释4,先判断是否带延时,如果是,nextPollTimeoutMillis就会被赋值,然后下次循环调用注释1的nativePollOnce就会阻塞一段时间。如果不是delay消息,就直接返回这个msg,给handler处理;

从上面分析可以看出,next方法是不断从MessageQueue里取出消息,有消息就处理,没有消息就调用nativePollOnce阻塞,nativePollOnce 底层是Linux的epoll机制,这里涉及到一个Linux IO 多路复用的知识点

Linux IO 多路复用,select、poll、epoll

Linux 上IO多路复用方案有 select、poll、epoll。它们三个中 epoll 的性能表现是最优秀的,能支持的并发量也最大。

  1. select 是操作系统提供的系统调用函数,通过它,我们可以把一个文件描述符的数组发给操作系统, 让操作系统去遍历,确定哪个文件描述符可以读写, 然后告诉我们去处理。

  2. poll:它和 select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制

  3. epoll:epoll 主要就是针对select的这三个可优化点进行了改进

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

回到 MessageQueuenext 方法,看看哪里可能阻塞

同步屏障消息没移除导致next一直阻塞

有一种情况,在存在同步屏障消息的情况下,当异步消息被处理完之后,如果没有及时把同步屏障消息移除,会导致同步消息一直没有机会处理,一直阻塞在nativePollOnce

同步屏障消息

Android 是禁止App往MessageQueue插入同步屏障消息的,代码会报错

系统一些高优先级的操作会使用到同步屏障消息,例如View在绘制的时候,最终都要调用ViewRootImplscheduleTraversals方法,会往MessageQueue插入同步屏障消息,绘制完成后会移除同步屏障消息。

->ViewRootImpl

@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信号回调的

Choreographer$FrameDisplayEventReceiver

private final class FrameDisplayEventReceiver extends DisplayEventReceiver

implements Runnable {

@Override

public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {

//

mTimestampNanos = timestampNanos;

mFrame = 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真正开始绘制的地方,会调用ViewRootImpldoTraversalperformTraversals

performTraversals里面会调用我们熟悉的View的onMeasureonLayoutonDraw

这里还可以延伸到vsync信号原理,以及为什么要等vsync信号回调才开始View的绘制流程、掉帧的原理、屏幕的双缓冲、三缓冲,由于文章篇幅关系,不是本文的重点,就不一一分析了~

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

异步消息

首先,SDK中限制了App不能post异步消息到MessageQueue里去的,相关字段被加了UnsupportedAppUsage注解

-> Message

@UnsupportedAppUsage

/package/ int flags;

/**

  • Returns true if the message is asynchronous, meaning that it is not

  • subject to {@link Looper} synchronization barriers.

  • @return True if the message is asynchronous.

  • @see #setAsynchronous(boolean)

*/

public boolean isAsynchronous() {

return (flags & FLAG_ASYNCHRONOUS) != 0;

}

不过呢,高版本的Handler的构造方法可以通过传async=true,来使用异步消息

public Handler(@Nullable Callback callback, boolean async) {}

复制代码

然后在Handler发送消息的时候,都会走到 enqueueMessage 方法,如下代码块所示,每个消息都带了异步属性,有优先处理权

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {

//如果mAsynchronous为true,就都设置为异步消息

if (mAsynchronous) {

msg.setAsynchronous(true);

}

return queue.enqueueMessage(msg, uptimeMillis);

}

对于低版本SDK,想要使用异步消息,可以通过反射调用Handler(@Nullable Callback callback, boolean async),参考androidx内部的一段代码如下

->androidx.arch.core.executor.DefaultTaskExecutor

private static Handler createAsync(@NonNull Looper looper) {

if (Build.VERSION.SDK_INT >= 28) {

return Handler.createAsync(looper);

}

if (Build.VERSION.SDK_INT >= 16) {

try {

return Handler.class.getDeclaredConstructor(Looper.class, Handler.Callback.class,

boolean.class)

.newInstance(looper, null, true);

} catch (IllegalAccessException ignored) {

} catch (InstantiationException ignored) {

} catch (NoSuchMethodException ignored) {

} catch (InvocationTargetException e) {

return new Handler(looper);

}

}

return new Handler(looper);

}

需要注意的是,App要谨慎使用异步消息,使用不当的情况下可能会出现主线程假死的问题,排查也比较困难

分析完MessageQueue#next再回头来看看 HandlerdispatchMessage方法

2.1.2 dispatchMessage

上面说到next方法轮循取消息一般情况下是没有问题的,那么只剩下处理消息的逻辑

Handler#dispatchMessage

/**

  • Handle system messages here.

*/

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

dispatchMessage 有三个逻辑,分别对应Handler 使用的三种方式

  1. Handler#post(Runnable r)

  2. 构造方法传CallBack,public Handler(@Nullable Callback callback, boolean async) {}

  3. Handler 重写 handleMessage 方法

所以,应用卡顿,原因一般都可以认为是Handler处理消息太耗时导致的,细分的原因可能是方法本身太耗时、算法效率低、cpu被抢占、内存不足、IPC超时等等。

2.2 卡顿监控

面试中,被问到如何监控App卡顿,统计方法耗时,我们可以从源码开始切入,讲讲如何通过Looper提供的Printer接口,计算Handler处理一个消息的耗时,判断是否出现卡顿。

2.2.1 卡顿监控方案一

看下Looper 循环的注释2和注释4,可以找到一种卡顿监控的方法

Looper#loop

public static void loop() {

for (;😉 {

//1、取消息

Message msg = queue.next(); // might block

//2、消息处理前回调

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是谷歌提供给我们的一个接口,可以监听Handler处理消息耗时,我们只需要调用Looper.getMainLooper().setMessageLogging(printer),即可从回调中拿到Handler处理一个消息的前后时间。

需要注意的是,监听到发生卡顿之后,dispatchMessage 早已调用结束,已经出栈,此时再去获取主线程堆栈,堆栈中是不包含卡顿的代码的。

所以需要在后台开一个线程,定时获取主线程堆栈,将时间点作为key,堆栈信息作为value,保存到Map中,在发生卡顿的时候,取出卡顿时间段内的堆栈信息即可。

不过这种方案只适合线下使用,原因如下:

  1. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);存在字符串拼接,频繁调用,会创建大量对象,造成内存抖动。

  2. 后台线程频繁获取主线程堆栈,对性能有一定影响,获取主线程堆栈,会暂停主线程的运行

2.2.2 卡顿监控方案二

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

通过Gradle Plugin+ASM,编译期在每个方法开始和结束位置分别插入一行代码,统计方法耗时,

伪代码如下

插桩前

fun method(){

run()

}

插桩后

fun method(){

input(1)

run()

output(1)

}

目前微信的Matrix 使用的卡顿监控方案就是字节码插桩,如下图所示

插桩需要注意的问题:

  1. 避免方法数暴增:在方法的入口和出口应该插入相同的函数,在编译时提前给代码中每个方法分配一个独立的 ID 作为参数。

  2. 过滤简单的函数:过滤一些类似直接 return、i++ 这样的简单函数,并且支持黑名单配置。对一些调用非常频繁的函数,需要添加到黑名单中来降低整个方案对性能的损耗。

微信Matrix做了大量优化,整体包体积增加1%-2%,帧率下降2帧以内,对性能影响整体可以接受,不过依然只会在灰度包使用。

再来说说ANR~

三、ANR 原理


ANR 的类型和触发ANR的流程

3.1 哪些场景会造成ANR呢

  • Service Timeout:比如前台服务在20s内未执行完成,后台服务是10s;

  • BroadcastQueue Timeout:比如前台广播在10s内未执行完成,后台60s

  • ContentProvider Timeout:内容提供者,在publish过超时10s;

  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。

相关超时定义可以参考ActivityManagerService

// 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;

// How long we wait until we timeout on key dispatching.

static final int KEY_DISPATCHING_TIMEOUT = 5*1000;

3.2 ANR触发流程

来简单分析下源码,ANR触发流程其实可以比喻成埋炸弹拆炸弹的过程,

以后台Service为例

3.2.1 埋炸弹

Context.startService

调用链如下:

AMS.startService

ActiveServices.startService

ActiveServices.realStartServiceLocked

ActiveServices.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.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),

app.repProcState);

}

}

注释1的bumpServiceExecutingLocked内部调用scheduleServiceTimeoutLocked

void scheduleServiceTimeoutLocked(ProcessRecord proc) {

Message msg = mAm.mHandler.obtainMessage(

ActivityManagerService.SERVICE_TIMEOUT_MSG);

msg.obj = proc;

// 发送deley消息,前台服务是20s,后台服务是10s

mAm.mHandler.sendMessageDelayed(msg,

proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);

}

注释2通知AMS启动服务之前,注释1处发送Handler延时消息,埋下炸弹,如果10s内(前台服务是20s)没人来拆炸弹,炸弹就会爆炸,即ActiveServices#serviceTimeout方法会被调用

3.2.2 拆炸弹

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

-> ActivityThread#handleCreateService

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,ServiceonCreate方法被调用,

注释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消息,炸弹被拆除。

3.2.3 引爆炸弹

假设Service的onCreate执行超过10s,那么炸弹就会引爆,也就是

ActiveServices#serviceTimeout方法会被调用

void serviceTimeout(ProcessRecord proc) {

if (anrMessage != null) {

mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);

}

}

所有ANR,最终都会调用AppErrorsappNotResponding方法

AppErrors #appNotResponding

final void appNotResponding(ProcessRecord app, ActivityRecord activity,

ActivityRecord parent, boolean aboveSystem, final String annotation) {

//1、写入event log

// Log the ANR to the event log.

EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,

app.processName, app.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(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) {

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);

// 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(

true, 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(app.pid, Process.SIGNAL_QUIT);

}

StatsLog.write(StatsLog.ANR_OCCURRED, …)

// 6、输出到drapbox

mService.addErrorToDropBox(“anr”, app, app.processName, activity, parent, annotation, cpuInfo, tracesFile, null);

synchronized (mService) {

mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);

//7、后台ANR,直接杀进程

if (isSilentANR) {

app.kill(“bg anr”, true);

return;

}

//8、错误报告

// 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());

//9、弹出ANR dialog,会调用handleShowAnrUi方法

// 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);

}

}

主要流程如下:

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触发流程小结

ANR触发流程,可以比喻为埋炸弹和拆炸弹的过程,

以启动Service为例,Service的onCreate方法调用之前会使用Handler发送延时10s的消息,Service 的onCreate方法执行完,会把这个延时消息移除掉。

假如Service的onCreate方法耗时超过10s,延时消息就会被正常处理,也就是触发ANR,会收集cpu、堆栈等信息,弹ANR Dialog。

service、broadcast、provider 的ANR原理都是埋定时炸弹和拆炸弹原理,

但是input的超时检测机制稍微有点不同,需要等收到下一次input事件,才会去检测上一次input事件是否超时,input事件里埋的炸弹是普通炸弹,需要通过扫雷来排查。

四、ANR 分析方法


上面已经分析了ANR触发流程,最终会把发生ANR时的线程堆栈、cpu等信息保存起来,我们一般都是分析 /data/anr/traces.txt 文件

4.1 模拟死锁导致ANR

private fun testAnr(){

val lock1 = Object()

val lock2 = Object()

//子线程持有锁1,想要竞争锁2

thread {

synchronized(lock1){

Thread.sleep(100)

synchronized(lock2){

Log.d(TAG, “testAnr: getLock2”)

}

}

}

//主线程持有锁2,想要竞争锁1

synchronized(lock2){

Thread.sleep(100)

synchronized(lock1){

Log.d(TAG, “testAnr: getLock1”)

}

}

}

触发ANR之后,一般我们会拉取anr日志: adb pull /data/traces.txt(文件名可能是anr_xxx.txt)

4.2 分析ANR 文件

首先看主线程,搜索 main

ANR日志中有很多信息,可以看到,主线程id是1(tid=1),在等待一个锁,这个锁一直被id为22的程持有,那么看下22号线程的堆栈

id为22的线程是Blocked状态,正在等待一个锁,这个锁被id为1的线程持有,同时这个22号线程还持有一个锁,这个锁是主线程想要的。

通过ANR日志,可以很清楚分析出这个ANR是死锁导致的,并且有具体堆栈信息。

上面只是举例一种死锁导致ANR的情况,实际项目中,可能有很多情况会导致ANR,例如内存不足、CPU被抢占、系统服务没有及时响应等等。

如果是线上问题,怎么样才能拿到ANR日志呢?

五、ANR 监控


前面已经分析了ANR触发流程,以及常规的线下分析方法,看起来还是有点繁琐的,需要pull出anr日志,然后分析线程堆栈等信息。对于线上ANR,如何搭建一个完善的ANR监控系统呢?

下面将介绍ANR监控的方式

5.1 抓取系统traces.txt 上传

1、当监控线程发现主线程卡死时,主动向系统发送SIGNAL_QUIT信号。

2、等待/data/anr/traces.txt文件生成。

3、文件生成以后进行上报。

看起来好像可行,不过有以下两个问题:

1、traces.txt 里面包含所有线程的信息,上传之后需要人工过滤分析

2、很多高版本系统需要root权限才能读取 /data/anr这个目录

既然这个方案存在问题,那么可还有其它办法?

5.2 ANRWatchDog

ANRWatchDog 是一个自动检测ANR的开源库

5.2.1 ANRWatchDog 原理

其源码只有两个类,核心是ANRWatchDog这个类,继承自Thread,它的run 方法如下,看注释处

public void run() {

setName(“|ANR-WatchDog|”);

long interval = _timeoutInterval;

// 1、开启循环

while (!isInterrupted()) {

boolean needPost = _tick == 0;

_tick += interval;

if (needPost) {

// 2、往UI线程post 一个Runnable,将_tick 赋值为0,将 _reported 赋值为false

_uiHandler.post(_ticker);

}

try {

// 3、线程睡眠5s

Thread.sleep(interval);

} catch (InterruptedException e) {

_interruptionListener.onInterrupted(e);

return ;

}

// If the main thread has not handled _ticker, it is blocked. ANR.

// 4、线程睡眠5s之后,检查 _tick 和 _reported 标志,正常情况下_tick 已经被主线程改为0,_reported改为false,如果不是,说明 2 的主线程Runnable一直没有被执行,主线程卡住了

if (_tick != 0 && !_reported) {

if (_namePrefix != null) {

// 5、判断发生ANR了,那就获取堆栈信息,回调onAppNotResponding方法

error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);

} else {

error = ANRError.NewMainOnly(_tick);

}

_anrListener.onAppNotResponding(error);

interval = _timeoutInterval;

_reported = true;

}

}

}

ANRWatchDog 的原理是比较简单的,概括为以下几个步骤

  1. 开启一个线程,死循环,循环中睡眠5s

  2. 往UI线程post 一个Runnable,将_tick 赋值为0,将 _reported 赋值为false

  3. 线程睡眠5s之后检查_tick和_reported字段是否被修改

  4. 如果_tick和_reported没有被修改,说明给主线程post的Runnable一直没有被执行,也就说明主线程卡顿至少5s**(只能说至少,这里存在5s内的误差)**。

  5. 将线程堆栈信息输出

其中涉及到并发的一个知识点,关于 volatile 关键字的使用,面试中的常客, volatile的特点是:保证可见性,禁止指令重排,适合在一个线程写,其它线程读的情况。

最后

在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

as not handled _ticker, it is blocked. ANR.

// 4、线程睡眠5s之后,检查 _tick 和 _reported 标志,正常情况下_tick 已经被主线程改为0,_reported改为false,如果不是,说明 2 的主线程Runnable一直没有被执行,主线程卡住了

if (_tick != 0 && !_reported) {

if (_namePrefix != null) {

// 5、判断发生ANR了,那就获取堆栈信息,回调onAppNotResponding方法

error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);

} else {

error = ANRError.NewMainOnly(_tick);

}

_anrListener.onAppNotResponding(error);

interval = _timeoutInterval;

_reported = true;

}

}

}

ANRWatchDog 的原理是比较简单的,概括为以下几个步骤

  1. 开启一个线程,死循环,循环中睡眠5s

  2. 往UI线程post 一个Runnable,将_tick 赋值为0,将 _reported 赋值为false

  3. 线程睡眠5s之后检查_tick和_reported字段是否被修改

  4. 如果_tick和_reported没有被修改,说明给主线程post的Runnable一直没有被执行,也就说明主线程卡顿至少5s**(只能说至少,这里存在5s内的误差)**。

  5. 将线程堆栈信息输出

其中涉及到并发的一个知识点,关于 volatile 关键字的使用,面试中的常客, volatile的特点是:保证可见性,禁止指令重排,适合在一个线程写,其它线程读的情况。

最后

在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-gzXysno3-1715710915639)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 0
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值