当前位置: 首页 > news >正文

内存泄漏修复示例

集成leakcanary

usdebugImplementation libs.leakcanary.android 
debuggable true

工具介绍

集成leakcanary之后,在运行过程中,如果有内存泄漏,会有弹窗提示,通过如下命令可启动泄漏日志页面

adb shell am start -n com.xxx.xxx/leakcanary.internal.activity.LeakLauncherActivity

泄漏修复示例

列举1.0版本中检测并修复的内存泄漏示例

1. BaseBleService非静态内部类MyBinder泄漏

泄漏日志如下:

┬───│ GC Root: Global variable in native code│├─ com.xxxxx.bluetooth.service.BaseBleService$MyBinder instance│    Leaking: UNKNOWN│    Retaining 2.2 kB in 20 objects│    this$0 instance of com.xxx.bluetooth.service.BaseBleService│    ↓ BaseBleService$MyBinder.this$0│                              ~~~~~~╰→ com.xxxxxx.bluetooth.service.BaseBleService instance•     Leaking: YES (ObjectWatcher was watching this because com.xxxx.bluetooth.service.BaseBleService received•     Service#onDestroy() callback and Service not held by ActivityThread)•     Retaining 1.6 kB in 19 objects•     key = eca2c1ba-6c10-43a1-90b0-1d12bc112f11•     watchDurationMillis = 11852•     retainedDurationMillis = 6640•     mApplication instance of com.xxxx.xxxx.MyApplication•     mBase instance of android.app.ContextImpl

大致定位到BaseBleService 的MyBinder 因持有this对象导致BaseBleService 泄漏

查看代码,在 BaseBleService 中有一个非静态的内部类 MyBinder ,在内部类中通过this隐式持有外部BaseBleService 实例,导致在 BaseBleService destroy的时候,但 MyBinder 仍持有其引用,阻止垃圾回收

BaseBleService.javapublic class MyBinder extends Binder {public MyBinder() {}public void clearDevice() {BaseBleService.this.mBleScanManager.clear();}...}

修改方案将MyBinder 改为静态内部类,并通过WeakReference对BaseBleService进行弱引用,从而解决泄漏

BaseBleService.javapublic static class MyBinder extends Binder {private WeakReference<BaseBleService> serviceRef;public MyBinder(BaseBleService service) {this.serviceRef = new WeakReference<>(service);}public void clearDevice() {BaseBleService baseBleService = serviceRef.get();if (baseBleService != null) {baseBleService.mBleScanManager.clear();}}....}

2. BaseBluetoothFragment onNewDataCallbackLister泄漏

泄漏日志:

                 ┬───│ GC Root: System class│├─ android.app.ActivityThread class│    Leaking: NO (ActivityThread↓ is not leaking and a class is never leaking)│    ↓ static ActivityThread.sCurrentActivityThread├─ android.app.ActivityThread instance│    Leaking: NO (BaseBleService↓ is not leaking and ActivityThread is a singleton)│    mInitialApplication instance of com.xxx.xxx.MyApplication│    mSystemContext instance of android.app.ContextImpl│    mSystemUiContext instance of android.app.ContextImpl│    ↓ ActivityThread.mServices├─ android.util.ArrayMap instance│    Leaking: NO (BaseBleService↓ is not leaking)│    ↓ ArrayMap.mArray├─ java.lang.Object[] array│    Leaking: NO (BaseBleService↓ is not leaking)│    ↓ Object[7]├─ com.xxxx.bluetooth.service.BaseBleService instance│    Leaking: NO (Service held by ActivityThread)│    mApplication instance of com.xxx.xxxx.MyApplication│    mBase instance of android.app.ContextImpl│    ↓ BaseBleService.lConnect│                     ~~~~~~~~├─ com.xxxxx.common.base.BaseBluetoothFragment$$ExternalSyntheticLambda3 instance│    Leaking: UNKNOWN│    Retaining 12 B in 1 objects│    ↓ BaseBluetoothFragment$$ExternalSyntheticLambda3.f$0│                                                      ~~~╰→ com.xxxx.xxx.ui.exercisepreparation.LandExercisePreparationFragment instance•     Leaking: YES (ObjectWatcher was watching this because com.xxxx.xxxx.ui.exercisepreparation.•     xxxxxxFragment received Fragment#onDestroy() callback. Conflicts with Fragment.•     mLifecycleRegistry.state is INITIALIZED)•     Retaining 1.2 MB in 3977 objects•     key = 39fc801c-5bf6-45ea-86ec-29621ba613aa•     watchDurationMillis = 5995•     retainedDurationMillis = 993

链路说明,LandExercisePreparationFragment 因BaseBluetoothFragment中的this对象被BaseBleService 中的lConnect持有,导致LandExercisePreparationFragment 在destroy的时候,因为还有持有链路而无法销毁,造成泄漏

结合代码


BaseBluetoothFragment.ktbinder.setConnectDeviceLister { connected ->LogUtils.v("蓝牙基类 -- > 设备连接监听 $connected  $activelyDisconnect ").......
}

BaseBluetoothFragment在 Service bind之后,通过lambda设置了setConnectDeviceLister 监听器,这个匿名内部类隐式的持有fragment的this对象,这个对象最终被baseBleService的lConnect持有。

修复方案

在 fragment销毁的时候,断开引用链路,将setConnectDeviceLister 设置为null


BaseBluetoothFragment.ktoverride fun onDestroy() {if (::binder.isInitialized) {binder.setNewDataLister(null)binder.setConnectDeviceLister(null)}}

3. MainFragment 因被静态变量持有导致泻漏

 ┬───│ GC Root: Thread object│├─ android.net.ConnectivityThread instance│    Leaking: NO (PathClassLoader↓ is not leaking)│    Thread name: 'ConnectivityThread'│    ↓ Thread.contextClassLoader├─ dalvik.system.PathClassLoader instance│    Leaking: NO (NetworkChange↓ is not leaking and A ClassLoader is never leaking)│    ↓ ClassLoader.runtimeInternalObjects├─ java.lang.Object[] array│    Leaking: NO (NetworkChange↓ is not leaking)│    ↓ Object[3764]├─ com.xxxx.common.utils.network.NetworkChange class│    Leaking: NO (a class is never leaking)│    ↓ static NetworkChange.listener│                           ~~~~~~~~╰→ com.xxxxx.main.MainFragment instance•     Leaking: YES (ObjectWatcher was watching this because com.xxxxx.main.MainFragment received•     Fragment#onDestroy() callback. Conflicts with Fragment.mLifecycleRegistry.state is INITIALIZED)•     Retaining 493.7 kB in 8263 objects•     key = ecc337e3-aef3-45eb-8995-8a2008ea86e4•     watchDurationMillis = 12461•     retainedDurationMillis = 7461

链路分析:MainFragment 因被NetworkChange 中的listener持有,导致onDestroy的时候无法回收

结合代码

public class NetworkChange {public static final String TAG = "NetworkChange";private static boolean isRegister = false;private static NetStateChangeObserver listener;public static void registerReceiver(Context context, NetStateChangeObserver lis) {listener = lis;........isRegister = true;}.....
}

MainFragment.ktNetworkChange.registerReceiver(requireContext(), this)

NetworkChange 中持有一个静态变量NetStateChangeObserver listener,在MainFragment registerReceiver的时候直接将this对象传递给了静态变量。导致在fragment销毁的时候,因为还被静态引用,所以无法销毁,导致泄漏

解决方案:在取消注册的时候,将静态listener置为null

    public static void unRegisterReceiver(Context context) {.......if (listener != null) {listener = null;}}

4.服务广播未解绑导致泻漏

 ┬───│ GC Root: System class│├─ android.provider.FontsContract class│    Leaking: NO (MyApplication↓ is not leaking and a class is never leaking)│    ↓ static FontsContract.sContext├─ com.xxxx.xxxx.MyApplication instance│    Leaking: NO (Application is a singleton)│    mBase instance of android.app.ContextImpl│    ↓ Application.mLoadedApk│                  ~~~~~~~~~~├─ android.app.LoadedApk instance│    Leaking: UNKNOWN│    Retaining 4.3 MB in 50494 objects│    mApplication instance of com.xxxx.xxxx.MyApplication│    Receivers│    ..xxxxActivity@342657624│    ....xxxxFragment$onCreate$usBroadcastReceiver$1@359382944│    ..MyApplication@319447736│    ....CJ@359062408│    ....v4@359062472│    ....ZV@359062536│    ....ProxyChangeListener$ProxyReceiver@359062600│    ....MediaRouter$VolumeChangeReceiver@359062664│    ....MediaRouter$WifiDisplayStatusChangedReceiver@359062720│    ....cs@359062776│    ....v4@359062840│    ....v4@359062904│    ....d@359062968│    ....C4@359063032│    ....VisibilityTracker@359063096│    ....RegisteredMediaRouteProviderWatcher$1@359063168│    ....FI@359063232│    ....jV@359063288│    ....z10@358194080│    ....NetworkTypeObserver$Receiver@359063392│    ....o@359063456│    ..ControllerService@319399632│    ....ControllerService$1@340346088│    ↓ LoadedApk.mServices│                ~~~~~~~~~├─ android.util.ArrayMap instance│    Leaking: UNKNOWN│    Retaining 4.3 MB in 50395 objects│    ↓ ArrayMap.mArray│               ~~~~~~├─ java.lang.Object[] array│    Leaking: UNKNOWN│    Retaining 4.3 MB in 50393 objects│    ↓ Object[2]│            ~~~╰→ com.xxxxx.main.MainActivity instance•     Leaking: YES (ObjectWatcher was watching this because com.xxx.main.MainActivity received•     Activity#onDestroy() callback and Activity#mDestroyed is true)•     Retaining 8.9 kB in 338 objects•     key = f273584b-ac53-45b9-926a-8c4dddbecb3d•     watchDurationMillis = 12488•     retainedDurationMillis = 7482•     mApplication instance of com.xxxx.xxxx.MyApplication•     mBase instance of androidx.appcompat.view.ContextThemeWrapper

日志分析,MainActivity 有已关联的广播,服务,但是在destroy的时候未注销关联,导致MainActivity 无法回收,可以看到 usBroadcastReceiver,ControllerService等等

结合代码

SocketService

/*** 绑定长连接服务*/
fun bindSocketService(context: Activity, serviceConnection: ServiceConnection){LogUtils.v("长链接  -->   绑定长连接服务")val service = Intent(context, SocketService::class.java)context.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE)
}

解决方案

在销货的时候添加解绑

/*** 解绑长连接服务*/
fun untieSocketService(context: Activity, serviceConnection: ServiceConnection){LogUtils.v("长链接  -->   解绑长连接服务")context.unbindService(serviceConnection)
}

usBroadcastReceiver:

val filter = IntentFilter()
filter.addAction(ACTION)
requireContext().registerReceiver(usBroadcastReceiver, filter)

解决方案

override fun onDestroy() {.....requireContext().unregisterReceiver(usBroadcastReceiver)
}

6.播发器SubtitleView 泄漏

 ┬───
09:37:07.203  D  │ GC Root: Thread object
09:37:07.203  D  │
09:37:07.203  D  ├─ android.net.ConnectivityThread instance
09:37:07.203  D  │    Leaking: NO (PathClassLoader↓ is not leaking)
09:37:07.203  D  │    Thread name: 'ConnectivityThread'
09:37:07.203  D  │    ↓ Thread.contextClassLoader
09:37:07.203  D  ├─ dalvik.system.PathClassLoader instance
09:37:07.203  D  │    Leaking: NO (GSYExoSubTitleVideoManager↓ is not leaking and A ClassLoader is never leaking)
09:37:07.203  D  │    ↓ ClassLoader.runtimeInternalObjects
09:37:07.203  D  ├─ java.lang.Object[] array
09:37:07.203  D  │    Leaking: NO (GSYExoSubTitleVideoManager↓ is not leaking)
09:37:07.204  D  │    ↓ Object[4815]
09:37:07.204  D  ├─ com.xxxxx.common.view.video.GSYExoSubTitleVideoManager class
09:37:07.204  D  │    Leaking: NO (a class is never leaking)
09:37:07.204  D  │    ↓ static GSYExoSubTitleVideoManager.videoManager
09:37:07.204  D  │                                        ~~~~~~~~~~~~
09:37:07.204  D  ├─ com.xxxx.common.view.video.GSYExoSubTitleVideoManager instance
09:37:07.204  D  │    Leaking: UNKNOWN
09:37:07.204  D  │    Retaining 460.0 kB in 3925 objects
09:37:07.204  D  │    context instance of com.xxxx.xxxx.MyApplication
09:37:07.204  D  │    ↓ GSYVideoBaseManager.playerManager
09:37:07.204  D  │                          ~~~~~~~~~~~~~
09:37:07.204  D  ├─ com.xxxx.common.view.video.GSYExoSubTitlePlayerManager instance
09:37:07.204  D  │    Leaking: UNKNOWN
09:37:07.204  D  │    Retaining 459.8 kB in 3919 objects
09:37:07.204  D  │    context instance of com.xxxx.xxx.MyApplication
09:37:07.204  D  │    ↓ GSYExoSubTitlePlayerManager.mediaPlayer
09:37:07.204  D  │                                  ~~~~~~~~~~~
09:37:07.204  D  ├─ com.xxx.common.view.video.GSYExoSubTitlePlayer instance
09:37:07.204  D  │    Leaking: UNKNOWN
09:37:07.205  D  │    Retaining 459.8 kB in 3918 objects
09:37:07.205  D  │    mAppContext instance of com.xxx.xxx.MyApplication
09:37:07.205  D  │    ↓ GSYExoSubTitlePlayer.mTextOutput
09:37:07.205  D  │                           ~~~~~~~~~~~
09:37:07.205  D  ├─ com.google.android.exoplayer2.ui.SubtitleView instance
09:37:07.205  D  │    Leaking: YES (View.mContext references a destroyed activity)
09:37:07.205  D  │    Retaining 375.1 kB in 3382 objects
09:37:07.205  D  │    View not part of a window view hierarchy
09:37:07.205  D  │    View.mAttachInfo is null (view detached)
09:37:07.205  D  │    View.mID = R.id.sub_title_view
09:37:07.205  D  │    View.mWindowAttachCount = 1
09:37:07.205  D  │    mContext instance of com.xxx.login.ui.EntranceActivity with mDestroyed = true
09:37:07.205  D  │    ↓ View.mContext
09:37:07.205  D  ╰→ com.xxx.login.ui.EntranceActivity instance
09:37:07.205  D  •     Leaking: YES (ObjectWatcher was watching this because com.xxx.login.ui.EntranceActivity received
09:37:07.205  D  •     Activity#onDestroy() callback and Activity#mDestroyed is true)
09:37:07.205  D  •     Retaining 47.4 kB in 942 objects
09:37:07.205  D  •     key = 49ed1eca-f233-43c3-96d4-37500f393238
09:37:07.205  D  •     watchDurationMillis = 8566
09:37:07.205  D  •     retainedDurationMillis = 3068
09:37:07.206  D  •     mApplication instance of com.xxx.xxx.MyApplication
09:37:07.206  D  •     mBase instance of androidx.appcompat.view.ContextThemeWrapper

泄漏日志分析:

EntranceActivity 无法回收,是因为被 SubtitleView 持有,此链路比较深,需要结合代码分析,

GSYExoSubTitlePlayerManager 中的GSYExoSubTitlePlayer mediaPlayer通过TextOutput mTextOutput的 SubtitleView

GSYExoSubTitleVideoManager(单例)→ GSYExoSubTitlePlayerManager → GSYExoSubTitlePlayer → SubtitleView → 已销毁的EntranceActivity。

`GSYExoSubTitlePlayer`中的`mTextOutput`(即`SubtitleView`)未及时释放,其`mContext`引用了已销毁的Activity。

单例`GSYExoSubTitleVideoManager`长期持有`PlayerManager`及`Player`实例,间接导致`SubtitleView`无法释放。

修复方案

GSYExoSubTitlePlayerManager.java@Override
public void release() {if (mediaPlayer != null) {.....mediaPlayer.setTextOutput(null);mediaPlayer.release();}}public static void releaseAllVideos() {if (GSYExoSubTitleVideoManager.instance().listener() != null) {GSYExoSubTitleVideoManager.instance().listener().onCompletion();}GSYExoSubTitleVideoManager.instance().releaseMediaPlayer();videoManager = null;
}

在播放器释放的时候将setTextOutput置空,断开Manager和 SubtitleView的引用,从而阻断SubtitleView中context对EntranceActivity的引用链路

7.Handler message未取消导致泄漏

泄漏日志:

┬───│ GC Root: System class│├─ android.app.ActivityThread class│    Leaking: NO (MessageQueue↓ is not leaking and a class is never leaking)│    ↓ static ActivityThread.sMainThreadHandler├─ android.app.ActivityThread$H instance│    Leaking: NO (MessageQueue↓ is not leaking)│    ↓ Handler.mQueue├─ android.os.MessageQueue instance│    Leaking: NO (MessageQueue#mQuitting is false)│    HandlerThread: "main"│    ↓ MessageQueue[1]│                  ~~~├─ android.os.Message instance│    Leaking: UNKNOWN│    Retaining 1.3 MB in 3549 objects│    Message.what = 0│    Message.when = 20885133 (134 ms after heap dump)│    Message.obj = null│    Message.callback = instance @322976080 of com.xxx.xxx.service.CadenceDeviceService$1│    Message.target = instance @321540304 of android.os.Handler│    ↓ Message.callback│              ~~~~~~~~├─ com.xxxx.bluetooth.service.CadenceDeviceService$1 instance│    Leaking: UNKNOWN│    Retaining 1.3 MB in 3547 objects│    Anonymous class implementing java.lang.Runnable│    this$0 instance of com.xxxx.bluetooth.service.CadenceDeviceService│    ↓ CadenceDeviceService$1.this$0│                             ~~~~~~╰→ com.xxx.bluetooth.service.CadenceDeviceService instance•     Leaking: YES (ObjectWatcher was watching this because com.xxx.bluetooth.service.CadenceDeviceService•     received Service#onDestroy() callback and Service not held by ActivityThread)•     Retaining 1.3 MB in 3546 objects•     key = 57d1c887-d6d4-46ad-b2fe-6670be28b88b•     watchDurationMillis = 7292•     retainedDurationMillis = 2291•     mApplication instance of com.xxx.xxx.MyApplication•     mBase instance of android.app.ContextImpl

因有未处理的message导致CadenceDeviceService 无法回收导致泄漏

结合代码

CadenceDeviceService.javaprivate Runnable mCounter =new Runnable() {@Overridepublic void run() {time++;hourMeterHandler.postDelayed(this,1000);}
};this.mHandler.postDelayed(new Runnable() {public void run() {CadenceDeviceService.this.mScanning = false;CadenceDeviceService.this.mBluetoothAdapter.stopLeScan(CadenceDeviceService.this.mLeScanCallback);}}, SCAN_PERIOD);

CadenceDeviceService中的mHandler,hourMeterHandler都有延时任务,这些任务在CadenceDeviceService销毁的时候,因为延时还没得到处理,从而阻止了CadenceDeviceService的回收

解决方案:

@Override
public void onDestroy() {super.onDestroy();hourMeterHandler.removeCallbacksAndMessages(null);mHandler.removeCallbacksAndMessages(null);
}

以上大概是总结的几种类型的泄漏和修复方案,类似的问题不重复展示,后续有典型的问题,继续补充

相关文章:

  • 101个α因子#27
  • 第4周_作业题_逐步构建你的深度神经网络
  • C++静态成员变量与对象生命周期解析
  • 前端学习笔记——Promis.All
  • HarmonyOS:帧率和丢帧分析实践
  • 齿轮,链轮,同步轮,丝杆传动sw画法
  • 十进制转二进制
  • Intel oneAPI对OpenCL 的支持
  • kafka在线增加分区副本数
  • OpenCV CUDA 模块图像过滤------创建一个高斯滤波器函数createGaussianFilter()
  • 【计算机网络 第8版】谢希仁编著 第五章运输层 题型总结1 UDP和TCP报文格式
  • 基于labview的声音采集与存储分析系统
  • python绘制股票K线
  • sockaddr结构体详解
  • 学习日记-day13-5.22
  • Python中accumulate方法
  • MySQL三种备份方式介绍
  • AI价值的冰与火之歌:企业数字化转型的迷雾与曙光
  • 探索微分方程的领域及AI推理
  • knife4j使用
  • 深圳做商城网站/怎么在网上推广产品
  • 网络供应商网站网址/青岛谷歌优化公司
  • 南昌网站开发培训班/百度总部地址
  • 临沂哪里做网站比较好/谷歌网页版入口在线
  • 常州网站建设机构/最新实时大数据
  • wordpress新闻网站主题/关键词优化推广公司哪家好