一.模块依赖关系
检测流程图
![](https://img-blog.csdnimg.cn/20210923102135687.jpg?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc3RvbmVfY29sZF9jb29s,size_20,color_FFFFFF,t_70,g_se,x_16)
ui显示流程图
![](https://img-blog.csdnimg.cn/20210914170616798.jpg?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc3RvbmVfY29sZF9jb29s,size_20,color_FFFFFF,t_70,g_se,x_16)
leakcanary-android-release流程图
![](https://img-blog.csdnimg.cn/20210924094406295.jpg?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc3RvbmVfY29sZF9jb29s,size_20,color_FFFFFF,t_70,g_se,x_16)
二.源码解析
1. leakcanary-android-utils
1.1 Handlers.kt 主线程handler获取,判断是否是主线程
//获取主线程,by lazy是kotlin懒加载赋值,调用的时候才赋值
internal val mainHandler by lazy { Handler(Looper.getMainLooper()) }
//是否处于主线程
internal fun checkMainThread() {
check(Looper.getMainLooper().thread === Thread.currentThread()) {//如果不满足,报IllegalStateException
"Should be called from the main thread, not ${Thread.currentThread()}"
}
}
//是否不处于主线程
internal fun checkNotMainThread() {
check(Looper.getMainLooper().thread !== Thread.currentThread()) {
"Should not be called from the main thread"
}
} |
1.2 Objects.kt 动态代理接口
//动态代理相关的
//https://blog.csdn.net/u012326462/article/details/81293186
//newProxyInstance,方法有三个参数:
//ClassLoader loader: 用哪个类加载器去加载代理对象
//Class<?>[] interfaces:动态代理类需要实现的接口
//InvocationHandler h:动态代理方法在执行时,会调用h里面的invoke方法去执行
//
//invoke三个参数:
//proxy:就是代理对象,newProxyInstance方法的返回对象
//method:调用的方法
//args: 方法中的参数
internal inline fun <reified T : Any> noOpDelegate(): T {
// https://blog.csdn.net/lv_fq/article/details/72869124
// 双冒号 表示把一个方法当做一个参数,传递到另一个方法中进行使用,通俗的来讲就是引用一个方法
// https://blog.csdn.net/lmo28021080/article/details/81505211?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-5.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-5.control
// val a = Test::class // KClass
// val b1 = Test::class.java // Class (KClass -> Class)
// val b2 = Test::class.java.kotlin // KClass (Class -> KClass)
val javaClass = T::class.java
return Proxy.newProxyInstance(
javaClass.classLoader, arrayOf(javaClass), NO_OP_HANDLER
) as T
}
//这里是koltin的函数表达式
private val NO_OP_HANDLER = InvocationHandler { _, _, _ ->
// no op
} |
1.3 Timing.kt 计算代码块运行时间
//计算代码块运行时间
internal inline fun measureDurationMillis(block: () -> Unit): Long {
val start = SystemClock.uptimeMillis()
block()
return SystemClock.uptimeMillis() - start
}
|
1.4 LogcatSharkLog 日志类
class LogcatSharkLog : Logger {
override fun d(message: String) {
if (message.length < 4000) {
Log.d("LeakCanary", message)
} else {
message.lines().forEach { line ->
Log.d("LeakCanary", line)
}
}
}
override fun d(
throwable: Throwable,
message: String
) {
d("$message\n${Log.getStackTraceString(throwable)}")
}
//https://www.jianshu.com/p/14db81e1576a
companion object {//伴生对象
// companion object 修饰为伴生对象,伴生对象在类中只能存在一个,类似于java中的静态方法 Java 中使用类访问静态成员,静态方法。
// 调用方法 LogcatSharkLog.install()
fun install() {
SharkLog.logger = LogcatSharkLog()
}
}
} |
2.leakcanary-object-watcher
2.1 Clock uptimeMillis用于获取自开机后,经过的时间,不包括深度睡眠的时间
fun interface Clock {//https://www.zhihu.com/question/362356051 fun interface表示单个抽象函数
/**
* On Android VMs, this should return android.os.SystemClock.uptimeMillis().
*/
fun uptimeMillis(): Long
companion object {//伴生对象
/**
* Utility function to create a [Clock] from the passed in [block] lambda
* instead of using the anonymous `object : Clock` syntax.
*
* 从传入的[block]lambda创建[Clock]的实用函数
* 而不是使用匿名的`object:Clock`语法。
*
* Usage:
*
* ```kotlin
* val clock = Clock {
*
* }
* ```
*/
//内联函数 https://blog.csdn.net/xlh1191860939/article/details/86736379
//当你调用一个inline function的时候,编译器不会产生一次函数调用,而是会在每次调用时,将inline function中的代码直接嵌入到调用处。
//crossinline https://blog.csdn.net/xlh1191860939/article/details/105228583
//crossinline “non-local returns”,非局部返回,这里是限制在内联函数中出现return局部返回
//operator https://blog.csdn.net/qq_34589749/article/details/103643764
//operator 操作符 Kotlin-14-运算符重载(operator)
//https://blog.csdn.net/chzphoenix/article/details/78094584
//
//invoke
//调用invoke时方法可以被省略,所以如果
//class ClickAction{
// operator fun invoke(...){
// ...
// }
//}
//可以直接:
//clickAction(...)
//注意clickAction是类的一个对象
//invoke 方法,Lambda 表达式调用位置 https://blog.csdn.net/mlsnatalie/article/details/88557502
//使用地方
//AppWatcher 构造函数 clock = { SystemClock.uptimeMillis() },
//val watchUptimeMillis = clock.uptimeMillis()
inline operator fun invoke(crossinline block: () -> Long): Clock =
object : Clock {
override fun uptimeMillis(): Long = block()
}
}
}
|
2.2 GcTrigger 触发GC方法
todo放流程图
/**
* [GcTrigger] is used to try triggering garbage collection and enqueuing [KeyedWeakReference] into
* the associated [java.lang.ref.ReferenceQueue]. The default implementation [Default] comes from
* AOSP Android 开源项目(AOSP) .
* ReferenceQueue 引用队列
*/
interface GcTrigger {
/**
* Attempts to run garbage collection.
*/
fun runGc()
/**
* Default implementation of [GcTrigger].
*/
//object 静态类,里边的方法,参数都是静态的
object Default : GcTrigger {
override fun runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perform a gc.
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
}
private fun enqueueReferences() {
// Hack(黑客). We don't have a programmatic way to wait for the reference queue daemon(守护进程) to move
// references to the appropriate queues.
try {
Thread.sleep(100)
} catch (e: InterruptedException) {
throw AssertionError()
}
}
}
}
|
2.3 KeyedWeakReference弱引用包装类
todo放流程图
//https://www.jianshu.com/p/73260a46291c
//对于软引用和弱引用,我们希望当一个对象被gc掉的时候通知用户线程,进行额外的处理时,就需要使用引用队列了。
//ReferenceQueue即这样的一个对象,当一个obj被gc掉之后,其相应的包装类,即ref对象会被放入queue中。
//我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等。
class KeyedWeakReference(
referent: Any,
val key: String,
val description: String,
val watchUptimeMillis: Long,
referenceQueue: ReferenceQueue<Any>//
) : WeakReference<Any>(
referent, referenceQueue
) {
/**
* Time at which the associated object ([referent]) was considered retained, or -1 if it hasn't
* been yet.
*/
@Volatile
var retainedUptimeMillis = -1L//观察开始的时间
override fun clear() {
super.clear()
retainedUptimeMillis = -1L
}
companion object {//伴生对象,静态对象,他的都是静态值,一个类只能有一个伴生对象
@Volatile
@JvmStatic var heapDumpUptimeMillis = 0L//dump开始的时间
}
} |
2.4 ReachabilityWatcher接口,开启观察
todo放流程图
fun interface ReachabilityWatcher {
/**
* Expects the provided [watchedObject] to become weakly reachable soon. If not,
* [watchedObject] will be considered retained.
*
* 期望提供的[watchedObject]很快变得弱可访问。如果没有,
* [watchedObject]将被视为保留
*/
fun expectWeaklyReachable(
watchedObject: Any,//需要检测的对象,任意
description: String//描述
)
} |
2.5 OnObjectRetainedListener对象泄漏的调用接口
todo放流程图
fun interface OnObjectRetainedListener {//单一函数
/**
* A watched object became retained.
*/
fun onObjectRetained()//单一函数
companion object {//伴生对象
/**
* Utility function to create a [OnObjectRetainedListener] from the passed in [block] lambda
* instead of using the anonymous `object : OnObjectRetainedListener` syntax.
*
* Usage:
*
* ```kotlin
* val listener = OnObjectRetainedListener {
*
* }
* ```
*/
inline operator fun invoke(crossinline block: () -> Unit): OnObjectRetainedListener =
object : OnObjectRetainedListener {
override fun onObjectRetained() {
block()
}
}
}
} |
2.5 ObjectWatcher保持一个map存放观察的对象,创建ReferenceQueue保存已被回收的对象的弱饮用包装类对象,expectWeaklyReachable开启观察
todo放流程图
class ObjectWatcher constructor(
private val clock: Clock, //时间
private val checkRetainedExecutor: Executor,//线程池
/**
* Calls to [watch] will be ignored when [isEnabled] returns false
*/
private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {
//接口,mutable可变的 https://blog.csdn.net/xsg2357/article/details/80417980
private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
/**
* References passed to [watch].
*/
//存放在观察的实例的key,和KeyedWeakReference
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()//
//ReferenceQueue里边存放已被回收的对象的包装类
private val queue = ReferenceQueue<Any>()//队列,存放KeyedWeakReference
//region hasRetainedObjects,retainedObjectCount,hasWatchedObjects,retainedObjects
/**
* Returns true if there are watched objects that aren't weakly reachable, and
* have been watched for long enough to be considered retained.
*/
val hasRetainedObjects: Boolean
@Synchronized get() {
removeWeaklyReachableObjects()
return watchedObjects.any { it.value.retainedUptimeMillis != -1L }
}
/**
* Returns the number of retained objects, ie the number of watched objects that aren't weakly
* reachable, and have been watched for long enough to be considered retained.
*/
val retainedObjectCount: Int
@Synchronized get() {
removeWeaklyReachableObjects()
return watchedObjects.count { it.value.retainedUptimeMillis != -1L }
}
/**
* Returns true if there are watched objects that aren't weakly reachable, even
* if they haven't been watched for long enough to be considered retained.
*/
val hasWatchedObjects: Boolean
@Synchronized get() {
removeWeaklyReachableObjects()
return watchedObjects.isNotEmpty()
}
/**
* Returns the objects that are currently considered retained. Useful for logging purposes.
* Be careful with those objects and release them ASAP as you may creating longer lived leaks
* then the one that are already there.
*/
val retainedObjects: List<Any>
@Synchronized get() {
removeWeaklyReachableObjects()
val instances = mutableListOf<Any>()
for (weakReference in watchedObjects.values) {
if (weakReference.retainedUptimeMillis != -1L) {
val instance = weakReference.get()
if (instance != null) {
instances.add(instance)
}
}
}
return instances
}
@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.add(listener)
}
@Synchronized fun removeOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.remove(listener)
}
/**
* Identical to [watch] with an empty string reference name.
*/
@Deprecated(
"Add description parameter explaining why an object is watched to help understand leak traces.",
replaceWith = ReplaceWith(
"expectWeaklyReachable(watchedObject, \"Explain why this object should be garbage collected soon\")"
)
)
fun watch(watchedObject: Any) {
expectWeaklyReachable(watchedObject, "")
}
@Deprecated(
"Method renamed expectWeaklyReachable() to clarify usage.",
replaceWith = ReplaceWith(
"expectWeaklyReachable(watchedObject, description)"
)
)
fun watch(
watchedObject: Any,
description: String
) {
expectWeaklyReachable(watchedObject, description)
}
//endregion
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
//是否启用 , AppWatcher 持有的ObjectWatcher 默认是启用的
if (!isEnabled()) {
return
}
//移除之前已经被回收的监听对象
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
//创建弱引用,创建要观察对象的弱引用,传入queue 作为gc 后的对象信息存储队列
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (description.isNotEmpty()) " ($description)" else "") +
" with key $key"
}
watchedObjects[key] = reference
checkRetainedExecutor.execute {
//在子线程里,先清除回收的对象,如果此watchedObject没有被回收,则记录他泄漏检测开始的时间
moveToRetained(key)
}
}
/**
* Clears all [KeyedWeakReference] that were created before [heapDumpUptimeMillis] (based on
* [clock] [Clock.uptimeMillis])
*
* 清除 heapDumpUptimeMillis之前创建的KeyedWeakReference
*/
@Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
val weakRefsToRemove =
watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
weakRefsToRemove.values.forEach { it.clear() }
watchedObjects.keys.removeAll(weakRefsToRemove.keys)
}
/**
* Clears all [KeyedWeakReference]
*/
@Synchronized fun clearWatchedObjects() {
// watchedObjects 存放在观察的实例的key,和KeyedWeakReference弱引用包装对象
//这里清空
watchedObjects.values.forEach { it.clear() }
watchedObjects.clear()
}
//在子线程里,先清除回收的对象,如果此watchedObject没有被回收,则记录他泄漏检测开始的时间
@Synchronized private fun moveToRetained(key: String) {
///移除已经被回收的观察对象
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
//记录泄漏时间
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//回调泄漏监听
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
private fun removeWeaklyReachableObjects() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
var ref: KeyedWeakReference?
do {
//ReferenceQueue会存放gc之后,被回收的弱饮用包装对象,
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
//将此对象在watchedObjects里删除
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
} |
3.leakcanary-object-watcher-android
3.1 Friendly.kt 工具类,获取主线程相关,动态代理相关
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "NOTHING_TO_INLINE")
@file:JvmName("leakcanary-object-watcher-android_Friendly")
//ok
package leakcanary.internal.friendly
//主线程handler
internal inline val mainHandler
get() = leakcanary.internal.mainHandler
//检查是否是主线程
internal inline fun checkMainThread() = leakcanary.internal.checkMainThread()
//动态代理
internal inline fun <reified T : Any> noOpDelegate(): T = leakcanary.internal.noOpDelegate() |
3.2 Applications.kt 判断是否DEBUGGABLE,如果debugable,就会打印log,LogcatSharkLog.install()
//判断是否DEBUGGABLE,如果debugable,就会打印log,LogcatSharkLog.install()
internal val Application.isDebuggableBuild: Boolean
get() = (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0 |
3.3 InstallableWatcher 观察者接口,Activity,Fragment,View,Service监听都实现了这个接口
todo放流程图
interface InstallableWatcher {
fun install()
fun uninstall()
} |
3.4 LeakCanaryDelegate,使用反射调用android-core模块里的InternalLeakCanary实例
todo放流程图
internal object LeakCanaryDelegate {//代表;
@Suppress("UNCHECKED_CAST")
val loadLeakCanary by lazy {//通过单例的INSTANCE字段获取InternalLeakCanary实例
try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}
object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
override fun invoke(application: Application) {
}
override fun onObjectRetained() {
}
}
} |
3.5 AppWatcherInstaller 安装类
todo放流程图
/**
* Content providers are loaded before the application class is created. [AppWatcherInstaller] is
* used to install [leakcanary.AppWatcher] on application start.
* LeakCanary利用ContentProvider的onCreate生命周期处于Application的onCreate生命周期之前的特性,
* 将注册部分写在ContentProvider中。
* ok
*
* sealed class密封类
* https://blog.csdn.net/huo108/article/details/80544645
* Sealed class(密封类) 是一个有特定数量子类的类,看上去和枚举有点类似,所不同的是,在枚举中,我们每个类型只有一个对象(实例);而在密封类中,同一个类可以拥有几个对象。
* Sealed class(密封类)的所有子类都必须与密封类在同一文件中
* Sealed class(密封类)的子类的子类可以定义在任何地方,并不需要和密封类定义在同一个文件中
* Sealed class(密封类)没有构造函数,不可以直接实例化,只能实例化内部的子类
*/
internal sealed class AppWatcherInstaller : ContentProvider() {//sealed
//代码中定义了两个内部类继承自 AppWatcherInstaller。当用户额外依赖 leakcanary-android-process 模块的时候,
// 自动在 process=":leakcanary" 也注册该provider。
/**
* [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
*/
internal class MainProcess : AppWatcherInstaller()
/**
* When using the `leakcanary-android-process` artifact instead of `leakcanary-android`,
* [LeakCanaryProcess] automatically sets up the LeakCanary code
*/
internal class LeakCanaryProcess : AppWatcherInstaller()
override fun onCreate(): Boolean {
//?.表示当前对象如果为空则不执行,
//!!.表示当前对象如果为空也执行,然后会抛出空异常
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
override fun query(
uri: Uri,
strings: Array<String>?,
s: String?,
strings1: Array<String>?,
s1: String?
): Cursor? {
return null
}
override fun getType(uri: Uri): String? {
return null
}
override fun insert(
uri: Uri,
contentValues: ContentValues?
): Uri? {
return null
}
override fun delete(
uri: Uri,
s: String?,
strings: Array<String>?
): Int {
return 0
}
override fun update(
uri: Uri,
contentValues: ContentValues?,
s: String?,
strings: Array<String>?
): Int {
return 0
}
} |
3.6 AppWatcher 安装manualInstall
todo放流程图
object AppWatcher {
//retained
private const val RETAINED_DELAY_NOT_SET = -1L
@Volatile
private var retainedDelayMillis = RETAINED_DELAY_NOT_SET //5s
private var installCause: Exception? = null
/**
* The [ObjectWatcher] used by AppWatcher to detect retained objects.
* Only set when [isInstalled] is true.
* 可以看到objectWatcher 是一个 ObjectWatcher对象,该对象负责检测持有对象的泄漏情况,
*/
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)//5s之后执行 todo 这里是主线程???
},
isEnabled = { true }
)
/** @see [manualInstall] */
val isInstalled: Boolean
get() = installCause != null
/**
* Enables usage of [AppWatcher.objectWatcher] which will expect passed in objects to become
* weakly reachable within [retainedDelayMillis] ms and if not will trigger LeakCanary (if
* LeakCanary is in the classpath).
*
* In the main process, this method is automatically called with default parameter values on app
* startup. You can call this method directly to customize the installation, however you must
* first disable the automatic call by overriding the `leak_canary_watcher_auto_install` boolean
* resource:
*
* ```
* <?xml version="1.0" encoding="utf-8"?>
* <resources>
* <bool name="leak_canary_watcher_auto_install">false</bool>
* </resources>
* ```
*
* [watchersToInstall] can be customized to a subset of the default app watchers:
*
* ```
* val watchersToInstall = AppWatcher.appDefaultWatchers(application)
* .filter { it !is RootViewWatcher }
* AppWatcher.manualInstall(
* application = application,
* watchersToInstall = watchersToInstall
* )
* ```
*
* [watchersToInstall] can also be customized to ignore specific instances (e.g. here ignoring
* leaks of BadSdkLeakingFragment):
*
* ```
* val watchersToInstall = AppWatcher.appDefaultWatchers(application, ReachabilityWatcher { watchedObject, description ->
* if (watchedObject !is BadSdkLeakingFragment) {
* AppWatcher.objectWatcher.expectWeaklyReachable(watchedObject, description)
* }
* })
* AppWatcher.manualInstall(
* application = application,
* watchersToInstall = watchersToInstall
* )
* ```
* AppWatcher是初始化观察对象的入口处。
* manualInstall方法的参数watchersToInstall默认实现Activity、Fragment、View、ViewModel和Service的监听。
*/
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
//*******检查是否为主线程********/
checkMainThread()
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
installCause = RuntimeException("manualInstall() first called here")
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
//android-core模块里,使用反射调用InternalLeakCanary构造函数
LeakCanaryDelegate.loadLeakCanary(application)
//开启监测
watchersToInstall.forEach {
it.install()
}
}
/**
* Creates a new list of default app [InstallableWatcher], created with the passed in
* [reachabilityWatcher] (which defaults to [objectWatcher]). Once installed,
* these watchers will pass in to [reachabilityWatcher] objects that they expect to become
* weakly reachable.
*
* The passed in [reachabilityWatcher] should probably delegate to [objectWatcher] but can
* be used to filter out specific instances.
*/
//提供了 四个默认监听的Watcher
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
} |
3.7 ActivityWatcher监听Activity泄漏
/**
* Expects activities to become weakly reachable soon after they receive the [Activity.onDestroy]
* callback.
* 通过application监听Activity的生命周期,在onDestory调用expectWeaklyReachable方法
* 做内存泄漏的检查工作。
*
* 调用ActivityInstaller.install 初始化方法
* 在Application 注册ActivityLifecycleCallbacks
* 在所有activity onDestroy的时候调用ObjectWatcher的 expectWeaklyReachable方法,检查过五秒后activity对象是否有被内存回收。标记内存泄漏。
* ok
*/
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {//利用动态代理实现了其他回调方法,感兴趣的可以查看 noOpDelegate 的源码
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
|
3.8 AndroidOFragmentDestroyWatcher SDK里的fragment监听
todo放流程图
internal class AndroidOFragmentDestroyWatcher(
private val reachabilityWatcher: ReachabilityWatcher //ObjectWatcher类实现了这个接口
) : (Activity) -> Unit {//他是个类,继承了一个(Activity) -> Unit lambda接口,表达式
//FragmentLifecycleCallbacks接口实例
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null) {
//监听view泄漏
reachabilityWatcher.expectWeaklyReachable(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
//监听fragment泄漏
reachabilityWatcher.expectWeaklyReachable(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
//lambda表达式,会调用的地方
override fun invoke(activity: Activity) {
val fragmentManager = activity.fragmentManager
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
} |
3.9 FragmentAndViewModelWatcher监听Fragment And ViewModel
class FragmentAndViewModelWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
// android框架自带的fragment泄漏监测支持从 AndroidO(26)开始。
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(reachabilityWatcher)
)
}
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
//ANDROIDX support库里的
fragmentDestroyWatchers.add(it)
}
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
//ANDROIDX support库里的
fragmentDestroyWatchers.add(it)
}
fragmentDestroyWatchers
}
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {//fragmentDestroyWatchers 函数lambda
watcher(activity)//调用函数
}
}
}
override fun install() {
//注册ActivityLifecycle生命周期监听函数
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
private fun getWatcherIfAvailable(
fragmentClassName: String,
watcherClassName: String,
reachabilityWatcher: ReachabilityWatcher
): ((Activity) -> Unit)? {//? 表示当前对象可以为空,即可以 = null
return if (classAvailable(fragmentClassName) &&
classAvailable(watcherClassName)
) {
//如果类存在,则创建监测类
val watcherConstructor =
Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)
@Suppress("UNCHECKED_CAST")
watcherConstructor.newInstance(reachabilityWatcher) as (Activity) -> Unit
} else {
null
}
}
//看看是否有某个类
private fun classAvailable(className: String): Boolean {
return try {
Class.forName(className)
true
} catch (e: Throwable) {
// e is typically expected to be a ClassNotFoundException
// Unfortunately, prior to version 25.0.2 of the support library the
// FragmentManager.FragmentLifecycleCallbacks class was a non static inner class.
// Our AndroidSupportFragmentDestroyWatcher class is compiled against the static version of
// the FragmentManager.FragmentLifecycleCallbacks class, leading to the
// AndroidSupportFragmentDestroyWatcher class being rejected and a NoClassDefFoundError being
// thrown here. So we're just covering our butts here and catching everything, and assuming
// any throwable means "can't use this". See https://github.com/square/leakcanary/issues/1662
false
}
}
companion object {
private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidXFragmentDestroyWatcher"
// Using a string builder to prevent Jetifier from changing this string to Android X Fragment
@Suppress("VariableNaming", "PropertyName")
private val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME =
//为啥用StringBuilder,上边的英文写着原因,Jetifier 会迁移指向 android.support.* 软件包的 Java、XML、
// POM 和 ProGuard 引用,更改它们以使其指向相应的 androidx.* 软件包。
StringBuilder("android.").append("support.v4.app.Fragment")
.toString()
private const val ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidSupportFragmentDestroyWatcher"
}
}
|
3.10 RootViewWatcher Curtains中的监听器会在windows rootView 变化的时候被全局调用。
/**
* Expects root views to become weakly reachable soon after they are removed from the window
* manager.
* Curtains中的监听器会在windows rootView 变化的时候被全局调用。Curtains是squareup 的另一个开源库,
* Curtains 提供了用于处理 Android 窗口的集中式 API。具体移步他的官方仓库。
*/
class RootViewWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val listener = OnRootViewAddedListener { rootView ->
//如果是 Dialog TOOLTIP, TOAST, UNKNOWN 等类型的windows
//trackDetached 为true
val trackDetached = when(rootView.windowType) {
PHONE_WINDOW -> {
when (rootView.phoneWindow?.callback?.wrappedCallback) {
// Activities are already tracked by ActivityWatcher
is Activity -> false
is Dialog -> rootView.resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
// Probably a DreamService
else -> true
}
}
// Android widgets keep detached popup window instances around.
POPUP_WINDOW -> false
TOOLTIP, TOAST, UNKNOWN -> true
}
//看到关键代码,就是 在Curtains中添加onRootViewsChangedListeners 监听器。当windowsType类型为 **Dialog(xml文件中配置)**
// ***TOOLTIP***, ***TOAST***,或者未知的时候 ,在 onViewDetachedFromWindow 的时候监听泄漏情况。
if (trackDetached) {
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
val watchDetachedView = Runnable {
reachabilityWatcher.expectWeaklyReachable(
rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
)
}
override fun onViewAttachedToWindow(v: View) {
mainHandler.removeCallbacks(watchDetachedView)
}
override fun onViewDetachedFromWindow(v: View) {
mainHandler.post(watchDetachedView)
}
})
}
}
override fun install() {
Curtains.onRootViewsChangedListeners += listener
}
override fun uninstall() {
Curtains.onRootViewsChangedListeners -= listener
}
}
|
3.11 ServiceWatcher监听service泄漏
@SuppressLint("PrivateApi")
class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher {
private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }
private val activityThreadInstance by lazy {
activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!//不为空时执行代码,如果为空,仍会报错;
}
//activityThreadServices 是个装了所有<IBinder, Service> 对的Map。代码中可以看到很粗暴地,
// 直接通过反射从ActivityThread实例中拿到了mServices 变量 。赋值给activityThreadServices。
private val activityThreadServices by lazy {
val mServicesField =
activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
mServicesField[activityThreadInstance] as Map<IBinder, Service>
}
private var uninstallActivityThreadHandlerCallback: (() -> Unit)? = null
private var uninstallActivityManager: (() -> Unit)? = null
override fun install() {
checkMainThread()
check(uninstallActivityThreadHandlerCallback == null) {
"ServiceWatcher already installed"
}
check(uninstallActivityManager == null) {
"ServiceWatcher already installed"
}
try {
swapActivityThreadHandlerCallback { mCallback ->
uninstallActivityThreadHandlerCallback = {
swapActivityThreadHandlerCallback {
mCallback//mCallback是原来的callback
}
}
// 代码中可以看到,主要是对于 STOP_SERVICE 的操作做了一个钩子,在之前执行 onServicePreDestroy。
// 主要作用是为该service 创建一个弱引用,并且加到servicesToBeDestroyed[token] 中 。
Handler.Callback { msg ->
if (msg.what == STOP_SERVICE) {
val key = msg.obj as IBinder
activityThreadServices[key]?.let {
onServicePreDestroy(key, it)
}
}
mCallback?.handleMessage(msg) ?: false
}
}
swapActivityManager { activityManagerInterface, activityManagerInstance ->
uninstallActivityManager = {
swapActivityManager { _, _ ->
activityManagerInstance
}
}
// 代码所示,替换后的ActivityManager 在调用serviceDoneExecuting 方法的时候添加了个钩子,
// 如果该service在之前加入的servicesToBeDestroyed map中,则调用onServiceDestroyed 监测该service内存泄漏。
Proxy.newProxyInstance(
activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
) { _, method, args ->
if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
val token = args!![0] as IBinder
if (servicesToBeDestroyed.containsKey(token)) {
onServiceDestroyed(token)
}
}
try {
if (args == null) {
method.invoke(activityManagerInstance)
} else {
method.invoke(activityManagerInstance, *args)
}
} catch (invocationException: InvocationTargetException) {
throw invocationException.targetException
}
}
}
} catch (ignored: Throwable) {
SharkLog.d(ignored) { "Could not watch destroyed services" }
}
}
override fun uninstall() {
checkMainThread()
uninstallActivityManager?.invoke()
uninstallActivityThreadHandlerCallback?.invoke()
uninstallActivityManager = null
uninstallActivityThreadHandlerCallback = null
}
//主要作用是为该service 创建一个弱引用,并且加到servicesToBeDestroyed[token] 中
private fun onServicePreDestroy(
token: IBinder,
service: Service
) {
servicesToBeDestroyed[token] = WeakReference(service)
}
private fun onServiceDestroyed(token: IBinder) {
servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
serviceWeakReference.get()?.let { service ->
reachabilityWatcher.expectWeaklyReachable(
service, "${service::class.java.name} received Service#onDestroy() callback"
)
}
}
}
//拿到ActivityThread 的Handler,将其回调的 handleMessage,换成加了料的Handler.Callback,加料代码如下
//swap传入一个返回一个,所以可以swap
private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?) -> Handler.Callback?) {
val mHField =
activityThreadClass.getDeclaredField("mH").apply { isAccessible = true }
val mH = mHField[activityThreadInstance] as Handler
val mCallbackField =
Handler::class.java.getDeclaredField("mCallback").apply { isAccessible = true }
val mCallback = mCallbackField[mH] as Handler.Callback?
mCallbackField[mH] = swap(mCallback)//传入的是要换的
}
//该方法完成了将ActivityManager替换成IActivityManager的一个动态代理类。代码如下
@SuppressLint("PrivateApi")
private fun swapActivityManager(swap: (Class<*>, Any) -> Any) {
val singletonClass = Class.forName("android.util.Singleton")
val mInstanceField =
singletonClass.getDeclaredField("mInstance").apply { isAccessible = true }
val singletonGetMethod = singletonClass.getDeclaredMethod("get")
val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
"android.app.ActivityManager" to "IActivityManagerSingleton"
} else {
"android.app.ActivityManagerNative" to "gDefault"
}
val activityManagerClass = Class.forName(className)
val activityManagerSingletonField =
activityManagerClass.getDeclaredField(fieldName).apply { isAccessible = true }
val activityManagerSingletonInstance = activityManagerSingletonField[activityManagerClass]
// Calling get() instead of reading from the field directly to ensure the singleton is
// created.
val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)
val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
mInstanceField[activityManagerSingletonInstance] =
swap(iActivityManagerInterface, activityManagerInstance!!)
}
companion object {//伴生对象,静态内部类,里边的都是静态值
private const val STOP_SERVICE = 116
private const val METHOD_SERVICE_DONE_EXECUTING = "serviceDoneExecuting"
}
} |
4.leakcanary-object-watcher-android-androidx
4.1 AndroidXFragmentDestroyWatcher
/**
* 这个类继承自一个函数,invoke是函数的方法,相当于一个接口!
* ok
*/
internal class AndroidXFragmentDestroyWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
//FragmentLifecycleCallbacks Fragment生命周期监听
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
//create的时候
override fun onFragmentCreated(
fm: FragmentManager,
fragment: Fragment,
savedInstanceState: Bundle?
) {
//ViewModelClearedWatcher install fragment
ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
}
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null) {
//expectWeaklyReachable 对view进行监听
reachabilityWatcher.expectWeaklyReachable(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
//expectWeaklyReachable 对fragment进行监听
reachabilityWatcher.expectWeaklyReachable(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
override fun invoke(activity: Activity) {//invoke是kotlin的高阶函数,在调用的时候的方法
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
//注册fragmentLifecycleCallbacks
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
//ViewModelClearedWatcher install activity
ViewModelClearedWatcher.install(activity, reachabilityWatcher)
}
}
}
|
4.2 ViewModelClearedWatcher 往ViewModelStoreOwner即Activity,Fragment,里添加一个监控ViewModel,监控onCleared方法
/**
* spy 间谍
* [AndroidXFragmentDestroyWatcher] calls [install] to add a spy [ViewModel] in every
* [ViewModelStoreOwner] instance (i.e. FragmentActivity and Fragment). [ViewModelClearedWatcher]
* holds on to the map of [ViewModel]s backing its store. When [ViewModelClearedWatcher] receives
* the [onCleared] callback, it adds each live [ViewModel] from the store to the [ReachabilityWatcher].
*
* 什么情况下viewmodel会泄漏?
*
*/
//该watcher 继承了ViewModel,生命周期被 ViewModelStoreOwner 管理。
//添加一个间谍ViewModel,用于监听onCleared事件
internal class ViewModelClearedWatcher(
storeOwner: ViewModelStoreOwner,
private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {
private val viewModelMap: Map<String, ViewModel>?
init {
// We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
// however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
// does not have ViewModelStore#keys. All versions currently have the mMap field.
//通过反射获取所有的 store 存储的所有viewModelMap
viewModelMap = try {
val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
mMapField.isAccessible = true
@Suppress("UNCHECKED_CAST")
mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) {
null
}
}
override fun onCleared() {
// viewmodle 被清理释放的时候回调,检查所有viewmodle 是否会有泄漏
viewModelMap?.values?.forEach { viewModel ->
reachabilityWatcher.expectWeaklyReachable(
viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
)
}
}
companion object {
fun install(//伴生函数,相当于静态方法
storeOwner: ViewModelStoreOwner,
reachabilityWatcher: ReachabilityWatcher
) {
val provider = ViewModelProvider(storeOwner, object : Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
})
//获取ViewModelClearedWatcher实例
provider.get(ViewModelClearedWatcher::class.java)
}
}
} |
5.leakcanary-object-watcher-android-support-fragments
5.1 AndroidSupportFragmentDestroyWatcher
internal class AndroidSupportFragmentDestroyWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null) {
reachabilityWatcher.expectWeaklyReachable(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
reachabilityWatcher.expectWeaklyReachable(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
}
} |
6.shark-log
6.1 SharkLog.kt
object SharkLog {
/**
* @see SharkLog
*/
interface Logger {
/**
* Logs a debug message formatted with the passed in arguments.
*/
fun d(message: String)
/**
* Logs a [Throwable] and debug message formatted with the passed in arguments.
*/
fun d(
throwable: Throwable,
message: String
)
}
@Volatile var logger: Logger? = null
/**
* @see Logger.d
*/
inline fun d(message: () -> String) {
// Local variable to prevent the ref from becoming null after the null check.
val logger = logger ?: return//如果不为空返回logger,如果为空则返回
logger.d(message.invoke())
}
/**
* @see Logger.d
*/
inline fun d(
throwable: Throwable,
message: () -> String
) {
// Local variable to prevent the ref from becoming null after the null check.
val logger = logger ?: return
logger.d(throwable, message.invoke())
}
}
|
7.plumber-android
8.leakcanary-android-core
8.1 Friendly.kt 主线程相关的
//ok
package leakcanary.internal.friendly
//主线程handler
internal inline val mainHandler
get() = leakcanary.internal.mainHandler
//是否主线程
internal inline fun checkMainThread() = leakcanary.internal.checkMainThread()
internal inline fun checkNotMainThread() = leakcanary.internal.checkNotMainThread()
//动态代理
internal inline fun <reified T : Any> noOpDelegate(): T = leakcanary.internal.noOpDelegate()
//计算某个方法时间
internal inline fun measureDurationMillis(block: () -> Unit) =
leakcanary.internal.measureDurationMillis(block)
8.2 Size.kit 返回大小
// https://stackoverflow.com/a/3758880
//ok
//1KB = 1000Byte
//1Kib = 1024Byte
internal fun humanReadableByteCount(
bytes: Long,
si: Boolean//是否是十进制千单位
): String {
val unit = if (si) 1000 else 1024
if (bytes < unit) return "$bytes B"
//ln自然对数,以常数e为底数的对数。记作lnN(N>0)。在物理学,生物学等自然科学中有重要的意义。
// 一般表示方法为lnx。数学中也常见以logx表示自然对数。若为了避免与基为10的常用对数lgx混淆,可用“全写”㏒ex。
val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
//K 与 Ki 分别表示 kilo-(千) 与 kibi-(二进制千) 。作为前缀使用时, k 表示 1,000,Ki 表示1,024。
//“Ki”来源于它在计算机方面 210 = 1,024 的使用。换算:1KiB = 1024 B,1MiB = 1024 KiB,1GiB = 1024 MiB。
val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
}
8.3 Tuples.kt Pair变为Triple
//Kotlin允许在不使用括号和点号的情况下调用函数,那么这种函数被称为 infix函数。
internal infix fun <A, B, C> Pair<A, B>.to(that: C): Triple<A, B, C> = Triple(first, second, that)
8.4 OnRetainInstanceListener.kt 监听RetainInstanceEvent4种情况
internal sealed class RetainInstanceEvent {//密封类
object NoMoreObjects : RetainInstanceEvent()
sealed class CountChanged : RetainInstanceEvent() {
class BelowThreshold(val retainedCount: Int) : RetainInstanceEvent()
class DumpingDisabled(val reason: String) : RetainInstanceEvent()
object DumpHappenedRecently : RetainInstanceEvent()
}
}
/**
* Called by LeakCanary when the number of retained instances updates .
*/
internal interface OnRetainInstanceListener {
/**
* Called when there's a change to the Retained Instances. See [RetainInstanceEvent] for
* possible events.
*/
fun onEvent(event: RetainInstanceEvent)
}
internal class DefaultOnRetainInstanceListener : OnRetainInstanceListener {
override fun onEvent(event: RetainInstanceEvent) {}
}
8.5 TvOnRetainInstanceListener tv相关通知
//tv相关通知
internal class TvOnRetainInstanceListener(private val application: Application) :
OnRetainInstanceListener {
override fun onEvent(event: RetainInstanceEvent) {
val message = when (event) {
NoMoreObjects -> {
//All retained objects were garbage collected
application.getString(R.string.leak_canary_notification_no_retained_object_title)
}
is BelowThreshold -> {
//%1$d retained objects. Heap dump threshold is %2$d.\nBackground the app to trigger heap dump immediately
//tv是把程序放到后台的时候,heapdump ??? todo
application.getString(
R.string.leak_canary_tv_toast_retained_objects,
event.retainedCount,
LeakCanary.config.retainedVisibleThreshold
)
}
is DumpingDisabled -> {
event.reason
}
//一分钟前dump了
is DumpHappenedRecently -> {
application.getString(R.string.leak_canary_notification_retained_dump_wait)
}
}
SharkLog.d { message }
mainHandler.post {
//显示toast,得到resumed activity
val resumedActivity = InternalLeakCanary.resumedActivity ?: return@post
TvToast.makeText(resumedActivity, message).show()
}
}
}
8.6 TvToast 电视上 toast
//tv相关通知
internal object TvToast {
/**
* Make an Android TV toast.
* Don't forget to call [Toast.show] to display the toast!
* @param activity Currently resumed [Activity] to display toast on. Note that it's not [Context]
* to prevent passing application context that could lead to crashes on older platforms.
* @param text The text to show. Can be formatted text.
*/
@SuppressLint("ShowToast")
fun makeText(
activity: Activity,//application的可能crash
text: CharSequence
): Toast {
val toast: Toast = Toast.makeText(activity, text, Toast.LENGTH_LONG)
val textView = toast.view.findViewById<TextView>(android.R.id.message)
textView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.leak_canary_icon, 0, 0, 0)
textView.compoundDrawablePadding =
activity.resources.getDimensionPixelSize(R.dimen.leak_canary_toast_icon_tv_padding)
return toast
}
}
8.7 OnHeapAnalyzedListener 分析之后调用接口
//分析之后调用接口
fun interface OnHeapAnalyzedListener {//fun interface 只有一个方法的接口
/**
* @see OnHeapAnalyzedListener
*/
//HeapAnalysis shark模块里的
fun onHeapAnalyzed(heapAnalysis: HeapAnalysis)
//伴生对象,里边的方法都是静态的
companion object {
/**
* Utility function to create a [OnHeapAnalyzedListener] from the passed in [block] lambda
* instead of using the anonymous `object : OnHeapAnalyzedListener` syntax.
*
* Usage:
*
* ```kotlin
* val listener = OnHeapAnalyzedListener {
*
* }
* ```
*/
//内联函数
//operator 类的实例可以直接用()调用的函数
//crossinline 通畅,穿越inline函数block里不能有return
//invoke 方法,Lambda 表达式调用位置 {}
//
inline operator fun invoke(crossinline block: (HeapAnalysis) -> Unit): OnHeapAnalyzedListener =
object : OnHeapAnalyzedListener {
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
block(heapAnalysis)
}
}
}
}
8.8 Serializables.kt序列化,反序列化
//将可序列化的对象序列化为ByteArray
internal fun Serializable.toByteArray(): ByteArray {
val outputStream = ByteArrayOutputStream()
ObjectOutputStream(outputStream).writeObject(this)
return outputStream.toByteArray()
}
internal object Serializables {
//reified 具体化
//在kotlin中一个内联函数(inline)可以被具体化(reified),这意味着我们可以得到使用泛型类型的Class。
//https://blog.csdn.net/u011215710/article/details/103906478
//泛型在运行时会被类型擦除,但是在inline函数中我们可以指定类型不被擦除, 因为inline函数在编译期会将字节码copy到调用它的方法里,
//所以编译器会知道当前的方法中泛型对应的具体类型是什么,然后把泛型替换为具体类型,从而达到不被擦除的目的,
// 在inline函数中我们可以通过reified关键字来标记这个泛型在编译时替换成具体类型
inline fun <reified T> fromByteArray(byteArray: ByteArray): T? {
val inputStream = ByteArrayInputStream(byteArray)
return try {
// https://blog.csdn.net/sinat_21693123/article/details/80876623
//as? as运算符 尝试把值转换成指定的类型,如果值不是合适的类型就返回null
ObjectInputStream(inputStream).readObject() as? T
} catch (ignored: Throwable) {
SharkLog.d(ignored) { "Could not deserialize bytes, ignoring" }
null
}
}
}
8.9 DefaultOnHeapAnalyzedListener 默认的OnHeapAnalyzedListener实现类
/**
* 默认的OnHeapAnalyzedListener实现类
*/
class DefaultOnHeapAnalyzedListener private constructor(private val applicationProvider: () -> Application) :
OnHeapAnalyzedListener {
// Kept this constructor for backward compatibility of public API.
@Deprecated(
message = "Use DefaultOnHeapAnalyzedListener.create() instead",
replaceWith = ReplaceWith("DefaultOnHeapAnalyzedListener.create()")
)
constructor(application: Application) : this({ application })
//获取application
private val application: Application by lazy { applicationProvider() }
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }
//插入数据库
val db = LeaksDbHelper(application).writableDatabase
val id = HeapAnalysisTable.insert(db, heapAnalysis)
db.releaseReference()
//根据结果确定通知点击之后显示的screen,Pair对
val (contentTitle, screenToShow) = when (heapAnalysis) {
//失败去失败页
is HeapAnalysisFailure -> application.getString(
R.string.leak_canary_analysis_failed
) to HeapAnalysisFailureScreen(id)
//成功去成功
is HeapAnalysisSuccess -> {
val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }
val leakTypeCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
application.getString(
//Found %1$d retained objects grouped as %2$d distinct leaks
R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount
) to HeapDumpScreen(id)
}
}
if (InternalLeakCanary.formFactor == TV) {
showToast(heapAnalysis)
printIntentInfo()
} else {
//发送通知
showNotification(screenToShow, contentTitle)
}
}
private fun showNotification(
screenToShow: Screen,
contentTitle: String
) {
//通知跳转页面,传入两个页面,基础页面是HeapDumpsScreen,然后根据成功失败来显示screenToShow
val pendingIntent = LeakActivity.createPendingIntent(
application, arrayListOf(HeapDumpsScreen(), screenToShow)
)
val contentText = application.getString(R.string.leak_canary_notification_message)
//显示通知
Notifications.showNotification(
application, contentTitle, contentText, pendingIntent,
R.id.leak_canary_notification_analysis_result,
LEAKCANARY_MAX
)
}
/**
* Android TV devices do not have notifications, therefore the only easy and non-invasive way
* to communicate with user is via Toast messages. These are used just to grab user attention and
* to direct them to Logcat where a much more detailed report will be printed.
*/
private fun showToast(heapAnalysis: HeapAnalysis) {
mainHandler.post {
val resumedActivity = InternalLeakCanary.resumedActivity ?: return@post
val message: String = when (heapAnalysis) {
is HeapAnalysisSuccess -> {
application.getString(
R.string.leak_canary_tv_analysis_success,
heapAnalysis.applicationLeaks.size,
heapAnalysis.libraryLeaks.size
)
}
is HeapAnalysisFailure -> application.getString(R.string.leak_canary_tv_analysis_failure)
}
TvToast.makeText(resumedActivity, message)
.show()
}
}
/**
* Android TV with API 26+ has a bug where the launcher icon doesn't appear, so users won't know how
* to launch LeakCanary Activity.
* This method prints an adb command that launched LeakCanary into the logcat
*/
private fun printIntentInfo() {
val leakClass = LeakActivity::class.java
SharkLog.d {
"""====================================
ANDROID TV LAUNCH INTENT
====================================
Run the following adb command to display the list of leaks:
adb shell am start -n "${application.packageName}/${leakClass.`package`?.name}.LeakLauncherActivity"
===================================="""
}
}
companion object {
//伴生对象,单例
fun create(): OnHeapAnalyzedListener =
DefaultOnHeapAnalyzedListener { InternalLeakCanary.application }
}
}
8.10 NotificationType 通知类型,包括通知channel,重要性importance
//通知类型,包括通知channel,重要性importance
internal enum class NotificationType(
val nameResId: Int,
val importance: Int
) {
LEAKCANARY_LOW(
//LeakCanary Low Priority
R.string.leak_canary_notification_channel_low, IMPORTANCE_LOW
),
LEAKCANARY_MAX(
//LeakCanary Result
R.string.leak_canary_notification_channel_result, IMPORTANCE_MAX
);
}
private const val IMPORTANCE_LOW = 2
private const val IMPORTANCE_MAX = 5
8.11 DebuggerControl isDebuggerConnected连着的时候会有问题,例如创建临时对象,debugger连着的时候,leakcanary不支持
internal object DebuggerControl {
val isDebuggerAttached: Boolean
get() = Debug.isDebuggerConnected()
}
8.12 RequestStoragePermissionActivity权限申请
@TargetApi(Build.VERSION_CODES.M) //
internal class RequestStoragePermissionActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
if (hasStoragePermission()) {
finish()
return
}
val permissions = arrayOf(WRITE_EXTERNAL_STORAGE)
requestPermissions(permissions, 42)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (!hasStoragePermission()) {
Toast.makeText(application, R.string.leak_canary_permission_not_granted, LENGTH_LONG)
.show()
}
finish()
}
override fun finish() {
// Reset the animation to avoid flickering.
overridePendingTransition(0, 0)
super.finish()
}
private fun hasStoragePermission(): Boolean {
return checkSelfPermission(WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED
}
companion object {
fun createPendingIntent(context: Context): PendingIntent {
val intent = Intent(context, RequestStoragePermissionActivity::class.java)
intent.flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TOP
//https://blog.csdn.net/bdmh/article/details/41804695
//https://blog.csdn.net/sinat_31057219/article/details/88743458?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163107200416780274110200%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163107200416780274110200&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-88743458.first_rank_v2_pc_rank_v29&utm_term=FLAG_IMMUTABLE&spm=1018.2226.3001.4187
//FLAG_UPDATE_CURRENT
//如果要创建的PendingIntent已经存在了,那么在保留原先PendingIntent的同时,
//将原先PendingIntent封装的Intent中的extra部分替换为现在新创建的PendingIntent的intent中extra的内容
val flags = if (Build.VERSION.SDK_INT >= 23) {
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE// FLAG_IMMUTABLE 设置 Intent 在 send 的时候不能更改
} else {
FLAG_UPDATE_CURRENT//更新
}
return PendingIntent.getActivity(context, 1, intent, flags)
}
}
}
8.13 VisibilityTracker 亮屏,并且有可见的activity
internal class VisibilityTracker(
private val listener: (Boolean) -> Unit
) : Application.ActivityLifecycleCallbacks by noOpDelegate(), BroadcastReceiver() {
private var startedActivityCount = 0
/**
* Visible activities are any activity started but not stopped yet. An activity can be paused
* yet visible: this will happen when another activity shows on top with a transparent background
* and the activity behind won't get touch inputs but still need to render / animate.
*/
private var hasVisibleActivities: Boolean = false
/**
* Assuming screen on by default.
*/
private var screenOn: Boolean = true
private var lastUpdate: Boolean = false
override fun onActivityStarted(activity: Activity) {
startedActivityCount++
if (!hasVisibleActivities && startedActivityCount == 1) {
hasVisibleActivities = true
updateVisible()
}
}
override fun onActivityStopped(activity: Activity) {
// This could happen if the callbacks were registered after some activities were already
// started. In that case we effectively considers those past activities as not visible.
if (startedActivityCount > 0) {
startedActivityCount--
}
if (hasVisibleActivities && startedActivityCount == 0 && !activity.isChangingConfigurations) {
hasVisibleActivities = false
updateVisible()
}
}
override fun onReceive(
context: Context,
intent: Intent
) {
screenOn = intent.action != ACTION_SCREEN_OFF
updateVisible()
}
private fun updateVisible() {
val visible = screenOn && hasVisibleActivities//亮屏,并且有可见的activity
if (visible != lastUpdate) {
lastUpdate = visible
listener.invoke(visible)//lambda表达式调用
}
}
}
//注册亮屏,熄屏监听
internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) {
val visibilityTracker = VisibilityTracker(listener)
registerActivityLifecycleCallbacks(visibilityTracker)
registerReceiver(visibilityTracker, IntentFilter().apply {
addAction(ACTION_SCREEN_ON)
addAction(ACTION_SCREEN_OFF)
})
}
8.14 HeapDumper返回DumpHeapResult,有两种HeapDump NoHeapDump
/** Dumps the heap into a file. */
internal interface HeapDumper {
/**
* @return a [File] referencing the dumped heap, or [.RETRY_LATER] if the heap could
* not be dumped.
*
* 如果dump成功 返回一个dumped heap
* 如果dump不成功返回.RETRY_LATER,是啥?todo
*/
fun dumpHeap(): DumpHeapResult
}
/** Dump heap result holding the file and the dump heap duration */
internal sealed class DumpHeapResult//密封类
internal data class HeapDump(
val file: File,
val durationMillis: Long
) : DumpHeapResult()
internal object NoHeapDump : DumpHeapResult()
8.15 NotificationReceiver通知
internal class NotificationReceiver : BroadcastReceiver() {
//两种情况
enum class Action {
DUMP_HEAP,
CANCEL_NOTIFICATION
}
override fun onReceive(
context: Context,
intent: Intent
) {
when (intent.action) {
DUMP_HEAP.name -> {
//onDumpHeapReceived 去dump
InternalLeakCanary.onDumpHeapReceived(forceDump = false)
}
CANCEL_NOTIFICATION.name -> {
// Do nothing, the notification has auto cancel true.
}
else -> {
SharkLog.d { "NotificationReceiver received unknown intent action for $intent" }
}
}
}
companion object {
fun pendingIntent(
context: Context,
action: Action//上边定义的enum
): PendingIntent {
val broadcastIntent = Intent(context, NotificationReceiver::class.java)
broadcastIntent.action = action.name//上边定义的enum 的name
val flags = if (Build.VERSION.SDK_INT >= 23) {
PendingIntent.FLAG_IMMUTABLE//不变的,具体用途 todo
} else {
0
}
return PendingIntent.getBroadcast(context, 0, broadcastIntent, flags)//注意这是Broadcast
}
}
}
8.16 LeakCanarySingleThreadFactory 没用到
/**
* This is intended to only be used with a single thread executor.
* 只用于单线程线程池
* nouse
*/
internal class LeakCanarySingleThreadFactory(threadName: String) : ThreadFactory {
private val threadName: String = "LeakCanary-$threadName"
override fun newThread(runnable: Runnable): Thread {
return Thread(runnable, threadName)
}
}
8.17 AndroidHeapDumper 用于dump,并计算dump的时间
//用于dump,并计算dump的时间
internal class AndroidHeapDumper(
context: Context,
private val leakDirectoryProvider: LeakDirectoryProvider//文件位置
) : HeapDumper {
private val context: Context = context.applicationContext
override fun dumpHeap(): DumpHeapResult {
//创建新的hprof 文件
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump
val waitingForToast = FutureResult<Toast?>()
//展示dump吐司
showToast(waitingForToast)
//如果展示吐司时间超过五秒,就不dump了
if (!waitingForToast.wait(5, SECONDS)) {
SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
return NoHeapDump
}
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Notifications.canShowNotification) {
//Dumping Heap
val dumpingHeap = context.getString(R.string.leak_canary_notification_dumping)
val builder = Notification.Builder(context)
.setContentTitle(dumpingHeap)
val notification = Notifications.buildNotification(context, builder, LEAKCANARY_LOW)
//发出通知
notificationManager.notify(R.id.leak_canary_notification_dumping_heap, notification)
}
//获取toast,一会用去取消
val toast = waitingForToast.get()
return try {
val durationMillis = measureDurationMillis {
//调用DumpHprofData
Debug.dumpHprofData(heapDumpFile.absolutePath)
}
if (heapDumpFile.length() == 0L) {
SharkLog.d { "Dumped heap file is 0 byte length" }
NoHeapDump
} else {
HeapDump(file = heapDumpFile, durationMillis = durationMillis)
}
} catch (e: Exception) {
SharkLog.d(e) { "Could not dump heap" }
// Abort heap dump
NoHeapDump
} finally {
//取消toast
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}
private fun showToast(waitingForToast: FutureResult<Toast?>) {
mainHandler.post(Runnable {
val resumedActivity = InternalLeakCanary.resumedActivity
if (resumedActivity == null) {
waitingForToast.set(null)
return@Runnable//@代表返回位置
}
val toast = Toast(resumedActivity)
// Resources from application context: https://github.com/square/leakcanary/issues/2023
val iconSize = context.resources.getDimensionPixelSize(
R.dimen.leak_canary_toast_icon_size
)
toast.setGravity(Gravity.CENTER_VERTICAL, 0, -iconSize)
toast.duration = Toast.LENGTH_LONG
// Inflating with application context: https://github.com/square/leakcanary/issues/1385
val inflater = LayoutInflater.from(context)
toast.view = inflater.inflate(R.layout.leak_canary_heap_dump_toast, null)
toast.show()
val toastIcon = toast.view.findViewById<View>(R.id.leak_canary_toast_icon)
toastIcon.translationY = -iconSize.toFloat()
toastIcon
.animate()
.translationY(0f)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
waitingForToast.set(toast)
}
})
})
}
//在主线程cancel toast
private fun cancelToast(toast: Toast?) {
if (toast == null) {
return
}
mainHandler.post { toast.cancel() }
}
}
8.18 ForegroundService 创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作
//https://blog.csdn.net/javazejian/article/details/52426425
//IntentService的特点:
//它本质是一种特殊的Service,继承自Service并且本身就是一个抽象类
//它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止
//它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务
//它内部通过HandlerThread和Handler实现异步操作
//创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作
internal abstract class ForegroundService(
name: String,
private val notificationContentTitleResId: Int,
private val notificationId: Int
) : IntentService(name) {
override fun onCreate() {
super.onCreate()
showForegroundNotification(
max = 100, progress = 0, indeterminate = true,//模糊的; 不确定的; 难以识别的;
contentText = getString(R.string.leak_canary_notification_foreground_text)//LeakCanary is working.
)
}
protected fun showForegroundNotification(
max: Int,
progress: Int,
indeterminate: Boolean,
contentText: String
) {
val builder = Notification.Builder(this)
.setContentTitle(getString(notificationContentTitleResId))
.setContentText(contentText)//LeakCanary is working.
.setProgress(max, progress, indeterminate)
val notification =
Notifications.buildNotification(this, builder, LEAKCANARY_LOW)
startForeground(notificationId, notification)
}
/**
* 实现异步任务的方法
* @param intent Activity传递过来的Intent,数据封装在intent中
*/
override fun onHandleIntent(intent: Intent?) {
onHandleIntentInForeground(intent)
}
protected abstract fun onHandleIntentInForeground(intent: Intent?)
override fun onDestroy() {
super.onDestroy()
stopForeground(true)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
}
8.19 FutureResult使用门拴控制 latch到了0,返回true,时间到了还没为0,返回false
internal class FutureResult<T> {
// AtomicReference类提供了一个可以原子读写的对象引用变量。
// 原子意味着尝试更改相同AtomicReference的多个线程(例如,使用比较和交换操作)不会使AtomicReference最终达到不一致的状态。
private val resultHolder: AtomicReference<T> = AtomicReference()
// countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
// 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,
// 计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
private val latch: CountDownLatch = CountDownLatch(1)//latch门闩; 插销; 碰锁; 弹簧锁;
fun wait(
timeout: Long,
unit: TimeUnit
): Boolean {
try {
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
//await(timeout,...)和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
//latch到了0,返回true,时间到了还没为0,返回false
return latch.await(timeout, unit)
} catch (e: InterruptedException) {
throw RuntimeException("Did not expect thread to be interrupted", e)
}
}
fun get(): T {
if (latch.count > 0) {
throw IllegalStateException("Call wait() and check its result")
}
return resultHolder.get()
}
fun set(result: T) {
resultHolder.set(result)
//将count值减1
latch.countDown()
}
}
8.20 LeakDirectoryProvider dump文件位置
internal class LeakDirectoryProvider constructor(
context: Context,
private val maxStoredHeapDumps: () -> Int,//7
private val requestExternalStoragePermission: () -> Boolean//false
) {
private val context: Context = context.applicationContext
fun listFiles(filter: FilenameFilter): MutableList<File> {
if (!hasStoragePermission() && requestExternalStoragePermission()) {
requestWritePermissionNotification()
}
val files = ArrayList<File>()
val externalFiles = externalStorageDirectory().listFiles(filter)//externalStorageDirectory
if (externalFiles != null) {
files.addAll(externalFiles)
}
val appFiles = appStorageDirectory().listFiles(filter)//appStorageDirectory
if (appFiles != null) {
files.addAll(appFiles)
}
return files
}
fun newHeapDumpFile(): File? {
cleanupOldHeapDumps()
var storageDirectory = externalStorageDirectory()//先externalStorageDirectory
if (!directoryWritableAfterMkdirs(storageDirectory)) {//如果不可以写
if (!hasStoragePermission()) {
if (requestExternalStoragePermission()) {
SharkLog.d { "WRITE_EXTERNAL_STORAGE permission not granted, requesting" }
requestWritePermissionNotification()
} else {
SharkLog.d { "WRITE_EXTERNAL_STORAGE permission not granted, ignoring" }
}
} else {
val state = Environment.getExternalStorageState()
if (Environment.MEDIA_MOUNTED != state) {
SharkLog.d { "External storage not mounted, state: $state" }
} else {
SharkLog.d {
"Could not create heap dump directory in external storage: [${storageDirectory.absolutePath}]"
}
}
}
// Fallback to app storage.
storageDirectory = appStorageDirectory()//转为appStorageDirectory
if (!directoryWritableAfterMkdirs(storageDirectory)) {
SharkLog.d {
"Could not create heap dump directory in app storage: [${storageDirectory.absolutePath}]"
}
return null
}
}
val fileName = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS'.hprof'", Locale.US).format(Date())
return File(storageDirectory, fileName)
}
@TargetApi(M)//是否有权限
fun hasStoragePermission(): Boolean {
if (SDK_INT < M) {
return true
}
// Once true, this won't change for the life of the process so we can cache it.
if (writeExternalStorageGranted) {
return true
}
writeExternalStorageGranted =
context.checkSelfPermission(WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED
return writeExternalStorageGranted
}
//发通知要权限,通知咋没看见过,因为默认是不开的
fun requestWritePermissionNotification() {
if (permissionNotificationDisplayed) {
return
}
permissionNotificationDisplayed = true
val pendingIntent = RequestStoragePermissionActivity.createPendingIntent(context)
val contentTitle = context.getString(
R.string.leak_canary_permission_notification_title
)
val packageName = context.packageName
val contentText =
//Click to enable storage permission for %s.
context.getString(R.string.leak_canary_permission_notification_text, packageName)
Notifications.showNotification(
context, contentTitle, contentText, pendingIntent,
R.id.leak_canary_notification_write_permission, LEAKCANARY_LOW
)
}
//Download下的leakcanary-开头的
@Suppress("DEPRECATION")
private fun externalStorageDirectory(): File {
val downloadsDirectory = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)
return File(downloadsDirectory, "leakcanary-" + context.packageName)
}
//app目录
private fun appStorageDirectory(): File {
val appFilesDirectory = context.cacheDir
return File(appFilesDirectory, "leakcanary")
}
private fun directoryWritableAfterMkdirs(directory: File): Boolean {
val success = directory.mkdirs()
return (success || directory.exists()) && directory.canWrite()
}
private fun cleanupOldHeapDumps() {
val hprofFiles = listFiles(FilenameFilter { _, name ->
name.endsWith(
HPROF_SUFFIX
)
})
val maxStoredHeapDumps = maxStoredHeapDumps()//最大为7
if (maxStoredHeapDumps < 1) {
throw IllegalArgumentException("maxStoredHeapDumps must be at least 1")
}
val filesToRemove = hprofFiles.size - maxStoredHeapDumps
if (filesToRemove > 0) {
SharkLog.d { "Removing $filesToRemove heap dumps" }
// Sort with oldest modified first.
hprofFiles.sortWith(Comparator { lhs, rhs ->
java.lang.Long.valueOf(lhs.lastModified())
.compareTo(rhs.lastModified())
})
for (i in 0 until filesToRemove) {
val path = hprofFiles[i].absolutePath
val deleted = hprofFiles[i].delete()//具体的删除的地方
if (deleted) {
filesDeletedTooOld += path//加入到list里
} else {
SharkLog.d { "Could not delete old hprof file ${hprofFiles[i].path}" }
}
}
}
}
companion object {
@Volatile
private var writeExternalStorageGranted: Boolean = false
@Volatile
private var permissionNotificationDisplayed: Boolean = false
private val filesDeletedTooOld = mutableListOf<String>()
val filesDeletedRemoveLeak = mutableListOf<String>()
private const val HPROF_SUFFIX = ".hprof"
fun hprofDeleteReason(file: File): String {
val path = file.absolutePath
return when {
filesDeletedTooOld.contains(path) -> "older than all other hprof files"//因为older被删
filesDeletedRemoveLeak.contains(path) -> "leak manually removed"//手动删除
else -> "unknown"
}
}
}
}
8.21 HeapAnalyzerService heap分析服务,后台线程里分析
/**
* This service runs in a main app process.
* heap分析服务
* ForegroundService需要有个通知吧
*/
internal class HeapAnalyzerService : ForegroundService(
HeapAnalyzerService::class.java.simpleName,
R.string.leak_canary_notification_analysing,
R.id.leak_canary_notification_analyzing_heap
), OnAnalysisProgressListener {
override fun onHandleIntentInForeground(intent: Intent?) {
if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
return
}
// Since we're running in the main process we should be careful not to impact it.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File //文件
val heapDumpReason = intent.getStringExtra(HEAPDUMP_REASON_EXTRA) //原因
val heapDumpDurationMillis = intent.getLongExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, -1)//dump时间
val config = LeakCanary.config
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
val fullHeapAnalysis = when (heapAnalysis) {
//分析成功
is HeapAnalysisSuccess -> heapAnalysis.copy(//一般copy用来原数据不被改变
dumpDurationMillis = heapDumpDurationMillis,//设置dumpDurationMillis
metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)//map加上一个
)
//分析失败
is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
}
//省略完善分析结果属性的代码
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)//分析完成
config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}
private fun analyzeHeap(
heapDumpFile: File,
config: Config
): HeapAnalysis {
//分析用HeapAnalyzer
val heapAnalyzer = HeapAnalyzer(this)//回调他自己onAnalysisProgress
//mapping关系
val proguardMappingReader = try {
ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME))
} catch (e: IOException) {
null
}
//
return heapAnalyzer.analyze(
heapDumpFile = heapDumpFile,
leakingObjectFinder = config.leakingObjectFinder,//todo
referenceMatchers = config.referenceMatchers,//todo
computeRetainedHeapSize = config.computeRetainedHeapSize,//true
objectInspectors = config.objectInspectors,//todo
metadataExtractor = config.metadataExtractor,//todo
proguardMapping = proguardMappingReader?.readProguardMapping()//todo
)
}
private fun missingFileFailure(
heapDumpFile: File
): HeapAnalysisFailure {
val deletedReason = LeakDirectoryProvider.hprofDeleteReason(heapDumpFile)
val exception = IllegalStateException(
"Hprof file $heapDumpFile missing, deleted because: $deletedReason"
)
return HeapAnalysisFailure(
heapDumpFile = heapDumpFile,
createdAtTimeMillis = System.currentTimeMillis(),
analysisDurationMillis = 0,
exception = HeapAnalysisException(exception)
)
}
//onAnalysisProgress总有10步
override fun onAnalysisProgress(step: OnAnalysisProgressListener.Step) {
val percent =
(100f * step.ordinal / OnAnalysisProgressListener.Step.values().size).toInt()
SharkLog.d { "Analysis in progress, working on: ${step.name}" }
val lowercase = step.name.replace("_", " ")
.toLowerCase(Locale.US)
val message = lowercase.substring(0, 1).toUpperCase(Locale.US) + lowercase.substring(1)
showForegroundNotification(100, percent, false, message)//发通知,调用的父类ForegroundService的方法
}
companion object {
private const val HEAPDUMP_FILE_EXTRA = "HEAPDUMP_FILE_EXTRA"
private const val HEAPDUMP_DURATION_MILLIS_EXTRA = "HEAPDUMP_DURATION_MILLIS_EXTRA"
private const val HEAPDUMP_REASON_EXTRA = "HEAPDUMP_REASON_EXTRA"
private const val PROGUARD_MAPPING_FILE_NAME = "leakCanaryObfuscationMapping.txt"
fun runAnalysis(
context: Context,
heapDumpFile: File,
heapDumpDurationMillis: Long? = null,
heapDumpReason: String = "Unknown"
) {
val intent = Intent(context, HeapAnalyzerService::class.java)
intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
heapDumpDurationMillis?.let {
//let函数默认当前这个对象作为闭包的it参数,返回值是函数里面最后一行,或者指定return。
//通过let语句,在?.let之后,如果为空不会有任何操作,只有在非空的时候才会执行let之后的操作
intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
}
startForegroundService(context, intent)
}
private fun startForegroundService(
context: Context,
intent: Intent
) {
if (SDK_INT >= 26) {
context.startForegroundService(intent)
} else {
// Pre-O behavior.
context.startService(intent)
}
}
}
}
8.22 Notifications发通知
internal object Notifications {
//applicationVisible表示应用处于前台可见
val canShowNotification: Boolean
get() = canShowBackgroundNotifications || InternalLeakCanary.applicationVisible
// Instant apps cannot show background notifications
// See https://github.com/square/leakcanary/issues/1197
// TV devices also can't do notifications
//是MOBILE,并且不是isInstantApp
private val canShowBackgroundNotifications =
InternalLeakCanary.formFactor == MOBILE && !InternalLeakCanary.isInstantApp
@Suppress("LongParameterList")
fun showNotification(
context: Context,
contentTitle: CharSequence,
contentText: CharSequence,
pendingIntent: PendingIntent?,
notificationId: Int,
type: NotificationType
) {
if (!canShowNotification) {
return
}
val builder = if (SDK_INT >= O) {
Notification.Builder(context, type.name)
} else Notification.Builder(context)
builder
.setContentText(contentText)
.setContentTitle(contentTitle)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
val notification =
buildNotification(context, builder, type)
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(notificationId, notification)
}
fun buildNotification(
context: Context,
builder: Notification.Builder,
type: NotificationType
): Notification {
builder.setSmallIcon(R.drawable.leak_canary_leak)
.setWhen(System.currentTimeMillis())
if (SDK_INT >= O) {
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//anroid O 要notificationChannel
var notificationChannel: NotificationChannel? =
notificationManager.getNotificationChannel(type.name)
//通知channel频道
if (notificationChannel == null) {
val channelName = context.getString(type.nameResId)
notificationChannel =
NotificationChannel(type.name, channelName, type.importance)
notificationManager.createNotificationChannel(notificationChannel)
}
builder.setChannelId(type.name)
builder.setGroup(type.name)
}
return if (SDK_INT < JELLY_BEAN) {
@Suppress("DEPRECATION")
builder.notification
} else {
builder.build()
}
}
}
8.23 HeapDumpTrigger判断是否可以dump heap,如果可以则dump,交与HeapAnalyzerService分析
//判断是否可以dump heap
// 如果都回收了不可以
// 没达到阈值并且可见,或者刚可见还没达到5s不可以
//最近dump还没超过60s则返回 不可以
// 可以dump,不可见的情况,返回后台了,就去dump,返回false,如果可以则dump,交与HeapAnalyzerService分析
internal class HeapDumpTrigger(
private val application: Application,
private val backgroundHandler: Handler,
private val objectWatcher: ObjectWatcher,
private val gcTrigger: GcTrigger,
private val heapDumper: HeapDumper,
private val configProvider: () -> Config
) {
//NotificationManager
private val notificationManager
get() =
application.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//applicationInvisibleAt不可见的时候为-1,可见的时候为SystemClock.uptimeMillis
private val applicationVisible
get() = applicationInvisibleAt == -1L
//下一次监测的具体时间点
@Volatile
private var checkScheduledAt: Long = 0L
//最近监测的时候,泄漏的对象数量
private var lastDisplayedRetainedObjectCount = 0
//最近dump的时间
private var lastHeapDumpUptimeMillis = 0L
//1.dismissRetainedCountNotification 所有都被回收了,30s之后去除通知
//2.%d retained objects, tap to dump heap 观察到有泄漏对象,30s之后去除通知
private val scheduleDismissRetainedCountNotification = {
dismissRetainedCountNotification()
}
//dismissNoRetainedOnTapNotification 手动dump,但没有泄漏对象,30s之后将通知去除
private val scheduleDismissNoRetainedOnTapNotification = {
dismissNoRetainedOnTapNotification()
}
/**
* When the app becomes invisible, we don't dump the heap immediately. Instead we wait in case
* the app came back to the foreground, but also to wait for new leaks that typically occur on
* back press (activity destroy).
*
* 判断是否刚可见但是还没超过5s
*/
private val applicationInvisibleLessThanWatchPeriod: Boolean
get() {
val applicationInvisibleAt = applicationInvisibleAt
return applicationInvisibleAt != -1L && SystemClock.uptimeMillis() - applicationInvisibleAt < AppWatcher.config.watchDurationMillis
}
//可见的时间
@Volatile
private var applicationInvisibleAt = -1L
//可见监听
fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
if (applicationVisible) {
applicationInvisibleAt = -1L
} else {
applicationInvisibleAt = SystemClock.uptimeMillis()
// Scheduling for after watchDuration so that any destroyed activity has time to become
// watch and be part of this analysis.
//5s后调度以便任何被破坏的活动有时间成为 watch 并成为此分析的一部分。
scheduleRetainedObjectCheck(
delayMillis = AppWatcher.config.watchDurationMillis//5s
)
}
}
private fun checkRetainedObjects() {
//是否可以dump heap
val iCanHasHeap = HeapDumpControl.iCanHasHeap()
//配置
val config = configProvider()
//不可以的话,return
if (iCanHasHeap is Nope) {
if (iCanHasHeap is NotifyingNope) {
// Before notifying that we can't dump heap, let's check if we still have retained object.
// objectWatcher参数
var retainedReferenceCount = objectWatcher.retainedObjectCount
//主动触发gc
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
//重新获取异常持有对象
retainedReferenceCount = objectWatcher.retainedObjectCount
}
val nopeReason = iCanHasHeap.reason()
val wouldDump = !checkRetainedCount(
retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
)
if (wouldDump) {
val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))//手机啥也不干,tv弹toast
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = uppercaseReason
)
}
} else {
SharkLog.d {
application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
return
}
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
//如果没达到阈值,或者不可见/刚可见还没达到5s,返回
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
//可以dump,就去dump
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {//离最近的dump小于60s
onRetainInstanceListener.onEvent(DumpHappenedRecently)//回调
showRetainedCountNotification(//发通知
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
scheduleRetainedObjectCheck(//计算下一次时间
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
//开始dumpheap
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()//干哈的?todo
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis//dump开始的时间
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> {
//省略 dump失败,等待重试代码和发送失败通知代码
if (retry) {
SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
scheduleRetainedObjectCheck(
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS//5s
)
} else {
SharkLog.d { "Failed to dump heap, will not automatically retry" }
}
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
}
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()//最近dump的时间
///清除 objectWatcher 中,在heapDumpUptimeMillis之前持有的对象,也就是已经dump的对象
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
// 发送文件到HeapAnalyzerService解析
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}
//干哈的?todo
private fun saveResourceIdNamesToMemory() {
val resources = application.resources
AndroidResourceIdNames.saveToMemory(
getResourceTypeName = { id ->
try {
resources.getResourceTypeName(id)
} catch (e: NotFoundException) {
null
}
},
getResourceEntryName = { id ->
try {
resources.getResourceEntryName(id)
} catch (e: NotFoundException) {
null
}
})
}
//用户手动dump
fun onDumpHeapReceived(forceDump: Boolean) {
backgroundHandler.post {//手动dump,放到后台线程
dismissNoRetainedOnTapNotification()
gcTrigger.runGc()
val retainedReferenceCount = objectWatcher.retainedObjectCount
if (!forceDump && retainedReferenceCount == 0) {//不是强制dump,并且数量为0的时候
SharkLog.d { "Ignoring user request to dump heap: no retained objects remaining after GC" }
@Suppress("DEPRECATION")
val builder = Notification.Builder(application)
.setContentTitle(
application.getString(R.string.leak_canary_notification_no_retained_object_title)
)
.setContentText(
application.getString(
R.string.leak_canary_notification_no_retained_object_content
)
)
.setAutoCancel(true)
.setContentIntent(NotificationReceiver.pendingIntent(application, CANCEL_NOTIFICATION))
val notification =
Notifications.buildNotification(application, builder, LEAKCANARY_LOW)
notificationManager.notify(
R.id.leak_canary_notification_no_retained_object_on_tap, notification
)
backgroundHandler.postDelayed(//通知
scheduleDismissNoRetainedOnTapNotification,
DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS
)
lastDisplayedRetainedObjectCount = 0
return@post
}
SharkLog.d { "Dumping the heap because user requested it" }
dumpHeap(retainedReferenceCount, retry = false, "user request")
}
}
/**
* 如果都回收了,(没达到阈值(并且可见,或者刚可见还没达到5s)),返回true,
* 可以dump,不可见的情况,就去dump,返回false
*/
private fun checkRetainedCount(
retainedKeysCount: Int,
retainedVisibleThreshold: Int,
nopeReason: String? = null
): Boolean {
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
lastDisplayedRetainedObjectCount = retainedKeysCount
//都回收了
if (retainedKeysCount == 0) {
if (countChanged) {
SharkLog.d { "All retained objects have been garbage collected" }
onRetainInstanceListener.onEvent(NoMoreObjects)
showNoMoreRetainedObjectNotification()
}
return true
}
val applicationVisible = applicationVisible
val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod
//region 没啥用,log用
if (countChanged) {
val whatsNext = if (applicationVisible) {
if (retainedKeysCount < retainedVisibleThreshold) {
"not dumping heap yet (app is visible & < $retainedVisibleThreshold threshold)"
} else {
if (nopeReason != null) {
"would dump heap now (app is visible & >=$retainedVisibleThreshold threshold) but $nopeReason"
} else {
"dumping heap now (app is visible & >=$retainedVisibleThreshold threshold)"
}
}
} else if (applicationInvisibleLessThanWatchPeriod) {
val wait =
AppWatcher.config.watchDurationMillis - (SystemClock.uptimeMillis() - applicationInvisibleAt)
if (nopeReason != null) {
"would dump heap in $wait ms (app just became invisible) but $nopeReason"
} else {
"dumping heap in $wait ms (app just became invisible)"
}
} else {
if (nopeReason != null) {
"would dump heap now (app is invisible) but $nopeReason"
} else {
"dumping heap now (app is invisible)"
}
}
SharkLog.d {
val s = if (retainedKeysCount > 1) "s" else ""
"Found $retainedKeysCount object$s retained, $whatsNext"
}
}
//endregion
if (retainedKeysCount < retainedVisibleThreshold) {
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
if (countChanged) {
onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
}
showRetainedCountNotification(
objectCount = retainedKeysCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
)
)
scheduleRetainedObjectCheck(
delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
)
return true
}
}
return false
}
//delayMillis时间之后,
fun scheduleRetainedObjectCheck(//5s
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
//下一次监测的具体时间点,如果>0,就是说有计划了,这里返回
if (checkCurrentlyScheduledAt > 0) {
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({//检测是否可以dump,放到后台线程
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
//All retained objects have been garbage collected
//所有都被回收了
private fun showNoMoreRetainedObjectNotification() {
backgroundHandler.removeCallbacks(scheduleDismissRetainedCountNotification)
if (!Notifications.canShowNotification) {
return
}
val builder = Notification.Builder(application)
.setContentTitle(
application.getString(R.string.leak_canary_notification_no_retained_object_title)
)
.setContentText(
application.getString(
R.string.leak_canary_notification_no_retained_object_content
)
)
.setAutoCancel(true)
.setContentIntent(NotificationReceiver.pendingIntent(application, CANCEL_NOTIFICATION))
val notification =
Notifications.buildNotification(application, builder, LEAKCANARY_LOW)
notificationManager.notify(R.id.leak_canary_notification_retained_objects, notification)
backgroundHandler.postDelayed(//通知
scheduleDismissRetainedCountNotification, DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS
)
}
//有待回收的对象
private fun showRetainedCountNotification(
objectCount: Int,
contentText: String
) {
backgroundHandler.removeCallbacks(scheduleDismissRetainedCountNotification)
if (!Notifications.canShowNotification) {
return
}
@Suppress("DEPRECATION")
val builder = Notification.Builder(application)
.setContentTitle(
//%d retained objects, tap to dump heap
application.getString(R.string.leak_canary_notification_retained_title, objectCount)
)
.setContentText(contentText)
.setAutoCancel(true)
.setContentIntent(NotificationReceiver.pendingIntent(application, DUMP_HEAP))
val notification =
Notifications.buildNotification(application, builder, LEAKCANARY_LOW)
notificationManager.notify(R.id.leak_canary_notification_retained_objects, notification)
}
private fun dismissRetainedCountNotification() {
backgroundHandler.removeCallbacks(scheduleDismissRetainedCountNotification)
notificationManager.cancel(R.id.leak_canary_notification_retained_objects)
}
private fun dismissNoRetainedOnTapNotification() {
backgroundHandler.removeCallbacks(scheduleDismissNoRetainedOnTapNotification)
notificationManager.cancel(R.id.leak_canary_notification_no_retained_object_on_tap)
}
companion object {
private const val WAIT_AFTER_DUMP_FAILED_MILLIS = 5_000L
private const val WAIT_FOR_OBJECT_THRESHOLD_MILLIS = 2_000L
private const val DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS = 30_000L
private const val WAIT_BETWEEN_HEAP_DUMPS_MILLIS = 60_000L
}
}
8.24 InternalLeakCanary 创建后台线程,交与HeapDumpTrigger定时判断是否需要dump,AndroidHeapDumper负责dump,交与HeapAnalyzerService分析
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
//用于添加动态图标
private const val DYNAMIC_SHORTCUT_ID = "com.squareup.leakcanary.dynamic_shortcut"
private lateinit var heapDumpTrigger: HeapDumpTrigger//lateinit 只用于变量 var,而 lazy 只用于常量 val,https://www.jianshu.com/p/e2cb4c65d4ff
// You're wrong https://discuss.kotlinlang.org/t/object-or-top-level-property-name-warning/6621/7
@Suppress("ObjectPropertyName")
private var _application: Application? = null
val application: Application
get() {
check(_application != null) {
"LeakCanary not installed, see AppWatcher.manualInstall()"
}
return _application!!
}
// BuildConfig.LIBRARY_VERSION is stripped so this static var is how we keep it around to find
// it later when parsing the heap dump.
@Suppress("unused")
@JvmStatic
private var version = BuildConfig.LIBRARY_VERSION
@Volatile
var applicationVisible = false
private set
private val isDebuggableBuild by lazy {
(application.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
}
fun createLeakDirectoryProvider(context: Context): LeakDirectoryProvider {
val appContext = context.applicationContext
return LeakDirectoryProvider(appContext, {
LeakCanary.config.maxStoredHeapDumps//7
}, {
LeakCanary.config.requestWriteExternalStoragePermission//false
})
}
//型号类型
internal enum class FormFactor {
MOBILE,
TV,
WATCH,
}
//获取是手机吗
val formFactor by lazy {
val currentModeType =
(application.getSystemService(UI_MODE_SERVICE) as UiModeManager).currentModeType
return@lazy when (currentModeType) {
Configuration.UI_MODE_TYPE_TELEVISION -> TV
Configuration.UI_MODE_TYPE_WATCH -> WATCH
else -> MOBILE
}
}
//是否是isInstantApp
val isInstantApp by lazy {
VERSION.SDK_INT >= VERSION_CODES.O && application.packageManager.isInstantApp
}
//onRetainInstanceListener通知
val onRetainInstanceListener by lazy {
when (formFactor) {
TV -> TvOnRetainInstanceListener(application)//tv的话有
else -> DefaultOnRetainInstanceListener()//手机的话不干啥
}
}
//可见的activity
var resumedActivity: Activity? = null
//sp
private val heapDumpPrefs by lazy {
application.getSharedPreferences("LeakCanaryHeapDumpPrefs", Context.MODE_PRIVATE)
}
//about页面的控制
internal var dumpEnabledInAboutScreen: Boolean
get() {
return heapDumpPrefs
.getBoolean("AboutScreenDumpEnabled", true)
}
set(value) {
heapDumpPrefs
.edit()
.putBoolean("AboutScreenDumpEnabled", value)
.apply()
}
override fun invoke(application: Application) {
_application = application
//只能运行在debug版本,release可以配置
checkRunningInDebuggableBuild()
//添加 addOnObjectRetainedListener,泄漏发生的时候调用onObjectRetained
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
//dump真正的地方AndroidHeapDumper,将其传给HeapDumpTrigger管理
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
//Gc触发器
val gcTrigger = GcTrigger.Default
//config
val configProvider = { LeakCanary.config }
//开辟一个线程,在后台监测是否可以dump
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
//创建内存快照转储触发器 HeapDumpTrigger
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
//添加application前后台变化监听
//监听application 前后台变动,并且记录来到后台时间,便于LeakCanary 针对刚刚切入后台的一些destroy操作做泄漏监测
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
//注册activity生命周期回调,获取当前resumed的activity实例
registerResumedActivityListener(application)
//添加动态的桌面快捷入口
addDynamicShortcut(application)
// We post so that the log happens after Application.onCreate()
// 判断是否应该DumpHeap
mainHandler.post {
// https://github.com/square/leakcanary/issues/1981
// We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
// which blocks until loaded and that creates a StrictMode violation.
backgroundHandler.post {
SharkLog.d {
when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}
}
}
private fun checkRunningInDebuggableBuild() {
if (isDebuggableBuild) {
return
}
if (!application.resources.getBoolean(R.bool.leak_canary_allow_in_non_debuggable_build)) {
throw Error(
"""
LeakCanary in non-debuggable build
LeakCanary should only be used in debug builds, but this APK is not debuggable.
Please follow the instructions on the "Getting started" page to only include LeakCanary in
debug builds: https://square.github.io/leakcanary/getting_started/
If you're sure you want to include LeakCanary in a non-debuggable build, follow the
instructions here: https://square.github.io/leakcanary/recipes/#leakcanary-in-release-builds
""".trimIndent()
)
}
}
private fun registerResumedActivityListener(application: Application) {
application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityResumed(activity: Activity) {
resumedActivity = activity
}
override fun onActivityPaused(activity: Activity) {
if (resumedActivity === activity) {
resumedActivity = null
}
}
})
}
//动态图标,长安点击出来的那个
@Suppress("ReturnCount")
private fun addDynamicShortcut(application: Application) {
if (VERSION.SDK_INT < VERSION_CODES.N_MR1) {
return
}
if (!application.resources.getBoolean(R.bool.leak_canary_add_dynamic_shortcut)) {
return
}
if (isInstantApp) {
// Instant Apps don't have access to ShortcutManager
return
}
val shortcutManager = application.getSystemService(ShortcutManager::class.java)!!
val dynamicShortcuts = shortcutManager.dynamicShortcuts
val shortcutInstalled =
dynamicShortcuts.any { shortcut -> shortcut.id == DYNAMIC_SHORTCUT_ID }
if (shortcutInstalled) {
return
}
val mainIntent = Intent(Intent.ACTION_MAIN, null)
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER)
mainIntent.setPackage(application.packageName)
val activities = application.packageManager.queryIntentActivities(mainIntent, 0)
.filter {
it.activityInfo.name != "leakcanary.internal.activity.LeakLauncherActivity"
}
if (activities.isEmpty()) {//app
return
}
val firstMainActivity = activities.first()
.activityInfo
// Displayed on long tap on app icon
val longLabel: String
// Label when dropping shortcut to launcher
val shortLabel: String
val leakActivityLabel = application.getString(R.string.leak_canary_shortcut_label)
if (activities.isEmpty()) {
longLabel = leakActivityLabel
shortLabel = leakActivityLabel
} else {
val firstLauncherActivityLabel = if (firstMainActivity.labelRes != 0) {
application.getString(firstMainActivity.labelRes)
} else {
application.packageManager.getApplicationLabel(application.applicationInfo)
}
val fullLengthLabel = "$firstLauncherActivityLabel $leakActivityLabel"
// short label should be under 10 and long label under 25
if (fullLengthLabel.length > 10) {
if (fullLengthLabel.length <= 25) {
longLabel = fullLengthLabel
shortLabel = leakActivityLabel
} else {
longLabel = leakActivityLabel
shortLabel = leakActivityLabel
}
} else {
longLabel = fullLengthLabel
shortLabel = fullLengthLabel
}
}
val componentName = ComponentName(firstMainActivity.packageName, firstMainActivity.name)
val shortcutCount = dynamicShortcuts.count { shortcutInfo ->
shortcutInfo.activity == componentName
} + shortcutManager.manifestShortcuts.count { shortcutInfo ->
shortcutInfo.activity == componentName
}
if (shortcutCount >= shortcutManager.maxShortcutCountPerActivity) {
return
}
val intent = LeakCanary.newLeakDisplayActivityIntent()
intent.action = "Dummy Action because Android is stupid"
val shortcut = Builder(application, DYNAMIC_SHORTCUT_ID)
.setLongLabel(longLabel)
.setShortLabel(shortLabel)
.setActivity(componentName)
.setIcon(Icon.createWithResource(application, R.mipmap.leak_canary_icon))
.setIntent(intent)
.build()
try {
shortcutManager.addDynamicShortcuts(listOf(shortcut))
} catch (ignored: Throwable) {
SharkLog.d(ignored) {
"Could not add dynamic shortcut. " +
"shortcutCount=$shortcutCount, " +
"maxShortcutCountPerActivity=${shortcutManager.maxShortcutCountPerActivity}"
}
}
}
//发现泄漏了,调用heapDumpTrigger的scheduleRetainedObjectCheck
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
//用户手动触发dump
fun onDumpHeapReceived(forceDump: Boolean) {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onDumpHeapReceived(forceDump)
}
}
//显示/隐藏快捷键快捷方法
fun setEnabledBlocking(//nouse,没有调用到
componentClassName: String,
enabled: Boolean
) {
val component = ComponentName(application, componentClassName)
val newState =
if (enabled) COMPONENT_ENABLED_STATE_ENABLED else COMPONENT_ENABLED_STATE_DISABLED
// Blocks on IPC.
application.packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP)
}
private const val LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump"
}
8.25 LeakCanary Config 配置,和几个公共方法newLeakDisplayActivityIntent showLeakDisplayActivityLauncherIcon dumpHeap
object LeakCanary {
/**
* LeakCanary configuration data class. Properties can be updated via [copy].
*
* @see [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,//控制是否dump
/**
* 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,//控制debugger模式是否dump
/**
* 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.Config.watchDurationMillis] 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,//数量阈值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]
*
* todo
*/
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]
*
* todo
*/
val objectInspectors: List<ObjectInspector> = AndroidObjectInspectors.appDefaults,
/**
* 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]
*
* 默认是DefaultOnHeapAnalyzedListener,后台线程分析完调用,将leaks加入到数据库中,
* 并发出notification,点击notification去LeakActivity
*/
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]
* todo
*/
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.
* 会耗时,计算泄漏的内存大小需要从GC根导航到整个对象图,
* 而[shark.HeapAnalyzer]会在找到所有泄漏实例后立即停止。
*/
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.
* 保持的heap个数,7个,不要设置为1,2个
*/
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.
* 如果没权限,requestWriteExternalStoragePermission为true的时候,会显示通知
*/
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]:
*
* ```
* LeakCanary.config = LeakCanary.config.copy(
* leakingObjectFinder = FilteringLeakingObjectFinder(
* AndroidObjectInspectors.appLeakingObjectFilters
* )
* )
* ```
*
* todo
*/
val leakingObjectFinder: LeakingObjectFinder = KeyedWeakReferenceFinder,
/**
* 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
) {
/**
* Construct a new Config via [LeakCanary.Config.Builder].
* Note: this method is intended to be used from Java code only. For idiomatic(惯用的) Kotlin use
* `copy()` to modify [LeakCanary.config].
*/
@Suppress("NEWER_VERSION_IN_SINCE_KOTLIN")
@SinceKotlin("999.9") // Hide from Kotlin code, this method is only for Java code
fun newBuilder() = Builder(this)
/**
* Builder for [LeakCanary.Config] intended to be used only from Java code.
*
* Usage:
* ```
* LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
* .retainedVisibleThreshold(3)
* .build();
* LeakCanary.setConfig(config);
* ```
*
* For idiomatic Kotlin use `copy()` method instead:
* ```
* LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3)
* ```
*/
class Builder internal constructor(config: Config) {
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
/** @see [LeakCanary.Config.dumpHeap] */
fun dumpHeap(dumpHeap: Boolean) =
apply { this.dumpHeap = dumpHeap }
/** @see [LeakCanary.Config.dumpHeapWhenDebugging] */
fun dumpHeapWhenDebugging(dumpHeapWhenDebugging: Boolean) =
apply { this.dumpHeapWhenDebugging = dumpHeapWhenDebugging }
/** @see [LeakCanary.Config.retainedVisibleThreshold] */
fun retainedVisibleThreshold(retainedVisibleThreshold: Int) =
apply { this.retainedVisibleThreshold = retainedVisibleThreshold }
/** @see [LeakCanary.Config.referenceMatchers] */
fun referenceMatchers(referenceMatchers: List<ReferenceMatcher>) =
apply { this.referenceMatchers = referenceMatchers }
/** @see [LeakCanary.Config.objectInspectors] */
fun objectInspectors(objectInspectors: List<ObjectInspector>) =
apply { this.objectInspectors = objectInspectors }
/** @see [LeakCanary.Config.onHeapAnalyzedListener] */
fun onHeapAnalyzedListener(onHeapAnalyzedListener: OnHeapAnalyzedListener) =
apply { this.onHeapAnalyzedListener = onHeapAnalyzedListener }
/** @see [LeakCanary.Config.metadataExtractor] */
fun metadataExtractor(metadataExtractor: MetadataExtractor) =
apply { this.metadataExtractor = metadataExtractor }
/** @see [LeakCanary.Config.computeRetainedHeapSize] */
fun computeRetainedHeapSize(computeRetainedHeapSize: Boolean) =
apply { this.computeRetainedHeapSize = computeRetainedHeapSize }
/** @see [LeakCanary.Config.maxStoredHeapDumps] */
fun maxStoredHeapDumps(maxStoredHeapDumps: Int) =
apply { this.maxStoredHeapDumps = maxStoredHeapDumps }
/** @see [LeakCanary.Config.requestWriteExternalStoragePermission] */
fun requestWriteExternalStoragePermission(requestWriteExternalStoragePermission: Boolean) =
apply { this.requestWriteExternalStoragePermission = requestWriteExternalStoragePermission }
/** @see [LeakCanary.Config.leakingObjectFinder] */
fun leakingObjectFinder(leakingObjectFinder: LeakingObjectFinder) =
apply { this.leakingObjectFinder = leakingObjectFinder }
fun build() = config.copy(
dumpHeap = dumpHeap,
dumpHeapWhenDebugging = dumpHeapWhenDebugging,
retainedVisibleThreshold = retainedVisibleThreshold,
referenceMatchers = referenceMatchers,
objectInspectors = objectInspectors,
onHeapAnalyzedListener = onHeapAnalyzedListener,
metadataExtractor = metadataExtractor,
computeRetainedHeapSize = computeRetainedHeapSize,
maxStoredHeapDumps = maxStoredHeapDumps,
requestWriteExternalStoragePermission = requestWriteExternalStoragePermission,
leakingObjectFinder = leakingObjectFinder
)
}
}
/**
* The current LeakCanary configuration. Can be updated at any time, usually by replacing it with
* a mutated copy, e.g.:
*
* ```
* LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3)
* ```
*
* In Java, use [LeakCanary.Config.Builder] instead:
* ```
* LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
* .retainedVisibleThreshold(3)
* .build();
* LeakCanary.setConfig(config);
* ```
*/
@JvmStatic
@Volatile
var config: Config = Config()
set(newConfig) {
val previousConfig = field
field = newConfig
logConfigChange(previousConfig, newConfig)
HeapDumpControl.updateICanHasHeap()
}
//打印config区别
private fun logConfigChange(
previousConfig: Config,
newConfig: Config
) {
SharkLog.d {
val changedFields = mutableListOf<String>()
Config::class.java.declaredFields.forEach { field ->
field.isAccessible = true
val previousValue = field[previousConfig]
val newValue = field[newConfig]
if (previousValue != newValue) {
changedFields += "${field.name}=$newValue"
}
}
val changesInConfig =
if (changedFields.isNotEmpty()) changedFields.joinToString(", ") else "no changes"
"Updated LeakCanary.config: Config($changesInConfig)"
}
}
/**
* Returns a new [Intent] that can be used to programmatically launch the leak display activity.
* 跳转LeakActivity的方法
*/
fun newLeakDisplayActivityIntent() = LeakActivity.createIntent(InternalLeakCanary.application)
/**
* Dynamically shows / hides the launcher icon for the leak display activity.
* Note: you can change the default value by overriding the `leak_canary_add_launcher_icon`
* boolean resource:
*
* ```
* <?xml version="1.0" encoding="utf-8"?>
* <resources>
* <bool name="leak_canary_add_launcher_icon">false</bool>
* </resources>
* ```
*
* 显示/隐藏快捷键快捷方法
*/
fun showLeakDisplayActivityLauncherIcon(showLauncherIcon: Boolean) {
InternalLeakCanary.setEnabledBlocking(
"leakcanary.internal.activity.LeakLauncherActivity", showLauncherIcon
)
}
/**
* Immediately triggers a heap dump and analysis, if there is at least one retained instance
* tracked by [AppWatcher.objectWatcher]. If there are no retained instances then the heap will not
* be dumped and a notification will be shown instead.
*
* 手动触发dump快捷方法,forceDump强制dump
*/
fun dumpHeap() = InternalLeakCanary.onDumpHeapReceived(forceDump = true)
}
8.26 DisplayLeakAdapter 显示引用连列表
@Suppress("DEPRECATION")
internal class DisplayLeakAdapter constructor(
context: Context,
private val leakTrace: LeakTrace,
private val header: CharSequence//
) : BaseAdapter() {
private val highlightColorHexString: String
private val leakColorHexString: String//#BE383F
private val referenceColorHexString: String//#9976A8
private val extraColorHexString: String
private val helpColorHexString: String
init {
highlightColorHexString = hexStringColor(context, R.color.leak_canary_class_name)
leakColorHexString = hexStringColor(context, R.color.leak_canary_leak)
referenceColorHexString = hexStringColor(context, R.color.leak_canary_reference)
extraColorHexString = hexStringColor(context, R.color.leak_canary_extra)
helpColorHexString = hexStringColor(context, R.color.leak_canary_help)
}
//OK 类型就两种,header,和 row
override fun getView(
position: Int,
convertView: View?,
parent: ViewGroup
): View {
return when (getItemViewType(position)) {
HEADER_ROW -> {//第1个
val view = convertView ?: parent.inflate(R.layout.leak_canary_leak_header)
bindHeaderRow(view)
view
}
CONNECTOR_ROW -> {//第2个到最后一个
val view = convertView ?: parent.inflate(R.layout.leak_canary_ref_row)
bindConnectorRow(view, position)
view
}
else -> {
throw IllegalStateException("Unexpected type ${getItemViewType(position)}")
}
}
}
//ok 绑定header
private fun bindHeaderRow(//第1个
view: View
) {
view.findViewById<TextView>(R.id.leak_canary_header_text)
.apply {
//图文//https://www.jianshu.com/p/6843f332c8df
//formHtml()方法已经将 HTML 内容中的超链接和图片转义成为 UrlSpan 和 ImageSpan,进而在 TextView 中完成显示。
// 但是此时是没有任何用户交互的,用户只能看到 HTML 的内容,下面介绍如何添加用户交互功能。
//
//要完成用户交互,这里我们需要在 TextView 中还需要调用textView.setMovementMethod()方法。
//
//Android 提供了 LinkMovementMethod 类以实现了对于文本内容中超链接的遍历,并且支持对于超链接的点击事件。
movementMethod = LinkMovementMethod.getInstance()
text = header
}
}
//绑定每行
private fun bindConnectorRow(//第2个到最后一个
view: View,
position: Int
) {
val titleView = view.findViewById<TextView>(R.id.leak_canary_row_title)
val connector = view.findViewById<DisplayLeakConnectorView>(R.id.leak_canary_row_connector)
//设置指针类型
connector.setType(getConnectorType(position))
titleView.text = when {
position == 1 -> {//第二个,index为1,第一个显示head了
"GC Root: ${leakTrace.gcRootType.description}"
}
position < count - 1 -> {//
val referencePathIndex = elementIndex(position)
//LeakTraceReference 引用连其中一个path
val referencePath = leakTrace.referencePath[referencePathIndex]
//是否怀疑泄漏
val isSuspect = leakTrace.referencePathElementIsSuspect(referencePathIndex)
//LeakTraceObject 引用连的对象
val leakTraceObject = referencePath.originObject
//typeName
// thread或者
// CLASS,
// ARRAY,
// INSTANCE
val typeName =
if (position == 2 && leakTrace.gcRootType == JAVA_FRAME) "thread" else leakTraceObject.typeName
//对象名字
var referenceName = referencePath.referenceDisplayName
referenceName = referenceName.replace("<".toRegex(), "<")
.replace(">".toRegex(), ">")
referenceName = if (isSuspect) {//颜色
Log.i("LeakCanary", "DisplayLeakAdapter leakColorHexString " + leakColorHexString)
"<u><font color='$leakColorHexString'>$referenceName</font></u>"
} else {
Log.i("LeakCanary", "DisplayLeakAdapter referenceColorHexString " + referenceColorHexString)
"<font color='$referenceColorHexString'>$referenceName</font>"
}
if (referencePath.referenceType == STATIC_FIELD) {
referenceName = "<i>$referenceName</i>"//斜体文本。静态的呈现斜体
}
if (isSuspect) {
referenceName = "<b>$referenceName</b>"//如果怀疑的话加粗
}
//是否静态
val staticPrefix = if (referencePath.referenceType == STATIC_FIELD) "static " else ""
//空格 + 静态 + 类名 + 对象名字
val htmlString = leakTraceObject.asHtmlString(typeName) +
"$INDENTATION$staticPrefix${referencePath.styledOwningClassSimpleName()}.$referenceName"
val builder = Html.fromHtml(htmlString) as SpannableStringBuilder
if (isSuspect) {
SquigglySpan.replaceUnderlineSpans(builder, view.context)//加上斜线
}
builder
}//第三个到 count-2,倒数第二个
else -> {//最后一个
Html.fromHtml(leakTrace.leakingObject.asHtmlString(leakTrace.leakingObject.typeName))
}
}
}
//
private fun LeakTraceObject.asHtmlString(typeName: String): String {
val packageEnd = className.lastIndexOf('.')
val extra: (String) -> String = { "<font color='$extraColorHexString'>$it</font>" }
val styledClassName = styledClassSimpleName()
var htmlString =
if (packageEnd != -1) "${
extra(
className.substring(
0, packageEnd
)
)
}.$styledClassName" else styledClassName
//包名+类名+ typename
htmlString += " ${extra(typeName)}<br>"
val reachabilityString = when (leakingStatus) {
UNKNOWN -> extra("UNKNOWN")
NOT_LEAKING -> "NO" + extra(" (${leakingStatusReason})")
LEAKING -> "YES" + extra(" (${leakingStatusReason})")
}
//是否泄漏
htmlString += "$INDENTATION${extra("Leaking: ")}$reachabilityString<br>"
//泄漏的多少内存多少个对象
retainedHeapByteSize?.let {
val humanReadableRetainedHeapSize = humanReadableByteCount(it.toLong(), si = true)
htmlString += "${INDENTATION}${extra("Retaining ")}$humanReadableRetainedHeapSize${
extra(
" in "
)
}$retainedObjectCount${extra(" objects")}<br>"
}
//一些其他参数
labels.forEach { label ->//key,watchDurationMillis,retainedDurationMillis,mApplication,mBase
htmlString += "$INDENTATION${extra(label)}<br>"
}
return htmlString
}
private fun LeakTraceObject.styledClassSimpleName(): String {//扩展方法
val simpleName = classSimpleName.replace("[]", "[ ]")
return "<font color='$highlightColorHexString'>$simpleName</font>"
}
private fun LeakTraceReference.styledOwningClassSimpleName(): String {
val simpleName = owningClassSimpleName.replace("[]", "[ ]")
return "<font color='$highlightColorHexString'>$simpleName</font>"
}
//获取前面指针的类型
@Suppress("ReturnCount")
private fun getConnectorType(position: Int): Type {
if (position == 1) {
return GC_ROOT
} else if (position == 2) {
return when (leakTrace.referencePath.size) {
0 -> END_FIRST_UNREACHABLE
1 -> START_LAST_REACHABLE
else -> {
val nextReachability = leakTrace.referencePath[1].originObject
if (nextReachability.leakingStatus != NOT_LEAKING) {
START_LAST_REACHABLE
} else START
}
}
} else {
val isLeakingInstance = position == count - 1
if (isLeakingInstance) {
val previousReachability = leakTrace.referencePath.last()
.originObject
return if (previousReachability.leakingStatus != LEAKING) {
END_FIRST_UNREACHABLE
} else END
} else {
val reachability = leakTrace.referencePath[elementIndex(position)].originObject
when (reachability.leakingStatus) {
UNKNOWN -> return NODE_UNKNOWN
NOT_LEAKING -> {
val nextReachability =
if (position + 1 == count - 1) leakTrace.leakingObject else leakTrace.referencePath[elementIndex(
position + 1
)].originObject
return if (nextReachability.leakingStatus != NOT_LEAKING) {
NODE_LAST_REACHABLE
} else {
NODE_REACHABLE
}
}
LEAKING -> {
val previousReachability =
leakTrace.referencePath[elementIndex(position - 1)].originObject
return if (previousReachability.leakingStatus != LEAKING) {
NODE_FIRST_UNREACHABLE
} else {
NODE_UNREACHABLE
}
}
else -> throw IllegalStateException(
"Unknown value: " + reachability.leakingStatus
)
}
}
}
}
override fun isEnabled(position: Int) = false
override fun getCount() = leakTrace.referencePath.size + 3//header + GC Root + 最后一个
override fun getItem(position: Int) = when {
position == 0 || position == 1 -> null //前两个
position == count - 1 -> leakTrace.leakingObject //最后一个
else -> leakTrace.referencePath[elementIndex(position)]//中间的显示leakTrace.referencePath里的
}
private fun elementIndex(position: Int) = position - 2//中间的
override fun getViewTypeCount() = 2
override fun getItemViewType(position: Int) = if (position == 0) HEADER_ROW else CONNECTOR_ROW
override fun getItemId(position: Int) = position.toLong()
companion object {
const val HEADER_ROW = 0
const val CONNECTOR_ROW = 1
//在html代码中每输入一个转义字符 就表示一个空格,输入十个 ,页面中就显示10个空格位置。
//而在html代码中输入空格,不管输入多少个空格,最终在页面中显示的空格位置只有一个。
val INDENTATION = " ".repeat(4)//indentation行首空格
// https://stackoverflow.com/a/6540378/703646 十六进制
private fun hexStringColor(
context: Context,
colorResId: Int
): String {
//使用的html
return String.format("#%06X", 0xFFFFFF and context.getColorCompat(colorResId))
}
}
}
8.27 Screen接口 代表页面,显示一个view
//Screen 代表页面,显示一个view
internal abstract class Screen : Serializable {
abstract fun createView(container: ViewGroup): View
}
8.28 Views.kt 定义了一些扩展函数 inflat,activity,goto,toback,onCreateOptionsMenu,onScreenExiting,notifyScreenExiting
//定义了一些扩展函数
internal fun ViewGroup.inflate(layoutResId: Int) = LayoutInflater.from(context)
.inflate(layoutResId, this, false)!!
internal val View.activity
get() = context as Activity
@Suppress("UNCHECKED_CAST")
internal fun <T : Activity> View.activity() = context as T
internal fun View.onCreateOptionsMenu(onCreateOptionsMenu: (Menu) -> Unit) {
activity<NavigatingActivity>().onCreateOptionsMenu = onCreateOptionsMenu
activity.invalidateOptionsMenu()
}
internal fun View.goTo(screen: Screen) {
activity<NavigatingActivity>().goTo(screen)
}
internal fun View.goBack() {
activity<NavigatingActivity>().goBack()
}
internal fun Context.getColorCompat(id: Int): Int {
return if (VERSION.SDK_INT >= 23) {
getColor(id)
} else {
resources.getColor(id)
}
}
//将响应放tag上
internal fun View.onScreenExiting(block: () -> Unit) {
@Suppress("UNCHECKED_CAST")
//callbacks是一个list
var callbacks = getTag(R.id.leak_canary_notification_on_screen_exit) as MutableList<() -> Unit>?
if (callbacks == null) {
callbacks = mutableListOf()
setTag(R.id.leak_canary_notification_on_screen_exit, callbacks)
}
callbacks.add(block)
}
internal fun View.notifyScreenExiting() {
@Suppress("UNCHECKED_CAST")
val callbacks = getTag(R.id.leak_canary_notification_on_screen_exit)
as MutableList<() -> Unit>?
//挨个callback调用invoke
callbacks?.forEach { it.invoke() }
}
8.29 简单的NavigatingActivity导航activity
/**
* A simple backstack navigating activity
*/
internal abstract class NavigatingActivity : Activity() {
private lateinit var backstack: ArrayList<BackstackFrame> //view层级关系
private lateinit var currentScreen: Screen //现在的view
private lateinit var container: ViewGroup //视图容器
private lateinit var currentView: View //现在的view
var onCreateOptionsMenu = NO_MENU //menu
fun installNavigation(
savedInstanceState: Bundle?,
container: ViewGroup
) {
this.container = container
if (savedInstanceState == null) {
backstack = ArrayList()
currentScreen = if (intent.hasExtra("screens")) {
@Suppress("UNCHECKED_CAST")
val screens = intent.getSerializableExtra("screens") as List<Screen>
screens.dropLast(1)//去掉最后1个开始取值 https://blog.csdn.net/sinat_31057219/article/details/105996133
.forEach { screen ->
backstack.add(BackstackFrame(screen))//放到backstack里面
}
screens.last()//最后一个
} else {
getLauncherScreen()
}
} else {
currentScreen = savedInstanceState.getSerializable("currentScreen") as Screen
@Suppress("UNCHECKED_CAST")
backstack = savedInstanceState.getParcelableArrayList<Parcelable>(
"backstack"
) as ArrayList<BackstackFrame>
}
currentView = currentScreen.createView(container)
container.addView(currentView)
actionBar?.run {
setHomeButtonEnabled(true)//决定左上角的图标是否可以点击。没有向左的小图标。 true 图标可以点击 false 不可以点击。
setDisplayHomeAsUpEnabled(true)// 给左上角图标的左边加上一个返回的图标
}
screenUpdated()
}
override fun onNewIntent(intent: Intent) {
if (intent.hasExtra("screens")) {
@Suppress("UNCHECKED_CAST")
val screens = intent.getSerializableExtra("screens") as List<Screen> //堆栈里放其他页面
goTo(intent.getSerializableExtra("screen") as Screen) //去screen页面
backstack.clear()
screens.dropLast(1)///去掉最后1个开始取值,放堆栈里
.forEach { screen ->
backstack.add(BackstackFrame(screen))
}
}
}
//子类可以覆盖这个方法
open fun getLauncherScreen(): Screen {
TODO("Launcher activities should override getLauncherScreen()")
}
//保存状态
public override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putSerializable("currentScreen", currentScreen)
outState.putParcelableArrayList("backstack", backstack)
}
//堆栈里还有,就goback,没有super.onBackPressed()
override fun onBackPressed() {
if (backstack.size > 0) {
goBack()
return
}
super.onBackPressed()
}
fun resetTo(screen: Screen) {
onCreateOptionsMenu = NO_MENU
currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_exit_alpha))//currentView动画
container.removeView(currentView)//container 移除当前view
currentView.notifyScreenExiting()//currentView 通知notifyScreenExiting
backstack.clear()//清空
currentScreen = screen //当前screen重置
currentView = currentScreen.createView(container)//创建当前view
currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_enter_alpha))//当前view开启动画
container.addView(currentView)//添加view
screenUpdated()//刷新menu,添加goback按钮,调用onNewScreen(子类可以覆盖)
}
fun goTo(screen: Screen) {
onCreateOptionsMenu = NO_MENU
currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_exit_forward))
container.removeView(currentView)//去掉当前view
currentView.notifyScreenExiting()//通知
//- 当状态需要保存的时候被安卓framework调用,通常会调用dispatchSaveInstanceState() 。
val backstackFrame = BackstackFrame(currentScreen, currentView)//这里会调用saveHierarchyState
backstack.add(backstackFrame)
currentScreen = screen
currentView = currentScreen.createView(container)
currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_enter_forward))
container.addView(currentView)
screenUpdated()//刷新menu,添加goback按钮,调用onNewScreen(子类可以覆盖)
}
fun refreshCurrentScreen() {//当前view刷新
onCreateOptionsMenu = NO_MENU
container.removeView(currentView)
currentView.notifyScreenExiting()
currentView = currentScreen.createView(container)
container.addView(currentView)
screenUpdated()//刷新menu,添加goback按钮,调用onNewScreen(子类可以覆盖)
}
fun goBack() {
onCreateOptionsMenu = NO_MENU
currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_exit_backward))
container.removeView(currentView)
currentView.notifyScreenExiting()
val latest = backstack.removeAt(backstack.size - 1)
currentScreen = latest.screen
currentView = currentScreen.createView(container)
currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_enter_backward))
container.addView(currentView, 0)
latest.restore(currentView)//会调用restoreHierarchyState
screenUpdated()//刷新menu,添加goback按钮,调用onNewScreen(子类可以覆盖)
}
private fun screenUpdated() {
invalidateOptionsMenu()//刷新menu
if (SDK_INT >= 18) {
actionBar?.run {//添加goback按钮
val goBack = backstack.size > 0
val indicator = if (goBack) 0 else android.R.drawable.ic_menu_close_clear_cancel
setHomeAsUpIndicator(indicator)
}
}
onNewScreen(currentScreen)//子类可以覆盖这个方法
}
//而在kotlin的世界里面则不是这样,在kotlin中它所有的类默认都是final的,那么就意味着不能被继承,
// 而且在类中所有的方法也是默认是final的,那么就是kotlin的方法默认也不能被重写。那么想在kotlin中继承父类应该怎么做呢?
protected open fun onNewScreen(screen: Screen) {//
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
onCreateOptionsMenu.invoke(menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
override fun onDestroy() {
super.onDestroy()
currentView.notifyScreenExiting()
}
companion object {
//伴随对象,静态方法,静态值
val NO_MENU: ((Menu) -> Unit) = {}
}
}
8.30 SimpleListAdapter 简化的adapter,就不用单独实现一个类了
//SimpleListAdapter bindView为扩展方法
internal class SimpleListAdapter<T>(
private val rowResId: Int,
private val items: List<T>,
private val bindView: SimpleListAdapter<T>.(View, Int) -> Unit
) : BaseAdapter() {
override fun getView(
position: Int,
convertView: View?,
parent: ViewGroup
): View {
val view = convertView ?: parent.inflate(rowResId)
bindView(view, position)
return view
}
override fun getItem(position: Int) = items[position]
override fun getItemId(position: Int) = position.toLong()
override fun getCount() = items.size
}
8.31 TimeFormatter时间格式化
//时间格式化
internal object TimeFormatter {
private const val MINUTE_MILLIS = 60 * 1000
private const val TWO_MINUTES_MILLIS = 2 * MINUTE_MILLIS
private const val FIFTY_MINUTES_MILLIS = 50 * MINUTE_MILLIS
private const val NINETY_MINUTES_MILLIS = 90 * MINUTE_MILLIS
private const val HOUR_MILLIS = 60 * MINUTE_MILLIS
private const val DAY_MILLIS = 24 * HOUR_MILLIS
private const val TWO_DAYS_MILLIS = 48 * HOUR_MILLIS
fun formatTimestamp(
context: Context,
timestampMillis: Long
): String {
// Based on https://stackoverflow.com/a/13018647
val nowMillis = System.currentTimeMillis()
return when (val diff = nowMillis - timestampMillis) {
in 0..MINUTE_MILLIS -> {
"just now"
}
in MINUTE_MILLIS..TWO_MINUTES_MILLIS -> {
"a minute ago"
}
in TWO_MINUTES_MILLIS..FIFTY_MINUTES_MILLIS -> {
"${diff / MINUTE_MILLIS} minutes ago"
}
in FIFTY_MINUTES_MILLIS..NINETY_MINUTES_MILLIS -> {
"an hour ago"
}
in NINETY_MINUTES_MILLIS..DAY_MILLIS -> {
"${diff / HOUR_MILLIS} hours ago"
}
in DAY_MILLIS..TWO_DAYS_MILLIS -> {
"yesterday"
}
else -> {
DateUtils.formatDateTime(
context, timestampMillis,
DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE
)
}
}
}
}
8.32 UiUtils UrlSpan改为事件响应,urlAction是一个通过url来决定action的lambda函数
internal object UiUtils {
//UrlSpan改为事件响应,urlAction是一个通过url来决定action的lambda函数
internal fun replaceUrlSpanWithAction(
title: SpannableStringBuilder,
urlAction: (String) -> (() -> Unit)?
) {
val urlSpans = title.getSpans(0, title.length, URLSpan::class.java)
for (span in urlSpans) {
val action: (() -> Unit)? = urlAction(span.url)
if (action != null) {
val start = title.getSpanStart(span)
val end = title.getSpanEnd(span)
val flags = title.getSpanFlags(span)
title.removeSpan(span)
val newSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
action()
}
}
title.setSpan(newSpan, start, end, flags)
}
}
}
}
8.33 引用链接item相关的3个类
//引用链 左侧小箭头 setType
internal class DisplayLeakConnectorView(
context: Context,
attrs: AttributeSet
)
/ * 红色波浪形下划线
*
* replaceUnderlineSpans 将UnderlineSpan换为SquigglySpan
*
* Squiggly adj. (线条)不规则的,波形的;
*/
internal class SquigglySpan(context: Context) : ReplacementSpan()
//引用链接item
internal class RowElementLayout(
context: Context,
attrs: AttributeSet
)
8.34 LeakActivity主页,主要是管理tab页,和处理手动选择dump文件importHprof方法
internal class LeakActivity : NavigatingActivity() {
private val leaksButton by lazy {
findViewById<View>(R.id.leak_canary_navigation_button_leaks)
}
private val leaksButtonIconView by lazy {
findViewById<View>(R.id.leak_canary_navigation_button_leaks_icon)
}
private val heapDumpsButton by lazy {
findViewById<View>(R.id.leak_canary_navigation_button_heap_dumps)
}
private val heapDumpsButtonIconView by lazy {
findViewById<View>(R.id.leak_canary_navigation_button_heap_dumps_icon)
}
private val aboutButton by lazy {
findViewById<View>(R.id.leak_canary_navigation_button_about)
}
private val aboutButtonIconView by lazy {
findViewById<View>(R.id.leak_canary_navigation_button_about_icon)
}
private val bottomNavigationBar by lazy {
findViewById<View>(R.id.leak_canary_bottom_navigation_bar)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.leak_canary_leak_activity)
//添加相应页面,根据intent/getLauncherScreen/savedInstanceState
installNavigation(savedInstanceState, findViewById(R.id.leak_canary_main_container))
leaksButton.setOnClickListener { resetTo(LeaksScreen()) }
heapDumpsButton.setOnClickListener { resetTo(HeapDumpsScreen()) }
aboutButton.setOnClickListener { resetTo(AboutScreen()) }
handleViewHprof(intent)
}
//处理跳转查看hprof文件的逻辑
private fun handleViewHprof(intent: Intent?) {
if (intent?.action != Intent.ACTION_VIEW) return
val uri = intent.data ?: return
if (uri.lastPathSegment?.endsWith(".hprof") != true) {
//Could not import %s, this is not an hprof file.
Toast.makeText(this, getString(R.string.leak_canary_import_unsupported_file_extension, uri.lastPathSegment), Toast.LENGTH_LONG).show()
return
}
resetTo(HeapDumpsScreen())//HeapDumpsScreen页面
AsyncTask.THREAD_POOL_EXECUTOR.execute {
importHprof(uri)//用线程加载hprof
}
}
override fun onNewScreen(screen: Screen) {
when (screen) {
is LeaksScreen -> {//第一页
bottomNavigationBar.visibility = View.VISIBLE
leaksButton.isSelected = true
leaksButtonIconView.alpha = 1.0f
heapDumpsButton.isSelected = false
heapDumpsButtonIconView.alpha = 0.4f
aboutButton.isSelected = false
aboutButtonIconView.alpha = 0.4f
}
is HeapDumpsScreen -> {//中间页
bottomNavigationBar.visibility = View.VISIBLE
leaksButton.isSelected = false
leaksButtonIconView.alpha = 0.4f
heapDumpsButton.isSelected = true
heapDumpsButtonIconView.alpha = 1.0f
aboutButton.isSelected = false
aboutButtonIconView.alpha = 0.4f
}
is AboutScreen -> {//第三页
bottomNavigationBar.visibility = View.VISIBLE
leaksButton.isSelected = false
leaksButtonIconView.alpha = 0.4f
heapDumpsButton.isSelected = false
heapDumpsButtonIconView.alpha = 0.4f
aboutButton.isSelected = true
aboutButtonIconView.alpha = 1.0f
}
else -> {
bottomNavigationBar.visibility = View.GONE
}
}
}
override fun getLauncherScreen(): Screen {
return LeaksScreen()//默认页面
}
fun requestImportHprof() {
val requestFileIntent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
}
//Import From…
val chooserIntent = Intent.createChooser(
requestFileIntent, resources.getString(R.string.leak_canary_import_from_title)
)
startActivityForResult(chooserIntent, FILE_REQUEST_CODE)
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
returnIntent: Intent?
) {
SharkLog.d {
"Got activity result with requestCode=$requestCode resultCode=$resultCode returnIntent=$returnIntent"
}
if (requestCode == FILE_REQUEST_CODE && resultCode == RESULT_OK && returnIntent != null) {
returnIntent.data?.let { fileUri ->
AsyncTask.THREAD_POOL_EXECUTOR.execute {
importHprof(fileUri)//加载文件
}
}
}
}
private fun importHprof(fileUri: Uri) {
try {
contentResolver.openFileDescriptor(fileUri, "r")
?.fileDescriptor?.let { fileDescriptor ->
val inputStream = FileInputStream(fileDescriptor)
InternalLeakCanary.createLeakDirectoryProvider(this)
.newHeapDumpFile()
?.let { target ->
inputStream.use { input ->
target.outputStream()
.use { output ->
//将文件copy到我们的目录里
input.copyTo(output, DEFAULT_BUFFER_SIZE)//input FileInputStream
}
}
//使用HeapAnalyzerService进行分析
HeapAnalyzerService.runAnalysis(this, target, heapDumpReason = "Imported by user")
}
}
} catch (e: IOException) {
SharkLog.d(e) { "Could not imported Hprof file" }
}
}
override fun onDestroy() {
super.onDestroy()
if (!isChangingConfigurations) {
Db.closeDatabase()//关闭db
}
}
override fun setTheme(resid: Int) {
// We don't want this to be called with an incompatible theme.
// This could happen if you implement runtime switching of themes
// using ActivityLifecycleCallbacks.
if (resid != R.style.leak_canary_LeakCanary_Base) {
return
}
super.setTheme(resid)
}
companion object {
private const val FILE_REQUEST_CODE = 0
fun createPendingIntent(
context: Context,
screens: ArrayList<Screen>
): PendingIntent {//在notification里用
val intent = Intent(context, LeakActivity::class.java)
intent.putExtra("screens", screens)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
val flags = if (Build.VERSION.SDK_INT >= 23) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
return PendingIntent.getActivity(context, 1, intent, flags)
}
fun createIntent(context: Context): Intent {
val intent = Intent(context, LeakActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
return intent
}
}
}
8.35 Cursors.kt 扩展函数
//扩展函数,Cursor.use,SQLiteDatabase.inTransaction
//Cursor用完关闭
internal inline fun <R> Cursor.use(block: (Cursor) -> R): R {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when (exception) {
null -> close()
else -> try {
close()
} catch (ignoredCloseException: Throwable) {
}
}
}
}
//ThreadLocal每个线程单独的变量
private val inTransaction = ThreadLocal<Boolean>()
//SQLiteDatabase.inTransaction每个线程的事物处理inTransaction 交易;处理;业务;买卖;办理
internal inline fun <T> SQLiteDatabase.inTransaction(block: SQLiteDatabase.() -> T): T {
if (inTransaction.getOrSet { false }) {
return block()
}
try {
inTransaction.set(true)
beginTransaction()
val result = block()
setTransactionSuccessful()
return result
} finally {
endTransaction()
inTransaction.set(false)
}
}
8.36 LeakTraceTable 引用连其中的一个
//LeakTraceTable表结构
internal object LeakTraceTable {
//leak_trace_index 引用连的位置,第几个
@Language("RoomSql")
const val create = """
CREATE TABLE leak_trace
(
id INTEGER PRIMARY KEY,
heap_analysis_id REFERENCES heap_analysis(id),
leak_id REFERENCES leak(id),
class_simple_name TEXT,
leak_trace_index INTEGER
)"""
@Language("RoomSql")
const val drop = "DROP TABLE IF EXISTS leak_trace"
fun insert(
db: SQLiteDatabase,
leakId: Long,
heapAnalysisId: Long,
leakTraceIndex: Int,
leakingObjectClassSimpleName: String
): Long {
val values = ContentValues()
values.put("heap_analysis_id", heapAnalysisId)
values.put("leak_id", leakId)
values.put("class_simple_name", leakingObjectClassSimpleName)
values.put("leak_trace_index", leakTraceIndex)//引用连的位置,第几个
return db.insertOrThrow("leak_trace", null, values)
}
fun deleteByHeapAnalysisId(
db: SQLiteDatabase,
heapAnalysisId: Long
) {
db.delete("leak_trace", "heap_analysis_id=$heapAnalysisId", null)
}
}
8.37 Io View扩展函数,执行在io线程
package leakcanary.internal.activity.db
import android.view.View
import leakcanary.internal.activity.db.Io.OnIo
import leakcanary.internal.friendly.checkMainThread
import leakcanary.internal.friendly.mainHandler
import leakcanary.internal.navigation.onScreenExiting
import java.util.concurrent.Executors
internal object Io {
private val serialExecutor = Executors.newSingleThreadExecutor()
interface OnIo {
fun updateUi(updateUi: View.() -> Unit)
}
private class IoContext : OnIo {
var updateUi: (View.() -> Unit)? = null
override fun updateUi(updateUi: View.() -> Unit) {
this.updateUi = updateUi
}
}
fun execute(block: () -> Unit) {
serialExecutor.execute(block)
}
//第二步
fun execute(
view: View,
block: OnIo.() -> Unit
) {
checkMainThread()
//原子封装对象
val viewWrapper: VolatileObjectRef<View> = VolatileObjectRef(view)
view.onScreenExiting {//当view不见得时候,设置element
viewWrapper.element = null
}
serialExecutor.execute backgroundExecute@{
if (viewWrapper.element == null) {
return@backgroundExecute
}
val context = IoContext()
//block里会处理后台事物,并且对context的updateUi赋值
block(context)
//block里设置了updateUi方法,如果有,则执行
val updateUi = context.updateUi
if (viewWrapper.element != null && updateUi != null) {
mainHandler.post mainThreadPost@{
val attachedView = viewWrapper.element ?: return@mainThreadPost
//刷新ui
updateUi(attachedView)
}
}
}
}
/**
* Similar to kotlin.jvm.internal.Ref.ObjectRef but volatile
* 封装原子性对象
*/
private class VolatileObjectRef<T>(
@Volatile
var element: T? = null
)
}
//第一步
//View扩展函数,执行在io线程
internal fun View.executeOnIo(block: OnIo.() -> Unit) {
Io.execute(this, block)
}
8.38 Db View扩展函数,执行在io线程
internal object Db {
// Accessed on the IO thread only.
private var dbHelper: LeaksDbHelper? = null
interface OnDb : OnIo {
val db: SQLiteDatabase
}
private class DbContext(override val db: SQLiteDatabase) : OnDb {
var updateUi: (View.() -> Unit)? = null
override fun updateUi(updateUi: View.() -> Unit) {
this.updateUi = updateUi
}
}
//第二步,执行方法块
fun execute(
view: View,
block: OnDb.() -> Unit
) {
val appContext = view.context.applicationContext
Io.execute(view) {
//创建dbHelper
if (dbHelper == null) {
dbHelper = LeaksDbHelper(appContext)
}
//获取DbContext,使用dbHelper的writableDatabase
val dbBlock = DbContext(dbHelper!!.writableDatabase)
block(dbBlock)//执行block方法
//block里设置了updateUi方法,如果有,则执行
val updateUi = dbBlock.updateUi
if (updateUi != null) {
updateUi(updateUi)
}
}
}
fun closeDatabase() {
// Closing on the serial IO thread to ensure we don't close while using the db.
Io.execute {
dbHelper?.close()
}
}
}
//第一步,扩展函数
internal fun View.executeOnDb(block: OnDb.() -> Unit) {
Db.execute(this, block)
}
8.39 根据signature来区分的泄漏
internal object LeakTable {
//signature 特征是唯一
// short_description 简单描述
// is_library_leak 是否是library泄漏
// is_read 是否read
@Language("RoomSql")
const val create = """
CREATE TABLE leak
(
id INTEGER PRIMARY KEY,
signature TEXT UNIQUE,
short_description TEXT,
is_library_leak INTEGER,
is_read INTEGER
)"""
//对signature建立一个索引
@Language("RoomSql")
const val createSignatureIndex = """
CREATE INDEX leak_signature
on leak (signature)
"""
//删除表
@Language("RoomSql")
const val drop = "DROP TABLE IF EXISTS leak"
//插入
fun insert(
db: SQLiteDatabase,
heapAnalysisId: Long,
leak: Leak
): Long {
//插入leak表
val values = ContentValues()
values.put("signature", leak.signature)
values.put("short_description", leak.shortDescription)
values.put("is_library_leak", if (leak is LibraryLeak) 1 else 0)
values.put("is_read", 0)
db.insertWithOnConflict("leak", null, values, SQLiteDatabase.CONFLICT_IGNORE)
val leakId =
db.rawQuery("SELECT id from leak WHERE signature = '${leak.signature}' LIMIT 1", null)
.use { cursor ->
if (cursor.moveToFirst()) cursor.getLong(0) else throw IllegalStateException(
"No id found for leak with signature '${leak.signature}'"
)
}
//插入leak_trace表
leak.leakTraces.forEachIndexed { index, leakTrace ->
LeakTraceTable.insert(
db = db,
leakId = leakId,
heapAnalysisId = heapAnalysisId,
leakTraceIndex = index,//位置,引用连的第几个第几个
leakingObjectClassSimpleName = leakTrace.leakingObject.classSimpleName//名字
)
}
return leakId
}
//根据signatures获取read
fun retrieveLeakReadStatuses(
db: SQLiteDatabase,
signatures: Set<String>
): Map<String, Boolean> {
return db.rawQuery(
"""
SELECT
signature
, is_read
FROM leak
WHERE signature IN (${signatures.joinToString { "'$it'" }})
""", null
)
.use { cursor ->
val leakReadStatuses = mutableMapOf<String, Boolean>()
while (cursor.moveToNext()) {
val signature = cursor.getString(0)//signature
val isRead = cursor.getInt(1) == 1//read标记
leakReadStatuses[signature] = isRead
}
leakReadStatuses
}
}
//全部的leak 分类
class AllLeaksProjection(
val signature: String,
val shortDescription: String,
val createdAtTimeMillis: Long,
val leakTraceCount: Int,
val isLibraryLeak: Boolean,
val isNew: Boolean
)
//获取全部的leak,按l.signature分组
//GROUP BY 1它的意思是按第一列分组,而不管它的名称是什么
fun retrieveAllLeaks(
db: SQLiteDatabase
): List<AllLeaksProjection> {
return db.rawQuery(
"""
SELECT
l.signature
, MIN(l.short_description)
, MAX(h.created_at_time_millis) as created_at_time_millis
, COUNT(*) as leak_trace_count
, MIN(l.is_library_leak) as is_library_leak
, MAX(l.is_read) as is_read
FROM leak_trace lt
LEFT JOIN leak l on lt.leak_id = l.id
LEFT JOIN heap_analysis h ON lt.heap_analysis_id = h.id
GROUP BY 1
ORDER BY leak_trace_count DESC, created_at_time_millis DESC
""", null
)
.use { cursor ->
val all = mutableListOf<AllLeaksProjection>()
while (cursor.moveToNext()) {
val group = AllLeaksProjection(
signature = cursor.getString(0),
shortDescription = cursor.getString(1),
createdAtTimeMillis = cursor.getLong(2),
leakTraceCount = cursor.getInt(3),
isLibraryLeak = cursor.getInt(4) == 1,//是否是library泄漏
isNew = cursor.getInt(5) == 0//是否new
)
all.add(group)
}
all
}
}
//signature是唯一的
fun markAsRead(
db: SQLiteDatabase,
signature: String
) {
val values = ContentValues().apply { put("is_read", 1) }
db.update("leak", values, "signature = ?", arrayOf(signature))
}
class LeakProjection(
val shortDescription: String,//描述
val isNew: Boolean,//是否新的
val isLibraryLeak: Boolean,//library
val leakTraces: List<LeakTraceProjection> //引用连
)
class LeakTraceProjection(
val leakTraceIndex: Int,//第几个位置
val heapAnalysisId: Long,//heap id
val classSimpleName: String,//类名
val createdAtTimeMillis: Long//创建时间
)
//获取LeakProjection,预测; 推断; 设想; 投射; 放映; 投影; 放映的影像; 投影图;
//获取一个signature的所有泄漏
fun retrieveLeakBySignature(
db: SQLiteDatabase,
signature: String
): LeakProjection? {
return db.rawQuery(
"""
SELECT
lt.leak_trace_index
, lt.heap_analysis_id
, lt.class_simple_name
, h.created_at_time_millis
, l.short_description
, l.is_read
, l.is_library_leak
FROM leak_trace lt
LEFT JOIN leak l on lt.leak_id = l.id
LEFT JOIN heap_analysis h ON lt.heap_analysis_id = h.id
WHERE l.signature = ?
ORDER BY h.created_at_time_millis DESC
""", arrayOf(signature)
)
.use { cursor ->
return if (cursor.moveToFirst()) {
val leakTraces = mutableListOf<LeakTraceProjection>()
val leakProjection = LeakProjection(
shortDescription = cursor.getString(4),
isNew = cursor.getInt(5) == 0,
isLibraryLeak = cursor.getInt(6) == 1,
leakTraces = leakTraces
)
leakTraces.addAll(generateSequence(cursor, {
if (cursor.moveToNext()) cursor else null
}).map {
LeakTraceProjection(
leakTraceIndex = cursor.getInt(0),
heapAnalysisId = cursor.getLong(1),
classSimpleName = cursor.getString(2),
createdAtTimeMillis = cursor.getLong(3)
)
})
leakProjection
} else {
null
}
}
}
//删除leak,首先在leak_trace表删除,然后在leak表删除
fun deleteByHeapAnalysisId(
db: SQLiteDatabase,
heapAnalysisId: Long
) {
LeakTraceTable.deleteByHeapAnalysisId(db, heapAnalysisId)
db.execSQL(
"""
DELETE
FROM leak
WHERE NOT EXISTS (
SELECT *
FROM leak_trace lt
WHERE leak.id = lt.leak_id)
"""
)
}
}
8.40 HeapAnalysisTable一个heap计算出来的泄漏信息
//一个heap计算出来的泄漏信息
internal object HeapAnalysisTable {
/**
* CopyOnWriteArrayList because registered listeners can remove themselves from this list while
* iterating and invoking them, which would trigger a ConcurrentModificationException (see #2019).
*/
private val updateListeners = CopyOnWriteArrayList<() -> Unit>()
//创建时间,执行时间,泄漏个数,exception总结,object大对象
@Language("RoomSql")
const val create = """CREATE TABLE heap_analysis
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at_time_millis INTEGER,
dump_duration_millis INTEGER DEFAULT -1,
leak_count INTEGER DEFAULT 0,
exception_summary TEXT DEFAULT NULL,
object BLOB
)"""
//删除
@Language("RoomSql")
const val drop = "DROP TABLE IF EXISTS heap_analysis"
//todo 这里是啥操作,这里是把block添加到updateListeners,并返回了一个函数块,块的作用是remove
fun onUpdate(block: () -> Unit): () -> Unit {
updateListeners.add(block)
return {
updateListeners.remove(block)
}
}
fun insert(
db: SQLiteDatabase,
heapAnalysis: HeapAnalysis
): Long {
val values = ContentValues()
values.put("created_at_time_millis", heapAnalysis.createdAtTimeMillis)
values.put("dump_duration_millis", heapAnalysis.dumpDurationMillis)
values.put("object", heapAnalysis.toByteArray())
when (heapAnalysis) {
is HeapAnalysisSuccess -> {
val leakCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
values.put("leak_count", leakCount)
}
is HeapAnalysisFailure -> {
val cause = heapAnalysis.exception.cause!!
val exceptionSummary = "${cause.javaClass.simpleName} ${cause.message}"
values.put("exception_summary", exceptionSummary)
}
}
return db.inTransaction {
//插入heap_analysis表
val heapAnalysisId = db.insertOrThrow("heap_analysis", null, values)
if (heapAnalysis is HeapAnalysisSuccess) {
heapAnalysis.allLeaks
.forEach { leakingInstance ->
//插入leak表,然后插入leak_trace表
LeakTable.insert(
db, heapAnalysisId, leakingInstance
)
}
}
heapAnalysisId
}.apply { notifyUpdateOnMainThread() }
}
private fun notifyUpdateOnMainThread() {
checkNotMainThread()
mainHandler.post {
updateListeners.forEach { it() }
}
}
//删除某个heap_analysis
inline fun <reified T : HeapAnalysis> retrieve(
db: SQLiteDatabase,
id: Long
): T? {
return db.rawQuery(
"""
SELECT
object
FROM heap_analysis
WHERE id=$id
""", null
)
.use { cursor ->
if (cursor.moveToNext()) {
val analysis = Serializables.fromByteArray<T>(cursor.getBlob(0))
if (analysis == null) {
delete(db, id, null)
}
analysis
} else {
null
}
}
}
fun retrieveAll(db: SQLiteDatabase): List<Projection> {
return db.rawQuery(
"""
SELECT
id
, created_at_time_millis
, leak_count
, exception_summary
FROM heap_analysis
ORDER BY created_at_time_millis DESC
""", null
)
.use { cursor ->
val all = mutableListOf<Projection>()
while (cursor.moveToNext()) {
val summary = Projection(
id = cursor.getLong(0),
createdAtTimeMillis = cursor.getLong(1),
leakCount = cursor.getInt(2),
exceptionSummary = cursor.getString(3)
)
all.add(summary)
}
all
}
}
fun delete(
db: SQLiteDatabase,
heapAnalysisId: Long,
heapDumpFile: File?
) {
if (heapDumpFile != null) {
AsyncTask.SERIAL_EXECUTOR.execute {
val path = heapDumpFile.absolutePath
val heapDumpDeleted = heapDumpFile.delete()
if (heapDumpDeleted) {
LeakDirectoryProvider.filesDeletedRemoveLeak += path
} else {
SharkLog.d { "Could not delete heap dump file ${heapDumpFile.path}" }
}
}
}
db.inTransaction {
db.delete("heap_analysis", "id=$heapAnalysisId", null)
LeakTable.deleteByHeapAnalysisId(db, heapAnalysisId)
}
notifyUpdateOnMainThread()
}
fun deleteAll(db: SQLiteDatabase) {
db.inTransaction {
rawQuery(
"""
SELECT
id,
object
FROM heap_analysis
""", null
)
.use { cursor ->
val all = mutableListOf<Pair<Long, HeapAnalysis>>()
while (cursor.moveToNext()) {
val id = cursor.getLong(0)
val analysis = Serializables.fromByteArray<HeapAnalysis>(cursor.getBlob(1))
if (analysis != null) {
all += id to analysis
}
}
all.forEach { (id, _) ->
db.delete("heap_analysis", "id=$id", null)
LeakTable.deleteByHeapAnalysisId(db, id)
}
AsyncTask.SERIAL_EXECUTOR.execute {
all.forEach { (_, analysis) ->
analysis.heapDumpFile.delete()
}
}
}
}
notifyUpdateOnMainThread()
}
class Projection(
val id: Long,
val createdAtTimeMillis: Long,
val leakCount: Int,
val exceptionSummary: String?
)
}
8.41 HeapDumpsScreen中间页
//中间页
internal class HeapDumpsScreen : Screen() {
override fun createView(container: ViewGroup) =
container.inflate(R.layout.leak_canary_heap_dumps_screen).apply {
val unsubscribeRefresh = HeapAnalysisTable.onUpdate {//这里有个刷新
activity<NavigatingActivity>().refreshCurrentScreen()
}
onScreenExiting { unsubscribeRefresh() }//退出的关掉数据库监听
onCreateOptionsMenu { menu ->
if (!ActivityManager.isUserAMonkey()) {
menu.add(R.string.leak_canary_delete_all)
.setOnMenuItemClickListener {
AlertDialog.Builder(context)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.leak_canary_delete_all)
.setMessage(R.string.leak_canary_delete_all_leaks_title)
.setPositiveButton(android.R.string.ok) { _, _ ->
executeOnDb {
//删除
HeapAnalysisTable.deleteAll(db)
updateUi {
//设置空
val listView = findViewById<ListView>(R.id.leak_canary_list)
listView.adapter =
SimpleListAdapter(
R.layout.leak_canary_simple_row, emptyList<Any>()
) { _, _ -> }
}
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
true
}
}
}
findViewById<View>(R.id.leak_canary_import_heap_dump).setOnClickListener {
activity<LeakActivity>().requestImportHprof()//选择dump文件
}
findViewById<View>(R.id.leak_canary_dump_heap_now).setOnClickListener {
LeakCanary.dumpHeap()//dump
}
executeOnDb {
val projections = HeapAnalysisTable.retrieveAll(db)
updateUi { onAnalysesRetrieved(projections) }
}
}
private fun View.onAnalysesRetrieved(projections: List<Projection>) {
activity.title = resources.getQuantityString(
R.plurals.leak_canary_heap_analysis_list_screen_title,
projections.size, projections.size
)
val listView = findViewById<ListView>(R.id.leak_canary_list)
listView.setOnItemClickListener { _, _, position, _ ->
val projection = projections[position]
val analysisScreen = if (projection.exceptionSummary != null) {
HeapAnalysisFailureScreen(projection.id)
} else {
HeapDumpScreen(projection.id)
}
goTo(analysisScreen)
}
listView.adapter =
SimpleListAdapter(R.layout.leak_canary_leak_row, projections) { view, position ->
val goneView = view.findViewById<TextView>(R.id.leak_canary_count_text)
goneView.visibility = View.GONE
val timeView = view.findViewById<TextView>(R.id.leak_canary_leak_text)
val countView = view.findViewById<TextView>(R.id.leak_canary_time_text)
val projection = getItem(position)
// Enable means "new"
countView.isEnabled = false
timeView.text = TimeFormatter.formatTimestamp(view.context, projection.createdAtTimeMillis)
val count = projection.exceptionSummary ?: resources.getQuantityString(
R.plurals.leak_canary_distinct_leaks,
projection.leakCount, projection.leakCount
)
countView.text = count
}
}
}
8.42 //第一页 ok
//第一页 ok
internal class LeaksScreen : Screen() {
override fun createView(container: ViewGroup) =
container.inflate(R.layout.leak_canary_list).apply {
//leak_canary_list里只有个ListView
val unsubscribeRefresh = HeapAnalysisTable.onUpdate {//数据库变了就更改
activity<NavigatingActivity>().refreshCurrentScreen()
}
onScreenExiting { unsubscribeRefresh() }//删除数据库变了就更改
executeOnDb {
val projections = LeakTable.retrieveAllLeaks(db)
updateUi { onGroupsRetrieved(projections) }
}
}
private fun View.onGroupsRetrieved(projections: List<AllLeaksProjection>) {
activity.title = resources.getQuantityString(
R.plurals.leak_canary_distinct_leaks,
projections.size, projections.size
)
val listView = findViewById<ListView>(R.id.leak_canary_list)
listView.adapter =
SimpleListAdapter(R.layout.leak_canary_leak_row, projections) { view, position ->
val countView = view.findViewById<TextView>(R.id.leak_canary_count_text)
val descriptionView = view.findViewById<TextView>(R.id.leak_canary_leak_text)
val timeView = view.findViewById<TextView>(R.id.leak_canary_time_text)
val newChipView = view.findViewById<TextView>(R.id.leak_canary_chip_new)
val libraryLeakChipView = view.findViewById<TextView>(R.id.leak_canary_chip_library_leak)
val projection = projections[position]
countView.isEnabled = projection.isNew
newChipView.visibility = if (projection.isNew) VISIBLE else GONE
libraryLeakChipView.visibility = if (projection.isLibraryLeak) VISIBLE else GONE
countView.text = projection.leakTraceCount.toString()
descriptionView.text = projection.shortDescription
val formattedDate =
TimeFormatter.formatTimestamp(view.context, projection.createdAtTimeMillis)
timeView.text =
resources.getString(R.string.leak_canary_group_list_time_label, formattedDate)
}
listView.setOnItemClickListener { _, _, position, _ ->
goTo(LeakScreen(projections[position].signature))
}
}
}
8.43 AboutScreen第三页
//ok
internal class AboutScreen : Screen() {
override fun createView(container: ViewGroup) =
container.inflate(R.layout.leak_canary_about_screen)
.apply {
//About LeakCanary %s
activity.title =
resources.getString(R.string.leak_canary_about_title, BuildConfig.LIBRARY_VERSION)
val aboutTextView = findViewById<TextView>(R.id.leak_canary_about_text)
aboutTextView.movementMethod = LinkMovementMethod.getInstance()//
val application = activity.application
val appName = application.packageManager.getApplicationLabel(application.applicationInfo)
val appPackageName = context.packageName
aboutTextView.text = Html.fromHtml(
String.format(
resources.getString(R.string.leak_canary_about_message), appName, appPackageName
)
)
val heapDumpTextView = findViewById<TextView>(R.id.leak_canary_about_heap_dump_text)
updateHeapDumpTextView(heapDumpTextView)
val heapDumpSwitchView =
findViewById<Switch>(R.id.leak_canary_about_heap_dump_switch_button)
heapDumpSwitchView.isChecked = InternalLeakCanary.dumpEnabledInAboutScreen
//这里有个开关
heapDumpSwitchView.setOnCheckedChangeListener { _, checked ->
// Updating the value wouldn't normally immediately trigger a heap dump, however
// by updating the view we also have a side effect of querying which will notify
// the heap dumper if the value has become positive.
InternalLeakCanary.dumpEnabledInAboutScreen = checked
updateHeapDumpTextView(heapDumpTextView)
}
}
private fun updateHeapDumpTextView(view: TextView) {
view.text = when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> view.resources.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> view.resources.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}