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导致的内存泄漏啊......

上一篇下一篇

猜你喜欢

热点阅读