一、可能遇到的问题
1.1、主线程加载大图
问题: 加载高分辨率图片,比如当你在 XML 布局文件中直接设置 android:background
属性引用高分辨率图片时,系统会在主线程加载和解码这张图片。
解决方法:使用 Glide 异步加载图片
scss
Glide.with(context)
.load(imageUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);
1.2、主线程读取配置文件
问题:解析大型 JSON/XML 配置文件,比如有时候,我们会将一些网络的配置数据放到文件里面,那么读取配置文件就会占用主线程
解决方法:使用协程异步读取
scss
lifecycleScope.launch(Dispatchers.IO) {
val config = assets.open("config.json").bufferedReader().use { it.readText() }
val parsedConfig = Json.decodeFromString<Config>(config)
withContext(Dispatchers.Main) {
applyConfig(parsedConfig)
}
}
1.3、主线程读取SP
同上,我们读取数据的时候,也要使用协程来代替。
1.4、字体加载
同上,我们读取数据的时候,也要使用协程来代替。 ResourcesCompat.getFont阻塞主线程,当通过 ResourcesCompat.getFont()
或 XML 属性加载时,会在主线程执行。
问题代码
ini
val typeface = ResourcesCompat.getFont(context, R.font.roboto_medium)
或
val typeface = Typeface.createFromAsset(
context.assets,
"fonts/roboto_medium.ttf"
)
textView.typeface = typeface
使用协程实现异步加载
scss
// 异步加载字体
val typeface by lazy {
runBlocking(Dispatchers.IO) {
ResourcesCompat.getFont(context, R.font.large_font)
}
}
还有一些网络操作,或者数据库也有可能导致耗时,不过现在越来越先进了,这两个不加协程,都会编译错误提示你。
二、使用工具帮助检测哪里可能存在耗时的地方
使用StrictMode,在Application的oncreate里面配置
less
// 线程策略
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads() // 检测磁盘读取
.detectDiskWrites() // 检测磁盘写入
.detectNetwork() // 检测网络操作
.detectCustomSlowCalls() // 检测自定义耗时调用
.penaltyLog() // 违规时打印日志
//.penaltyDeath()
.build()
)
// VM策略
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() // 检测 SQLite 对象泄漏
.detectLeakedClosableObjects() // 检测未关闭的 Closable 对象泄漏
.detectActivityLeaks() // 检测 Activity 泄漏
.penaltyLog() // 违规时打印日志
//.penaltyDeath()
.build()
)
然后在控制台搜索StrictMode即可找到可能存在耗时的地方。
注意:我们现在的手机内存是越来越大了,但你的手机不代表别人的手机,根据监控报错,还是有很多人的手机配置比较低,所以我们要把这些当回事。
可以看到,上述的情况,大部分都是通过异步加载来解决问题的。但有些情况是异步解决不了的。
- 死锁,锁竞争
- 视图布局过于复杂:测量(onMeasure)、布局(onLayout)、绘制(onDraw)过程耗时过长。
这些就需要,优化代码逻辑才能解决。
因此,解决ANR需要结合异步化 、代码优化 和性能监测等一系列手段。