Android内存泄漏解决流程
2020-11-04 本文已影响0人
巴黎没有摩天轮Li
前言
“A small leak will sink a great ship.” - Benjamin Franklin
Step1 - Find Leak
我们在日常开发的时候,除了自己在编写代码的时候注意,但未免还是会出现泄漏的情况,可见一个自动捕捉内存泄漏的重要性。
Tools - LeakCanary
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
ok,一行代码解决,初始化时通过ContentProvider,并且时在Debug才会生效。
Tools - MAT
mat相比于LeakCanary稍微麻烦,并且没有后者的UI提醒展示,需要自己去发掘定位点,不过mat提供了相对醒目的堆信息分配饼图展示。
image.png
首先,将堆转储一下,将.hprof文件保存一下,这时候的.hprof文件并不能被mat打开,
而是需要将文件通过Android SDK中的platform-tools 中的hprof-conv脚本转一下。
命令转
mat
正则匹配泄漏class
找到该类被谁引用
找到引用的类的GCRoot 弱引用等除外
泄漏点找到了
案例分析
我司用了车牌扫描的OCR,实则是第三方SDK造成的内存泄漏,不过这边我记录下分析过程。
2020-11-04 14:16:52.028 5318-8372/com.xxx D/LeakCanary:
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ====================================
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: HEAP ANALYSIS RESULT
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ====================================
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: 2 APPLICATION LEAKS
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: References underlined with "~~~" are likely causes.
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Learn more at https://squ.re/leaks.
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: 193757 bytes retained by leaking objects
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Signature: 4278cbd4494049851ebec273d84d9d93819e469
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ┬───
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ GC Root: Global variable in native code
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Leaking: NO (LPRManager$LazyHolder↓ is not leaking and A ClassLoader is never leaking)
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ↓ PathClassLoader.runtimeInternalObjects
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ├─ java.lang.Object[] array
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Leaking: NO (LPRManager$LazyHolder↓ is not leaking)
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ↓ Object[].[1652]
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ├─ exocr.lpr.LPRManager$LazyHolder class
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Leaking: NO (a class is never leaking)
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ↓ static LPRManager$LazyHolder.INSTANCE
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ~~~~~~~~
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ├─ exocr.lpr.LPRManager instance
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Leaking: UNKNOWN
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Retaining 379738 bytes in 4132 objects
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ mContext instance of com.xxx.module.self.carenjoyment.view.CarEnjoymentInputCarNoActivity with mDestroyed = true
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ↓ LPRManager.mContext
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ~~~~~~~~
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ╰→ com.xxx.module.self.carenjoyment.view.CarEnjoymentInputCarNoActivity instance
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Leaking: YES (ObjectWatcher was watching this because com.xxx.module.self.carenjoyment.view.
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: CarEnjoymentInputCarNoActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Retaining 193757 bytes in 3100 objects
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: key = e9801daf-2a1f-4f71-9269-735fde0b51d3
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: watchDurationMillis = 6712
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: retainedDurationMillis = 1708
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: mContext instance of com.xxx.module.self.carenjoyment.view.CarEnjoymentInputCarNoActivity with mDestroyed = true
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: mApplication instance of com.xxx.EhaiApplication
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: mBase instance of android.app.ContextImpl, not wrapping known Android context
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: 53850 bytes retained by leaking objects
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Signature: 93e9a0d17de54d359228d39b68810ccb2228b8c
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ┬───
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ GC Root: Global variable in native code
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Leaking: NO (LPRManager$LazyHolder↓ is not leaking and A ClassLoader is never leaking)
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ↓ PathClassLoader.runtimeInternalObjects
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ├─ java.lang.Object[] array
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Leaking: NO (LPRManager$LazyHolder↓ is not leaking)
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ↓ Object[].[1652]
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ├─ exocr.lpr.LPRManager$LazyHolder class
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Leaking: NO (a class is never leaking)
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ↓ static LPRManager$LazyHolder.INSTANCE
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ~~~~~~~~
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ├─ exocr.lpr.LPRManager instance
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Leaking: UNKNOWN
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Retaining 379738 bytes in 4132 objects
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ mContext instance of com.xxx.module.self.carenjoyment.view.CarEnjoymentInputCarNoActivity with mDestroyed = true
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ↓ LPRManager.recoHandler
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ~~~~~~~~~~~
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ├─ exocr.lpr.CardRecoActivity$2 instance
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Leaking: UNKNOWN
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Retaining 53882 bytes in 694 objects
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ Anonymous subclass of android.os.Handler
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ this$0 instance of exocr.lpr.CardRecoActivity with mDestroyed = true
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ↓ CardRecoActivity$2.this$0
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: │ ~~~~~~
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ╰→ exocr.lpr.CardRecoActivity instance
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Leaking: YES (ObjectWatcher was watching this because exocr.lpr.CardRecoActivity received Activity#onDestroy()
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: callback and Activity#mDestroyed is true)
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Retaining 53850 bytes in 693 objects
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: key = 33bc1d1e-df80-4a34-b8ac-7aba952445ad
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: watchDurationMillis = 9994
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: retainedDurationMillis = 4993
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: mApplication instance of com.xxx.EhaiApplication
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: mBase instance of android.app.ContextImpl, not wrapping known Android context
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ====================================
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: 0 LIBRARY LEAKS
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: ====================================
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: METADATA
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Please include this in bug reports and Stack Overflow questions.
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Build.VERSION.SDK_INT: 29
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Build.MANUFACTURER: OPPO
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: LeakCanary version: 2.5
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: App process name: com.xxx
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Stats: LruCache[maxSize=3000,hits=6508,misses=91808,hitRate=6%]
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: RandomAccess[bytes=5054703,reads=91808,travel=44562833587,range=28229152,size=33999969]
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Analysis duration: 8657 ms
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Heap dump file path: /data/user/0/com.xxx/files/leakcanary/2020-11-04_14-16-40_008.hprof
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Heap dump timestamp: 1604470612004
2020-11-04 14:16:52.029 5318-8372/com.xxx D/LeakCanary: Heap dump duration: 2849 ms
Step2 - Find Leak from program
我们看下代码,看看是怎么泄漏的。
recognize()方法最终跳转到了SDK中的扫车牌界面。
内部类单例
private void startDeteted() {
this.bCamera = hardwareSupportCheck();
if (this.bCamera) {
// 最终进行了页面跳转
Intent intent = new Intent(this.mContext, CardRecoActivity.class);
this.mContext.startActivity(intent);
} else {
if (this.dataCallBack != null) {
this.dataCallBack.onCameraDenied();
}
if (this.mViewEvent != null) {
this.mViewEvent.onCameraDenied();
}
}
}
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
// 省略部分代码
CardRecoActivity.this.restartPreview();
}
};
原来还是比较常见的Handler导致的内存泄漏啊......