1、内存优化概述
我们最常见的内存问题有以下几种:
- 内存抖动:短时间内频繁分配和回收内存,直观表现是内存曲线呈现锯齿装
- 内存泄漏:仍然持有实际上已经没用的对象导致它不能被回收
- 内存溢出:可用内存不足,会导致程序崩溃
我们排查内存问题,可以使用以下一些工具:
- Android Studio的profiler
- Memory Analyzer(MAT)
- LeakCanary开源库
2、内存抖动实战
模拟内存抖动
class MainActivity : AppCompatActivity() {
private val handler = Handler(Looper.getMainLooper())
private val runnable = object: Runnable {
override fun run() {
val arr = Array(10000) { "$it" }
handler.postDelayed(this, 30)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
handler.post(runnable)
}
}
查看profiler
查看占用内存比较多的对象,看它们分配的调用栈
3、内存泄漏实战
模拟内存泄漏
interface Callback {
fun callback()
}
object CallbackManager {
val callbacks : MutableList<Callback> = ArrayList()
}
class LeakActivity : AppCompatActivity(), Callback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_leak)
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.girl)
findViewById<ImageView>(R.id.imageView).setImageBitmap(bitmap)
CallbackManager.callbacks.add(this)
}
override fun callback() {
}
}
不断地进入这个界面,然后看内存变化情况如下图
内存不断地增长,这时候如果record了
这时候可以初步确定出现了内存泄漏,也可以通过profiler初步排查,但是这样不太准确。我们可以通过dump来查看,也就是点这个按钮
可以生成一个Heap Dump记录,如下
点击进去就可以很明显地看出来是LeakActivity泄漏了
也可以导出.hprof文件,使用MAT查看,不过需要经过转换
hprof-conv /Users/qianyuelin/develop/source/mine/Performance/memory.hprof /Users/qianyuelin/develop/source/mine/Performance/converted.hprof
4、Bitmap大图检测
Bitmap是造成很多内存问题的罪魁祸首,不过随着各种开源库如Glide等的不断完善,现在已经好很多了。但是Bitmap对于内存优化来说还是不可忽视。如果加载一个Bitmap到比它宽高要小的View上,那么就可能存在内存浪费。我们可以通过ARTHook来检测这种情况.
首先添加依赖
implementation 'me.weishu:epic:0.11.0'
添加代码
class App: Application() {
override fun onCreate() {
super.onCreate()
DexposedBridge.hookAllConstructors(ImageView::class.java, object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
DexposedBridge.findAndHookMethod(ImageView::class.java, "setImageBitmap", Bitmap::class.java, ImageHook())
}
})
}
class ImageHook : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam) {
super.afterHookedMethod(param)
val bitmap = param.args[0] as Bitmap
val imageView = param.thisObject as ImageView
if(imageView.width <= 0) {
imageView.post { check(bitmap, imageView) }
} else {
check(bitmap, imageView)
}
}
private fun check(bitmap: Bitmap, imageView: ImageView) {
if(imageView.width < bitmap.width && imageView.height < bitmap.height) {
Log.w("BigImage", "View size: ${imageView.width} * ${imageView.height}, image size: ${bitmap.width} * ${bitmap.height}", Throwable())
}
}
}
}
然后加载一个大图片到一个小的ImageView,可以看到如下log
2021-06-25 03:10:33.977 28343-28343/com.sahooz.performance W/BigImage: View size: 275 * 275, image size: 2343 * 3520
java.lang.Throwable
at com.sahooz.performance.App$ImageHook.check(App.kt:37)
at com.sahooz.performance.App$ImageHook.access$check(App.kt:21)
at com.sahooz.performance.App$ImageHook$afterHookedMethod$1.run(App.kt:29)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:7876)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
5、线上内存监控方案
- 方案一:在特定场景下(比如说内存占用超过80%),Debug.dumpHprofData()生成文件,在合适的时机(比如说Wi-Fi连接的时候)回传服务器,然后开发者手动导入内存分析工具来分析。
- 方案二:修改LeakCanary,带到线上
6、参考
本文主要参考了慕课网这个课程