LeakCanary 利用办法你实的用对了吗?

刚刚阅读1回复0
kewenda
kewenda
  • 管理员
  • 注册排名1
  • 经验值176125
  • 级别管理员
  • 主题35225
  • 回复0
楼主

序言

不成承认,Square 公司出品的缓存外泄SBML LeakCanary 能很便利快速的查验出 App 中存有的缓存外泄难题。当他们下定决心要万万别在工程项目中导入 LeakCanary 的时候,时常也会听见人声:

“LeakCanary 收集毗连单纯,无须全手动挪用。”“LeakCanary 虽好,但是太卡。”“LeakCanary 虽好,但难以圣戈当斯区选用。”

曾一度我也是所以认为的,曲至我深切详尽科学研究了下才辨认出,汗青事实可能并没有所以单纯。责任编纂是试著从 LeakCanary 的许多高阶用语,来再次深切科学研究前述的观点。 variations会附有完整标识符,可间接选用。

想选用 LeakCanary 的许多高阶用语,详细来说是必要他们积极主动掌控 LeakCanary 的挪用更佳时机,加进许多自订的适用性,上面就看呵呵如何全手动挪用 LeakCanary ?

如何全手动挪用 LeakCanary ?

恒定情况下,他们若是加进上面带队标识符,就能在 App 中选用 LeakCanary 了。

dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation com.squareup.leakcanary:leakcanary-android:2.9.1 } 手动挪用

那是是不是勤奋做到的?是选用了 ContentProvider 的读取监视机造来做的。单纯讲大致营业流程如下表所示:

先继续施行 Application中的attachBaseContext 表达式;接着会继续施行 ContentProvider 中的 onCreate 表达式;最末才会走到 Application 中的 onCreate 表达式中;

那上面就看呵呵 LeakCanary 是是不是手动挪用的,详细来说是在 AndroidManifest.xml 文档中新闻稿:

<application> <provider android:name="leakcanary.internal.MainProcessAppWatcherInstaller" android:authorities="${applicationId}.leakcanary-installer" android:enabled="@bool/leak_canary_watcher_auto_install" android:exported="false" /> </application>

有一个必要高度存眷的点是,provider 的 enabled 情况是通过天然资本文档中的位来下定决心的,那是停行利用手动挪用的关键性。MainProcessAppWatcherInstaller 表述如下表所示:

internal class MainProcessAppWatcherInstaller : ContentProvider() { override fun onCreate(): Boolean { val application = context!!.applicationContext as Application AppWatcher.manualInstall(application) return true } }

由此可见,挪用的次要体例论是 AppWatcher.manualInstall(application) 表达式。其表述大致如下表所示:

@JvmOverloads fun manualInstall( application: Application, retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) ) {

appDefaultWatchers 中是预设适用性高度存眷缓存外泄的类别,全力撑持的有 Activity、Fragment、RootView 和 Service。

全手动挪用

想对 LeakCanary 加进许多自订的适用性,就必要停行利用手动挪用的体例论,上面也有提到在天然资本文档中加进 leak_canary_watcher_auto_install **值即可,如下表所示:

<?xml version="1.0" encoding="utf-8"?> <resources> <bool name="leak_canary_watcher_auto_install">false</bool> </resources>

全手动挪用的时候,他们就能按照本身的必要加进想查验的类别,若是他们不想查验 RootView 的类别,则能如下表所示表述:

val watchersToInstall = AppWatcher.appDefaultWatchers(application) .filter { it !is RootViewWatcher } AppWatcher.manualInstall( application = application, watchersToInstall = watchersToInstall )

挪用的时候确实是能勤奋做到开箱即用,关于想延迟挪用以及自订适用性的话,也能很便利快速的全力撑持。

上面就会起头摸索如何处理 LeakCanary 卡顿相关的难题。

如何处理卡顿?

LeakCanary 形成卡顿的原因是在主历程中 dump hprof 文档,.hprof 凡是会有上百兆,整个过程至少会持续 20 秒(中位数)以上。所以在那个过程中,用户有任何繁琐的操做城市使 App 不胜重负表示卡顿,若是是性能差的老机器,什么都不操做都可能呈现 ANR 的难题。

针对前述难题通过用的处理计划是把整个 dump hprof 文档的过程放到一个零丁的历程中做,如许就会尽可能少的影响主历程的操做。快手开源的 KOOM 库选用的也是那种体例,当然 LeakCanary 自己也供给了多历程的体例。

选用 leakcanary-android-process

选用时必要导入 leakcanary-android-process 模块,如下表所示:

dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation com.squareup.leakcanary:leakcanary-android-process:2.9.1 }

此依赖包中选用 WorkManager 来处置跨历程通信,处置的体例也长短常巧妙,若是加进依赖就能勤奋做到跨历程。大致思绪如下表所示:

在 leakcanary-android-process 包中表述 RemoteLeakCanaryWorkerService 并在 AndroidManifest 文档中新闻稿为零丁的历程;leakcanary-android-core 包中会判断 RemoteLeakCanaryWorkerService 类能否存有,如存有则选用 WorkManager 启动子历程停止 Dump 操做,不然在子线程中处置。

此中 RemoteLeakCanaryWorkerService 表述如下表所示:

<manifest xmlns:android="<http://schemas.android.com/apk/res/android>" package="com.squareup.leakcanary"> <application> <service android:name="leakcanary.internal.RemoteLeakCanaryWorkerService" android:exported="false" android:process=":leakcanary" /> </application> </manifest>

选用 WorkManager dump 缓存的体例论如下表所示:

// EventListener 是 LeakCanary 的事务回调,那里仅仅处置了 Dump 缓存的事务 object RemoteWorkManagerHeapAnalyzer : EventListener { private const val REMOTE_SERVICE_CLASS_NAME = "leakcanary.internal.RemoteLeakCanaryWorkerService" override fun onEvent(event: Event) { if (event is HeapDump) { val application = InternalLeakCanary.application val heapAnalysisRequest = OneTimeWorkRequest.Builder(RemoteHeapAnalyzerWorker::class.java).apply { val dataBuilder = Data.Builder() .putString(ARGUMENT_PACKAGE_NAME, application.packageName) .putString(ARGUMENT_CLASS_NAME, REMOTE_SERVICE_CLASS_NAME) setInputData(event.asWorkerInputData(dataBuilder)) with(WorkManagerHeapAnalyzer) { addExpeditedFlag() } }.build() SharkLog.d { "Enqueuing heap analysis for ${event.file} on WorkManager remote worker" } val workManager = WorkManager.getInstance(application) workManager.enqueue(heapAnalysisRequest) } } }

最末效果如下表所示,在 dump 事务前后,打印日记的历程由 25405 酿成 25426。

选用 KOOM

除了选用 LeakCanary 自带的跨历程计划之外,还能选用 KOOM 库中的一个包 koom-fast-dump ,在 LeakCanary 的适用性体例如下表所示:

LeakCanary.config = LeakCanary.config.copy( heapDumper = HeapDumper { // 核心标识符就那带队,留意此体例会期待子历程返回收罗成果,万万别在UI线程挪用! ForkJvmHeapDumper.getInstance().dump(it.absolutePath) })

LeakCanary 预设的 dump 选用的是 Debug.dumpHprofData() ,标识符如下表所示:

object AndroidDebugHeapDumper : HeapDumper { override fun dumpHeap(heapDumpFile: File) { Debug.dumpHprofData(heapDumpFile.absolutePath) } }

选用 koom-fast-dump 与 LeakCanary 自带的包 leakcanary-android-process 效果是一样的,城市切换到子历程,日记如下表所示:

小结

无论是选用 koom-fast-dump 仍是 leakcanary-android-process,都能处理 LeakCanary dump 缓存时卡顿的难题。预设情况下,选用 leakcanary-android-process愈加便利快速,若是是想想自订 HeapDump 相关体例论话,选用 koom-fast-dump会相对单纯一点。

通过上面的介绍可知,LeakCanary 能通过适用性 Config 来自订 HeapDump 体例论,除此之外还能监听 LeakCanary 的次要事务,接着做许多他们想的工作,好比把相关难题上传到 Crash 平台或者是量量平台上,便利快速从宏不雅的角度治理缓存外泄难题。

如何在圣戈当斯区选用?

处理了卡顿难题之后,在圣戈当斯区选用 LeakCanary 似乎也不是所以高不可攀了,上面他们看呵呵如何在圣戈当斯区选用 LeakCanary。

想在圣戈当斯区选用 LeakCanary 首要要确定以下难题:

如何获取 LeakCanary 阐发缓存外泄的成果?缓存外泄的成果以何种形式上报到量量平台上?如何确定合理的监控收罗更佳时机,勤奋做到尽可能小的影响用户?监听 LeakCanary 事务

监听 LeakCanary dump 以及缓存阐发事务能通过 LeakCanary.Config 停止适用性,SDK 内部内置了呵呵监听器,如下表所示:

object LeakCanary { data class Config( // ... val eventListeners: List<EventListener> = listOf( LogcatEventListener, ToastEventListener, LazyForwardingEventListener { if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener }, when { RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath -> RemoteWorkManagerHeapAnalyzer WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer else -> BackgroundThreadHeapAnalyzer } ), ) { } }

能看出,他们在控造台看到的日记打印(LogcatEventListener)、App中的通知提醒(NotificationEventListener)等体例论都是在此处适用性的。包罗上面提到选用子历程 dump 缓存的体例论是在 RemoteWorkManagerHeapAnalyzer 内部实现的。

他们想获得对应的阐发成果也必要通过此体例。他们通过实现 EventListener 接口即可获取对接的成果,实现大致如下表所示:

private class RecordToService : EventListener { /** * SDK 内部事务回调,能在此处过滤出缓存外泄的成果 */ override fun onEvent(event: EventListener.Event) { if (event !is EventListener.Event.HeapAnalysisDone<*>) { return } if (event is EventListener.Event.HeapAnalysisDone.HeapAnalysisSucceeded) { record(event.heapAnalysis) } } /** * 处置缓存外泄的成果 */ private fun record(heapAnalysis: HeapAnalysisSuccess) { val allLeaks = heapAnalysis.allLeaks // 处置成果 } }

事务表述好之后通过以下适用性停止挪用:

class LeakCanaryConfig { // 挪用适用性 fun init(app: Application) { val eventListeners = LeakCanary.config.eventListeners.toMutableList().apply { // 将他们自订的事务加进到事务列表中,也能按照本身的需求删除许多圣戈当斯区不必要的事务 add(RecordToService()) } LeakCanary.config = LeakCanary.config.copy( eventListeners = eventListeners ) } }

到那了他们就已经可以拿到 LeakCanary 阐发的缓存外泄成果了。但是那里的成果,跟他们日常平凡选用的 Crash 上报信息其实不能间接婚配,因为那里并没有间接能选用的仓库信息,必要他们本身停止拼接。

上面就看呵呵如何通过 LeakCanary 中的信息构造对应的 Throwable。

构建 Throwable

那部门根本没有什么难点,间接根据 LeakTrace 对象中的字段停止拼接即可,上面是完整的标识符。

internal class LeakCanaryThrowable(private val leakTrace: LeakTrace) : Throwable() { override val message: String get() = leakTrace.leakingObject.message() override fun getStackTrace(): Array<StackTraceElement> { val stackTrace = mutableListOf<StackTraceElement>() stackTrace.add(StackTraceElement("GcRoot", leakTrace.gcRootType.name, "GcRoot.kt", 42)) for (cause in leakTrace.referencePath) { stackTrace.add(buildStackTraceElement(cause)) } return stackTrace.toTypedArray() } private fun buildStackTraceElement(reference: LeakTraceReference): StackTraceElement { val file = reference.owningClassName.substringAfterLast(".") + ".kt" return StackTraceElement(reference.owningClassName, reference.referenceDisplayName, file, 0) } private fun LeakTraceObject.message(): String { return buildString { append("辨认出缓存外泄难题,") append( if (retainedHeapByteSize != null) { val humanReadableRetainedHeapSize = humanReadableByteCount(retainedHeapByteSize!!.toLong()) "$className, Retaining $humanReadableRetainedHeapSize in $retainedObjectCount objects." } else { className } ) } } private fun humanReadableByteCount(bytes: Long): String { val unit = 1000 if (bytes < unit) return "$bytes B" val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() val pre = "kMGTPE"[exp - 1] return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre) } }

将仓库打印出来的效果如下表所示:

LeakCanaryThrowable 构建后之后就能按照本身团队选用的 Crash 上报 SDK 停止上传了。

调整监控战略

到目前为行 LeakCanary 固然能在子历程 dump缓存而且阐发成果了,但是在圣戈当斯区版本运行几对性能仍是有些影响的。为了尽可能削减那些影响,就必要调整 LeakCanary 监控的更佳时机了,尽量是在用户不选用现阶段 App 的时候停止处置。

可能的场景是 App 切到后台或者是手机息屏时才起头处置相关的使命,LeakCanary 也供给了应该的东西包,详细来说必要导入 leakcanary-android-release 包,如下表所示:

dependencies { // LeakCanary for releases releaseImplementation com.squareup.leakcanary:leakcanary-android-release:${leakCanaryVersion} }

上面就必要对之前的 LeakCanaryConfig 类停止革新了,必要加进 BackgroundTrigger 以及 ScreenOffTrigger ,那两个触发器的体例论大致如下表所示:

class LeakCanaryConfig { fun init(app: Application) { // App 进入后台触发器 BackgroundTrigger( application = app, analysisClient = analysisClient, analysisExecutor = analysisExecutor, analysisCallback = analysisCallback ).start() // 手机息屏触发器 ScreenOffTrigger( application = app, analysisClient = analysisClient, analysisExecutor = analysisExecutor, analysisCallback = analysisCallback ).start() } }

可能会觉得就算是如许适用性,也会觉得不是所以安心,其实也能通过云端下发适用性的体例来动态控造能否开启 LeakCanary 的监控功用。如下表所示,通过 HeapAnalysisClient 自订拦截器

private val analysisClient by lazy { HeapAnalysisClient( heapDumpDirectoryProvider = { File("") }, // stripHeapDump: remove all user data from hprof before analysis. config = HeapAnalysisConfig(stripHeapDump = true), // Default interceptors may cancel analysis for several other reasons. interceptors = listOf(flagInterceptor) + HeapAnalysisClient.defaultInterceptors(app) ) } private val flagInterceptor = object : HeapAnalysisInterceptor { override fun intercept(chain: HeapAnalysisInterceptor.Chain): HeapAnalysisJob.Result { // 通过开关控造使命能否停止 if(enable) { chain.job.cancel("cancel reason") } return chain.proceed() } }

除了他们上面自订的拦截器之外,SDK内部还预造了许多极端情况的场景,如下表所示:

fun defaultInterceptors(application: Application): List<HeapAnalysisInterceptor> { return listOf( // 仅全力撑持特定 Android 版本 GoodAndroidVersionInterceptor(), // 存储空间太小也不全力撑持 MinimumDiskSpaceInterceptor(application), // 可用缓存太小也不全力撑持 MinimumMemoryInterceptor(application), MinimumElapsedSinceStartInterceptor(), OncePerPeriodInterceptor(application), SaveResourceIdsInterceptor(application.resources) ) }

有了前述体例论的综合加持,在圣戈当斯区版本中选用 LeakCanary 的影响范畴可能并没有现象中的大。当然 LeakCanary 官方对那部门内容仍是持隆重立场的,leakcanary-android-release 自己仍是处于试验阶段。

当然若是有内测渠道,能先在内测的版本中跑起来。

小结

其实 leakcanary-android 与 leakcanary-android-release 两个包的依赖图大致如下表所示:

+--- project :leakcanary-android-release | +--- project :shark-android | | \--- project :shark | | \--- project :shark-graph | | \--- project :shark-hprof | | \--- project :shark-log | \--- project :leakcanary-android-utils +--- project :leakcanary-android | +--- project :leakcanary-android-core (*) | +--- project :leakcanary-object-watcher-android | \--- org.jetbrains.kotlin:kotlin-stdlib +--- project :leakcanary-android-core | +--- project :shark-android | +--- project :leakcanary-object-watcher-android-core | +--- project :leakcanary-object-watcher-android-androidx | \--- project :leakcanary-object-watcher-android-support-fragments

由此可见,:leakcanary-android-release 模块并没有依赖 :leakcanary-android ,仅有 :shark-android 、:leakcanary-android-utils 模块是通用的。

阐发源码能知,:leakcanary-android-release 和:leakcanary-android两个包在 HeapDump 以及成果处置上都有差别,leakcanary-android-release 模块也难以选用 leakcanary-android 中的多历程体例论,因为其内部写死是选用 Debug.dumpHprofData 的。好在其触发前提比力苛刻,小范畴选用影响可控。

选用 LeakCanary 收罗缓存外泄的建议体例如下表所示:

Debug 情况

加进 leakcanary-android 依赖,选用预设的许多事务监听器(日记、通知),便利快速定位排除难题;加进 leakcanary-android-process 依赖,在子历程中处置耗时使命,优化开发体验;自订事务监听器,上报对应的成果;Release 情况

leakcanary-android-release 依赖,仅在许多特定的情况下触发使命,削减对用户选用的影响;自订事务监听器,上报对应的成果;

以上体例论的标识符已上传至 gist ,感兴趣的同窗能自取。

总结

详细来说,恒定在 Debug 情况中选用 LeakCanary 确实是加进带队依赖就能搞定了,包罗对多历程的开启也是如斯,吗算是开箱即用了。由此由此可见其设想功底了。

在 Release 情况选用,也有对应的计划。但是整体计划还处于尝试阶段,建议控造好选用范畴。一种是云端开启采样体例开启,另一种是在内测版本中选用控造好选用范畴。

回过甚再来看他们之前对 LeakCanary 留下的刻板印象:

“LeakCanary 虽好,但是太卡。”“LeakCanary 虽好,但难以圣戈当斯区选用。”

读到那里我相信你对上面的难题已经有了本身的观点了。古云说:“士别三日,当刮目相待”,关于那些在持续更新的手艺也应如斯,要时刻连结开下学习的心态,唯有如斯,才有打破。

针对前述性能优化中的缓存外泄难题,整理了一套 《Android性能优化手册》进修文档,那中间不只记录了缓存优化手艺,还有启动优化、UI衬着优化、卡顿优化、收集优化、存储优化、耗电优化等。有必要的能 点击下方小卡片曲传达 或 私信回复:“性能优化”货取!!!

0
回帖 返回游戏

LeakCanary 利用办法你实的用对了吗? 期待您的回复!

取消
载入表情清单……
载入颜色清单……
插入网络图片

取消确定

图片上传中
编辑器信息
提示信息