目录
一、Android异常
1.1、ANR
1.1.1、什么是ANR
ANR(Application Not Responding)是安卓系统在应用程序未及时响应时触发的无响应对话框。默认情况下,Activity的最长响应时间为5秒,BroadcastReceiver的最长响应时间为10秒。若超时未完成操作,系统将强制弹出ANR对话框,影响用户体验。ANR的本质是主线程被耗时操作阻塞。以下条件都可以造成ANR发生:
- InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件
- BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
- Service Timeout :前台服务20秒内,后台服务在200秒内没有执行完毕。
- ContentProvider Timeout :ContentProvider的publish在10s内没进行完。
1.1.2、ANR的主要原因
ANR产生的核心原因可归纳为以下两点:
- 主线程执行耗时I/O操作:包括网络请求、文件读写等,Android - 0后禁止主线程网络操作。
- 主线程存在其他阻塞操作:如复杂计算或线程等待(Thread.sleep())。
根本原因均为主线程无法及时处理UI事件。解决方案需依赖Handler机制将耗时任务转移至子线程,主线程仅处理UI更新。
1.1.3、主线程的操作
- Activity的所有生命周期回调方法
- Service的默认执行线程(需使用IntentService处理耗时任务)
- BroadcastReceiver的onReceive()方法
- 未关联子线程Looper的Handler回调(HandlerMessage/postRunnable)
- AsyncTask的回调方法(除doInBackground外)
1.1.4、ANR的解决方案
ANR的解决方案包括:
- 使用AsyncTask处理I/O或网络请求
- 通过Thread或HandlerThread提升子线程优先级(避免与主线程同级)
- 利用Handler实现工作线程与主线程通信
- 避免在Activity的onCreate/onResume中执行耗时操作
1.2、OOM
1.2.1、什么是OOM
OOM(Out of Memory)异常指当前占用的内存加上申请的内存资源超过了Dalvik虚拟机设定的最大内存限制。安卓系统为每个APP分配独立的工作区间(Dalvik虚拟机空间),若内存占用超过限制,系统会抛出OOM错误。常见场景为加载大尺寸Bitmap时触发。
1.2.2、易混淆概念
①、内存溢出
内存溢出即OOM,表现为内存占用超过虚拟机最大限制,导致程序崩溃。
②、内存抖动
内存抖动由短时间内大量对象创建并快速释放引发,频繁触发GC(垃圾回收),导致堆内存压力增大。
③、内存泄漏
内存泄漏指无用对象仍被GC Roots引用,导致无法回收。长期积累可能引发OOM。三者中OOM最严重,直接影响程序稳定性。
1.2.3、如何解决OOM
OOM解决主要分为两类:Bitmap优化和非Bitmap优化
①、Bitmap
图片显示
- 加载图片时需匹配显示需求,例如列表滑动时暂停加载大图,仅显示缩略图。
及时释放内存
- Bitmap占用JAVA和C两部分内存,需主动调用recycle()释放C层内存,避免累积占用。
图片压缩
- 大图加载前需计算缩放比例(inSampleSize),减少内存占用。
inBitmap属性
- inBitmap属性复用已分配内存区域,减少重复申请,提升解码效率。
捕获异常
- 捕获OOM需捕获Error而非Exception,因OOM属于错误类型。
②、其它
以ListView为例:ListView需使用convertView复用机制,结合LRU缓存Bitmap。避免在getView()中频繁创建对象,防止内存抖动。多进程技术可扩展内存范围,但需谨慎使用以避免逻辑复杂性。
还有注意避免由内存泄漏引发的问题,避免内存泄漏的产生,比如:单例对象的Context问题、非静态内部类持有外部类引用的问题、广播未反注册问题等,平时开发中可以集成LeakCanary自动捕获。
二、性能优化
推荐阅读:Android性能优化专栏
2.1、Bitmap
①、recycle方法
recycle方法用于释放Bitmap占用的内存,包括Java堆内存和Native内存。在Android3.0之前,Bitmap像素数据与对象一起存放在堆中,回收时仅需清理堆内存;而3.0之后,Bitmap直接存储在Native内存,需调用recycle()方法主动释放。
源码分析:recycle()会释放Native内存并清理数据对象引用,但并非立即执行,而是向垃圾回收器发送指令,待无其他引用时触发回收。调用后,Bitmap标记为dead状态,再调用其方法(如getPixels()或setPixels())会引发异常。
注意事项:
- 操作不可逆,需确保Bitmap后续不再使用。
- 官方建议优先依赖垃圾回收器自动管理,仅在特定场景主动调用。
②、LRU
LRU(最近最少使用)算法通过LinkedHashMap实现缓存管理,核心逻辑如下:
- 数据结构:使用final LinkedHashMap存储强引用缓存对象。
- 核心方法:
- trimToSize():当缓存满时,移除最久未使用的对象并添加新对象。内部通过循环判断当前尺寸是否超过最大值,调用remove()方法逐项清理。
- put():添加缓存对象时同步计算尺寸,若超限则触发trimToSize()。
- remove():删除指定缓存对象并更新尺寸,支持自定义逻辑的空方法entryRemoved()。
- 面试要点:LRU通过LinkedHashMap的访问顺序特性实现缓存淘汰策略,核心是维护访问时序与尺寸控制。
③、计算inSampleSize
inSampleSize用于计算Bitmap加载时的缩放比例,避免直接加载大图导致OOM。实现步骤:
- 获取原始宽高,默认比例设为1。
- 比较目标尺寸与原始尺寸,取宽高缩放比例的最小值作为最终inSampleSize。
- 关键参数:通过BitmapFactory.Options.inJustDecodeBounds设为true可仅读取图片尺寸而不加载像素数据,优化计算效率。
④、缩略图
缩略图生成依赖inSampleSize的缩放比例,具体流程:
- 设置BitmapFactory.Options.inJustDecodeBounds=true,仅解码图片边界信息。
- 根据计算出的inSampleSize调整目标尺寸。
- 将inJustDecodeBounds设为false,按比例加载缩略图至内存。
⑤、三级缓存
三级缓存结构(网络→本地→内存)优化图片加载效率:
- 首次请求:从网络获取图片,并保存至本地(SD卡)和内存缓存。
- 重复请求:优先检查内存缓存,未命中则查询本地缓存,最后回退至网络。
- 核心价值:减少重复网络请求,提升加载速度并节省用户流量。
2.2、UI卡顿
2.2.1、UI卡顿原理
UI卡顿的核心原理与60 FPS和16毫秒的渲染机制相关。
①、FPS
- 60 FPS是安卓系统规定的流畅帧率标准,即每秒60帧,换算为每帧16毫秒的渲染时间窗口。
- 若程序操作未在16毫秒内完成,会导致丢帧现象,引发卡顿。
- 卡顿常见原因包括:复杂布局嵌套、过多背景叠加、动画执行次数过多等造成的CPU/GPU负载过重。
- GC操作会暂停所有线程,若在16毫秒渲染窗口内发生大量GC,会导致渲染超时,进而引发卡顿。
②、overdraw
- 过度绘制指同一像素在同一帧时间内被多次绘制,常见于多层UI结构或设置invisible属性的视图。
- 开发者可通过手机GPU调试工具观察过渡绘制情况,优化目标是减少深红色区域,优先呈现蓝色区域。
- overdraw主因是UI布局中存在非必要重叠背景,例如Activity、Layout及子View均设置独立背景时,移除冗余背景可显著降低过渡绘制。
总结:UI卡顿本质是渲染性能不足,具体表现为:
- 布局复杂度超标导致16毫秒内无法完成渲染。
- 资源重叠或冗余引发过渡绘制。
- 动画或GC操作占用过多系统资源。
2.2.2、UI卡顿原因分析
①、UI线程轻微耗时操作:UI线程执行轻微耗时操作(如轻量级计算)会阻塞渲染,需通过工作线程异步处理避免卡顿。
②、布局过于复杂:嵌套过深或结构复杂的布局会延长测量(measure)与摆放(layout)时间,需在需求评审阶段与UI设计师协同简化。
③、动画执行次数过多:高频动画会加剧CPU/GPU负载,需权衡视觉效果与性能消耗。
④、过度绘制:冗余背景或视图重叠导致像素多次绘制,需通过代码优化减少无效渲染。
⑤、频繁触发measure、layout:频繁调用measure()/layout()会触发视图树重新渲染,可通过自定义View替代复杂嵌套布局以降低开销。
⑥、内存频繁GC:内存抖动引发GC会中断渲染线程,需优化对象分配策略。
⑦、冗余资源及逻辑等导致加载和执行缓慢:低效代码逻辑或冗余资源加载会延迟渲染,需通过代码重构提升执行效率。
⑧、ANR产生:主线程耗时操作直接触发ANR,需严格遵循异步任务规范。
2.2.3、卡顿优化总结
①、布局优化
- 使用include/merge/ViewStub标签减少嵌套。
- 优先用GONE替代INVISIBLE以避免无效绘制。
- 采用相对位置替代固定宽高以简化计算。
- 复杂布局建议改用自定义View。
②、列表及Adapter优化
- 复用getView()中的实例,避免重复创建。
- 列表滑动时暂停数据加载,仅显示缩略图或默认值。
③、背景和图片等内存分配优化
- 移除冗余背景,压缩图片资源。
- 控制GC触发时机,避免渲染窗口内回收内存。
④、避免ANR
- 耗时操作必须通过子线程处理,推荐使用HandlerThread、IntentService等异步框架。
2.3、内存泄漏
2.3.1、Java内存泄漏
JAVA内存泄漏指未被释放的对象因被实例持有而无法被垃圾回收。
①、Java内存分配策略
JAVA内存分配策略分为三类:
- 静态存储区:存放静态数据和全局变量,内存分配在编译时完成,变量生命周期与程序一致。
- 栈区:存储方法体内的局部变量,方法结束后自动释放,效率高但容量有限。
- 堆区:存储通过new创建的对象,由垃圾回收器管理内存释放。
栈区与堆区的区别:
- 栈区存储基本类型变量和对象引用变量,作用域结束后内存自动释放。
- 堆区存储对象实例和数组,内存由垃圾回收器回收。
②、Java是如何管理内存的
JAVA内存管理的核心是对象分配与释放:
- 对象通过new关键字在堆中分配内存。
- 内存释放由垃圾回收器(GC)自动完成。
GC工作原理:
- 监控对象引用关系,将对象视为有向图中的顶点。
- 根顶点(如main方法)可达的对象为有效对象,不可达对象可被回收。
③、Java中的内存泄漏
JAVA内存泄漏定义为无用对象持续占用内存且无法释放,导致内存浪费。满足以下条件即视为内存泄漏:
- 对象在引用图中可达。
- 对象已无实际用途。
- GC无法回收该对象。
后果:内存泄漏积累可能导致内存溢出(OOM)。
2.3.2、Android内存泄漏
①、单例
单例模式因生命周期与应用一致,若持有Activity的引用会导致Activity无法回收。正确写法应使用Application Context而非Activity Context。
②、匿名内部类
非静态内部类默认持有外部类引用,若创建静态实例会导致外部类无法回收。
解决方案:将内部类声明为static。
③、Handler
Handler因持有Activity引用且消息队列未处理完,会导致Activity无法回收。解决方案:
- 将Handler改为静态内部类。
- 使用弱引用持有外部类。
④、避免使用static变量
静态变量生命周期与应用一致,可能导致内存占用过高。优化建议:
- 避免静态变量初始化时加载。
- 对必需静态变量进行生命周期管理。
⑤、资源未关闭造成的内存泄漏
未关闭资源(如广播、游标、流等)会导致内存泄漏。解决方法:在Activity销毁时主动关闭或注销资源。
⑥、AsyncTask造成的内存泄漏
AsyncTask因非静态内部类持有外部类引用,需在onDestroy中调用cancel终止任务。
补充:
- Bitmap需调用recycle释放C层内存。
- 集合类需及时清理无用引用。
2.4、内存管理
2.4.1、内存管理机制概述
- 内存是操作系统调度的数据存储区域,现代多进程操作系统中内存管理至关重要。
- 分配机制:操作系统为每个进程分配合理内存大小,确保进程正常运行。
- 回收机制:系统内存不足时,通过杀死占用内存的进程回收资源,并将副作用降到最低。
- 安卓系统内存管理与PC端不同,移动设备内存资源更少,需更谨慎管理。
2.4.2、安卓内存管理机制
安卓内存管理机制分为分配机制和回收机制两部分。
①、分配机制
- 弹性分配方式:系统初始为APP分配小额内存,根据设备物理尺寸动态调整。
- 分配上限:额外内存分配有限制,系统最大限度让更多进程存活于内存中,减少应用启动时间。
②、回收机制
- 进程优先级分类:
- 前台进程:屏幕显示的进程。
- 可见进程:用户仍可见的进程。
- 服务进程:运行服务的进程(如定位、推送)。
- 后台进程:后台计算的进程。
- 空进程:无运行内容的进程,可随时回收。
- 回收原则:优先级越低,被杀死概率越高;前台、可见及服务进程通常不会被杀死。
- 回收效率:系统倾向于杀死能回收更多内存的进程,减少对用户体验的影响。
2.4.3、内存管理机制的特点
- 更小的项目内存占用:提升用户体验。
- 合理释放系统资源:避免频繁释放导致内存抖动。
- 内存紧张时释放非重要资源:为系统提供可用内存。
- 保存重要数据:便于APP重启时直接复用。
2.4.4、内存优化
①、Service
- 推荐方案:使用IntentService替代普通Service
- 优势:自动创建子线程处理耗时任务,执行完毕自动退出
②、UI资源释放
通过onTrimMemory()回调在UI不可见时释放资源
③、回收非重要资源
根据内存紧张等级释放不同级别资源。
④、Bitmap优化
- 压缩Bitmap:根据设备分辨率调整。
- 及时回收Bitmap:调用recycle方法或使用软引用缓存。
⑤、数据结构优化
- 推荐SparseArray替代HashMap:减少内存占用。
- 避免使用枚举:内存消耗较高,内存消耗是常量两倍多。
⑥、框架选择
慎用ButterKnife等框架,依赖注入框架会有额外开销,扫描注解消耗系统资源。
⑦、APK优化
使用zipalign工具压缩资源,减少运行时内存占用
⑧、多进程策略
- 分离高内存模块:如定位、推送或WebView进程。
- 注意数据传输与安全问题:多进程需谨慎使用。
2.4.5、内存溢出与内存泄漏
- 内存溢出(OOM):常见于未压缩Bitmap,解决方法包括压缩图片、使用inBitmap属性等。
- 内存泄露:对象未被GC回收,通常因错误引用(如非静态内部类持有外部类引用)。
- 检测工具:LeakCanary或MAT分析内存映像文件。
2.5、冷启动优化
2.5.1、什么是冷启动
①、冷启动定义
冷启动指应用启动前系统中不存在该应用的任何进程信息(包括Activity、Service等)。典型场景包括:
- 设备开机后首次启动应用
- 应用进程被杀死后重新启动
- 此类场景下应用启动时间最长,需完成全部初始化工作。
②、冷启动和热启动的区别
| 对比维度 | 冷启动 | 热启动 |
| 定义 | 系统需创建新进程 | 直接复用后台现有进程 |
| 初始化流程 | 需创建Application类及MainActivity | 仅需创建MainActivity |
| 性能消耗 | 高(需完整初始化) | 低(跳过Application初始化) |
关键差异:冷启动会触发Application类的构造方法及onCreate(),而热启动直接进入Activity生命周期。
举例说明:在MyApplication的onCreate()中设置3秒延时(Thread.sleep(3000))
- 冷启动:首次启动出现3秒白屏(执行Application初始化)
- 热启动:点击Home键返回后重新进入,无白屏(跳过Application初始化)
③、冷启动时间的计算
- 计算范围:从应用启动(创建进程)开始,到完成视图第一次绘制(Activity内容对用户可见)为止
- 测量方法:Android 4.4+通过logcat自动打印启动时间
2.5.2、冷启动流程
冷启动流程分为四个阶段:
- 进程创建:系统从Zygote进程fork新应用进程
- 组件初始化:依次创建Application类及MainActivity
- 布局加载:执行inflate布局操作
- 界面渲染:通过measure、layout、draw三阶段显示ContentView
总结:完整冷启动调用链如下:
- Application构造方法 → attachBaseContext() → onCreate()
- Activity生命周期:onCreate() → onStart() → onResume()
- 最终通过视图测量、布局、绘制完成界面渲染
2.5.3、冷启动时间优化
冷启动优化核心策略包括:
- 减少初始化工作:将第三方SDK改为懒加载(需要时再初始化),权衡懒加载可能带来的运行时延迟
- 避免业务操作:Application只初始化全局必需数据,不放置业务模块相关数据初始化
- 避免耗时操作:禁止在Application中进行IO操作(如读取文件/SD卡)
- 避免静态变量:不在Application中使用静态变量保存数据(防止内存泄漏)
- 优化布局:减少布局复杂性和层级深度,使用ViewStub实现按需加载
- 异步初始化:将资源初始化延迟或放入子线程执行
2.6、其它优化
①、不使用静态变量存储数据
- 静态变量存储数据存在风险:安卓应用进程不稳定,可能被系统回收,导致静态变量数据丢失。
- 进程回收机制:进程被终止后,系统会重新创建Application对象并恢复用户离开时的Activity,造成应用未被终止的假象,但静态变量数据已重置。
- 替代方案:推荐使用文件、SharedPreferences或ContentProvider传递数据,若通过Intent传递参数,需对变量进行非空检查以避免空指针异常。
②、SharedPreferences安全问题
| 问题类型 | 具体表现 | 影响 |
| 跨进程同步问题 | 多进程读写时,各进程维护独立副本,无法实时同步数据。 | 数据不一致,需应用结束后才持久化修改。 |
| 文件过大问题 | 存储大量键值对时,主线程可能阻塞,引发UI卡顿;解析时产生临时对象,导致频繁GC。 | 内存抖动、内存泄漏,甚至OOM;高频访问的Key与低频Key混存影响性能。 |
| 使用建议 | 避免跨进程读写;控制文件大小;分离高频与低频Key。 | 确保数据安全性与性能优化。 |
③、内存对象的序列化
- 序列化定义:将对象状态转换为可存储或传输的形式。
- 安卓序列化方式:
- Serializable:Java标准方式,但生成临时变量易引发频繁GC,影响性能。
- Parcelable:安卓特有方式,内存操作性能更优,但不支持磁盘存储且底层变更可能导致数据不可读。
- 选择建议:进程间通信优先用Parcelable;需持久化数据时改用Serializable。
④、避免在UI线程中做繁重的操作
- UI线程限制:耗时操作(如网络请求、数据库读写)会导致动画延迟和界面卡顿。
- 优化方法:使用异步框架(如AsyncTask、HandlerThread)或工具(StrictMode)追踪卡顿点。
- 设备性能瓶颈:移动端并发IO处理能力有限,即使应用空闲,其他应用的IO操作仍可能引发ANR或网络延迟。
- 核心原则:UI线程仅处理核心UI逻辑,耗时任务必须异步执行。
OK,关于更多其它的优化还是推荐阅读:Android性能优化专栏的内容,那今天的内容就这么多了,下期再会!
792

被折叠的 条评论
为什么被折叠?



