1.数据结构优化
1.1 ArrayList 原理
查找快,删除,新增元素慢,数组越大,性能越差
1.2 LinkedList 原理
查找慢,删除,新增快
1.3 HashMap原理
扩容机制规则会浪费25%的内存(扩容因子0.75)
1.4 Android特有的数据结构
SparseArray->两个数组,一个存Key,一个存Value;由于存储数据有序,利用二分查找法 性能更高。且数据越多,性能优势越明显;内部还有延迟删除机制。
局限性:key只能是整数
注意:如果value为int值,使用SparseIntArray,效率更高
根据需求使用合理的数据结构,能有效的提高APP性能
2.内存优化
2.0 内存抖动
内存波动图呈锯齿状、频繁GC导致卡顿(STW)
2.1 内存泄漏:
程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费。 长生命周期对象持有短生命周期对象强引用,从而导致短生命周期对象无法被回收!
2.2 内存溢出:
OOM,内存泄漏会导致内存溢出;file文件资源等未释放也会导致内存溢出
2.3 内存回收的判断方法
可达性分析法:GC Root
2.4 四大引用
强引用,弱引用,软引用,虚引用
2.5 常见内存泄漏场景
-- 资源性对象未关闭(文件等)
-- 注册对象未注销(如广播,EventBus等)
-- 类的静态变量持有大数据对象(静态变量为GCRoot点)
-- 单例造成的内存泄漏
-- 非静态内部类的静态实例
-- Handler临时性内存泄漏
-- 集合中的对象只有添加没有删除造成的内存泄漏
-- WebView造成的内存泄漏(理解泄漏原理)
-- Thread导致的内存泄漏,Thread生命周期不可控,可能导致引用了短生命周期的
2.6 内存泄漏检测工具
-- MAT(Memory Analyzer Tool),一款Eclipse开发的内存检测工具,可检测对象的引用链,和现存的对象个数等。
-- Android Studio 的 Profiler 可检测实时内存使用情况,并可调用GC,判断是否有内存泄漏现象
-- LeakCanary
原理:注册Activity和Fragment的监听,在OnDestroy时,添加Activity或Fragment到WeakReference(与ReferenceQueque关联)中,接着从ReferenceQueque中查看是否有该对象,如果没有,则放入观察队列,再调用GC,如果还没有释放,则添加到内存泄漏队列,大于5个时,dump heap文件,利用haha分析内存。再用一个View的服务显示出内存泄漏的位置
-- StrickMode,严苛模式,可设置内存泄漏监听,当发生内存泄漏时,会打印相关的日志
3.启动优化
3.1 启动时间统计
adb shell am start -S -W packageName/className
total time:启动该APP的时间
wait time:包含暂停上个Activity到启动该APP的时间
主要看total time
3.2 启动方式
-- 冷启动:第一次打开APP
-- 热启动:Activity在内存中,把存在的Activity带到前台;按home键,再点击该应用
-- 温启动:退出了APP,但是进程还未被杀掉。再次打开该应用,不用重新创建进程
启动时间;冷启动 > 温启动 > 热启动
Google启动计划中,冷启动5秒左右,温启东2秒左右,热启动1.5秒左右
3.3 检测方法耗时工具
Android studio Profiler 进行函数调用时间跟踪
设置APP -> Edit Configurations -> Profilling -> Start this recording on startup: java/kotlin Method Trace
再点击profile运行
开始跟踪后,点击Stop结束跟踪
结束后可选择要观察的时间段,右侧点击Top Down 可看到每个函数执行的额时间,根据此时间结合代码做对应的优化
3.4 StrickMode
严苛模式监听不合理的代码,可优化速度
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectCustomSlowCalls() .detectDiskWrites() .penaltyLog() .build()) StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .detectActivityLeaks() .penaltyLog() .penaltyDeath() .build())
3.5常见优化方案
-- Activity可采用异步加载布局文件AsyncLayoutInflater
-- 合理的使用异步初始化,延迟初始化,懒加载机制
-- 启动过程避免耗时操作,如数据库I/O操作不要放在主线程执行
-- 类加载优化,提前异步执行类加载
-- 合理使用IdleHandler进行延迟初始化
-- 简化布局,见4,布局和卡顿优化
4.布局优化
4.1 Layout Inspector
查看布局层级,点击眼睛 选择第一项可过滤系统的布局
4.2 常见的布局优化
-- 使用 ViewStub 来延迟加载布局。
-- 使用 include 标签来重用布局。
-- 使用 merge 标签来优化布局文件。
-- 使用 ConstraintLayout 来减少布局层级。
-- 使用 Lint 来检查布局文件中的问题。
5.卡顿优化
5.1 systrace
5.2 Profiler的Sys trace
VSync的方波周期就表示帧率,如发现连续的长周期方波,可通过CPU Usage选择执行长 的这一段,查看具体执行情况。可查看sys trace的具体用法
5.3 Looper的Logging打印每次消息的运行时间
原理如源码所示,在Looper中的loop方法中,queue.next()后,消息执行前和执行后都有相应日志打印,日志的时间间隔,即为消息执行完的大致时间,可根据此时间判断是否有超时执行情况
使用方法:创建一个类实现Printer接口,实现println方法即可,统计第一次打印日志到第二次打印日志的时间
class LogMonitor: Printer { private var writeFlag = false private var time:Long = 0 private val TAG = "LogMonitor" override fun println(x: String?) { x?.let { Log.e(TAG,it) } writeFlag =!writeFlag if (writeFlag){ time = System.currentTimeMillis() }else{ var useTime = System.currentTimeMillis() - time Log.e(TAG,"use time ${useTime}") } } }
在APP中初始化
Looper.getMainLooper().setMessageLogging(LogMonitor())
5.4 利用Choreographer的回调监听每一帧执行的时间
Choreographer.getInstance().postFrameCallback(object:Choreographer.FrameCallback{ private var lastTime:Long = 0 override fun doFrame(frameTimeNanos: Long) { if (lastTime == 0L){ lastTime = frameTimeNanos Choreographer.getInstance().postFrameCallback(this) return } var subTime = frameTimeNanos - lastTime subTime = subTime/1000000 if (subTime > 16.6f){ Log.e(TAG,"lost frame ${subTime / 16.6f}") } lastTime = frameTimeNanos Choreographer.getInstance().postFrameCallback(this) } })
6.电量优化
6.1 Doze低电耗模式
Doze中文是打盹,系统会定期退出打盹一小会时间,让应用完成其延迟的活动。在此维护期间,系统会运行所有待处理的同步,作业和闹钟,并允许应用访问网络。
随着时间的推移,系统安排的维护期的次数越来越少,这有助于长期处于不活动状态时降低耗电量。
用户可通过移动设备,打开屏幕或连接至充电器唤醒设备,系统就会立即退出低电耗模式,且所有应用都会恢复正常活动。
6.2 WorkManager
WorkManager 是适合用于持久性工作的推荐解决方案。如果工作始终要通过应用重启和系统重新启动来调度,便是持久性的工作。由于大多数后台处理操作都是通过持久性工作完成的,因此 WorkManager 是适用于后台处理操作的主要推荐 API。
详情请参考:应用架构:数据层 - 使用 WorkManager 调度任务 - Android 开发者 | Android Developers
使用:WorkManager 使用入门 | Android 开发者 | Android Developers
6.3 Battery Historian2.0
环境搭建:电量优化Battery Historian2.0 配置 - 简书
6.4 Profiler的ENERGY
可查看用电的柱状图
6.5 电量优化总结
·减少操作:应用是否存在可删减的多余操作?是否可以缓存已下载的数据,而不是每次重新下载
·推迟操作:应用是否需要立即执行某项操作?是否可以等到设备充电后或者WiFi连接时再将数据备份到云端
·合并操作:工作是否可以批处理,而不是多次将设备置于活动状态?比如请求的接口,部分接口是否可以合并为一个
7.网络优化
7.1.HTTPDNS
HTTPDNS是面向多端应用(移动端APP,PC客户端应用)的域名解析服务,解析请求基于HTTP(S)协议,有效解决了传统域名解析容易被劫持、解析不准确、更新不及时、服务不稳定等问题。
阿里HTTPDNS入口:HTTPDNS_域名解析_域名防劫持_开发与运维-阿里云
7.2.常见的网络优化方案
-- 连接优化:keep-alive = true 保持长链接,复用socket,减少重新建立连接的开销
HTTP1只能复用一个,HTTP2可多路复用,OKHTTP3支持HTTP2
-- 数据压缩:可以用protobuf数据格式传输数据;开启gzip数据压缩 content-encoding = gzip
-- 使用webp代替png/jpg
-- 不同网络的不同图片下发:如原图(300*300)
2/3g使用低清晰度图片:100*100
4G判断信号强度,强:300*300,中:200*200;弱:100*100
WiFi:300*300
-- HTTP开启缓存:如首页数据加入缓存
DoraemonKit模拟弱网环境工具
8.APK瘦身
-- 开启代码混淆 minifyEnabled = true;开启后不止是混淆了代码,未使用的代码不会打进包里
-- 开启资源混淆 shrinkResources = true;开启后,未用的资源不会打进包里
-- Lint 检测未用资源
-- productFlavors 分渠道打包
-- 开启split abi 拆分机制
splits {
abi {
enable true
reset()
include 'arm64-v8a','armeabi-v7a'
universalApk true
}
}
-- 小图标使用矢量图,阿里矢量图库 iconfont-阿里巴巴矢量图标库
-- 添加lite版的依赖;如protobuf-lite
-- 分模块引入依赖
-- 插件化加载不常用的模块
-- 资源混淆;AndResGuard
9.ANR问题分析
9.1.ANR类型
9.1.1.KeyDispatchTimeout(常见)
input事件在5S内没有处理完成发生了ANR
logcat日志关键字:Input event dispatching timed out
9.1.2.BroadcastTimeout
前台Broadcast:onReceiver在10s内没有处理完成发生ANR
后台Broadcast: onReceiver在60s内没有处理完成发生ANR
logcat日志关键字:Timeout of broadcast BroadcastRecord
9.1.3.Service Timeout
前台Service:onCreate,onStart, onBind 等生命周期在20s内没有处理完成发生ANR
后台Service:同上在200s内没有处理完成发生ANR
logcat日志关键字:Timeout executing service
9.1.4.ContentProvider Timeout
ContentProvider在10s内没有处理完成发生ANR
logcat日志关键字:timeout publishing content providers
9.2.常见出现ANR的场景
-- 主线程频繁进行耗时的IO操作,如数据库写入
-- 多线程操作的死锁,主线程被block
-- 主线程被Binder对端block
-- System Server中WatchDog出现ANR
-- service Binder的连接达到上限无法和System server 通信
-- 系统资源耗尽(管道、CPU、IO)
9.3.ANR问题解决方法
/data/anr/trace_*.txt ANR日志路径
先 Cmd line:包名,找到我们的应用
首先看是否是资源耗尽、IO阻塞导致的ANR
死锁导致的可搜索:held by关键字
应用层的ANR可能在main调用链里面指定出现ANR的位置
9.4.自己实现ANR监控方案
9.4.1.FileObserver:
监控某个目录/文件,状态发生改变,创建,删除文件
监控 data/anr,是否有创建,有的话就表示有ANR发生,可上传trace文件,但不一定是我们 的APP发生了ANR。
6.0之后就被禁止了,可通过手机厂商更改配置打开监听
9.4.2.WatchDog方案
WatchDog是个开源的框架,GitHub地址:https://github.com/SalomonBrys/ANR-WatchDog
原理:启动一个异步线程,在while循环中,使用主线程的Handler发送一个消息,线程休眠 指定的时间5s,当线程唤醒之后,如果发送的消息还没被主线程执行,即认为主线程发生了 卡顿。
优点兼容性好,非侵入式
缺点:检测并不准确