Skip to content

Commit

Permalink
Add support library fragment watcher (#1611)
Browse files Browse the repository at this point in the history
* watch leaking support library fragments and their views

* add support-fragments to leakcnacary-core, remove duplicated code for fragment watcher creation
  • Loading branch information
AndreasBoehm authored and pyricau committed Nov 26, 2019
1 parent b80fa47 commit 7c339c0
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 11 deletions.
3 changes: 2 additions & 1 deletion build.gradle
Expand Up @@ -24,6 +24,7 @@ buildscript {
runner : 'androidx.test:runner:1.1.0',
],
],
android_support: 'com.android.support:support-v4:28.0.0',
junit : 'junit:junit:4.12',
kotlin : [
gradlePlugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}",
Expand All @@ -42,7 +43,7 @@ buildscript {
}
dependencies {
classpath deps.kotlin.gradlePlugin
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.16'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0'
classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.9.18"
Expand Down
1 change: 1 addition & 0 deletions leakcanary-android-core/build.gradle
Expand Up @@ -5,6 +5,7 @@ dependencies {
api project(':shark-android')
api project(':leakcanary-object-watcher-android')
api project(':leakcanary-object-watcher-android-androidx')
api project(':leakcanary-object-watcher-android-support-fragments')

implementation deps.kotlin.stdlib

Expand Down
25 changes: 25 additions & 0 deletions leakcanary-object-watcher-android-support-fragments/build.gradle
@@ -0,0 +1,25 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

dependencies {
api project(':leakcanary-object-watcher-android')

implementation deps.kotlin.stdlib
// Optional dependency
compileOnly deps.android_support
}

android {
compileSdkVersion versions.compileSdk
defaultConfig {
minSdkVersion versions.minSdk
consumerProguardFiles 'consumer-proguard-rules.pro'
}
lintOptions {
disable 'GoogleAppIndexingWarning'
error 'ObsoleteSdkInt'
check 'Interoperability'
}
}

apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
@@ -0,0 +1,2 @@
# AndroidSupportFragmentDestroyWatcher is loaded via reflection
-keep class leakcanary.internal.AndroidSupportFragmentDestroyWatcher { *; }
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=leakcanary-object-watcher-android-support-fragments
POM_NAME=LeakCanary Object Watcher for Android extension: Android support library fragments support
POM_PACKAGING=aar
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.fragments.android_support" />
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2019 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package leakcanary.internal

import android.app.Activity
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentActivity
import android.support.v4.app.FragmentManager
import leakcanary.AppWatcher.Config
import leakcanary.ObjectWatcher

internal class AndroidSupportFragmentDestroyWatcher(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) : (Activity) -> Unit {

private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null && configProvider().watchFragmentViews) {
objectWatcher.watch(view)
}
}

override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
if (configProvider().watchFragments) {
objectWatcher.watch(fragment)
}
}
}

override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 Square, Inc.
* Copyright (C) 2019 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -33,6 +33,10 @@ internal object FragmentDestroyWatcher {
private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidXFragmentDestroyWatcher"

private const val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME = "android.support.v4.app.Fragment"
private const val ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidSupportFragmentDestroyWatcher"

fun install(
application: Application,
objectWatcher: ObjectWatcher,
Expand All @@ -46,15 +50,22 @@ internal object FragmentDestroyWatcher {
)
}

if (classAvailable(ANDROIDX_FRAGMENT_CLASS_NAME) &&
classAvailable(ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME)
) {
val watcherConstructor = Class.forName(ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME)
.getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java)
@kotlin.Suppress("UNCHECKED_CAST")
fragmentDestroyWatchers.add(
watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit
)
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}

getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}

if (fragmentDestroyWatchers.size == 0) {
Expand All @@ -73,6 +84,26 @@ internal object FragmentDestroyWatcher {
})
}

private fun getWatcherIfAvailable(
fragmentClassName: String,
watcherClassName: String,
objectWatcher: ObjectWatcher,
configProvider: () -> AppWatcher.Config
): ((Activity) -> Unit)? {

return if (classAvailable(fragmentClassName) &&
classAvailable(watcherClassName)
) {
val watcherConstructor = Class.forName(watcherClassName)
.getDeclaredConstructor(ObjectWatcher::class.java, Function0::class.java)
@Suppress("UNCHECKED_CAST")
watcherConstructor.newInstance(objectWatcher, configProvider) as (Activity) -> Unit

} else {
null
}
}

private fun classAvailable(className: String): Boolean {
return try {
Class.forName(className)
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Expand Up @@ -6,6 +6,7 @@ include ':leakcanary-android-sample'
include ':leakcanary-object-watcher'
include ':leakcanary-object-watcher-android'
include ':leakcanary-object-watcher-android-androidx'
include ':leakcanary-object-watcher-android-support-fragments'
include ':shark'
include ':shark-android'
include ':shark-cli'
Expand Down

0 comments on commit 7c339c0

Please sign in to comment.