一、背景
在Android应用中,除了正常的业务开发,我们也要关注性能问题。卡顿、内存溢出、内存泄漏等问题,直接的表现会反馈到用户体验上,用户体验不好导致应用被卸载或者换到其他平台。
在性能优化,各大公司都会付出一些代价,或者安排专人负责。有些新手也想做,但是无从下手,对专业工具和专业代码使用以及分析比较吃力,排查起来也比较费劲。如果有专业的工具能够只管的把这些记录并标记好。这样新手也可以通过详情的问题去排查,那么LeaksCanary就是这款工具了。
二、性能的简单介绍(在面试的时候,也经常会被问起)
1、卡顿:卡顿的主要涉及到线程的使用,在LeaksCanary中,会给出当前线程的使用信息
2、内存溢出:是程序在申请内存时,没有足够的内存空间供其使用
3、内存泄漏:是程序在申请内存后,无法释放已申请的内存空间
三、LeaksCanary
1、介绍
LeakCanary是Square公司为Android开发者提供的一个自动检测内存泄漏的工具,
LeakCanary本质上是一个基于MAT进行Android应用程序内存泄漏自动化检测的的开源工具,我们可以通过集成LeakCanary提供的jar包到自己的工程中,一旦检测到内存泄漏,LeakCanary就会dump Memory信息,并通过另一个进程分析内存泄漏的信息并展示出来,随时发现和定位内存泄漏问题,而不用每次在开发流程中都抽出专人来进行内存泄漏问题检测,极大地方便了Android应用程序的开发。
2、github地址:
GitHub - square/leakcanary: A memory leak detection library for Android.https://www.baidu.com/link?url=bcGyW7ySy9cW2azMXz5h-je6EtkwbMggSIlaEiFfWbtIqf3Yx3rak5qcn3Om3WJp&wd=&eqid=87caa544001114160000000463e1f198
3、接入
3.1依赖库:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:2.9.1'
3.2、代码引入
2.9.1的库已kotlin开发语言为核心,网络上大多数还是1.6.1。这就导致在接入的时候无法方式不同。
1.6.1版本,直接在application中,通过LeakCanary.install(application),来完成。
2.9.1接入:
在androidManifest中引入
<provider
android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false" />
leak_canary_watcher_auto_install:
<resources>
<bool name="leak_canary_watcher_auto_install">true</bool>
</resources>
是否自动安装,如果你不想自动安装,可以自行社会一个变量引入,默认不自动安装
介绍:MainProcessAppWatcherInstaller
internal class MainProcessAppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
MainProcessAppWatcherInstaller安装是ContentProvider的一个派生类。安装也是通过
AppWatcher.manualInstall(application)
安装方法manualInstall会做安装前的检查
如果不想通过自动安装,可以通过
Java:AppWatcher.INSTANCE.manualInstall(application);
kotlin:AppWatcher.manualInstall(application);
如果不想自己控制,可以在xml资源文件,设置为true。
注意:
如果手动安装,最好判断一下是否已安装AppWatcher.isInstalled()否则会报错
四、卡顿问题优化:
LeakCanary 造成卡顿的原因就是在主进程中 dump hprof 文件,文件就会涉及到IO操作,在读写时,占用大量线程,导致页面会出现卡顿情况。针对这个问题,可以通过引入多进程方案。避免在IO时,影响主进程
leakcanary-android-process
依赖库:
debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.9.1'
该库的核心是RemoteLeakCanaryWorkerService,这个服务就是多进程的核心。我们使用的时候,需要提前注册这个service。
在该service中:
override fun onCreate() {
// Ideally we wouldn't need to install AppWatcher at all here, however
// the installation triggers InternalsLeakCanary to store the application instance
// which is then used by the event listeners that respond to analysis progress.
if (!AppWatcher.isInstalled) {
val application = super.getApplicationContext() as Application
AppWatcher.manualInstall(
application,
// Nothing to watch in the :leakcanary process.
watchersToInstall = emptyList()
)
}
super.onCreate()
}
在oncreate中,执行安装。
其实,在核心库中,已加入了该service。我们只需要通过
HeapDump:核心
RemoteWorkManagerHeapAnalyzer
WorkManger也是一款开源工具,用于后台工作的架构组件,需要兼顾机会和有保证的执行。机会性执行意味着WorkManager将尽快完成您的后台工作。
依赖库:
def versions_work = "2.3.3"
implementation "androidx.work:work-runtime:$versions_work"
五、线上接入:
目前很多调试都是在debug下进行,通过专业工具去处理。但是我们一旦打包以后会移除debug这些模块。如果线上发生了这些,如何获取?
很多开发人员通过获取句柄文件来分析,但是如果我们没有问题设备,就无法获取到dump的句柄文件。这时,如果能把句柄文件传回到日志服务器,这样就方便开发人员定位信息。
线上使用 LeakCanary,首要要确定以下问题:
- 如何获取 LeakCanary 分析的结果?
- 结果以何种形式上报到质量平台上?
- 如何确定合理的监控采集时机,尽可能小的影响用户体验?
定义自己的监听:
上面我们介绍了workmanager这个工具,他就是通过定义一个监听,来完成后台的处理,减少主进程的线程操作,降低卡顿。同样我们也可以定义自己的EventListener。
目前LeakCanary的方法中已提供了一些监听:
val eventListeners: List<EventListener> = listOf(
LogcatEventListener,
ToastEventListener,
LazyForwardingEventListener {
if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
},
when {
RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->
RemoteWorkManagerHeapAnalyzer
WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
else -> BackgroundThreadHeapAnalyzer
}
),
小试牛刀:
import leakcanary.EventListener
class MyLeakCanaryEventListener:EventListener {
override fun onEvent(event: EventListener.Event) {
}
}
Event:
目前已提供了如下Event
1、class DumpingHeap(uniqueId: String) : Event(uniqueId)
从“LeakCanary堆转储”HandlerThread发送
2、class HeapDump( uniqueId: String, val file: File, val durationMillis: Long, val reason: String) : Event(uniqueId)
从“LeakCanary堆转储”HandlerThread发送
3、class HeapDumpFailed( uniqueId: String, val exception: Throwable, val willRetryLater: Boolean) : Event(uniqueId)
从“LeakCanary堆转储”HandlerThread发送。失败
4、class HeapAnalysisProgress( uniqueId: String, val step: Step, val progressPercent: Double) : Event(uniqueId)
从执行分析的线程发送。
5、sealed class HeapAnalysisDone<T : HeapAnalysis>( uniqueId: String, val heapAnalysis: T, showIntent: Intent) : Event(uniqueId)
5.1、class HeapAnalysisSucceeded( uniqueId: String, heapAnalysis: HeapAnalysisSuccess, val unreadLeakSignatures: Set<String>, showIntent: Intent)
分析成功,
5.2、class HeapAnalysisFailed( uniqueId: String, heapAnalysis: HeapAnalysisFailure, showIntent: Intent)
分析失败
这些类都是继承了class Event( val uniqueId: String),且都是内部类。
分析结果:
if (event is EventListener.Event.HeapAnalysisDone.HeapAnalysisSucceeded) {
//分析成功
val successEvent = event as EventListener.Event.HeapAnalysisDone.HeapAnalysisSucceeded
val list = successEvent.heapAnalysis.allLeaks
list?.let {
for (leak in it) {
leak.leakTraces
}
}
}
这样我们就可以得到了分析结果。
六、注册监听:
注册监听涉及到LeakCanary的配置,这边我们先讲解一下LeakCanary.Config配置参数
data class Config(
/**
* Whether LeakCanary should dump the heap when enough retained instances are found. This needs
* to be true for LeakCanary to work, but sometimes you may want to temporarily disable
* LeakCanary (e.g. for a product demo).
*
* Defaults to true.
*/
val dumpHeap: Boolean = true,
/**
* If [dumpHeapWhenDebugging] is false then LeakCanary will not dump the heap
* when the debugger is attached. The debugger can create temporary memory leaks (for instance
* if a thread is blocked on a breakpoint).
*
* Defaults to false.
*/
val dumpHeapWhenDebugging: Boolean = false,
/**
* When the app is visible, LeakCanary will wait for at least
* [retainedVisibleThreshold] retained instances before dumping the heap. Dumping the heap
* freezes the UI and can be frustrating for developers who are trying to work. This is
* especially frustrating as the Android Framework has a number of leaks that cannot easily
* be fixed.
*
* When the app becomes invisible, LeakCanary dumps the heap after
* [AppWatcher.retainedDelayMillis] ms.
*
* The app is considered visible if it has at least one activity in started state.
*
* A higher threshold means LeakCanary will dump the heap less often, therefore it won't be
* bothering developers as much but it could miss some leaks.
*
* Defaults to 5.
*/
val retainedVisibleThreshold: Int = 5,
/**
* Known patterns of references in the heap, added here either to ignore them
* ([IgnoredReferenceMatcher]) or to mark them as library leaks ([LibraryLeakReferenceMatcher]).
*
* When adding your own custom [LibraryLeakReferenceMatcher] instances, you'll most
* likely want to set [LibraryLeakReferenceMatcher.patternApplies] with a filter that checks
* for the Android OS version and manufacturer. The build information can be obtained by calling
* [shark.AndroidBuildMirror.fromHeapGraph].
*
* Defaults to [AndroidReferenceMatchers.appDefaults]
*/
val referenceMatchers: List<ReferenceMatcher> = AndroidReferenceMatchers.appDefaults,
/**
* List of [ObjectInspector] that provide LeakCanary with insights about objects found in the
* heap. You can create your own [ObjectInspector] implementations, and also add
* a [shark.AppSingletonInspector] instance created with the list of internal singletons.
*
* Defaults to [AndroidObjectInspectors.appDefaults]
*/
val objectInspectors: List<ObjectInspector> = AndroidObjectInspectors.appDefaults,
/**
* Deprecated, add to LeakCanary.config.eventListeners instead.
* Called on a background thread when the heap analysis is complete.
* If you want leaks to be added to the activity that lists leaks, make sure to delegate
* calls to a [DefaultOnHeapAnalyzedListener].
*
* Defaults to [DefaultOnHeapAnalyzedListener]
*/
@Deprecated(message = "Add to LeakCanary.config.eventListeners instead")
val onHeapAnalyzedListener: OnHeapAnalyzedListener = DefaultOnHeapAnalyzedListener.create(),
/**
* Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata].
* Called on a background thread during heap analysis.
*
* Defaults to [AndroidMetadataExtractor]
*/
val metadataExtractor: MetadataExtractor = AndroidMetadataExtractor,
/**
* Whether to compute the retained heap size, which is the total number of bytes in memory that
* would be reclaimed if the detected leaks didn't happen. This includes native memory
* associated to Java objects (e.g. Android bitmaps).
*
* Computing the retained heap size can slow down the analysis because it requires navigating
* from GC roots through the entire object graph, whereas [shark.HeapAnalyzer] would otherwise
* stop as soon as all leaking instances are found.
*
* Defaults to true.
*/
val computeRetainedHeapSize: Boolean = true,
/**
* How many heap dumps are kept on the Android device for this app package. When this threshold
* is reached LeakCanary deletes the older heap dumps. As several heap dumps may be enqueued
* you should avoid going down to 1 or 2.
*
* Defaults to 7.
*/
val maxStoredHeapDumps: Int = 7,
/**
* LeakCanary always attempts to store heap dumps on the external storage if the
* WRITE_EXTERNAL_STORAGE is already granted, and otherwise uses the app storage.
* If the WRITE_EXTERNAL_STORAGE permission is not granted and
* [requestWriteExternalStoragePermission] is true, then LeakCanary will display a notification
* to ask for that permission.
*
* Defaults to false because that permission notification can be annoying.
*/
val requestWriteExternalStoragePermission: Boolean = false,
/**
* Finds the objects that are leaking, for which LeakCanary will compute leak traces.
*
* Defaults to [KeyedWeakReferenceFinder] which finds all objects tracked by a
* [KeyedWeakReference], ie all objects that were passed to
* [ObjectWatcher.expectWeaklyReachable].
*
* You could instead replace it with a [FilteringLeakingObjectFinder], which scans all objects
* in the heap dump and delegates the decision to a list of
* [FilteringLeakingObjectFinder.LeakingObjectFilter]. This can lead to finding more leaks
* than the default and shorter leak traces. This also means that every analysis during a
* given process life will bring up the same leaking objects over and over again, unlike
* when using [KeyedWeakReferenceFinder] (because [KeyedWeakReference] instances are cleared
* after each heap dump).
*
* The list of filters can be built from [AndroidObjectInspectors]:
*
* ```kotlin
* LeakCanary.config = LeakCanary.config.copy(
* leakingObjectFinder = FilteringLeakingObjectFinder(
* AndroidObjectInspectors.appLeakingObjectFilters
* )
* )
* ```
*/
val leakingObjectFinder: LeakingObjectFinder = KeyedWeakReferenceFinder,
/**
* Dumps the Java heap. You may replace this with your own implementation if you wish to
* change the core heap dumping implementation.
*/
val heapDumper: HeapDumper = AndroidDebugHeapDumper,
/**
* Listeners for LeakCanary events. See [EventListener.Event] for the list of events and
* which thread they're sent from. You most likely want to keep this list and add to it, or
* remove a few entries but not all entries. Each listener is independent and provides
* additional behavior which you can disable by not excluding it:
*
* ```kotlin
* // No cute canary toast (very sad!)
* LeakCanary.config = LeakCanary.config.run {
* copy(
* eventListeners = eventListeners.filter {
* it !is ToastEventListener
* }
* )
* }
* ```
*/
val eventListeners: List<EventListener> = listOf(
LogcatEventListener,
ToastEventListener,
LazyForwardingEventListener {
if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
},
when {
RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->
RemoteWorkManagerHeapAnalyzer
WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
else -> BackgroundThreadHeapAnalyzer
}
),
/**
* Deprecated: This is a no-op, set a custom [leakingObjectFinder] instead.
*/
@Deprecated("This is a no-op, set a custom leakingObjectFinder instead")
val useExperimentalLeakFinders: Boolean = false
)
默认都是有值,对应参数如下:
private var dumpHeap = config.dumpHeap
private var dumpHeapWhenDebugging = config.dumpHeapWhenDebugging
private var retainedVisibleThreshold = config.retainedVisibleThreshold
private var referenceMatchers = config.referenceMatchers
private var objectInspectors = config.objectInspectors
private var onHeapAnalyzedListener = config.onHeapAnalyzedListener
private var metadataExtractor = config.metadataExtractor
private var computeRetainedHeapSize = config.computeRetainedHeapSize
private var maxStoredHeapDumps = config.maxStoredHeapDumps
private var requestWriteExternalStoragePermission =
config.requestWriteExternalStoragePermission
private var leakingObjectFinder = config.leakingObjectFinder
private var heapDumper = config.heapDumper
private var eventListeners = config.eventListeners
private var useExperimentalLeakFinders = config.useExperimentalLeakFinders
新增我们的监听:
val eventListeners = LeakCanary.config.eventListeners.toMutableList().apply {
add(MyLeakCanaryEventListener())
}
LeakCanary.config=LeakCanary.config.copy(
eventListeners=eventListeners
)
这样整个线上监听基本完成。
我们只要对LeakTrace进行分析,把结果存储后,在一个合适的时机上报给服务器即可
七、AppWatcher的看守配置:
知道如何去分析这些问题,LeakCanary还提供了看门机制,监听哪些也是可以通过配置的。
在AppWatcher中,也提供了Config
data class Config(
@Deprecated("Call AppWatcher.manualInstall() with a custom watcher list")
val watchActivities: Boolean = true,
@Deprecated("Call AppWatcher.manualInstall() with a custom watcher list")
val watchFragments: Boolean = true,
@Deprecated("Call AppWatcher.manualInstall() with a custom watcher list")
val watchFragmentViews: Boolean = true,
@Deprecated("Call AppWatcher.manualInstall() with a custom watcher list")
val watchViewModels: Boolean = true,
@Deprecated("Call AppWatcher.manualInstall() with a custom retainedDelayMillis value")
val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5),
@Deprecated("Call AppWatcher.appDefaultWatchers() with a custom ReachabilityWatcher")
val enabled: Boolean = true
)
可以看守的有以下,默认都是开启,如果你想关闭哪个,默认设为false即可
AppWatcher.config= AppWatcher.config.copy(watchActivities = false, enabled = false)
这样,我们就可以很好的看守我们想要的结果。
八、ANR日志收集
ANR或者crash的创建,也意味着ActivityThread的线程已经挂了,这时候我们任何的做法都不能依赖主线程来完成,这样会导致抓取不到日志。
我们知道,虚拟机android的跨线程通讯都是binder,虽然ActivityThread挂了,但是AMS没有挂,AMS还存活在SystemManager中,又因为每个进程在fork出来的时候都有自己的binder。所以这个时候我们可以通过AMS来获取。
日志收集核心:获取、存储、上报。获取到存储是核心流程,如何将获取的日志进行存储,IO机制显然无法满足这么短时间的操作。这个时候我们可以参考内存映射mmp机制。目前包括一些开源的都是采用mmap,常见的框架如微信的mmkv,mmkv是采用了mmap,也都是c在处理,利用内存映射来获取日志文档,这样就很容易获取到日志信息。
九、总结
通过接入、卡顿优化、拦截、配置等,能够很好的满足我们从线上到线下的各种把控。可以很好的完善应用的监控与优化机制。
Debug模式,直接在LeaksApp中可以很好的看到问题列表。其他更多的可以参考github的开源信息,或者自己接入调试。
版权归原作者 蜗牛、Z 所有, 如有侵权,请联系我们删除。