1、什么是ANR?
ANR = Application Not Responding,应用无响应。
一句话解释
Android 系统检测到主线程(UI 线程)长时间卡住不响应,超过规定时间阈值,系统弹出应用无响应弹窗,可选择等待或强制关闭。
触发本质
Android 所有 UI 刷新、事件分发都在主线程;一旦主线程被耗时操作阻塞,没法处理用户点击、绘制界面,系统判定卡死,就触发 ANR。
常见触发场景阈值
- 按键 / 触摸事件(主线程阻塞):5 秒 没响应 → ANR
- 广播
onReceive:前台 10s / 后台 60s - Service 回调:20 秒 处理不完
核心产生原因
- 主线程做耗时操作网络请求、文件 IO、数据库查询、循环计算、解析大 JSON / 大图。
- 主线程死锁 / 等待同步锁不当、Handler 死循环、等待子线程未唤醒。
- UI 卡顿绘制超时布局嵌套过深、View 过度绘制、自定义 View onDraw 耗时。
- 广播 / 服务处理耗时onReceive、Service onCreate/onStartCommand 写耗时逻辑。
- 内存问题频繁 GC、内存泄漏导致卡顿、主线程调度被抢占。
通用解决方法
- 耗时全丢子线程IO、网络、数据库、复杂计算 全部放到子线程 / 协程 / 线程池。
- 主线程只做 UI 刷新只负责控件赋值、跳转、弹窗,不做任何阻塞操作。
- 优化布局与绘制减少布局嵌套、去掉过度绘制、onDraw 避免创建对象、少做耗时运算。
- 广播 / 服务轻量化onReceive 只发消息、启线程、抛事件,不做耗时业务。
- 避免主线程死锁合理使用同步锁,不主线程等待子线程结果。
- 内存优化解决内存泄漏、大图压缩复用、减少频繁对象创建,降低 GC 卡顿。
- ANR 日志定位查看
anr/traces.txt,定位卡死的主线程堆栈,精准改代码。
2、Activity的启动流程?
Activity 三种启动流程:冷启动、温启动、热启动
1. 冷启动
场景:App 完全没开过、后台被杀、手机重启后第一次点开。过程:系统先创建进程 → 初始化 App → 初始化 Application → 走 Activity 完整生命周期(onCreate→onStart→onResume)。特点:最慢,从头到尾全部重新来,啥都要初始化一遍。
2. 温启动
场景:App 后台还在,但目标 Activity 已经被销毁了(内存不足被系统回收)。过程:进程还在、不用重新创建,直接重建目标 Activity,走 onCreate 等流程,不用初始化 Application。特点:速度中等,进程保留,只重建页面。
3. 热启动
场景:App 在后台挂着,页面没被销毁,只是切到后台了。过程:进程、Activity 都还活着,直接从后台切到前台,只走 onStart → onResume,不走 onCreate。特点:最快,不用重建任何东西,直接唤醒页面。
一句话极简区分
- 冷启动:App 彻底死了,从头新建进程 + 页面,最慢;
- 温启动:App 进程还在,页面死了,只重建页面;
- 热启动:App 和页面都活着,只是藏后台,直接唤醒,最快。
3、冷启动的原理?
什么是冷启动
App完全没运行过、后台被系统彻底杀掉、手机重启后第一次点开,就是冷启动,是最慢的一种启动。
冷启动完整流程(通俗版)
- 第一步:系统创建 App 进程你点击图标,系统发现这个 App连进程都没有,先单独给它开辟一个新进程。
- 第二步:创建并初始化 Application进程建好后,先跑 Application,执行它的构造方法、
onCreate,全局配置、SDK 初始化都在这里走一遍。 - 第三步:启动目标 ActivityApplication 初始化完,系统开始创建目标 Activity,走完整生命周期:
onCreate → onStart → onResume - 第四步:加载布局、绘制页面解析 XML 布局、加载图片、渲染 UI,最后把页面显示到屏幕上。
核心特点
- 要经历:创建进程 → 初始化 Application → 初始化 Activity → 加载布局渲染 UI 全套流程。
- 啥都是从零新建,没有任何缓存复用,所以最慢、最容易卡顿。
为啥冷启动容易慢?
Application 里塞太多 SDK 初始化、主线程做耗时操作、布局太复杂、Activity onCreate 写太多耗时逻辑,都会拖慢冷启动。
4、如何优化Activity的启动速度?
一、先懂核心
Activity 启动慢,90% 都是主线程堵车了,初始化、加载数据、布局太复杂,卡住 UI 渲染。
二、具体优化方法(按优先级)
1. 优化 Application 初始化
- 别在 Application 主线程做耗时操作第三方 SDK、初始化、数据库、大图预加载,全部丢子线程、懒加载。
- 非必要 SDK 延迟初始化,用到再初始化,不要一打开 App 全加载。
2. 优化布局 XML
- 减少布局嵌套,别多层 LinearLayout 嵌套,多用 ConstraintLayout。
- 减少冗余 View,页面不用的控件删掉,不要写大量默认隐藏的 View。
- 用 ViewStub 延迟加载 不立即显示的布局(详情页、弹窗布局)。
- 避免 过度绘制、自定义 View onDraw 做耗时计算、频繁 new 对象。
3. 优化 Activity 生命周期
- onCreate 只做必须的网络请求、大数据解析、复杂业务逻辑不要放 onCreate,丢子线程 / 协程。
- 能延迟加载的内容,等页面渲染完再加载,不要阻塞首屏。
- 避免在主线程做 IO、数据库、循环计算、解析大 JSON。
4. 首屏体验优化
- 做启动页占位图,避免白屏、黑屏,视觉上感觉秒开。
- 首屏先加载骨架屏 / 默认占位,数据回来再刷新,不阻塞页面显示。
5. 资源与图片优化
- 图片压缩、用 WebP,避免大图一次性加载进内存。
- 用图片框架异步加载,不要自己在主线程解码图片。
6. 其他小优化
- 减少 Activity 启动时的 Intent 大数据传递,避免 Binder 卡顿。
- 避免内存泄漏、频繁 GC,GC 频繁也会拖慢启动。
- 合理使用 懒加载、按需初始化,不用不创建。
三、一句话总结
- App 全局别在 Application 瞎初始化
- 布局别嵌套、冗余控件砍掉、ViewStub 懒加载
- onCreate 不做耗时,网络 / IO 全丢子线程
- 首屏做占位,视觉秒开
5、Handler运行机制
Handler 运行机制 通俗极简总结
- 四大角色 Handler、Message、MessageQueue、Looper
- 通俗流程
- Looper:就是一个死循环,不停从消息队列里取消息。
- MessageQueue:就是消息排队的篮子,所有消息都放这里排队。
- Message:就是要传递的任务 / 消息。
- Handler:就是发消息、接消息、处理任务的工具。
- 整体运转流程子线程用 Handler 发消息 → 消息进入 MessageQueue 排队 → Looper 死循环不断取出消息 → 交给 Handler 回调处理 → 切到主线程更新 UI。
- 一句话核心Looper 转圈轮询、队列存消息、Handler 收发消息,实现子线程发任务、主线程更新 UI。
6、MessageQueue 底层原理 通俗极简总结
- 本质是什么MessageQueue 不是普通队列,底层是单向链表,按执行时间排序,不是按插入顺序排队。
- 核心作用存放 Handler 发过来的 Message,给 Looper 循环取消息用。
- 两个核心操作
- enqueueMessage:往链表插入消息,按when 执行时间插队排序,时间早的排前面。
- next() :Looper 调用,不断阻塞取出下一条要执行的消息。
- 阻塞等待原理没有消息 / 还没到执行时间时,底层通过 Linux epoll 阻塞休眠,不耗 CPU;等有新消息或时间到,唤醒继续取消息。
- 特殊消息屏障有同步屏障时,只处理异步 Message,跳过普通同步消息,用来保障 UI 绘制优先级。
- 一句话总结MessageQueue 底层是按时间排序的单向链表,靠epoll 阻塞休眠等待消息,Looper 循环从这里拿消息分发执行。
7、Message 底层原理 通俗极简总结
- 本质Message 就是消息载体,装任务、数据、执行时间、目标 Handler 的一个对象。
- 核心结构
- what:消息标识
- arg1/arg2:存简单整型数据
- obj:存任意对象
- when:任务要执行的时间
- target:绑定的目标 Handler
- next:链表指针,给 MessageQueue 做单向链表用
- 复用机制(核心)底层维护消息缓存池,是个静态链表;不用每次都 new Message,obtain () 从缓存池拿,用完 recycle () 放回池子,减少频繁创建 GC。
- 工作流程Handler.obtainMessage → 从缓存池取对象 → 赋值 → 入队 MessageQueue → Looper 取出 → 交给 target Handler 处理。
- 一句话总结Message 是带缓存池复用的消息封装对象,靠链表串联进队列,避免频繁 new,高效传任务和数据。
8、Android 延时消息原理 极简通俗总结
- 延时不是定时器轮询发
postDelayed/sendMessageDelayed时,不会开子线程倒计时。 - 只记一个未来执行时间系统把当前时间 + 延时时间,算出一个 when 执行时刻,存到 Message 里。
- 入队按时间排序Message 放进 MessageQueue 链表,按
when从小到大排队,时间越靠前排越前面。 - Looper 阻塞等待Looper 调用
next()取消息:
- 如果队头消息还没到执行时间,就阻塞休眠,不耗 CPU;
- 等到时间点到了,自动被唤醒,取出消息交给 Handler 执行。
- 一句话核心****延时只是记录未来时间,队列按时间排队,Looper 等到点再分发,不靠手动倒计时。
9、 volatile /synchronized/ Atomic 三者适用场景
先记住核心区别
- volatile:只保证可见性,不保证原子性
- synchronized:可见性 + 原子性 + 排他锁,啥都能管
- Atomic 原子类:无锁、高并发下保证可见性 + 原子性,比锁快
一、volatile 什么时候用
适用场景
单线程写,多线程读
- 主线程改静态 String、Boolean、Int
- 子线程只读取,不会去修改
- 只是做状态标记、标识值
你的场景正好匹配
主线程更新 static volatile String pickId网络子线程只拿来用、不修改 → 用 volatile 完全没问题
不能用 volatile 的场景
多线程同时修改同一个变量;或者:读→改→写 复合操作(比如 i++)。
二、synchronized 什么时候用
适用场景
- 多线程同时读 + 同时改
- 有复合操作:取值 → 计算 → 再赋值
- 要保证代码同一时间只能一个线程执行
特点
- 重量级、加锁,安全但性能稍差
- 既能保证可见性,又能防止并发错乱
三、Atomic 原子类 什么时候用
常用:AtomicBoolean、AtomicInteger、AtomicReference
适用场景
- 多线程高并发读写
- 不想用 synchronized 加锁、想要高性能
- 字符串共享变量多线程读写:用 AtomicReference
特点
无锁并发,底层 CAS,比同步锁效率高。
极简选型口诀
- 一写多读、只是状态 / 字符串标识 → volatile
- 多线程并发读写、要加锁保安全 → synchronized
- 多线程高频读写、要高性能 → Atomic 原子类
套你项目直接用
你现在:主线程改、网络子线程只读👉 直接用:
java
运行
public static volatile String pickId = "";
完全安全,不用加锁、不用原子类。