2026小知识点(9)

28 阅读10分钟

1、什么是ANR?

ANR = Application Not Responding,应用无响应。

一句话解释

Android 系统检测到主线程(UI 线程)长时间卡住不响应,超过规定时间阈值,系统弹出应用无响应弹窗,可选择等待或强制关闭。

触发本质

Android 所有 UI 刷新、事件分发都在主线程;一旦主线程被耗时操作阻塞,没法处理用户点击、绘制界面,系统判定卡死,就触发 ANR。

常见触发场景阈值
  1. 按键 / 触摸事件(主线程阻塞):5 秒 没响应 → ANR
  2. 广播 onReceive:前台 10s / 后台 60s
  3. Service 回调:20 秒 处理不完
核心产生原因
  1. 主线程做耗时操作网络请求、文件 IO、数据库查询、循环计算、解析大 JSON / 大图。
  2. 主线程死锁 / 等待同步锁不当、Handler 死循环、等待子线程未唤醒。
  3. UI 卡顿绘制超时布局嵌套过深、View 过度绘制、自定义 View onDraw 耗时。
  4. 广播 / 服务处理耗时onReceive、Service onCreate/onStartCommand 写耗时逻辑。
  5. 内存问题频繁 GC、内存泄漏导致卡顿、主线程调度被抢占。
通用解决方法
  1. 耗时全丢子线程IO、网络、数据库、复杂计算 全部放到子线程 / 协程 / 线程池。
  2. 主线程只做 UI 刷新只负责控件赋值、跳转、弹窗,不做任何阻塞操作。
  3. 优化布局与绘制减少布局嵌套、去掉过度绘制、onDraw 避免创建对象、少做耗时运算。
  4. 广播 / 服务轻量化onReceive 只发消息、启线程、抛事件,不做耗时业务。
  5. 避免主线程死锁合理使用同步锁,不主线程等待子线程结果。
  6. 内存优化解决内存泄漏、大图压缩复用、减少频繁对象创建,降低 GC 卡顿。
  7. 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完全没运行过、后台被系统彻底杀掉、手机重启后第一次点开,就是冷启动,是最慢的一种启动。

冷启动完整流程(通俗版)
  1. 第一步:系统创建 App 进程你点击图标,系统发现这个 App连进程都没有,先单独给它开辟一个新进程。
  2. 第二步:创建并初始化 Application进程建好后,先跑 Application,执行它的构造方法、onCreate,全局配置、SDK 初始化都在这里走一遍。
  3. 第三步:启动目标 ActivityApplication 初始化完,系统开始创建目标 Activity,走完整生命周期:onCreate → onStart → onResume
  4. 第四步:加载布局、绘制页面解析 XML 布局、加载图片、渲染 UI,最后把页面显示到屏幕上。
核心特点
  • 要经历:创建进程 → 初始化 Application → 初始化 Activity → 加载布局渲染 UI 全套流程。
  • 啥都是从零新建,没有任何缓存复用,所以最慢、最容易卡顿
为啥冷启动容易慢?

Application 里塞太多 SDK 初始化、主线程做耗时操作、布局太复杂、Activity onCreate 写太多耗时逻辑,都会拖慢冷启动。

4、如何优化Activity的启动速度?

一、先懂核心

Activity 启动慢,90% 都是主线程堵车了,初始化、加载数据、布局太复杂,卡住 UI 渲染。

二、具体优化方法(按优先级)
1. 优化 Application 初始化
  1. 别在 Application 主线程做耗时操作第三方 SDK、初始化、数据库、大图预加载,全部丢子线程、懒加载
  2. 非必要 SDK 延迟初始化,用到再初始化,不要一打开 App 全加载。
2. 优化布局 XML
  1. 减少布局嵌套,别多层 LinearLayout 嵌套,多用 ConstraintLayout。
  2. 减少冗余 View,页面不用的控件删掉,不要写大量默认隐藏的 View。
  3. ViewStub 延迟加载 不立即显示的布局(详情页、弹窗布局)。
  4. 避免 过度绘制、自定义 View onDraw 做耗时计算、频繁 new 对象。
3. 优化 Activity 生命周期
  1. onCreate 只做必须的网络请求、大数据解析、复杂业务逻辑不要放 onCreate,丢子线程 / 协程。
  2. 能延迟加载的内容,等页面渲染完再加载,不要阻塞首屏。
  3. 避免在主线程做 IO、数据库、循环计算、解析大 JSON
4. 首屏体验优化
  1. 启动页占位图,避免白屏、黑屏,视觉上感觉秒开。
  2. 首屏先加载骨架屏 / 默认占位,数据回来再刷新,不阻塞页面显示。
5. 资源与图片优化
  1. 图片压缩、用 WebP,避免大图一次性加载进内存。
  2. 用图片框架异步加载,不要自己在主线程解码图片。
6. 其他小优化
  1. 减少 Activity 启动时的 Intent 大数据传递,避免 Binder 卡顿。
  2. 避免内存泄漏、频繁 GC,GC 频繁也会拖慢启动。
  3. 合理使用 懒加载、按需初始化,不用不创建。
三、一句话总结
  1. App 全局别在 Application 瞎初始化
  2. 布局别嵌套、冗余控件砍掉、ViewStub 懒加载
  3. onCreate 不做耗时,网络 / IO 全丢子线程
  4. 首屏做占位,视觉秒开

5、Handler运行机制

Handler 运行机制 通俗极简总结

  1. 四大角色 Handler、Message、MessageQueue、Looper
  2. 通俗流程
  • Looper:就是一个死循环,不停从消息队列里取消息。
  • MessageQueue:就是消息排队的篮子,所有消息都放这里排队。
  • Message:就是要传递的任务 / 消息
  • Handler:就是发消息、接消息、处理任务的工具。
  1. 整体运转流程子线程用 Handler 发消息 → 消息进入 MessageQueue 排队Looper 死循环不断取出消息 → 交给 Handler 回调处理 → 切到主线程更新 UI。
  2. 一句话核心Looper 转圈轮询、队列存消息、Handler 收发消息,实现子线程发任务、主线程更新 UI。

6、MessageQueue 底层原理 通俗极简总结

  1. 本质是什么MessageQueue 不是普通队列,底层是单向链表,按执行时间排序,不是按插入顺序排队。
  2. 核心作用存放 Handler 发过来的 Message,给 Looper 循环取消息用。
  3. 两个核心操作
  • enqueueMessage:往链表插入消息,按when 执行时间插队排序,时间早的排前面。
  • next() :Looper 调用,不断阻塞取出下一条要执行的消息。
  1. 阻塞等待原理没有消息 / 还没到执行时间时,底层通过 Linux epoll 阻塞休眠,不耗 CPU;等有新消息或时间到,唤醒继续取消息。
  2. 特殊消息屏障同步屏障时,只处理异步 Message,跳过普通同步消息,用来保障 UI 绘制优先级。
  3. 一句话总结MessageQueue 底层是按时间排序的单向链表,靠epoll 阻塞休眠等待消息,Looper 循环从这里拿消息分发执行。

7、Message 底层原理 通俗极简总结

  1. 本质Message 就是消息载体,装任务、数据、执行时间、目标 Handler 的一个对象。
  2. 核心结构
  • what:消息标识
  • arg1/arg2:存简单整型数据
  • obj:存任意对象
  • when:任务要执行的时间
  • target:绑定的目标 Handler
  • next:链表指针,给 MessageQueue 做单向链表用
  1. 复用机制(核心)底层维护消息缓存池,是个静态链表;不用每次都 new Message,obtain () 从缓存池拿,用完 recycle () 放回池子,减少频繁创建 GC
  2. 工作流程Handler.obtainMessage → 从缓存池取对象 → 赋值 → 入队 MessageQueue → Looper 取出 → 交给 target Handler 处理。
  3. 一句话总结Message 是带缓存池复用的消息封装对象,靠链表串联进队列,避免频繁 new,高效传任务和数据。

8、Android 延时消息原理 极简通俗总结

  1. 延时不是定时器轮询postDelayed/sendMessageDelayed 时,不会开子线程倒计时
  2. 只记一个未来执行时间系统把当前时间 + 延时时间,算出一个 when 执行时刻,存到 Message 里。
  3. 入队按时间排序Message 放进 MessageQueue 链表,按 when 从小到大排队,时间越靠前排越前面
  4. Looper 阻塞等待Looper 调用 next() 取消息:
  • 如果队头消息还没到执行时间,就阻塞休眠,不耗 CPU;
  • 等到时间点到了,自动被唤醒,取出消息交给 Handler 执行。
  1. 一句话核心****延时只是记录未来时间,队列按时间排队,Looper 等到点再分发,不靠手动倒计时。

9、 volatile /synchronized/ Atomic 三者适用场景

先记住核心区别

  1. volatile:只保证可见性不保证原子性
  2. synchronized:可见性 + 原子性 + 排他锁,啥都能管
  3. Atomic 原子类:无锁、高并发下保证可见性 + 原子性,比锁快

一、volatile 什么时候用

适用场景

单线程写,多线程读

  • 主线程改静态 String、Boolean、Int
  • 子线程只读取,不会去修改
  • 只是做状态标记、标识值

你的场景正好匹配

主线程更新 static volatile String pickId网络子线程只拿来用、不修改 → 用 volatile 完全没问题

不能用 volatile 的场景

多线程同时修改同一个变量;或者:读→改→写 复合操作(比如 i++)。


二、synchronized 什么时候用

适用场景

  1. 多线程同时读 + 同时改
  2. 复合操作:取值 → 计算 → 再赋值
  3. 要保证代码同一时间只能一个线程执行

特点

  • 重量级、加锁,安全但性能稍差
  • 既能保证可见性,又能防止并发错乱

三、Atomic 原子类 什么时候用

常用:AtomicBoolean、AtomicInteger、AtomicReference

适用场景

  1. 多线程高并发读写
  2. 不想用 synchronized 加锁、想要高性能
  3. 字符串共享变量多线程读写:用 AtomicReference

特点

无锁并发,底层 CAS,比同步锁效率高。


极简选型口诀
  1. 一写多读、只是状态 / 字符串标识volatile
  2. 多线程并发读写、要加锁保安全synchronized
  3. 多线程高频读写、要高性能Atomic 原子类

套你项目直接用

你现在:主线程改、网络子线程只读👉 直接用:

java

运行

public static volatile String pickId = "";

完全安全,不用加锁、不用原子类。