├── .editorconfig
├── .gitattributes
├── .github
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── 1-leak.md
│ ├── 2-bug.md
│ ├── 3-feature.md
│ ├── 4-doc.md
│ └── 5-sdk.md
├── SUPPORT.md
├── workflows-disabled
│ └── greetings.yml
└── workflows
│ ├── main.yml
│ └── stale.yaml
├── .gitignore
├── LICENSE.txt
├── README.md
├── build.gradle.kts
├── config
├── detekt-config.yml
└── hooks
│ └── pre-push
├── docs
├── assets
│ ├── adaptative_icon.sketch
│ ├── icon_1024.png
│ ├── icon_512.png
│ ├── kanary-200px.png
│ ├── kanary-large.png
│ ├── leakcanary_shirt.psd
│ ├── repository-open-graph.png
│ ├── screenshot.png
│ ├── shark.psd
│ ├── source_icon.png
│ ├── sticker.png
│ ├── vector_falling_canary.svg
│ ├── vector_icon.afdesign
│ └── vector_icon.svg
├── blog-articles.md
├── changelog.md
├── code_of_conduct.md
├── dev-env.md
├── faq.md
├── fundamentals-fixing-a-memory-leak.md
├── fundamentals-how-leakcanary-works.md
├── fundamentals.md
├── getting_started.md
├── how_to_help.md
├── images
│ ├── analysis-done.png
│ ├── android-tv-leaks.png
│ ├── bugsnag-leak.png
│ ├── bugsnag-list.png
│ ├── building-leak-traces-notification.png
│ ├── disable_dumping.png
│ ├── dumping-toast.png
│ ├── finding-retained-notification.png
│ ├── heap-dump.png
│ ├── launcher.png
│ ├── leak-screen.png
│ ├── library-leak.png
│ ├── logo-2.0-200px.png
│ ├── logo-2.0.png
│ ├── logo.png
│ ├── retained-notification.png
│ ├── screenshot-2.0.png
│ ├── shark.png
│ └── signature.png
├── index.md
├── leakcanary-for-releases.md
├── recipes.md
├── recorded-presentations.md
├── releasing.md
├── requirements.txt
├── shark.md
├── snippets
│ └── bugsnag-uploader.md
├── support.md
├── theme
│ └── main.html
├── ui-tests.md
├── upgrading-to-leakcanary-2.0.md
└── uploading.md
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── leakcanary
├── leakcanary-android-core
│ ├── api
│ │ └── leakcanary-android-core.api
│ ├── build.gradle.kts
│ ├── consumer-proguard-rules.pro
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ ├── lint.xml
│ └── src
│ │ ├── androidTest
│ │ ├── AndroidManifest.xml
│ │ ├── assets
│ │ │ └── leaks-v24.db
│ │ └── java
│ │ │ └── leakcanary
│ │ │ ├── AndroidExtensionsTest.kt
│ │ │ ├── DatabaseMigrationTest.kt
│ │ │ ├── DatabaseRule.kt
│ │ │ ├── LeakActivityTest.kt
│ │ │ └── ManualInstallTest.kt
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── ic_launcher-web.png
│ │ ├── java
│ │ │ └── leakcanary
│ │ │ │ ├── BackgroundThreadHeapAnalyzer.kt
│ │ │ │ ├── EventListener.kt
│ │ │ │ ├── LazyForwardingEventListener.kt
│ │ │ │ ├── LeakCanary.kt
│ │ │ │ ├── LeakCanaryAndroidInternalUtils.kt
│ │ │ │ ├── LogcatEventListener.kt
│ │ │ │ ├── NotificationEventListener.kt
│ │ │ │ ├── RemoteWorkManagerHeapAnalyzer.kt
│ │ │ │ ├── ToastEventListener.kt
│ │ │ │ ├── TvEventListener.kt
│ │ │ │ ├── WorkManagerHeapAnalyzer.kt
│ │ │ │ └── internal
│ │ │ │ ├── AndroidDebugHeapAnalyzer.kt
│ │ │ │ ├── DebuggerControl.kt
│ │ │ │ ├── DisplayLeakAdapter.kt
│ │ │ │ ├── DisplayLeakConnectorView.kt
│ │ │ │ ├── HeapAnalyzerWorker.kt
│ │ │ │ ├── HeapDumpControl.kt
│ │ │ │ ├── HeapDumpTrigger.kt
│ │ │ │ ├── InternalLeakCanary.kt
│ │ │ │ ├── LazyImmediateFuture.kt
│ │ │ │ ├── LeakCanaryFileProvider.kt
│ │ │ │ ├── LeakCanarySingleThreadFactory.kt
│ │ │ │ ├── LeakCanaryTextView.kt
│ │ │ │ ├── LeakDirectoryProvider.kt
│ │ │ │ ├── NotificationReceiver.kt
│ │ │ │ ├── NotificationType.kt
│ │ │ │ ├── Notifications.kt
│ │ │ │ ├── OnRetainInstanceListener.kt
│ │ │ │ ├── RemoteHeapAnalyzerWorker.kt
│ │ │ │ ├── RequestPermissionActivity.kt
│ │ │ │ ├── RowElementLayout.kt
│ │ │ │ ├── SerializableIntent.kt
│ │ │ │ ├── Serializables.kt
│ │ │ │ ├── SquigglySpan.kt
│ │ │ │ ├── SquigglySpanRenderer.kt
│ │ │ │ ├── VisibilityTracker.kt
│ │ │ │ ├── activity
│ │ │ │ ├── LeakActivity.kt
│ │ │ │ ├── LeakViews.kt
│ │ │ │ ├── db
│ │ │ │ │ ├── Cursors.kt
│ │ │ │ │ ├── Db.kt
│ │ │ │ │ ├── HeapAnalysisTable.kt
│ │ │ │ │ ├── Io.kt
│ │ │ │ │ ├── LeakTable.kt
│ │ │ │ │ ├── LeakTraceTable.kt
│ │ │ │ │ ├── LeaksDbHelper.kt
│ │ │ │ │ └── ScopedLeaksDb.kt
│ │ │ │ ├── screen
│ │ │ │ │ ├── AboutScreen.kt
│ │ │ │ │ ├── HeapAnalysisFailureScreen.kt
│ │ │ │ │ ├── HeapDumpRenderer.kt
│ │ │ │ │ ├── HeapDumpScreen.kt
│ │ │ │ │ ├── HeapDumpsScreen.kt
│ │ │ │ │ ├── HprofExplorerScreen.kt
│ │ │ │ │ ├── LeakScreen.kt
│ │ │ │ │ ├── LeakTraceWrapper.kt
│ │ │ │ │ ├── LeaksScreen.kt
│ │ │ │ │ └── RenderHeapDumpScreen.kt
│ │ │ │ └── ui
│ │ │ │ │ ├── SimpleListAdapter.kt
│ │ │ │ │ ├── TimeFormatter.kt
│ │ │ │ │ └── UiUtils.kt
│ │ │ │ ├── friendly
│ │ │ │ └── Friendly.kt
│ │ │ │ ├── navigation
│ │ │ │ ├── BackstackFrame.kt
│ │ │ │ ├── NavigatingActivity.kt
│ │ │ │ ├── Screen.kt
│ │ │ │ └── Views.kt
│ │ │ │ ├── tv
│ │ │ │ ├── TvOnRetainInstanceListener.kt
│ │ │ │ └── TvToast.kt
│ │ │ │ └── utils
│ │ │ │ ├── Size.kt
│ │ │ │ └── Tuples.kt
│ │ └── res
│ │ │ ├── anim
│ │ │ ├── leak_canary_enter_alpha.xml
│ │ │ ├── leak_canary_enter_backward.xml
│ │ │ ├── leak_canary_enter_forward.xml
│ │ │ ├── leak_canary_exit_alpha.xml
│ │ │ ├── leak_canary_exit_backward.xml
│ │ │ └── leak_canary_exit_forward.xml
│ │ │ ├── color
│ │ │ ├── leak_canary_bottom_menu.xml
│ │ │ └── leak_canary_count_text.xml
│ │ │ ├── drawable-v21
│ │ │ ├── leak_canary_gray_fill.xml
│ │ │ ├── leak_canary_list_selector.xml
│ │ │ ├── leak_canary_primary_button.xml
│ │ │ ├── leak_canary_secondary_button.xml
│ │ │ ├── leak_canary_tab_background.xml
│ │ │ └── leak_canary_tab_selector_ripple.xml
│ │ │ ├── drawable
│ │ │ ├── leak_canary_count_background.xml
│ │ │ ├── leak_canary_dump.xml
│ │ │ ├── leak_canary_gray_fill.xml
│ │ │ ├── leak_canary_icon.xml
│ │ │ ├── leak_canary_icon_foreground.xml
│ │ │ ├── leak_canary_icon_monochrome.xml
│ │ │ ├── leak_canary_info.xml
│ │ │ ├── leak_canary_info_rectangle.xml
│ │ │ ├── leak_canary_leak.xml
│ │ │ ├── leak_canary_list_selector.xml
│ │ │ ├── leak_canary_primary_button.xml
│ │ │ ├── leak_canary_secondary_button.xml
│ │ │ ├── leak_canary_tab_background.xml
│ │ │ ├── leak_canary_toast_background.xml
│ │ │ └── leak_canary_tv_icon.xml
│ │ │ ├── layout
│ │ │ ├── leak_canary_about_screen.xml
│ │ │ ├── leak_canary_heap_analysis_failure_screen.xml
│ │ │ ├── leak_canary_heap_dump_leak_title.xml
│ │ │ ├── leak_canary_heap_dump_toast.xml
│ │ │ ├── leak_canary_heap_dumps_screen.xml
│ │ │ ├── leak_canary_heap_render.xml
│ │ │ ├── leak_canary_hprof_explorer.xml
│ │ │ ├── leak_canary_leak_activity.xml
│ │ │ ├── leak_canary_leak_chips.xml
│ │ │ ├── leak_canary_leak_header.xml
│ │ │ ├── leak_canary_leak_row.xml
│ │ │ ├── leak_canary_leak_screen.xml
│ │ │ ├── leak_canary_list.xml
│ │ │ ├── leak_canary_ref_row.xml
│ │ │ └── leak_canary_simple_row.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ └── leak_canary_icon.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── leak_canary_icon.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── leak_canary_icon.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── leak_canary_icon.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── leak_canary_icon.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── leak_canary_icon.png
│ │ │ ├── values-v21
│ │ │ └── leak_canary_themes.xml
│ │ │ ├── values
│ │ │ ├── leak_canary_attrs.xml
│ │ │ ├── leak_canary_bools.xml
│ │ │ ├── leak_canary_colors.xml
│ │ │ ├── leak_canary_dimens.xml
│ │ │ ├── leak_canary_ids.xml
│ │ │ ├── leak_canary_public.xml
│ │ │ ├── leak_canary_strings.xml
│ │ │ └── leak_canary_themes.xml
│ │ │ └── xml
│ │ │ └── leak_canary_file_paths.xml
│ │ └── test
│ │ └── java
│ │ └── leakcanary
│ │ ├── LeakCanaryConfigTest.kt
│ │ └── internal
│ │ └── activity
│ │ └── screen
│ │ └── LeakTraceWrapperTest.kt
├── leakcanary-android-instrumentation
│ ├── api
│ │ └── leakcanary-android-instrumentation.api
│ ├── build.gradle.kts
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ │ ├── androidTest
│ │ ├── AndroidManifest.xml
│ │ ├── assets
│ │ │ └── large-dump.hprof
│ │ ├── java
│ │ │ └── leakcanary
│ │ │ │ ├── AndroidDetectLeaksAssertTest.kt
│ │ │ │ ├── Benchmark.kt
│ │ │ │ ├── DetectLeaksAfterTestSuccessTest.kt
│ │ │ │ ├── IndexingTest.kt
│ │ │ │ ├── LifecycleLeaksTest.kt
│ │ │ │ ├── LifecycleTestUtils.kt
│ │ │ │ ├── ObjectInspectorTest.kt
│ │ │ │ ├── ProfiledTest.kt
│ │ │ │ ├── Profiler.kt
│ │ │ │ ├── SkipLeakDetectionTest.kt
│ │ │ │ ├── TestActivity.kt
│ │ │ │ ├── TestDescriptionHolderTest.kt
│ │ │ │ └── TestUtils.kt
│ │ └── res
│ │ │ └── layout
│ │ │ └── activity_test.xml
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ ├── AndroidDetectLeaksAssert.kt
│ │ ├── AndroidDetectLeaksInterceptor.kt
│ │ ├── DetectLeaksAfterTestSuccess.kt
│ │ ├── DetectLeaksAssert.kt
│ │ ├── DetectLeaksInterceptor.kt
│ │ ├── HeapAnalysisDecision.kt
│ │ ├── HeapAnalysisReporter.kt
│ │ ├── LeakAssertions.kt
│ │ ├── NoLeakAssertionFailedError.kt
│ │ ├── SkipLeakDetection.kt
│ │ └── internal
│ │ ├── InstrumentationHeapAnalyzer.kt
│ │ ├── RetryingHeapAnalyzer.kt
│ │ └── friendly
│ │ └── Friendly.kt
├── leakcanary-android-process
│ ├── api
│ │ └── leakcanary-android-process.api
│ ├── build.gradle.kts
│ ├── consumer-proguard-rules.pro
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ ├── LeakCanaryProcess.kt
│ │ └── internal
│ │ └── RemoteLeakCanaryWorkerService.kt
├── leakcanary-android-release
│ ├── api
│ │ └── leakcanary-android-release.api
│ ├── build.gradle.kts
│ ├── consumer-proguard-rules.pro
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ ├── BackgroundTrigger.kt
│ │ ├── ConditionalInterceptor.kt
│ │ ├── GoodAndroidVersionInterceptor.kt
│ │ ├── HeapAnalysisClient.kt
│ │ ├── HeapAnalysisConfig.kt
│ │ ├── HeapAnalysisInterceptor.kt
│ │ ├── HeapAnalysisJob.kt
│ │ ├── JobContext.kt
│ │ ├── MinimumDiskSpaceInterceptor.kt
│ │ ├── MinimumElapsedSinceStartInterceptor.kt
│ │ ├── MinimumMemoryInterceptor.kt
│ │ ├── OncePerPeriodInterceptor.kt
│ │ ├── ProcessInfo.kt
│ │ ├── SaveResourceIdsInterceptor.kt
│ │ ├── ScreenOffTrigger.kt
│ │ └── internal
│ │ ├── BackgroundListener.kt
│ │ ├── RealHeapAnalysisJob.kt
│ │ └── friendly
│ │ └── Friendly.kt
├── leakcanary-android-startup
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ └── AndroidManifest.xml
├── leakcanary-android-test
│ ├── api
│ │ └── leakcanary-android-test.api
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ ├── androidTest
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── leakcanary
│ │ │ └── RepeatingAndroidInProcessScenarioTest.kt
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ ├── RepeatingAndroidInProcessScenario.kt
│ │ └── TargetContextHeapDumpDirectoryProvider.kt
├── leakcanary-android-uiautomator
│ ├── api
│ │ └── leakcanary-android-uiautomator.api
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ ├── AndroidDeviceTempHeapDumpDirectoryProvider.kt
│ │ ├── RepeatingUiAutomatorScenario.kt
│ │ ├── UiAutomatorShellFileDeleter.kt
│ │ └── UiAutomatorShellHeapDumper.kt
├── leakcanary-android-utils
│ ├── api
│ │ └── leakcanary-android-utils.api
│ ├── build.gradle.kts
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ ├── AndroidDebugHeapDumper.kt
│ │ ├── LogcatSharkLog.kt
│ │ └── internal
│ │ ├── Handlers.kt
│ │ ├── Objects.kt
│ │ └── Timing.kt
├── leakcanary-android
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── leakcanary
│ │ │ └── LeakActivityTest.kt
│ │ └── main
│ │ └── AndroidManifest.xml
├── leakcanary-app-aidl
│ ├── api
│ │ └── leakcanary-app-aidl.api
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── aidl
│ │ └── org
│ │ │ └── leakcanary
│ │ │ └── internal
│ │ │ └── LeakUiApp.aidl
│ │ └── java
│ │ └── org
│ │ └── leakcanary
│ │ └── internal
│ │ └── ParcelableHeapAnalysis.kt
├── leakcanary-app-service
│ ├── api
│ │ └── leakcanary-app-service.api
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── org
│ │ └── leakcanary
│ │ └── internal
│ │ └── LeakUiAppClient.kt
├── leakcanary-app
│ ├── build.gradle.kts
│ ├── detekt-baseline.xml
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── org
│ │ │ └── leakcanary
│ │ │ ├── LeakCanaryApp.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── WhileSubscribedOrRetained.kt
│ │ │ ├── data
│ │ │ ├── DatabaseDispatchers.kt
│ │ │ ├── DatabaseModule.kt
│ │ │ └── HeapRepository.kt
│ │ │ ├── screens
│ │ │ ├── BackStackViewModel.kt
│ │ │ ├── ClientAppAnalysesScreen.kt
│ │ │ ├── ClientAppAnalysisScreen.kt
│ │ │ ├── ClientAppsScreen.kt
│ │ │ ├── Destination.kt
│ │ │ ├── LeakScreen.kt
│ │ │ ├── ScreenHost.kt
│ │ │ ├── TreeMapScreen.kt
│ │ │ └── TreemapLayout.kt
│ │ │ ├── service
│ │ │ └── LeakUiAppService.kt
│ │ │ ├── ui
│ │ │ └── theme
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Theme.kt
│ │ │ │ └── Type.kt
│ │ │ └── util
│ │ │ ├── CurrentActivityProvider.kt
│ │ │ ├── Handlers.kt
│ │ │ ├── LeakTraceWrapper.kt
│ │ │ ├── Serializables.kt
│ │ │ ├── Sharer.kt
│ │ │ └── TimeFormatter.kt
│ │ ├── res
│ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ └── xml
│ │ │ ├── backup_rules.xml
│ │ │ └── data_extraction_rules.xml
│ │ └── sqldelight
│ │ └── dev
│ │ └── leakcanary
│ │ └── sqldelight
│ │ ├── App.sq
│ │ ├── HeapAnalysis.sq
│ │ ├── Leak.sq
│ │ └── LeakTrace.sq
├── leakcanary-core
│ ├── api
│ │ └── leakcanary-core.api
│ ├── build.gradle.kts
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── leakcanary
│ │ │ ├── DatetimeFormattedHeapDumpFileProvider.kt
│ │ │ ├── DumpingRepeatingScenarioObjectGrowthDetector.kt
│ │ │ ├── HeapDumpDirectoryProvider.kt
│ │ │ ├── HeapDumpFileProvider.kt
│ │ │ ├── HeapDumpStorageStrategy.kt
│ │ │ ├── HeapDumper.kt
│ │ │ └── ObjectGrowthWarmupHeapDumper.kt
│ │ └── test
│ │ └── java
│ │ └── leakcanary
│ │ ├── DumpingRepeatingScenarioObjectGrowthDetectorTest.kt
│ │ └── ObjectGrowthWarmupHeapDumperTest.kt
├── leakcanary-deobfuscation-gradle-plugin
│ ├── api
│ │ └── leakcanary-deobfuscation-gradle-plugin.api
│ ├── build.gradle.kts
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── squareup
│ │ │ └── leakcanary
│ │ │ └── deobfuscation
│ │ │ ├── CopyObfuscationMappingFileTask.kt
│ │ │ ├── LeakCanaryDeobfuscationExtension.kt
│ │ │ └── LeakCanaryLeakDeobfuscationPlugin.kt
│ │ └── test
│ │ ├── java
│ │ └── com
│ │ │ └── squareup
│ │ │ └── leakcanary
│ │ │ └── deobfuscation
│ │ │ ├── CopyObfuscationMappingFileTaskTest.kt
│ │ │ └── LeakCanaryLeakDeobfuscationPluginTest.kt
│ │ └── test-project
│ │ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── leakcanary
│ │ └── test
│ │ └── TestProjectClass.java
├── leakcanary-gc
│ ├── api
│ │ └── leakcanary-gc.api
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ ├── FinalizingInProcessGcTrigger.kt
│ │ └── GcTrigger.kt
├── leakcanary-jvm-test
│ ├── api
│ │ └── leakcanary-jvm-test.api
│ ├── build.gradle.kts
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── leakcanary
│ │ │ ├── HotSpotHeapDumper.kt
│ │ │ ├── RepeatingJvmInProcessScenario.kt
│ │ │ └── RepositoryRootHeapDumpDirectoryProvider.kt
│ │ └── test
│ │ └── java
│ │ └── leakcanary
│ │ └── JvmLiveObjectGrowthDetectorTest.kt
└── leakcanary-test-core
│ ├── api
│ └── leakcanary-test-core.api
│ ├── build.gradle.kts
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ ├── main
│ └── java
│ │ └── leakcanary
│ │ ├── TestDescriptionHolder.kt
│ │ ├── TestHeapDumpFileProvider.kt
│ │ └── TestNameProvider.kt
│ └── test
│ └── java
│ └── leakcanary
│ └── TestNameProviderTest.kt
├── mkdocs.yml
├── object-watcher
├── object-watcher-android-androidx
│ ├── api
│ │ └── object-watcher-android-androidx.api
│ ├── build.gradle.kts
│ ├── consumer-proguard-rules.pro
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ └── internal
│ │ ├── AndroidXFragmentDestroyWatcher.kt
│ │ └── ViewModelClearedWatcher.kt
├── object-watcher-android-core
│ ├── api
│ │ └── object-watcher-android-core.api
│ ├── build.gradle.kts
│ ├── consumer-proguard-rules.pro
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── leakcanary
│ │ │ │ ├── ActivityWatcher.kt
│ │ │ │ ├── AppWatcher.kt
│ │ │ │ ├── FragmentAndViewModelWatcher.kt
│ │ │ │ ├── InstallableWatcher.kt
│ │ │ │ ├── RootViewWatcher.kt
│ │ │ │ ├── ServiceWatcher.kt
│ │ │ │ └── internal
│ │ │ │ ├── AndroidOFragmentDestroyWatcher.kt
│ │ │ │ ├── Applications.kt
│ │ │ │ ├── LeakCanaryDelegate.kt
│ │ │ │ └── friendly
│ │ │ │ └── Friendly.kt
│ │ └── res
│ │ │ └── values
│ │ │ ├── leak_canary_public.xml
│ │ │ └── watcher_bools.xml
│ │ └── test
│ │ └── java
│ │ └── leakcanary
│ │ └── AppWatcherTest.kt
├── object-watcher-android-startup
│ ├── api
│ │ └── object-watcher-android-startup.api
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ └── AppWatcherStartupInitializer.kt
├── object-watcher-android
│ ├── api
│ │ └── object-watcher-android.api
│ ├── build.gradle.kts
│ ├── consumer-proguard-rules.pro
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── leakcanary
│ │ │ └── internal
│ │ │ └── MainProcessAppWatcherInstaller.kt
│ │ └── res
│ │ └── values
│ │ ├── leak_canary_public.xml
│ │ └── watcher_bools.xml
└── object-watcher
│ ├── api
│ └── object-watcher.api
│ ├── build.gradle.kts
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ ├── main
│ └── java
│ │ └── leakcanary
│ │ ├── Clock.kt
│ │ ├── DefaultDelayDeletableObjectReporter.kt
│ │ ├── DelayedDeletableObjectReporter.kt
│ │ ├── DelayedExecutor.kt
│ │ ├── DeletableObjectReporter.kt
│ │ ├── KeyedWeakReference.kt
│ │ ├── ObjectWatcher.kt
│ │ ├── OnObjectRetainedListener.kt
│ │ ├── ReachabilityWatcher.kt
│ │ ├── ReferenceQueueRetainedObjectTracker.kt
│ │ ├── RetainedObjectTracker.kt
│ │ ├── TrackedObjectReachability.kt
│ │ ├── TriggeredDeletableObjectReporter.kt
│ │ └── UptimeClock.kt
│ └── test
│ └── java
│ └── leakcanary
│ └── ReferenceQueueRetainedObjectTrackerTest.kt
├── plumber
├── plumber-android-core
│ ├── api
│ │ └── plumber-android-core.api
│ ├── build.gradle.kts
│ ├── consumer-proguard-rules.pro
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ ├── AndroidLeakFixes.kt
│ │ ├── FixedWindowCallback.java
│ │ ├── ViewLocationHolderLeakFix.kt
│ │ └── internal
│ │ ├── FragmentExtensions.kt
│ │ ├── ReferenceCleaner.kt
│ │ └── friendly
│ │ └── Friendly.kt
├── plumber-android-startup
│ ├── api
│ │ └── plumber-android-startup.api
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── leakcanary
│ │ └── PlumberStartupInitializer.kt
└── plumber-android
│ ├── api
│ └── plumber-android.api
│ ├── build.gradle.kts
│ ├── consumer-proguard-rules.pro
│ ├── detekt-baseline.xml
│ ├── gradle.properties
│ └── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── leakcanary
│ │ └── internal
│ │ └── PlumberInstaller.kt
│ └── res
│ └── values
│ ├── plumber_bools.xml
│ └── plumber_public.xml
├── samples
└── leakcanary-android-sample
│ ├── build.gradle.kts
│ ├── detekt-baseline.xml
│ └── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ └── java
│ │ └── leakcanary
│ │ └── tests
│ │ └── TuPeuxPasTest.kt
│ ├── debug
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── example
│ │ └── leakcanary
│ │ ├── DebugExampleApplication.kt
│ │ └── ExampleSetup.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── leakcanary
│ │ │ ├── ExampleApplication.kt
│ │ │ ├── LeakingService.kt
│ │ │ ├── LeakingSingleton.kt
│ │ │ ├── LeakingThread.kt
│ │ │ └── MainActivity.kt
│ └── res
│ │ ├── drawable
│ │ └── leak_canary_sample_icon.png
│ │ ├── layout
│ │ └── main_activity.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ └── strings.xml
│ └── release
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── example
│ └── leakcanary
│ └── ReleaseExampleApplication.kt
├── settings.gradle
├── shark-cli.sh
└── shark
├── shark-android
├── api
│ └── shark-android.api
├── build.gradle.kts
├── detekt-baseline.xml
├── gradle.properties
└── src
│ ├── main
│ ├── java
│ │ └── shark
│ │ │ ├── AndroidBuildMirror.kt
│ │ │ ├── AndroidExtensions.kt
│ │ │ ├── AndroidMetadataExtractor.kt
│ │ │ ├── AndroidObjectGrowthDetector.kt
│ │ │ ├── AndroidObjectGrowthReferenceMatchers.kt
│ │ │ ├── AndroidObjectInspectors.kt
│ │ │ ├── AndroidReferenceMatchers.kt
│ │ │ ├── AndroidResourceIdNames.kt
│ │ │ ├── AndroidServices.kt
│ │ │ └── internal
│ │ │ └── friendly
│ │ │ └── Friendly.kt
│ └── resources
│ │ └── META-INF
│ │ └── proguard
│ │ └── shark.pro
│ └── test
│ ├── java
│ └── shark
│ │ ├── AndroidObjectInspectorsTest.kt
│ │ ├── AndroidReferenceMatcher_XIAMI__RESOURCES_IMPL_Test.kt
│ │ ├── AndroidResourceIdNamesTest.kt
│ │ ├── HprofIOPerfTest.kt
│ │ ├── HprofRetainedHeapPerfTest.kt
│ │ ├── LegacyHprofTest.kt
│ │ ├── LruCacheTuning.kt
│ │ ├── MetricsDualSourceProvider.kt
│ │ ├── Resources.kt
│ │ └── StringPathFinderOptimTest.kt
│ └── resources
│ ├── compose_leak.hprof
│ ├── gc_root_in_non_primary_heap.hprof
│ ├── gcroot_unknown_object.hprof
│ ├── leak_asynctask_m.hprof
│ ├── leak_asynctask_o.hprof
│ ├── leak_asynctask_pre_m.hprof
│ └── unloaded_classes-stripped.hprof
├── shark-cli
├── build.gradle.kts
├── detekt-baseline.xml
├── gradle.properties
└── src
│ └── main
│ └── java
│ └── shark
│ ├── AnalyzeCommand.kt
│ ├── DeobfuscateHprofCommand.kt
│ ├── DumpProcessCommand.kt
│ ├── HeapGrowthCommand.kt
│ ├── InteractiveCommand.kt
│ ├── Main.kt
│ ├── Neo4JCommand.kt
│ ├── SharkCliCommand.kt
│ └── StripHprofCommand.kt
├── shark-graph
├── api
│ └── shark-graph.api
├── build.gradle.kts
├── detekt-baseline.xml
├── gradle.properties
└── src
│ ├── main
│ └── java
│ │ └── shark
│ │ ├── CloseableHeapGraph.kt
│ │ ├── GraphContext.kt
│ │ ├── HeapField.kt
│ │ ├── HeapGraph.kt
│ │ ├── HeapObject.kt
│ │ ├── HeapValue.kt
│ │ ├── HprofHeapGraph.kt
│ │ ├── HprofIndex.kt
│ │ └── internal
│ │ ├── ByteSubArray.kt
│ │ ├── ClassFieldsReader.kt
│ │ ├── FieldValuesReader.kt
│ │ ├── HprofInMemoryIndex.kt
│ │ ├── IndexedObject.kt
│ │ ├── LruCache.kt
│ │ ├── SortedBytesMap.kt
│ │ ├── UnsortedByteEntries.kt
│ │ ├── aosp
│ │ ├── ByteArrayComparator.kt
│ │ └── ByteArrayTimSort.kt
│ │ └── hppc
│ │ ├── HPPC.kt
│ │ ├── LongLongScatterMap.kt
│ │ ├── LongObjectScatterMap.kt
│ │ ├── LongScatterSet.kt
│ │ └── Tuples.kt
│ └── test
│ └── java
│ └── shark
│ ├── ByteArrayTimSortTest.kt
│ ├── HprofDeobfuscatorTest.kt
│ ├── HprofHeapGraphEdgeCasesTest.kt
│ ├── HprofIndexParsingTest.kt
│ ├── HprofPrimitiveArrayStripperTest.kt
│ ├── HprofWriterTest.kt
│ ├── JvmHprofParsingTest.kt
│ ├── LongScatterSetAssertion.kt
│ ├── LongScatterSetTest.kt
│ └── SortedBytesMapTest.kt
├── shark-hprof-test
├── build.gradle.kts
├── detekt-baseline.xml
└── src
│ └── main
│ └── kotlin
│ └── shark
│ ├── HprofWriterHelper.kt
│ └── ProguardMappingHelper.kt
├── shark-hprof
├── api
│ └── shark-hprof.api
├── build.gradle.kts
├── detekt-baseline.xml
├── gradle.properties
└── src
│ ├── main
│ └── java
│ │ └── shark
│ │ ├── ByteArraySourceProvider.kt
│ │ ├── ConstantMemoryMetricsDualSourceProvider.kt
│ │ ├── DualSourceProvider.kt
│ │ ├── FileSourceProvider.kt
│ │ ├── GcRoot.kt
│ │ ├── HprofDeobfuscator.kt
│ │ ├── HprofHeader.kt
│ │ ├── HprofPrimitiveArrayStripper.kt
│ │ ├── HprofRecord.kt
│ │ ├── HprofRecordReader.kt
│ │ ├── HprofRecordTag.kt
│ │ ├── HprofVersion.kt
│ │ ├── HprofWriter.kt
│ │ ├── OnHprofRecordListener.kt
│ │ ├── OnHprofRecordTagListener.kt
│ │ ├── PrimitiveType.kt
│ │ ├── ProguardMapping.kt
│ │ ├── ProguardMappingReader.kt
│ │ ├── RandomAccessHprofReader.kt
│ │ ├── RandomAccessSource.kt
│ │ ├── RandomAccessSourceProvider.kt
│ │ ├── StreamingHprofReader.kt
│ │ ├── StreamingRecordReaderAdapter.kt
│ │ ├── StreamingSourceProvider.kt
│ │ ├── ThrowingCancelableFileSourceProvider.kt
│ │ └── ValueHolder.kt
│ └── test
│ └── java
│ └── shark
│ ├── HprofReaderPrimitiveArrayTest.kt
│ └── ProguardMappingTest.kt
├── shark-log
├── api
│ └── shark-log.api
├── build.gradle.kts
├── detekt-baseline.xml
├── gradle.properties
└── src
│ ├── main
│ └── java
│ │ └── shark
│ │ └── SharkLog.kt
│ └── test
│ └── java
│ └── shark
│ └── SharkLogTest.kt
├── shark-test
├── build.gradle.kts
├── detekt-baseline.xml
└── src
│ └── main
│ └── kotlin
│ └── shark
│ ├── HeapDumpRule.kt
│ └── JvmTestHeapDumper.kt
└── shark
├── api
└── shark.api
├── build.gradle.kts
├── detekt-baseline.xml
├── gradle.properties
└── src
├── main
└── java
│ └── shark
│ ├── ActualMatchingReferenceReaderFactory.kt
│ ├── AndroidNativeSizeMapper.kt
│ ├── AndroidObjectSizeCalculator.kt
│ ├── AndroidReferenceReaderFactory.kt
│ ├── AndroidReferenceReaders.kt
│ ├── ApacheHarmonyInstanceRefReaders.kt
│ ├── AppSingletonInspector.kt
│ ├── ByteSize.kt
│ ├── ChainingInstanceReferenceReader.kt
│ ├── ClassReferenceReader.kt
│ ├── DelegatingObjectReferenceReader.kt
│ ├── DominatorTree.kt
│ ├── Dominators.kt
│ ├── FieldInstanceReferenceReader.kt
│ ├── FilteringLeakingObjectFinder.kt
│ ├── FlatteningPartitionedInstanceReferenceReader.kt
│ ├── GcRootProvider.kt
│ ├── GcRootReference.kt
│ ├── HeapAnalysis.kt
│ ├── HeapAnalysisException.kt
│ ├── HeapAnalyzer.kt
│ ├── HeapTraversal.kt
│ ├── JavaLocalReferenceReader.kt
│ ├── JdkReferenceMatchers.kt
│ ├── JvmObjectGrowthDetector.kt
│ ├── JvmObjectGrowthReferenceMatchers.kt
│ ├── KeyedWeakReferenceFinder.kt
│ ├── LeakNodeStatus.kt
│ ├── LeakTrace.kt
│ ├── LeakTraceObject.kt
│ ├── LeakTraceReference.kt
│ ├── LeakTracer.kt
│ ├── LeakingObjectFinder.kt
│ ├── LeaksAndUnreachableObjects.kt
│ ├── MatchingGcRootProvider.kt
│ ├── MetadataExtractor.kt
│ ├── ObjectArrayReferenceReader.kt
│ ├── ObjectDominators.kt
│ ├── ObjectGrowthDetector.kt
│ ├── ObjectInspector.kt
│ ├── ObjectInspectors.kt
│ ├── ObjectReporter.kt
│ ├── OnAnalysisProgressListener.kt
│ ├── OpenJdkInstanceRefReaders.kt
│ ├── OpenJdkReferenceReaderFactory.kt
│ ├── PathFindingResults.kt
│ ├── PrioritizingShortestPathFinder.kt
│ ├── RealLeakTracerFactory.kt
│ ├── Reference.kt
│ ├── ReferenceLocationType.kt
│ ├── ReferenceMatcher.kt
│ ├── ReferencePattern.kt
│ ├── ReferenceReader.kt
│ ├── RepeatingScenarioObjectGrowthDetector.kt
│ ├── Retained.kt
│ ├── ShortestPathFinder.kt
│ ├── ShortestPathObjectNode.kt
│ ├── VirtualizingMatchingReferenceReaderFactory.kt
│ └── internal
│ ├── ByteStringCompat.java
│ ├── FieldIdReader.kt
│ ├── IntIntPairUtils.kt
│ ├── InternalSharedExpanderHelpers.kt
│ ├── InternalSharkCollectionsHelper.kt
│ ├── JavaFrames.kt
│ ├── KeyedWeakReferenceMirror.kt
│ ├── ReferencePathNode.kt
│ ├── ShallowSizeCalculator.kt
│ ├── Strings.kt
│ └── ThreadObjects.kt
└── test
├── java
└── shark
│ ├── FlatteningPartitionedInstanceReferenceReaderTest.kt
│ ├── HeapAnalysisStringRenderingTest.kt
│ ├── HeapAnalyzerTest.kt
│ ├── HeapDumps.kt
│ ├── HprofHeapGraphTest.kt
│ ├── LabelerTest.kt
│ ├── LeakStatusTest.kt
│ ├── LeakTraceRenderingTest.kt
│ ├── MetadataExtractorTest.kt
│ ├── ObjectGrowthDetectorTest.kt
│ ├── OpenJdkInstanceRefReadersTest.kt
│ ├── ReferenceMatcherTest.kt
│ ├── RetainedSizeTest.kt
│ ├── TestUtil.kt
│ ├── UnreachableObjectRenderingTest.kt
│ └── internal
│ ├── AndroidReferenceReadersHprofTest.kt
│ ├── DominatorTreeTest.kt
│ └── ShallowSizeCalculatorTest.kt
└── resources
├── hashmap_api_25.hprof
└── safe_iterable_map.hprof
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_size = 2
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 |
9 | [*.{kt,kts}]
10 | kotlin_imports_layout=ascii
11 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | *.bat text eol=crlf
4 | *.jar binary
5 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | See https://square.github.io/leakcanary/code_of_conduct
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | See https://square.github.io/leakcanary/how_to_help/
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1-leak.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F424Leak in your app"
3 | about: Use Stack Overflow instead
4 | title: "\U0001F649 [This issue will be immediately closed]"
5 | labels: 'Close immediately'
6 | assignees: ''
7 |
8 | ---
9 |
10 | 🛑 𝙎𝙏𝙊𝙋
11 |
12 | This issue tracker is not for help with memory leaks detected by LeakCanary in your own app.
13 |
14 | To fix a leak:
15 |
16 | * First, learn the fundamentals: https://square.github.io/leakcanary/fundamentals/
17 | * Then, create a Stack Overflow question: https://stackoverflow.com/questions/tagged/leakcanary
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41BBug report"
3 | about: 'Build errors, bugs and runtime crashes in version 2.0'
4 | title: ''
5 | labels: 'type: bug'
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Description
11 |
12 | [Description of the issue]
13 |
14 | ### Steps to Reproduce
15 |
16 | [Provide a sample project, a .hprof file or a failing test]
17 |
18 | 1. [First Step]
19 | 2. [Second Step]
20 | 3. [and so on...]
21 |
22 | **Expected behavior:** [What you expect to happen]
23 |
24 | ### Version Information
25 |
26 | * LeakCanary version:
27 | * Android OS version:
28 | * Gradle version:
29 |
30 | ### Additional Information
31 |
32 | Any additional information, configuration or data that might be necessary to reproduce the issue.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/3-feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F64FFeature request"
3 | about: Suggest an idea for LeakCanary
4 | title: ''
5 | labels: 'type: enhancement'
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Problem description
11 |
12 | ### Potential solutions
13 |
14 | ### Additional information
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/4-doc.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F4DADocumentation request"
3 | about: Point out what's confusing or missing
4 | title: ''
5 | labels: 'type: documentation'
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/5-sdk.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F916Leak in Android SDK / support library"
3 | about: Help LeakCanary identify known leaks
4 | title: ''
5 | labels: 'type: leak'
6 | assignees: ''
7 |
8 | ---
9 |
10 | Read this first: https://square.github.io/leakcanary/faq/#can-a-leak-be-caused-by-the-android-sdk
11 |
12 | ### LeakTrace information
13 |
14 | ```
15 | {REPLACE THIS LINE WITH THE OUTPUT FROM LEAKCANARY}
16 | ```
17 |
--------------------------------------------------------------------------------
/.github/SUPPORT.md:
--------------------------------------------------------------------------------
1 | See https://square.github.io/leakcanary/support
--------------------------------------------------------------------------------
/.github/workflows-disabled/greetings.yml:
--------------------------------------------------------------------------------
1 | name: Greetings
2 |
3 | on: [pull_request, issues]
4 |
5 | jobs:
6 | greeting:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/first-interaction@v1
10 | with:
11 | repo-token: ${{ secrets.GITHUB_TOKEN }}
12 | issue-message: '🙏Thank you for opening an issue! LeakCanary is maintained by [volunteers](https://github.com/square/leakcanary/graphs/contributors) from the community. Please be kind and remember that LeakCanary isn''t anyone''s main job 😘.'
13 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yaml:
--------------------------------------------------------------------------------
1 | name: Mark stale issues
2 | on:
3 | schedule:
4 | - cron: "30 1 * * *"
5 |
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | permissions:
10 | issues: write
11 | steps:
12 | - uses: actions/stale@v5
13 | with:
14 | repo-token: ${{ secrets.GITHUB_TOKEN }}
15 | days-before-stale: 30
16 | days-before-close: 7
17 | stale-issue-message: 'This issue is not actionable without the required information. Please comment or this will be closed in 7 days.'
18 | close-issue-message: 'This issue was closed because it did not provide enough information to make it actionable.'
19 | stale-issue-label: stale
20 | stale-pr-label: stale
21 | any-of-labels: 'status: needs more info, status: needs heap dump'
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle
2 | .gradle
3 | gradlew.bat
4 | build
5 | local.properties
6 | reports
7 |
8 | # Maven
9 | target
10 | pom.xml.*
11 | release.properties
12 | gen-external-apklibs
13 |
14 | # Eclipse
15 | .classpath
16 | .project
17 | .settings
18 | eclipsebin
19 |
20 | # IntelliJ IDEA
21 | .idea
22 | *.iml
23 | *.ipl
24 | *.iws
25 | classes/
26 | idea-classes/
27 | coverage-error.log
28 |
29 | # Android
30 | gen
31 | bin
32 | project.properties
33 | out
34 |
35 | # Finder
36 | .DS_Store
37 |
38 | # Docs
39 | site
40 |
41 | # Generated dokka
42 | docs/api
43 |
44 | # Python virtual env
45 | venv
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LeakCanary 🐤
2 |
3 | A memory leak detection library for Android.
4 |
5 | ### [square.github.io/leakcanary](https://square.github.io/leakcanary)
6 |
7 | 🙏 If you like LeakCanary you can show support by starring ⭐ this repository.
8 |
9 | ## License
10 |
11 | Copyright 2015 Square, Inc.
12 |
13 | Licensed under the Apache License, Version 2.0 (the "License");
14 | you may not use this file except in compliance with the License.
15 | You may obtain a copy of the License at
16 |
17 | http://www.apache.org/licenses/LICENSE-2.0
18 |
19 | Unless required by applicable law or agreed to in writing, software
20 | distributed under the License is distributed on an "AS IS" BASIS,
21 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 | See the License for the specific language governing permissions and
23 | limitations under the License.
24 |
--------------------------------------------------------------------------------
/config/hooks/pre-push:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Running static analysis..."
4 |
5 | # Run static analysis tools
6 | ./gradlew detekt --no-configuration-cache
7 |
8 | status=$?
9 |
10 | if [ "$status" = 0 ] ; then
11 | echo "Static analysis found no problems."
12 | exit 0
13 | else
14 | echo 1>&2 "Static analysis found violations! Fix them before pushing your code!"
15 | echo "See generated reports above or in /<project_dir>/build/reports folder"
16 | exit 1
17 | fi
18 |
--------------------------------------------------------------------------------
/docs/assets/adaptative_icon.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/adaptative_icon.sketch
--------------------------------------------------------------------------------
/docs/assets/icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/icon_1024.png
--------------------------------------------------------------------------------
/docs/assets/icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/icon_512.png
--------------------------------------------------------------------------------
/docs/assets/kanary-200px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/kanary-200px.png
--------------------------------------------------------------------------------
/docs/assets/kanary-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/kanary-large.png
--------------------------------------------------------------------------------
/docs/assets/leakcanary_shirt.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/leakcanary_shirt.psd
--------------------------------------------------------------------------------
/docs/assets/repository-open-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/repository-open-graph.png
--------------------------------------------------------------------------------
/docs/assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/screenshot.png
--------------------------------------------------------------------------------
/docs/assets/shark.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/shark.psd
--------------------------------------------------------------------------------
/docs/assets/source_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/source_icon.png
--------------------------------------------------------------------------------
/docs/assets/sticker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/sticker.png
--------------------------------------------------------------------------------
/docs/assets/vector_icon.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/assets/vector_icon.afdesign
--------------------------------------------------------------------------------
/docs/blog-articles.md:
--------------------------------------------------------------------------------
1 | * [Memory Leaks in Android](https://www.raywenderlich.com/4690472-memory-leaks-in-android)
2 | * [Detect memory leaks in your instrumentation tests using LeakCanary](https://proandroiddev.com/detecting-memory-leaks-in-your-instrumentation-tests-using-leakcanary-1268e911d5ce)
3 | * [9 ways to avoid memory leaks in Android](https://android.jlelse.eu/9-ways-to-avoid-memory-leaks-in-android-b6d81648e35e)
4 | * [LeakCanary - Deobfuscation Feature Explained](https://www.polidea.com/blog/leakcanary-deobfuscation-feature-explained/)
5 | * [LeakCanary - An in-depth example of Android memory-leaks in MVP architecture](https://github.com/tricknology/MVPMemoryLeak/wiki)
6 | * [Detecting memory leaks in Android applications](https://dropbox.tech/mobile/detecting-memory-leaks-in-android-applications)
7 |
8 | Your article should be here, please update this list! Any technical level welcome.
9 |
--------------------------------------------------------------------------------
/docs/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | To use LeakCanary, add the `leakcanary-android` dependency to your app's `build.gradle` file:
4 |
5 | ```groovy
6 | dependencies {
7 | // debugImplementation because LeakCanary should only run in debug builds.
8 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:{{ leak_canary.release }}'
9 | }
10 | ```
11 |
12 | **That's it, there is no code change needed!**
13 |
14 | Confirm that LeakCanary is running on startup by filtering on the `LeakCanary` tag in [Logcat](https://developer.android.com/studio/command-line/logcat):
15 |
16 | ```
17 | D LeakCanary: LeakCanary is running and ready to detect leaks
18 | ```
19 |
20 | !!! info
21 | LeakCanary automatically detects leaks of the following objects:
22 |
23 | * destroyed `Activity` instances
24 | * destroyed `Fragment` instances
25 | * destroyed fragment `View` instances
26 | * cleared `ViewModel` instances
27 | * destroyed `Service` instance
28 |
29 | What's next? Learn the [Fundamentals](fundamentals.md)!
30 |
--------------------------------------------------------------------------------
/docs/how_to_help.md:
--------------------------------------------------------------------------------
1 | # How to help
2 |
3 | 🙏🙏🙏
4 |
5 | LeakCanary is maintained by volunteers. Your help is welcome and will benefit the entire Android community!
6 |
7 | Here's how you can help:
8 |
9 | * Contribute to [Help Wanted](https://github.com/square/leakcanary/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+help+wanted%22) issues.
10 | * Answer [StackOverflow questions](http://stackoverflow.com/questions/tagged/leakcanary?sort=active).
11 | * Provide feedback on [pull requests](https://github.com/square/leakcanary/pulls).
12 | * Contribute code by forking the repository on GitHub and sending a pull request. Please read [Dev Environment for LeakCanary contributors](dev-env.md). When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible.
13 |
--------------------------------------------------------------------------------
/docs/images/analysis-done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/analysis-done.png
--------------------------------------------------------------------------------
/docs/images/android-tv-leaks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/android-tv-leaks.png
--------------------------------------------------------------------------------
/docs/images/bugsnag-leak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/bugsnag-leak.png
--------------------------------------------------------------------------------
/docs/images/bugsnag-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/bugsnag-list.png
--------------------------------------------------------------------------------
/docs/images/building-leak-traces-notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/building-leak-traces-notification.png
--------------------------------------------------------------------------------
/docs/images/disable_dumping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/disable_dumping.png
--------------------------------------------------------------------------------
/docs/images/dumping-toast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/dumping-toast.png
--------------------------------------------------------------------------------
/docs/images/finding-retained-notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/finding-retained-notification.png
--------------------------------------------------------------------------------
/docs/images/heap-dump.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/heap-dump.png
--------------------------------------------------------------------------------
/docs/images/launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/launcher.png
--------------------------------------------------------------------------------
/docs/images/leak-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/leak-screen.png
--------------------------------------------------------------------------------
/docs/images/library-leak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/library-leak.png
--------------------------------------------------------------------------------
/docs/images/logo-2.0-200px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/logo-2.0-200px.png
--------------------------------------------------------------------------------
/docs/images/logo-2.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/logo-2.0.png
--------------------------------------------------------------------------------
/docs/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/logo.png
--------------------------------------------------------------------------------
/docs/images/retained-notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/retained-notification.png
--------------------------------------------------------------------------------
/docs/images/screenshot-2.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/screenshot-2.0.png
--------------------------------------------------------------------------------
/docs/images/shark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/shark.png
--------------------------------------------------------------------------------
/docs/images/signature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/docs/images/signature.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # LeakCanary 🐤
2 |
3 | LeakCanary is a memory leak detection library for Android.
4 |
5 | <p align="center">
6 | <img src="images/screenshot-2.0.png" />
7 | </p>
8 |
9 | LeakCanary's knowledge of the internals of the Android Framework gives it a unique ability to narrow
10 | down the cause of each leak, helping developers dramatically reduce jank, `Application Not Responding`
11 | freezes and `OutOfMemoryError` crashes.
12 |
13 | [Get started!](getting_started.md)
14 |
15 | !!! quote
16 | *“A small leak will sink a great ship.”* - Benjamin Franklin
17 |
18 |
--------------------------------------------------------------------------------
/docs/recorded-presentations.md:
--------------------------------------------------------------------------------
1 | * [Live leak investigations](https://www.youtube.com/watch?v=Sx0k4ipqwBs), investigating leaks on Stack Overflow and fixes for AOSP leaks
2 | * [Fixing leaks in Firefox](https://www.youtube.com/watch?v=kHHOhPPRytc)
3 | * [Shark: Diving into the guts of LeakCanary's Hprof parser](https://www.droidcon.com/media-detail?video=362742252)
4 | * [LeakCanary 2: Leaner, Better, Faster, Kotliner!](https://www.youtube.com/watch?v=LEX8dn4BLUw)
5 | * [LeakCanary, then what? Nuking Nasty Memory Leaks](https://www.youtube.com/watch?v=fhE--eTEW84)
6 | * [Memory Leak Hunt](https://www.youtube.com/watch?v=KwArTJHLq5g), a live investigation.
7 | * [Installing LeakCanary](https://caster.io/lessons/installing-leakcanary) (LeakCanary 1.5)
8 | * [How to use LeakCanary](https://www.youtube.com/watch?v=qtrZVPGdDkU)
9 |
10 | Your presentation should be here, please update this list! Any technical level welcome.
11 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs==1.4.2
2 | mkdocs-material==9.1.3
3 | mkdocs-markdownextradata-plugin==0.2.5
4 |
--------------------------------------------------------------------------------
/docs/support.md:
--------------------------------------------------------------------------------
1 | # LeakCanary Support
2 |
3 | If you're looking for help with LeakCanary:
4 |
5 | * Learn the [Fundamentals](fundamentals.md)
6 | * Try the [code recipes](recipes.md)
7 | * Read the [FAQ](https://square.github.io/leakcanary/faq/)
8 | * Watch [recorded presentations](recorded-presentations.md)
9 | * Read [blog articles](blog-articles.md)
10 | * Ask a question [on StackOverflow](http://stackoverflow.com/questions/tagged/leakcanary?sort=active)
11 |
12 |
13 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | GROUP=com.squareup.leakcanary
2 | VERSION_NAME=3.0-alpha-9
3 |
4 | POM_DESCRIPTION=LeakCanary
5 | POM_INCEPTION_YEAR=2015
6 | POM_URL=https://github.com/square/leakcanary/
7 | POM_SCM_URL=https://github.com/square/leakcanary/
8 | POM_SCM_CONNECTION=scm:git:https://github.com/square/leakcanary.git
9 | POM_SCM_DEV_CONNECTION=scm:git:git@github.com:square/leakcanary.git
10 |
11 | POM_LICENSE_NAME=The Apache Software License, Version 2.0
12 | POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
13 | POM_LICENSE_DIST=repo
14 |
15 | POM_DEVELOPER_ID=square
16 | POM_DEVELOPER_NAME=Square, Inc.
17 | POM_DEVELOPER_URL=https://github.com/square/
18 | SONATYPE_STAGING_PROFILE=com.squareup
19 |
20 | android.useAndroidX=true
21 | #Gradle properties: https://docs.gradle.org/current/userguide/build_environment.html
22 | org.gradle.caching=true
23 | org.gradle.configureondemand=true
24 | org.gradle.jvmargs=-XX:+UseParallelGC -Dfile.encoding=UTF-8
25 | org.gradle.parallel=true
26 | android.defaults.buildfeatures.buildconfig=true
27 | android.nonTransitiveRClass=false
28 | android.nonFinalResIds=false
29 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/consumer-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Loaded via reflection & referenced by shark.AndroidReferenceMatchers.LEAK_CANARY_INTERNAL and shark.AndroidReferenceMatchers.LEAK_CANARY_HEAP_DUMPER
2 | -keep class leakcanary.internal.InternalLeakCanary { *; }
3 | # Marshmallow removed Notification.setLatestEventInfo()
4 | -dontwarn android.app.Notification
5 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-android-core
2 | POM_NAME=LeakCanary for Android - Core
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/lint.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <lint>
3 | <issue id="SetTextI18n" severity="ignore" />
4 | </lint>
5 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2019 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <manifest />
18 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/androidTest/assets/leaks-v24.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-android-core/src/androidTest/assets/leaks-v24.db
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/androidTest/java/leakcanary/DatabaseRule.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.database.sqlite.SQLiteDatabase
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import leakcanary.internal.activity.db.LeaksDbHelper
6 | import leakcanary.internal.activity.db.ScopedLeaksDb
7 | import org.junit.rules.TestRule
8 | import org.junit.runner.Description
9 | import org.junit.runners.model.Statement
10 |
11 | class DatabaseRule(private val updateDb: (SQLiteDatabase) -> Unit = {}) : TestRule {
12 | override fun apply(
13 | base: Statement,
14 | description: Description
15 | ): Statement {
16 | return object : Statement() {
17 | override fun evaluate() {
18 | val instrumentation = InstrumentationRegistry.getInstrumentation()
19 | val context = instrumentation.targetContext
20 | context.deleteDatabase(LeaksDbHelper.DATABASE_NAME)
21 | ScopedLeaksDb.writableDatabase(context, updateDb)
22 | base.evaluate()
23 | context.deleteDatabase(LeaksDbHelper.DATABASE_NAME)
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-android-core/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/BackgroundThreadHeapAnalyzer.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.os.Handler
4 | import android.os.HandlerThread
5 | import leakcanary.EventListener.Event
6 | import leakcanary.EventListener.Event.HeapDump
7 | import leakcanary.internal.AndroidDebugHeapAnalyzer
8 | import leakcanary.internal.InternalLeakCanary
9 |
10 | /**
11 | * Starts heap analysis on a background [HandlerThread] when receiving a [HeapDump] event.
12 | */
13 | object BackgroundThreadHeapAnalyzer : EventListener {
14 |
15 | internal val heapAnalyzerThreadHandler by lazy {
16 | val handlerThread = HandlerThread("HeapAnalyzer")
17 | handlerThread.start()
18 | Handler(handlerThread.looper)
19 | }
20 |
21 | override fun onEvent(event: Event) {
22 | if (event is HeapDump) {
23 | heapAnalyzerThreadHandler.post {
24 | val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(event) { event ->
25 | InternalLeakCanary.sendEvent(event)
26 | }
27 | InternalLeakCanary.sendEvent(doneEvent)
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/LazyForwardingEventListener.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import leakcanary.EventListener.Event
4 |
5 | /**
6 | * Forwards events to the [EventListener] provided by lazyEventListener which
7 | * is evaluated lazily, when the first comes in.
8 | */
9 | class LazyForwardingEventListener(
10 | lazyEventListener: () -> EventListener
11 | ) : EventListener {
12 |
13 | private val eventListenerDelegate by lazy(lazyEventListener)
14 |
15 | override fun onEvent(event: Event) {
16 | eventListenerDelegate.onEvent(event)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/DebuggerControl.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Square, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package leakcanary.internal
17 |
18 | import android.os.Debug
19 |
20 | /**
21 | * Gives the opportunity to skip checking if a reference is gone when the debugger is connected.
22 | * An attached debugger might retain references and create false positives.
23 | */
24 | internal object DebuggerControl {
25 |
26 | val isDebuggerAttached: Boolean
27 | get() = Debug.isDebuggerConnected()
28 | }
29 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/LazyImmediateFuture.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import com.google.common.util.concurrent.ListenableFuture
4 | import java.util.concurrent.Executor
5 | import java.util.concurrent.TimeUnit
6 |
7 | internal class LazyImmediateFuture<V>(
8 | valueProvider: () -> V
9 | ) : ListenableFuture<V> {
10 |
11 | private val value by lazy {
12 | valueProvider()
13 | }
14 |
15 | override fun cancel(mayInterruptIfRunning: Boolean) = false
16 |
17 | override fun isCancelled() = false
18 |
19 | override fun isDone() = true
20 |
21 | override fun get() = value
22 |
23 | override fun get(
24 | timeout: Long,
25 | unit: TimeUnit?
26 | ): V = value
27 |
28 | override fun addListener(
29 | listener: Runnable,
30 | executor: Executor
31 | ) {
32 | executor.execute(listener)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/NotificationType.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import com.squareup.leakcanary.core.R
4 |
5 | internal enum class NotificationType(
6 | val nameResId: Int,
7 | val importance: Int
8 | ) {
9 | LEAKCANARY_LOW(
10 | R.string.leak_canary_notification_channel_low, IMPORTANCE_LOW
11 | ),
12 | LEAKCANARY_MAX(
13 | R.string.leak_canary_notification_channel_result, IMPORTANCE_MAX
14 | );
15 | }
16 |
17 | private const val IMPORTANCE_LOW = 2
18 | private const val IMPORTANCE_MAX = 5
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/OnRetainInstanceListener.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | internal sealed class RetainInstanceEvent {
4 | object NoMoreObjects : RetainInstanceEvent()
5 | sealed class CountChanged : RetainInstanceEvent() {
6 | class BelowThreshold(val retainedCount: Int) : RetainInstanceEvent()
7 | class DumpingDisabled(val reason: String) : RetainInstanceEvent()
8 | object DumpHappenedRecently : RetainInstanceEvent()
9 | }
10 | }
11 |
12 | /**
13 | * Called by LeakCanary when the number of retained instances updates .
14 | */
15 | internal fun interface OnRetainInstanceListener {
16 |
17 | /**
18 | * Called when there's a change to the Retained Instances. See [RetainInstanceEvent] for
19 | * possible events.
20 | */
21 | fun onEvent(event: RetainInstanceEvent)
22 | }
23 |
24 | internal class DefaultOnRetainInstanceListener : OnRetainInstanceListener {
25 |
26 | override fun onEvent(event: RetainInstanceEvent) {}
27 | }
28 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/SerializableIntent.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import android.content.Intent
4 | import java.io.Serializable
5 |
6 | /**
7 | * Wraps an Intent to serialize it as its URI string.
8 | */
9 | internal class SerializableIntent(intent: Intent) : Serializable {
10 |
11 | private val uri = intent.toUri(0)
12 |
13 | // Intent is not Serializable
14 | @Transient
15 | private var _intent: Intent? = intent
16 |
17 | val intent: Intent
18 | get() = _intent.run {
19 | this ?: Intent.parseUri(uri, 0)
20 | .apply { _intent = this }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/Serializables.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import shark.SharkLog
4 | import java.io.ByteArrayInputStream
5 | import java.io.ByteArrayOutputStream
6 | import java.io.ObjectInputStream
7 | import java.io.ObjectOutputStream
8 | import java.io.Serializable
9 |
10 | internal fun Serializable.toByteArray(): ByteArray {
11 | val outputStream = ByteArrayOutputStream()
12 | ObjectOutputStream(outputStream).writeObject(this)
13 | return outputStream.toByteArray()
14 | }
15 |
16 | internal object Serializables {
17 |
18 | inline fun <reified T> fromByteArray(byteArray: ByteArray): T? {
19 | val inputStream = ByteArrayInputStream(byteArray)
20 | return try {
21 | ObjectInputStream(inputStream).readObject() as? T
22 | } catch (ignored: Throwable) {
23 | SharkLog.d(ignored) { "Could not deserialize bytes, ignoring" }
24 | null
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/activity/ui/SimpleListAdapter.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal.activity.ui
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import android.widget.BaseAdapter
6 | import leakcanary.internal.navigation.inflate
7 |
8 | internal class SimpleListAdapter<T>(
9 | private val rowResId: Int,
10 | private val items: List<T>,
11 | private val bindView: SimpleListAdapter<T>.(View, Int) -> Unit
12 | ) : BaseAdapter() {
13 | override fun getView(
14 | position: Int,
15 | convertView: View?,
16 | parent: ViewGroup
17 | ): View {
18 | val view = convertView ?: parent.inflate(rowResId)
19 | bindView(view, position)
20 | return view
21 | }
22 |
23 | override fun getItem(position: Int) = items[position]
24 |
25 | override fun getItemId(position: Int) = position.toLong()
26 |
27 | override fun getCount() = items.size
28 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/activity/ui/UiUtils.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal.activity.ui
2 |
3 | import android.text.SpannableStringBuilder
4 | import android.text.style.ClickableSpan
5 | import android.text.style.URLSpan
6 | import android.view.View
7 |
8 | internal object UiUtils {
9 |
10 | internal fun replaceUrlSpanWithAction(
11 | title: SpannableStringBuilder,
12 | urlAction: (String) -> (() -> Unit)?
13 | ) {
14 | val urlSpans = title.getSpans(0, title.length, URLSpan::class.java)
15 | for (span in urlSpans) {
16 | val action: (() -> Unit)? = urlAction(span.url)
17 | if (action != null) {
18 | val start = title.getSpanStart(span)
19 | val end = title.getSpanEnd(span)
20 | val flags = title.getSpanFlags(span)
21 | title.removeSpan(span)
22 | val newSpan = object : ClickableSpan() {
23 | override fun onClick(widget: View) {
24 | action()
25 | }
26 | }
27 | title.setSpan(newSpan, start, end, flags)
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/friendly/Friendly.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "NOTHING_TO_INLINE")
2 | @file:JvmName("leakcanary-android-core_Friendly")
3 |
4 | package leakcanary.internal.friendly
5 |
6 | internal inline val mainHandler
7 | get() = leakcanary.internal.mainHandler
8 |
9 | internal inline fun checkMainThread() = leakcanary.internal.checkMainThread()
10 |
11 | internal inline fun checkNotMainThread() = leakcanary.internal.checkNotMainThread()
12 |
13 | internal inline fun <reified T : Any> noOpDelegate(): T = leakcanary.internal.noOpDelegate()
14 |
15 | internal inline fun measureDurationMillis(block: () -> Unit) =
16 | leakcanary.internal.measureDurationMillis(block)
17 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/navigation/Screen.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal.navigation
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import java.io.Serializable
6 |
7 | /**
8 | * Replaces Fragments, MVP, MVC, MVVM, MVMVMVM and everything else in just one tiny class.
9 | * A screen is a location to go to, and it can build a view to display.
10 | */
11 | internal abstract class Screen : Serializable {
12 |
13 | abstract fun createView(container: ViewGroup): View
14 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/utils/Size.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal.utils
2 |
3 | import kotlin.math.ln
4 | import kotlin.math.pow
5 |
6 | // https://stackoverflow.com/a/3758880
7 | internal fun humanReadableByteCount(
8 | bytes: Long,
9 | si: Boolean
10 | ): String {
11 | val unit = if (si) 1000 else 1024
12 | if (bytes < unit) return "$bytes B"
13 | val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
14 | val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
15 | return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
16 | }
17 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/java/leakcanary/internal/utils/Tuples.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal.utils
2 |
3 | internal infix fun <A, B, C> Pair<A, B>.to(that: C): Triple<A, B, C> = Triple(first, second, that)
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/anim/leak_canary_enter_alpha.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <alpha
3 | xmlns:android="http://schemas.android.com/apk/res/android"
4 | android:fromAlpha="0"
5 | android:toAlpha="1"
6 | android:duration="@android:integer/config_mediumAnimTime"/>
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/anim/leak_canary_enter_backward.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <translate
3 | xmlns:android="http://schemas.android.com/apk/res/android"
4 | android:fromXDelta="-100%" android:toXDelta="0%"
5 | android:fromYDelta="0%" android:toYDelta="0%"
6 | android:duration="@android:integer/config_mediumAnimTime"/>
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/anim/leak_canary_enter_forward.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <translate
3 | xmlns:android="http://schemas.android.com/apk/res/android"
4 | android:fromXDelta="100%" android:toXDelta="0%"
5 | android:fromYDelta="0%" android:toYDelta="0%"
6 | android:duration="@android:integer/config_mediumAnimTime"/>
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/anim/leak_canary_exit_alpha.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <alpha
3 | xmlns:android="http://schemas.android.com/apk/res/android"
4 | android:fromAlpha="1"
5 | android:toAlpha="0"
6 | android:duration="@android:integer/config_mediumAnimTime"/>
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/anim/leak_canary_exit_backward.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <translate
3 | xmlns:android="http://schemas.android.com/apk/res/android"
4 | android:fromXDelta="0%" android:toXDelta="100%"
5 | android:fromYDelta="0%" android:toYDelta="0%"
6 | android:duration="@android:integer/config_mediumAnimTime"/>
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/anim/leak_canary_exit_forward.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <translate
3 | xmlns:android="http://schemas.android.com/apk/res/android"
4 | android:fromXDelta="0%" android:toXDelta="-100%"
5 | android:fromYDelta="0%" android:toYDelta="0%"
6 | android:duration="@android:integer/config_mediumAnimTime"/>
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/color/leak_canary_bottom_menu.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:state_selected="false" android:color="@color/leak_canary_gray_darkest_40p" />
4 | <item android:state_selected="true" android:color="@color/leak_canary_gray_darkest" />
5 | </selector>
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/color/leak_canary_count_text.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:state_enabled="true" android:color="@color/leak_canary_gray_darkest" />
4 | <item android:color="@color/leak_canary_yellow" />
5 | </selector>
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable-v21/leak_canary_gray_fill.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <ripple xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:color="?android:attr/colorControlHighlight">
4 | <item android:id="@android:id/mask">
5 | <shape>
6 | <solid android:color="@color/leak_canary_gray" />
7 | <corners android:radius="20dp" />
8 | </shape>
9 | </item>
10 | <item>
11 | <shape>
12 | <solid android:color="@color/leak_canary_gray" />
13 | <corners android:radius="20dp" />
14 | </shape>
15 | </item>
16 | </ripple>
17 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable-v21/leak_canary_list_selector.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:state_focused="true" android:state_pressed="false">
4 | <color android:color="?android:attr/colorControlHighlight" />
5 | </item>
6 | <item>
7 | <color android:color="@android:color/transparent" />
8 | </item>
9 | </selector>
10 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable-v21/leak_canary_primary_button.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <ripple xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:color="@color/leak_canary_yellow_button_pressed">
4 | <item>
5 | <shape>
6 | <solid android:color="@color/leak_canary_yellow_button" />
7 | <corners android:radius="12dp" />
8 | </shape>
9 | </item>
10 | <item android:id="@android:id/mask">
11 | <shape>
12 | <solid android:color="@color/leak_canary_yellow_button" />
13 | <corners android:radius="12dp" />
14 | </shape>
15 | </item>
16 | </ripple>
17 |
18 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable-v21/leak_canary_secondary_button.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <ripple xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:color="@color/leak_canary_gray_6f">
4 | <item>
5 | <shape>
6 | <solid android:color="@color/leak_canary_gray_3f" />
7 | <corners android:radius="12dp" />
8 | </shape>
9 | </item>
10 | <item android:id="@android:id/mask">
11 | <shape>
12 | <solid android:color="@color/leak_canary_gray_3f" />
13 | <corners android:radius="12dp" />
14 | </shape>
15 | </item>
16 | </ripple>
17 |
18 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable-v21/leak_canary_tab_background.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:drawable="@drawable/leak_canary_tab_selector_ripple" />
4 | </selector>
5 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable-v21/leak_canary_tab_selector_ripple.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <ripple
3 | xmlns:android="http://schemas.android.com/apk/res/android"
4 | android:color="@color/leak_canary_gray_darkest_25p"
5 | />
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_count_background.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:state_enabled="true">
4 | <shape>
5 | <solid android:color="@color/leak_canary_count_new" />
6 | <corners android:radius="20dp" />
7 | <stroke android:width="2dp" android:color="@color/leak_canary_count_new_border" />
8 | </shape>
9 | </item>
10 | <item>
11 | <shape>
12 | <solid android:color="@color/leak_canary_count_default" />
13 | <corners android:radius="20dp" />
14 | </shape>
15 | </item>
16 | </selector>
17 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_dump.xml:
--------------------------------------------------------------------------------
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
2 | android:width="24dp"
3 | android:height="23dp"
4 | android:viewportWidth="24"
5 | android:viewportHeight="23">
6 | <path
7 | android:pathData="M3.25,5H10.25"
8 | android:strokeWidth="2.5"
9 | android:fillColor="#00000000"
10 | android:strokeColor="#000000"
11 | android:strokeLineCap="round"/>
12 | <path
13 | android:pathData="M2.25,5.25h0.5v2.5h-0.5z"
14 | android:strokeWidth="0.5"
15 | android:fillColor="#000000"
16 | android:strokeColor="#000000"/>
17 | <path
18 | android:pathData="M3,8C3,6.8954 3.8954,6 5,6H19C20.1046,6 21,6.8954 21,8V16C21,17.1046 20.1046,18 19,18H5C3.8954,18 3,17.1046 3,16V8Z"
19 | android:strokeWidth="2"
20 | android:fillColor="#00000000"
21 | android:strokeColor="#000000"/>
22 | </vector>
23 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_gray_fill.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:state_pressed="true">
4 | <shape>
5 | <solid android:color="@color/leak_canary_gray_3f" />
6 | <corners android:radius="20dp" />
7 | </shape>
8 | </item>
9 | <item>
10 | <shape>
11 | <solid android:color="@color/leak_canary_gray" />
12 | <corners android:radius="20dp" />
13 | </shape>
14 | </item>
15 | </selector>
16 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_info.xml:
--------------------------------------------------------------------------------
1 | <vector android:height="24dp" android:tint="#151C1F"
2 | android:viewportHeight="24.0" android:viewportWidth="24.0"
3 | android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
4 | <path android:fillColor="#FF000000" android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
5 | </vector>
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_info_rectangle.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <shape xmlns:android="http://schemas.android.com/apk/res/android">
3 | <stroke android:width="2dp" android:color="@color/leak_canary_class_name" />
4 | <corners android:radius="6dp" />
5 | </shape>
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_list_selector.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:state_focused="true" android:state_pressed="false">
4 | <color android:color="@color/leak_canary_gray_3f" />
5 | </item>
6 | <item>
7 | <color android:color="@android:color/transparent" />
8 | </item>
9 | </selector>
10 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_primary_button.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:state_pressed="true">
4 | <shape>
5 | <solid android:color="@color/leak_canary_yellow_button_pressed" />
6 | <corners android:radius="12dp" />
7 | </shape>
8 | </item>
9 | <item>
10 | <shape>
11 | <solid android:color="@color/leak_canary_yellow_button" />
12 | <corners android:radius="12dp" />
13 | </shape>
14 | </item>
15 | </selector>
16 |
17 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_secondary_button.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:state_pressed="true">
4 | <shape>
5 | <solid android:color="@color/leak_canary_gray_6f" />
6 | <corners android:radius="12dp" />
7 | </shape>
8 | </item>
9 | <item>
10 | <shape>
11 | <solid android:color="@color/leak_canary_gray_3f" />
12 | <corners android:radius="12dp" />
13 | </shape>
14 | </item>
15 | </selector>
16 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_tab_background.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <selector xmlns:android="http://schemas.android.com/apk/res/android">
3 | <item android:state_pressed="false" android:drawable="@android:color/transparent" />
4 | <item android:drawable="@color/leak_canary_gray_darkest_25p" />
5 | </selector>
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/drawable/leak_canary_toast_background.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <shape xmlns:android="http://schemas.android.com/apk/res/android">
3 | <solid android:color="#cc000000"/>
4 | <corners android:radius="16dp"/>
5 | </shape>
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/layout/leak_canary_heap_analysis_failure_screen.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:layout_width="match_parent"
4 | android:layout_height="match_parent"
5 | >
6 |
7 |
8 | <LinearLayout
9 | android:layout_width="match_parent"
10 | android:layout_height="wrap_content"
11 | android:orientation="vertical"
12 | >
13 |
14 | <include layout="@layout/leak_canary_leak_header" />
15 |
16 | <TextView
17 | android:id="@+id/leak_canary_stacktrace"
18 | android:layout_width="match_parent"
19 | android:layout_height="wrap_content"
20 | android:layout_margin="20dp"
21 | android:textSize="12sp"
22 | android:fontFamily="monospace"
23 | />
24 | </LinearLayout>
25 |
26 | </ScrollView>
27 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/layout/leak_canary_heap_dump_leak_title.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <TextView xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:id="@+id/leak_canary_heap_dump_leaks"
4 | android:layout_width="match_parent"
5 | android:layout_height="wrap_content"
6 | android:paddingLeft="20dp"
7 | android:paddingTop="32dp"
8 | android:paddingBottom="24dp"
9 | android:textStyle="bold"
10 | android:textSize="20sp"
11 | android:textColor="@color/leak_canary_white"
12 | />
13 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/layout/leak_canary_heap_render.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:layout_width="match_parent"
4 | android:layout_height="match_parent"
5 | >
6 | <ProgressBar
7 | android:id="@+id/leak_canary_loading"
8 | android:layout_width="wrap_content"
9 | android:layout_height="wrap_content"
10 | android:layout_gravity="center"
11 | android:indeterminate="true"
12 | style="?android:attr/progressBarStyleLarge"
13 | />
14 | <ImageView
15 | android:id="@+id/leak_canary_heap_rendering"
16 | android:layout_width="wrap_content"
17 | android:layout_height="wrap_content"
18 | />
19 |
20 | </FrameLayout>
21 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/layout/leak_canary_leak_header.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 |
3 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
4 | android:layout_width="match_parent"
5 | android:layout_height="wrap_content"
6 | >
7 | <leakcanary.internal.LeakCanaryTextView
8 | android:id="@+id/leak_canary_header_text"
9 | android:layout_width="match_parent"
10 | android:layout_height="wrap_content"
11 | android:layout_marginStart="20dp"
12 | android:layout_marginEnd="20dp"
13 | android:background="@drawable/leak_canary_info_rectangle"
14 | android:padding="16dp"
15 | />
16 | </FrameLayout>
17 |
18 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/layout/leak_canary_list.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:layout_width="match_parent"
4 | android:layout_height="match_parent"
5 | >
6 | <ListView
7 | android:id="@+id/leak_canary_list"
8 | android:layout_width="match_parent"
9 | android:layout_height="match_parent"
10 | android:listSelector="@drawable/leak_canary_list_selector"
11 | android:divider="@null"
12 | android:dividerHeight="0dp"
13 | android:requiresFadingEdge="vertical"
14 | />
15 | </FrameLayout>
16 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/mipmap-anydpi-v26/leak_canary_icon.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3 | <background android:drawable="@color/leak_canary_white"/>
4 | <foreground android:drawable="@drawable/leak_canary_icon_foreground"/>
5 | <monochrome android:drawable="@drawable/leak_canary_icon_monochrome" />
6 | </adaptive-icon>
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/mipmap-hdpi/leak_canary_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-android-core/src/main/res/mipmap-hdpi/leak_canary_icon.png
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/mipmap-mdpi/leak_canary_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-android-core/src/main/res/mipmap-mdpi/leak_canary_icon.png
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/mipmap-xhdpi/leak_canary_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-android-core/src/main/res/mipmap-xhdpi/leak_canary_icon.png
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/mipmap-xxhdpi/leak_canary_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-android-core/src/main/res/mipmap-xxhdpi/leak_canary_icon.png
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/mipmap-xxxhdpi/leak_canary_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-android-core/src/main/res/mipmap-xxxhdpi/leak_canary_icon.png
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/values/leak_canary_attrs.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2018 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <resources>
18 | <declare-styleable name="leak_canary_MoreDetailsView">
19 | <attr name="leak_canary_plus_color" format="color" />
20 | </declare-styleable>
21 | </resources>
22 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/values/leak_canary_bools.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <bool name="leak_canary_add_dynamic_shortcut">true</bool>
4 | <bool name="leak_canary_add_launcher_icon">true</bool>
5 | <bool name="leak_canary_allow_in_non_debuggable_build">false</bool>
6 | </resources>
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/main/res/xml/leak_canary_file_paths.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <paths>
3 | <external-path name="downloads" path="Download/" />
4 | <files-path name="leakcanary" path="leakcanary/" />
5 | <cache-path name="leakcanary_cache" path="leakcanary/" />
6 |
7 | </paths>
8 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-core/src/test/java/leakcanary/LeakCanaryConfigTest.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import org.assertj.core.api.Assertions.assertThat
4 | import org.junit.Test
5 | import kotlin.reflect.full.memberFunctions
6 | import kotlin.reflect.full.memberProperties
7 |
8 | class LeakCanaryConfigTest {
9 |
10 | /**
11 | * Validates that each field in [LeakCanary.Config] has a matching builder function
12 | * in [LeakCanary.Config.Builder]
13 | */
14 | @Test fun `LeakCanary Config Builder matches LeakCanary Config`() {
15 | assertThat(configProperties())
16 | .containsExactlyInAnyOrderElementsOf(configBuilderFunctions())
17 | }
18 |
19 | private fun configBuilderFunctions() = LeakCanary.Config.Builder::class.memberFunctions
20 | .map { it.name }
21 | .subtract(setOf("build", "equals", "hashCode", "toString"))
22 |
23 | private fun configProperties() = LeakCanary.Config::class.memberProperties
24 | .map { it.name }
25 | }
26 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>MaximumLineLength:NoLeakAssertionFailedError.kt$NoLeakAssertionFailedError.Companion$ </ID>
6 | <ID>MultiLineIfElse:InstrumentationHeapAnalyzer.kt$InstrumentationHeapAnalyzer$result</ID>
7 | <ID>NoConsecutiveBlankLines:DetectLeaksAfterTestSuccess.kt$ </ID>
8 | <ID>NoConsecutiveBlankLines:LeakAssertions.kt$ </ID>
9 | <ID>NoConsecutiveBlankLines:SkipLeakDetection.kt$ </ID>
10 | <ID>StringTemplate:AndroidDetectLeaksAssert.kt$AndroidDetectLeaksAssert${classSimpleName}</ID>
11 | <ID>StringTemplate:AndroidDetectLeaksAssert.kt$AndroidDetectLeaksAssert${methodName}</ID>
12 | </CurrentIssues>
13 | </SmellBaseline>
14 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-android-instrumentation
2 | POM_NAME=LeakCanary extension for Android instrumentation tests.
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2018 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <manifest xmlns:android="http://schemas.android.com/apk/res/android">
18 |
19 | <application
20 | android:name="androidx.multidex.MultiDexApplication"
21 | >
22 | <activity android:name="leakcanary.TestActivity"/>
23 | </application>
24 | </manifest>
25 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/androidTest/assets/large-dump.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-android-instrumentation/src/androidTest/assets/large-dump.hprof
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/AndroidDetectLeaksAssertTest.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import leakcanary.TestUtils.assertLeak
4 | import org.junit.After
5 | import org.junit.Before
6 | import org.junit.Test
7 | import java.util.Date
8 |
9 | /**
10 | * Tests that the [AndroidDetectLeaksAssert] can detect leaks
11 | * in instrumentation tests
12 | */
13 | class AndroidDetectLeaksAssertTest {
14 |
15 | @Before fun setUp() {
16 | AppWatcher.objectWatcher
17 | .clearAllObjectsTracked()
18 | }
19 |
20 | @After fun tearDown() {
21 | AppWatcher.objectWatcher
22 | .clearAllObjectsTracked()
23 | }
24 |
25 | @Test fun detectsLeak() {
26 | leaking = Date()
27 | val objectWatcher = AppWatcher.objectWatcher
28 | objectWatcher.expectWeaklyReachable(leaking, "This date should not live beyond the test")
29 | assertLeak(Date::class.java)
30 | }
31 |
32 | companion object {
33 | private lateinit var leaking: Any
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/TestActivity.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.FragmentActivity
5 | import com.squareup.leakcanary.instrumentation.test.R
6 |
7 | class TestActivity : FragmentActivity() {
8 | override fun onCreate(savedInstanceState: Bundle?) {
9 | super.onCreate(savedInstanceState)
10 | setContentView(R.layout.activity_test)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/androidTest/res/layout/activity_test.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 | android:id="@+id/fragments"
4 | android:layout_width="match_parent"
5 | android:layout_height="match_parent"
6 | />
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2018 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <manifest>
18 | </manifest>
19 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/DetectLeaksAssert.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | /**
4 | * The interface for the implementation that [LeakAssertions.assertNoLeaks] delegates to.
5 | * You can call [DetectLeaksAssert.update] to provide your own implementation.
6 | *
7 | * The default implementation is [AndroidDetectLeaksAssert].
8 | */
9 | fun interface DetectLeaksAssert {
10 |
11 | fun assertNoLeaks(tag: String)
12 |
13 | companion object {
14 | @Volatile
15 | internal var delegate: DetectLeaksAssert = AndroidDetectLeaksAssert()
16 |
17 | fun update(delegate: DetectLeaksAssert) {
18 | DetectLeaksAssert.delegate = delegate
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/DetectLeaksInterceptor.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | /**
4 | * Decides whether to dump & analyze the heap to look for leaks in instrumentation tests.
5 | * The implementation might block for a while to allow temporary leaks to be flushed out, as those
6 | * aren't that interesting to report and heap analysis increases test duration significantly.
7 | */
8 | fun interface DetectLeaksInterceptor {
9 | fun waitUntilReadyForHeapAnalysis(): HeapAnalysisDecision
10 | }
11 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/HeapAnalysisDecision.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | sealed class HeapAnalysisDecision {
4 | object AnalyzeHeap : HeapAnalysisDecision()
5 | class NoHeapAnalysis(val reason: String) : HeapAnalysisDecision()
6 | }
7 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/HeapAnalysisReporter.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import shark.HeapAnalysis
4 |
5 | /**
6 | * Reports the results of a heap analysis created by [AndroidDetectLeaksAssert].
7 | */
8 | fun interface HeapAnalysisReporter {
9 | fun reportHeapAnalysis(heapAnalysis: HeapAnalysis)
10 | }
11 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/LeakAssertions.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | object LeakAssertions {
4 |
5 | /**
6 | * Asserts that there are no leak in the heap at this point in time.
7 | *
8 | * This method should be called on the instrumentation thread.
9 | *
10 | * This method is may block the current thread for a significant amount of time,
11 | * as it might need to dump the heap and analyze it.
12 | *
13 | * If leaks are found, this method is expected to throw an exception, which will fail the test.
14 | *
15 | * The specific details depend on what you configured in [DetectLeaksAssert.update].
16 | *
17 | * [tag] identifies the calling code, which can then be used for reporting purposes or to skip
18 | * leak detection for specific tags in a subset of tests (see [SkipLeakDetection]).
19 | */
20 | fun assertNoLeaks(tag: String = NO_TAG) {
21 | DetectLeaksAssert.delegate.assertNoLeaks(tag)
22 | }
23 |
24 | const val NO_TAG = ""
25 | }
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-instrumentation/src/main/java/leakcanary/internal/friendly/Friendly.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
2 | @file:JvmName("leakcanary-android-instrumentation_Friendly")
3 | package leakcanary.internal.friendly
4 |
5 | internal inline fun <reified T : Any> noOpDelegate(): T = leakcanary.internal.noOpDelegate()
6 |
7 | internal inline fun checkNotMainThread() = leakcanary.internal.checkNotMainThread()
8 |
9 | internal inline fun measureDurationMillis(block: () -> Unit) =
10 | leakcanary.internal.measureDurationMillis(block)
11 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-process/api/leakcanary-android-process.api:
--------------------------------------------------------------------------------
1 | public final class leakcanary/LeakCanaryProcess {
2 | public static final field INSTANCE Lleakcanary/LeakCanaryProcess;
3 | public final fun isInAnalyzerProcess (Landroid/content/Context;)Z
4 | }
5 |
6 | public final class leakcanary/internal/RemoteLeakCanaryWorkerService : androidx/work/multiprocess/RemoteWorkerService {
7 | public fun <init> ()V
8 | public fun getApplicationContext ()Landroid/content/Context;
9 | public fun onCreate ()V
10 | }
11 |
12 | public final class leakcanary/internal/RemoteLeakCanaryWorkerService$FakeAppContextConfigurationProvider : android/content/ContextWrapper, androidx/work/Configuration$Provider {
13 | public fun <init> (Landroid/content/Context;)V
14 | public synthetic fun getApplicationContext ()Landroid/content/Context;
15 | public fun getApplicationContext ()Lleakcanary/internal/RemoteLeakCanaryWorkerService$FakeAppContextConfigurationProvider;
16 | public fun getWorkManagerConfiguration ()Landroidx/work/Configuration;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-process/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.shark.sharkLog)
9 | api(projects.objectWatcher.objectWatcherAndroidCore)
10 |
11 | implementation(libs.kotlin.stdlib)
12 | implementation(libs.androidX.work.multiprocess)
13 | }
14 |
15 | android {
16 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
17 | defaultConfig {
18 | minSdk = libs.versions.androidMinSdk.get().toInt()
19 | }
20 | buildFeatures.buildConfig = false
21 | namespace = "com.squareup.leakcanary"
22 | lint {
23 | checkOnly += "Interoperability"
24 | disable += "GoogleAppIndexingWarning"
25 | ignore += "InvalidPackage"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-process/consumer-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # A ContentProvider that gets created by Android on :leakcanary process startup
2 | -keep class leakcanary.internal.LeakCanaryProcessAppWatcherInstaller { <init>(); }
3 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-process/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-android-process
2 | POM_NAME=LeakCanary Android running in a separate process
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-process/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2018 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <manifest xmlns:android="http://schemas.android.com/apk/res/android">
18 |
19 | <application>
20 | <service
21 | android:name="leakcanary.internal.RemoteLeakCanaryWorkerService"
22 | android:exported="false"
23 | android:process=":leakcanary" />
24 | </application>
25 |
26 | </manifest>
27 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-release/consumer-proguard-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-android-release/consumer-proguard-rules.pro
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-release/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-android-release
2 | POM_NAME=LeakCanary for released Android apps
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-release/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest />
3 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-release/src/main/java/leakcanary/ConditionalInterceptor.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import leakcanary.HeapAnalysisInterceptor.Chain
4 | import leakcanary.HeapAnalysisJob.Result
5 |
6 | /**
7 | * An interceptor that runs only when [evaluateCondition] returns true.
8 | */
9 | class ConditionalInterceptor(
10 | private val delegate: HeapAnalysisInterceptor,
11 | private val evaluateCondition: (HeapAnalysisJob) -> Boolean
12 | ) : HeapAnalysisInterceptor {
13 | override fun intercept(chain: Chain): Result {
14 | if (evaluateCondition(chain.job)) {
15 | return delegate.intercept(object : Chain {
16 | override val job = chain.job
17 |
18 | override fun proceed(): Result {
19 | return chain.proceed()
20 | }
21 | })
22 | } else {
23 | return chain.proceed()
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-release/src/main/java/leakcanary/GoodAndroidVersionInterceptor.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.os.Build
4 | import leakcanary.HeapAnalysisInterceptor.Chain
5 |
6 | class GoodAndroidVersionInterceptor : HeapAnalysisInterceptor {
7 | private val errorMessage: String? by lazy {
8 | val sdkInt = Build.VERSION.SDK_INT
9 | if (// findObjectById() sometimes failing. See #1759
10 | sdkInt != 23 &&
11 | // findObjectById() sometimes failing. See #1759
12 | sdkInt != 25 &&
13 | // Android 11 seem to sometimes have super slow heap dumps.
14 | // See https://issuetracker.google.com/issues/168634429
15 | sdkInt < 30
16 | ) {
17 | null
18 | } else {
19 | "Build.VERSION.SDK_INT $sdkInt not supported"
20 | }
21 | }
22 |
23 | override fun intercept(chain: Chain): HeapAnalysisJob.Result {
24 | errorMessage?.let {
25 | chain.job.cancel(it)
26 | }
27 | return chain.proceed()
28 | }
29 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-release/src/main/java/leakcanary/HeapAnalysisInterceptor.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | fun interface HeapAnalysisInterceptor {
4 |
5 | fun intercept(chain: Chain): HeapAnalysisJob.Result
6 |
7 | interface Chain {
8 | val job: HeapAnalysisJob
9 |
10 | fun proceed(): HeapAnalysisJob.Result
11 | }
12 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-release/src/main/java/leakcanary/MinimumDiskSpaceInterceptor.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.app.Application
4 | import leakcanary.HeapAnalysisInterceptor.Chain
5 | import leakcanary.HeapAnalysisJob.Result
6 |
7 | class MinimumDiskSpaceInterceptor(
8 | private val application: Application,
9 | private val minimumDiskSpaceBytes: Long = 200_000_000,
10 | private val processInfo: ProcessInfo = ProcessInfo.Real
11 | ) : HeapAnalysisInterceptor {
12 |
13 | override fun intercept(chain: Chain): Result {
14 | val availableDiskSpace = processInfo.availableDiskSpaceBytes(application.filesDir!!)
15 | if (availableDiskSpace < minimumDiskSpaceBytes) {
16 | chain.job.cancel("availableDiskSpace $availableDiskSpace < minimumDiskSpaceBytes $minimumDiskSpaceBytes")
17 | }
18 | return chain.proceed()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-release/src/main/java/leakcanary/MinimumElapsedSinceStartInterceptor.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.annotation.SuppressLint
4 | import leakcanary.HeapAnalysisInterceptor.Chain
5 | import leakcanary.HeapAnalysisJob.Result
6 | import java.util.concurrent.TimeUnit
7 |
8 | @SuppressLint("NewApi")
9 | class MinimumElapsedSinceStartInterceptor(
10 | private val minimumElapsedSinceStartMillis: Long = TimeUnit.SECONDS.toMillis(30),
11 | private val processInfo: ProcessInfo = ProcessInfo.Real
12 | ) : HeapAnalysisInterceptor {
13 |
14 | override fun intercept(chain: Chain): Result {
15 | if (processInfo.elapsedMillisSinceStart < minimumElapsedSinceStartMillis) {
16 | chain.job.cancel("app started less than $minimumElapsedSinceStartMillis ms ago.")
17 | }
18 | return chain.proceed()
19 | }
20 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-release/src/main/java/leakcanary/internal/friendly/Friendly.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "NOTHING_TO_INLINE")
2 | @file:JvmName("leakcanary-android-release_Friendly")
3 |
4 | package leakcanary.internal.friendly
5 |
6 | internal inline val mainHandler
7 | get() = leakcanary.internal.mainHandler
8 |
9 | internal inline fun checkMainThread() = leakcanary.internal.checkMainThread()
10 |
11 | internal inline fun <reified T : Any> noOpDelegate(): T = leakcanary.internal.noOpDelegate()
12 |
13 | internal inline fun measureDurationMillis(block: () -> Unit) =
14 | leakcanary.internal.measureDurationMillis(block)
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-startup/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.leakcanary.leakcanaryAndroidCore)
9 | // AppWatcher AndroidX Startup installer
10 | implementation(projects.objectWatcher.objectWatcherAndroidStartup)
11 | // Plumber AndroidX Startup installer
12 | implementation(projects.plumber.plumberAndroidStartup)
13 | }
14 |
15 | android {
16 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
17 | defaultConfig {
18 | minSdk = libs.versions.androidMinSdk.get().toInt()
19 | }
20 | buildFeatures.buildConfig = false
21 | namespace = "com.squareup.leakcanary.startup"
22 | lint {
23 | checkOnly += "Interoperability"
24 | disable += "GoogleAppIndexingWarning"
25 | ignore += "InvalidPackage"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-startup/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-android-startup
2 | POM_NAME=AndroidX Startup config for LeakCanary Android
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-startup/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest />
3 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-test/api/leakcanary-android-test.api:
--------------------------------------------------------------------------------
1 | public final class leakcanary/RepeatingAndroidInProcessScenarioKt {
2 | public static final fun repeatingAndroidInProcessScenario (Lshark/HeapDiff$Companion;Lshark/ObjectGrowthDetector;Lleakcanary/HeapDumpDirectoryProvider;Lleakcanary/HeapDumper;Lleakcanary/HeapDumpStorageStrategy;)Lshark/RepeatingScenarioObjectGrowthDetector;
3 | public static synthetic fun repeatingAndroidInProcessScenario$default (Lshark/HeapDiff$Companion;Lshark/ObjectGrowthDetector;Lleakcanary/HeapDumpDirectoryProvider;Lleakcanary/HeapDumper;Lleakcanary/HeapDumpStorageStrategy;ILjava/lang/Object;)Lshark/RepeatingScenarioObjectGrowthDetector;
4 | }
5 |
6 | public final class leakcanary/TargetContextHeapDumpDirectoryProvider : leakcanary/HeapDumpDirectoryProvider {
7 | public fun <init> (Ljava/lang/String;)V
8 | public fun heapDumpDirectory ()Ljava/io/File;
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-test/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-android-test
2 | POM_NAME=LeakCanary Android - Test
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-test/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3 |
4 | <application
5 | android:name="androidx.multidex.MultiDexApplication"
6 | >
7 | </application>
8 | </manifest>
9 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-test/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2018 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <manifest>
18 | </manifest>
19 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-test/src/main/java/leakcanary/TargetContextHeapDumpDirectoryProvider.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import java.io.File
5 |
6 | class TargetContextHeapDumpDirectoryProvider(
7 | private val heapDumpDirectoryName: String
8 | ) : HeapDumpDirectoryProvider {
9 | override fun heapDumpDirectory() = File(
10 | InstrumentationRegistry.getInstrumentation().targetContext.filesDir,
11 | heapDumpDirectoryName
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-uiautomator/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.leakcanary.leakcanaryCore)
9 | api(projects.leakcanary.leakcanaryTestCore)
10 | api(projects.shark.sharkAndroid)
11 | api(libs.androidX.test.uiautomator)
12 |
13 | implementation(libs.androidX.test.monitor)
14 | }
15 |
16 | android {
17 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
18 | defaultConfig {
19 | targetSdk = libs.versions.androidCompileSdk.get().toInt()
20 | minSdk = 18
21 | }
22 | buildFeatures.buildConfig = false
23 | namespace = "com.squareup.leakcanary.android.uiautomator"
24 | }
25 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-uiautomator/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-android-uiautomator
2 | POM_NAME=LeakCanary Android - Utilities for UI Automator
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-uiautomator/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3 |
4 | <!--
5 | Performing the heap growth analysis requires more heap. We avoid doing this for
6 | in process LeakCanary because apps might not expect it, but that seems more ok
7 | for a ui automation app.
8 | -->
9 | <application
10 | android:largeHeap="true"/>
11 |
12 | </manifest>
13 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-uiautomator/src/main/java/leakcanary/AndroidDeviceTempHeapDumpDirectoryProvider.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import java.io.File
4 |
5 | class AndroidDeviceTempHeapDumpDirectoryProvider(
6 | private val heapDumpDirectoryName: String
7 | ) : HeapDumpDirectoryProvider {
8 | override fun heapDumpDirectory() = File("/data/local/tmp/", heapDumpDirectoryName)
9 | }
10 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-uiautomator/src/main/java/leakcanary/UiAutomatorShellFileDeleter.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.uiautomator.UiDevice
5 | import java.io.File
6 |
7 | object UiAutomatorShellFileDeleter {
8 | fun deleteFileUsingShell(file: File) {
9 | val instrumentation = InstrumentationRegistry.getInstrumentation()
10 | val device = UiDevice.getInstance(instrumentation)
11 | device.executeShellCommand("rm ${file.absolutePath}")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/api/leakcanary-android-utils.api:
--------------------------------------------------------------------------------
1 | public final class leakcanary/AndroidDebugHeapDumper : leakcanary/HeapDumper {
2 | public static final field INSTANCE Lleakcanary/AndroidDebugHeapDumper;
3 | public fun dumpHeap (Ljava/io/File;)V
4 | }
5 |
6 | public final class leakcanary/AndroidDebugHeapDumperKt {
7 | public static final fun forAndroidInProcess (Lleakcanary/HeapDumper$Companion;)Lleakcanary/AndroidDebugHeapDumper;
8 | }
9 |
10 | public final class leakcanary/LogcatSharkLog : shark/SharkLog$Logger {
11 | public static final field Companion Lleakcanary/LogcatSharkLog$Companion;
12 | public fun <init> ()V
13 | public fun d (Ljava/lang/String;)V
14 | public fun d (Ljava/lang/Throwable;Ljava/lang/String;)V
15 | }
16 |
17 | public final class leakcanary/LogcatSharkLog$Companion {
18 | public final fun install ()V
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.leakcanary.leakcanaryCore)
9 | api(projects.shark.sharkLog)
10 |
11 | implementation(libs.kotlin.stdlib)
12 | }
13 |
14 | android {
15 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
16 | defaultConfig {
17 | minSdk = libs.versions.androidMinSdk.get().toInt()
18 | }
19 | buildFeatures.buildConfig = false
20 | namespace = "com.squareup.leakcanary.utils"
21 | lint {
22 | checkOnly += "Interoperability"
23 | disable += "GoogleAppIndexingWarning"
24 | error += "ObsoleteSdkInt"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>FinalNewline:LogcatSharkLog.kt$leakcanary.LogcatSharkLog.kt</ID>
6 | <ID>FinalNewline:Objects.kt$leakcanary.internal.Objects.kt</ID>
7 | </CurrentIssues>
8 | </SmellBaseline>
9 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-android-utils
2 | POM_NAME=LeakCanary Android Utils
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest />
3 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/src/main/java/leakcanary/AndroidDebugHeapDumper.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.os.Debug
4 | import java.io.File
5 |
6 | /**
7 | * Dumps the Android heap using [Debug.dumpHprofData].
8 | *
9 | * Note: despite being part of the Debug class, [Debug.dumpHprofData] can be called from non
10 | * debuggable non profileable builds.
11 | */
12 | object AndroidDebugHeapDumper : HeapDumper {
13 | override fun dumpHeap(heapDumpFile: File) {
14 | Debug.dumpHprofData(heapDumpFile.absolutePath)
15 | }
16 | }
17 |
18 | fun HeapDumper.Companion.forAndroidInProcess() = AndroidDebugHeapDumper
19 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/src/main/java/leakcanary/LogcatSharkLog.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.util.Log
4 | import shark.SharkLog
5 | import shark.SharkLog.Logger
6 |
7 | class LogcatSharkLog : Logger {
8 |
9 | override fun d(message: String) {
10 | if (message.length < 4000) {
11 | Log.d("LeakCanary", message)
12 | } else {
13 | message.lines().forEach { line ->
14 | Log.d("LeakCanary", line)
15 | }
16 | }
17 | }
18 |
19 | override fun d(
20 | throwable: Throwable,
21 | message: String
22 | ) {
23 | d("$message\n${Log.getStackTraceString(throwable)}")
24 | }
25 |
26 | companion object {
27 | fun install() {
28 | SharkLog.logger = LogcatSharkLog()
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/src/main/java/leakcanary/internal/Handlers.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 |
6 | internal val mainHandler by lazy { Handler(Looper.getMainLooper()) }
7 |
8 | internal val isMainThread: Boolean get() = Looper.getMainLooper().thread === Thread.currentThread()
9 |
10 | internal fun checkMainThread() {
11 | check(isMainThread) {
12 | "Should be called from the main thread, not ${Thread.currentThread()}"
13 | }
14 | }
15 |
16 | internal fun checkNotMainThread() {
17 | check(!isMainThread) {
18 | "Should not be called from the main thread"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/src/main/java/leakcanary/internal/Objects.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import java.lang.reflect.InvocationHandler
4 | import java.lang.reflect.Proxy
5 |
6 | internal inline fun <reified T : Any> noOpDelegate(): T {
7 | val javaClass = T::class.java
8 | return Proxy.newProxyInstance(
9 | javaClass.classLoader, arrayOf(javaClass), NO_OP_HANDLER
10 | ) as T
11 | }
12 |
13 | private val NO_OP_HANDLER = InvocationHandler { _, _, _ ->
14 | // no op
15 | }
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android-utils/src/main/java/leakcanary/internal/Timing.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import android.os.SystemClock
4 |
5 | /**
6 | * Executes the given [block] and returns elapsed time in milliseconds using [SystemClock.uptimeMillis]
7 | */
8 | internal inline fun measureDurationMillis(block: () -> Unit): Long {
9 | val start = SystemClock.uptimeMillis()
10 | block()
11 | return SystemClock.uptimeMillis() - start
12 | }
13 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-android
2 | POM_NAME=LeakCanary Android
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2018 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <manifest />
18 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app-aidl/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | implementation(libs.kotlin.stdlib)
9 | implementation(projects.shark.shark)
10 | }
11 |
12 | android {
13 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
14 | defaultConfig {
15 | minSdk = libs.versions.androidMinSdk.get().toInt()
16 | }
17 | buildFeatures {
18 | aidl = true
19 | }
20 | buildFeatures.buildConfig = false
21 | namespace = "com.squareup.leakcanary.app.aidl"
22 | lint {
23 | checkOnly += "Interoperability"
24 | disable += "GoogleAppIndexingWarning"
25 | ignore += "InvalidPackage"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app-aidl/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-app-aidl
2 | POM_NAME=Service definitions to communicate with the LeakCanary UI app.
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app-aidl/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest />
3 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app-aidl/src/main/aidl/org/leakcanary/internal/LeakUiApp.aidl:
--------------------------------------------------------------------------------
1 | package org.leakcanary.internal;
2 |
3 | import android.net.Uri;
4 |
5 | parcelable ParcelableHeapAnalysis;
6 |
7 | interface LeakUiApp {
8 | void sendHeapAnalysis(in ParcelableHeapAnalysis heapAnalysis, in Uri heapDumpUri);
9 | }
10 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app-aidl/src/main/java/org/leakcanary/internal/ParcelableHeapAnalysis.kt:
--------------------------------------------------------------------------------
1 | package org.leakcanary.internal
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 | import shark.HeapAnalysis
6 |
7 | class ParcelableHeapAnalysis(val wrapped: HeapAnalysis) : Parcelable {
8 |
9 | private constructor(source: Parcel) : this(source.readSerializable() as HeapAnalysis)
10 |
11 | override fun writeToParcel(dest: Parcel, flags: Int) {
12 | dest.writeSerializable(wrapped)
13 | }
14 |
15 | override fun describeContents() = 0
16 |
17 | companion object {
18 | @Suppress("UNCHECKED_CAST")
19 | @JvmField val CREATOR = object : Parcelable.Creator<ParcelableHeapAnalysis> {
20 | override fun createFromParcel(source: Parcel): ParcelableHeapAnalysis {
21 | return ParcelableHeapAnalysis(source)
22 | }
23 |
24 | override fun newArray(size: Int): Array<ParcelableHeapAnalysis?> {
25 | return arrayOfNulls(size)
26 | }
27 | }
28 |
29 | fun HeapAnalysis.asParcelable(): ParcelableHeapAnalysis = ParcelableHeapAnalysis(this)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app-service/api/leakcanary-app-service.api:
--------------------------------------------------------------------------------
1 | public final class org/leakcanary/internal/LeakUiAppClient {
2 | public fun <init> (Landroid/content/Context;)V
3 | public final fun sendHeapAnalysis (Lshark/HeapAnalysis;)V
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app-service/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | implementation(libs.kotlin.stdlib)
9 | }
10 |
11 | android {
12 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
13 | defaultConfig {
14 | minSdk = libs.versions.androidMinSdk.get().toInt()
15 | }
16 | buildFeatures.buildConfig = false
17 | namespace = "com.squareup.leakcanary.app.service"
18 | lint {
19 | checkOnly += "Interoperability"
20 | disable += "GoogleAppIndexingWarning"
21 | ignore += "InvalidPackage"
22 | }
23 | }
24 |
25 | dependencies {
26 | implementation(projects.leakcanary.leakcanaryAppAidl)
27 | implementation(projects.leakcanary.leakcanaryAndroidCore)
28 | implementation(projects.shark.shark)
29 | implementation(projects.shark.sharkAndroid)
30 | }
31 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app-service/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-app-service
2 | POM_NAME=Service that communicates with the LeakCanary UI app.
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app-service/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3 |
4 | <queries>
5 | <package android:name="org.leakcanary" />
6 | </queries>
7 |
8 | </manifest>
9 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>FinalNewline:Color.kt$org.leakcanary.ui.theme.Color.kt</ID>
6 | <ID>FinalNewline:Theme.kt$org.leakcanary.ui.theme.Theme.kt</ID>
7 | <ID>FinalNewline:Type.kt$org.leakcanary.ui.theme.Type.kt</ID>
8 | <ID>MaximumLineLength:LeakScreen.kt$LeakViewModel$ </ID>
9 | <ID>MultiLineIfElse:LeakTraceWrapper.kt$LeakTraceWrapper$null</ID>
10 | <ID>NoBlankLineBeforeRbrace:ClientAppAnalysesScreen.kt$ </ID>
11 | <ID>NoConsecutiveBlankLines:TreeMapScreen.kt$ </ID>
12 | <ID>ParameterListWrapping:DatabaseModule.kt$DatabaseModule$( app: Application, @WriteAheadLoggingEnabled wolEnabled: Boolean )</ID>
13 | <ID>SpacingAroundKeyword:HeapRepository.kt$HeapRepository$if</ID>
14 | <ID>StringTemplate:LeakScreen.kt${leakingStatusReason}</ID>
15 | </CurrentIssues>
16 | </SmellBaseline>
17 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/java/org/leakcanary/LeakCanaryApp.kt:
--------------------------------------------------------------------------------
1 | package org.leakcanary
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 | import org.leakcanary.util.ActivityProviderCallbacks
6 |
7 | @HiltAndroidApp
8 | class LeakCanaryApp : Application() {
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 | registerActivityLifecycleCallbacks(ActivityProviderCallbacks())
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/java/org/leakcanary/screens/Destination.kt:
--------------------------------------------------------------------------------
1 | package org.leakcanary.screens
2 |
3 | import android.os.Parcelable
4 | import java.io.File
5 | import kotlinx.parcelize.Parcelize
6 |
7 | sealed class Destination(val title: String) : Parcelable {
8 |
9 | @Parcelize
10 | object ClientAppsDestination : Destination("Apps")
11 |
12 | // TODO Figure out dynamic titles, this should say "X Heap Analyses"
13 | // Should also show the app name, icon..
14 | // Can use content for now.
15 | @Parcelize
16 | class ClientAppAnalysesDestination(val packageName: String) : Destination("Heap Analyses")
17 |
18 | @Parcelize
19 | class ClientAppAnalysisDestination(val analysisId: Long) : Destination("Analysis")
20 |
21 | @Parcelize
22 | class TreeMapDestination(val heapDump: File) : Destination("TreeMap")
23 |
24 | @Parcelize
25 | object LeaksDestination : Destination("Leaks")
26 |
27 | @Parcelize
28 | class LeakDestination(
29 | val leakSignature: String,
30 | val selectedAnalysisId: Long? = null
31 | ) : Destination("Leak")
32 | }
33 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/java/org/leakcanary/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package org.leakcanary.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/java/org/leakcanary/util/Handlers.kt:
--------------------------------------------------------------------------------
1 | package org.leakcanary.util
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 |
6 | val mainHandler by lazy { Handler(Looper.getMainLooper()) }
7 |
8 | val isMainThread: Boolean get() = Looper.getMainLooper().thread === Thread.currentThread()
9 |
10 | fun checkMainThread() {
11 | check(isMainThread) {
12 | "Should be called from the main thread, not ${Thread.currentThread()}"
13 | }
14 | }
15 |
16 | fun checkNotMainThread() {
17 | check(!isMainThread) {
18 | "Should not be called from the main thread"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/java/org/leakcanary/util/Serializables.kt:
--------------------------------------------------------------------------------
1 | package org.leakcanary.util
2 |
3 | import java.io.ByteArrayInputStream
4 | import java.io.ByteArrayOutputStream
5 | import java.io.ObjectInputStream
6 | import java.io.ObjectOutputStream
7 | import java.io.Serializable
8 | import shark.SharkLog
9 |
10 | internal fun Serializable.toByteArray(): ByteArray {
11 | val outputStream = ByteArrayOutputStream()
12 | ObjectOutputStream(outputStream).writeObject(this)
13 | return outputStream.toByteArray()
14 | }
15 |
16 | internal object Serializables {
17 |
18 | inline fun <reified T> fromByteArray(byteArray: ByteArray): T? {
19 | val inputStream = ByteArrayInputStream(byteArray)
20 | return try {
21 | ObjectInputStream(inputStream).readObject() as? T
22 | } catch (ignored: Throwable) {
23 | SharkLog.d(ignored) { "Could not deserialize bytes, ignoring" }
24 | null
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/java/org/leakcanary/util/Sharer.kt:
--------------------------------------------------------------------------------
1 | package org.leakcanary.util
2 |
3 | import android.content.Intent
4 | import dagger.Binds
5 | import dagger.Module
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.android.components.ActivityRetainedComponent
8 | import javax.inject.Inject
9 |
10 | interface Sharer {
11 | fun share(content: String)
12 | }
13 |
14 | class ActivitySharer @Inject constructor(
15 | private val activityProvider: CurrentActivityProvider
16 | ) : Sharer {
17 | override fun share(content: String) {
18 | val intent = Intent(Intent.ACTION_SEND).apply {
19 | type = "text/plain"
20 | putExtra(Intent.EXTRA_TEXT, content)
21 | }
22 |
23 | activityProvider.withActivity {
24 | startActivity(
25 | Intent.createChooser(intent, "Share with…")
26 | )
27 | }
28 | }
29 | }
30 |
31 | @Module
32 | @InstallIn(ActivityRetainedComponent::class)
33 | interface SharerModule {
34 | @Binds fun bindSharer(sharer: ActivitySharer): Sharer
35 | }
36 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3 | <background android:drawable="@drawable/ic_launcher_background" />
4 | <foreground android:drawable="@drawable/ic_launcher_foreground" />
5 | </adaptive-icon>
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3 | <background android:drawable="@drawable/ic_launcher_background" />
4 | <foreground android:drawable="@drawable/ic_launcher_foreground" />
5 | </adaptive-icon>
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/leakcanary/leakcanary-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <color name="purple_200">#FFBB86FC</color>
4 | <color name="purple_500">#FF6200EE</color>
5 | <color name="purple_700">#FF3700B3</color>
6 | <color name="teal_200">#FF03DAC5</color>
7 | <color name="teal_700">#FF018786</color>
8 | <color name="black">#FF000000</color>
9 | <color name="white">#FFFFFFFF</color>
10 | </resources>
11 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | <resources>
2 | <string name="app_name">My Application</string>
3 | </resources>
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 |
4 | <style name="Theme.MyApplication" parent="android:Theme.Material.Light.NoActionBar" />
5 | </resources>
6 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?><!--
2 | Sample backup rules file; uncomment and customize as necessary.
3 | See https://developer.android.com/guide/topics/data/autobackup
4 | for details.
5 | Note: This file is ignored for devices older that API 31
6 | See https://developer.android.com/about/versions/12/backup-restore
7 | -->
8 | <full-backup-content>
9 | <!--
10 | <include domain="sharedpref" path="."/>
11 | <exclude domain="sharedpref" path="device.xml"/>
12 | -->
13 | </full-backup-content>
14 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?><!--
2 | Sample data extraction rules file; uncomment and customize as necessary.
3 | See https://developer.android.com/about/versions/12/backup-restore#xml-changes
4 | for details.
5 | -->
6 | <data-extraction-rules>
7 | <cloud-backup>
8 | <!-- TODO: Use <include> and <exclude> to control what is backed up.
9 | <include .../>
10 | <exclude .../>
11 | -->
12 | </cloud-backup>
13 | <!--
14 | <device-transfer>
15 | <include .../>
16 | <exclude .../>
17 | </device-transfer>
18 | -->
19 | </data-extraction-rules>
20 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-app/src/main/sqldelight/dev/leakcanary/sqldelight/App.sq:
--------------------------------------------------------------------------------
1 | CREATE TABLE app (
2 | package_name TEXT NOT NULL PRIMARY KEY,
3 | leak_count INTEGER NOT NULL DEFAULT 0
4 | );
5 |
6 | insertOrIgnore:
7 | INSERT OR IGNORE INTO app (
8 | package_name
9 | )
10 | VALUES (?);
11 |
12 | selectAll:
13 | SELECT
14 | package_name
15 | , leak_count
16 | FROM app;
17 |
18 | updateLeakCounts:
19 | UPDATE app
20 | SET leak_count = (
21 | SELECT
22 | COUNT(DISTINCT lt.leak_signature)
23 | FROM leak_trace lt
24 | JOIN heap_analysis h ON lt.heap_analysis_id = h.id
25 | WHERE h.app_package_name=app.package_name
26 | );
27 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id("com.vanniktech.maven.publish")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | api(projects.leakcanary.leakcanaryGc)
13 | api(projects.shark.shark)
14 | implementation(libs.okio2)
15 |
16 | testImplementation(libs.assertjCore)
17 | testImplementation(libs.junit)
18 | testImplementation(projects.shark.sharkHprofTest)
19 | }
20 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-core/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>MaximumLineLength:DumpingRepeatingScenarioObjectGrowthDetector.kt$DumpingRepeatingScenarioObjectGrowthDetector$ </ID>
6 | <ID>MaximumLineLength:ObjectGrowthWarmupHeapDumperTest.kt$ObjectGrowthWarmupHeapDumperTest$ </ID>
7 | <ID>NoConsecutiveBlankLines:DatetimeFormattedHeapDumpFileProvider.kt$ </ID>
8 | <ID>NoConsecutiveBlankLines:DumpingRepeatingScenarioObjectGrowthDetector.kt$ </ID>
9 | <ID>NoConsecutiveBlankLines:DumpingRepeatingScenarioObjectGrowthDetectorTest.kt$DumpingRepeatingScenarioObjectGrowthDetectorTest$ </ID>
10 | <ID>NoConsecutiveBlankLines:ObjectGrowthWarmupHeapDumperTest.kt$ObjectGrowthWarmupHeapDumperTest$ </ID>
11 | <ID>NoUnusedImports:HeapDumpFileProvider.kt$leakcanary.HeapDumpFileProvider.kt</ID>
12 | </CurrentIssues>
13 | </SmellBaseline>
14 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-core/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-core
2 | POM_NAME=LeakCanary Core
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-core/src/main/java/leakcanary/HeapDumpDirectoryProvider.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import java.io.File
4 |
5 | fun interface HeapDumpDirectoryProvider {
6 | /**
7 | * Expected to be called only once per [HeapDumpFileProvider] implementation instance.
8 | */
9 | fun heapDumpDirectory(): File
10 | }
11 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-core/src/main/java/leakcanary/HeapDumpFileProvider.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import java.io.File
4 | import java.util.Date
5 |
6 | fun interface HeapDumpFileProvider {
7 |
8 | /**
9 | * Returns a [File] that can be passed to a [HeapDumper] to dump the heap.
10 | */
11 | fun newHeapDumpFile(): File
12 | }
13 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-core/src/main/java/leakcanary/HeapDumper.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import java.io.File
4 |
5 | fun interface HeapDumper {
6 |
7 | /**
8 | * Dumps the heap. The implementation is expected to be blocking until the heap is dumped
9 | * or heap dumping failed.
10 | *
11 | * Implementations can throw a runtime exception if heap dumping failed.
12 | */
13 | fun dumpHeap(heapDumpFile: File)
14 |
15 | /**
16 | * This allows external modules to add factory methods for implementations of this interface as
17 | * extension functions of this companion object.
18 | */
19 | companion object
20 | }
21 |
22 | fun HeapDumper.withGc(gcTrigger: GcTrigger = GcTrigger.inProcess()): HeapDumper {
23 | val delegate = this
24 | return HeapDumper { file ->
25 | gcTrigger.runGc()
26 | delegate.dumpHeap(file)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-deobfuscation-gradle-plugin/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>NoUnusedImports:LeakCanaryLeakDeobfuscationPlugin.kt$com.squareup.leakcanary.deobfuscation.LeakCanaryLeakDeobfuscationPlugin.kt</ID>
6 | </CurrentIssues>
7 | </SmellBaseline>
8 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-deobfuscation-gradle-plugin/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-deobfuscation-gradle-plugin
2 | POM_NAME=LeakCanary Deobfuscation Plugin
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-deobfuscation-gradle-plugin/src/test/test-project/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?><!--
2 | ~ Copyright (C) 2015 Square, Inc.
3 | ~
4 | ~ Licensed under the Apache License, Version 2.0 (the "License");
5 | ~ you may not use this file except in compliance with the License.
6 | ~ You may obtain a copy of the License at
7 | ~
8 | ~ http://www.apache.org/licenses/LICENSE-2.0
9 | ~
10 | ~ Unless required by applicable law or agreed to in writing, software
11 | ~ distributed under the License is distributed on an "AS IS" BASIS,
12 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | ~ See the License for the specific language governing permissions and
14 | ~ limitations under the License.
15 | -->
16 | <manifest package="com.leakcanary.test" />
17 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-deobfuscation-gradle-plugin/src/test/test-project/src/main/java/com/leakcanary/test/TestProjectClass.java:
--------------------------------------------------------------------------------
1 | package com.leakcanary.test;
2 |
3 | public class TestProjectClass {
4 | public void foo() {
5 |
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-gc/api/leakcanary-gc.api:
--------------------------------------------------------------------------------
1 | public final class leakcanary/FinalizingInProcessGcTrigger : leakcanary/GcTrigger {
2 | public static final field INSTANCE Lleakcanary/FinalizingInProcessGcTrigger;
3 | public fun runGc ()V
4 | }
5 |
6 | public final class leakcanary/FinalizingInProcessGcTriggerKt {
7 | public static final fun inProcess (Lleakcanary/GcTrigger$Companion;)Lleakcanary/FinalizingInProcessGcTrigger;
8 | }
9 |
10 | public abstract interface class leakcanary/GcTrigger {
11 | public static final field Companion Lleakcanary/GcTrigger$Companion;
12 | public abstract fun runGc ()V
13 | }
14 |
15 | public final class leakcanary/GcTrigger$Companion {
16 | public final fun getDefault ()Lleakcanary/FinalizingInProcessGcTrigger;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-gc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id("com.vanniktech.maven.publish")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | implementation(libs.kotlin.stdlib)
13 | }
14 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-gc/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-gc
2 | POM_NAME=LeakCanary - GC Utilities
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-gc/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2018 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <manifest>
18 | </manifest>
19 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-jvm-test/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id("com.vanniktech.maven.publish")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | api(projects.leakcanary.leakcanaryTestCore)
13 | api(projects.shark.shark)
14 |
15 | testImplementation(libs.assertjCore)
16 | testImplementation(libs.junit)
17 | testImplementation(projects.shark.sharkHprofTest)
18 | }
19 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-jvm-test/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>NoConsecutiveBlankLines:HotSpotHeapDumper.kt$ </ID>
6 | </CurrentIssues>
7 | </SmellBaseline>
8 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-jvm-test/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-jvm-test
2 | POM_NAME=LeakCanary Jvm Test
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-jvm-test/src/main/java/leakcanary/HotSpotHeapDumper.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import com.sun.management.HotSpotDiagnosticMXBean
4 | import java.io.File
5 | import java.lang.management.ManagementFactory
6 |
7 | object HotSpotHeapDumper : HeapDumper {
8 | private val hotspotMBean: HotSpotDiagnosticMXBean by lazy {
9 | val mBeanServer = ManagementFactory.getPlatformMBeanServer()
10 | ManagementFactory.newPlatformMXBeanProxy(
11 | mBeanServer,
12 | "com.sun.management:type=HotSpotDiagnostic",
13 | HotSpotDiagnosticMXBean::class.java
14 | )
15 | }
16 |
17 | override fun dumpHeap(heapDumpFile: File) {
18 | val live = true
19 | hotspotMBean.dumpHeap(heapDumpFile.absolutePath, live)
20 | }
21 | }
22 |
23 | fun HeapDumper.Companion.forJvmInProcess() = HotSpotHeapDumper
24 |
25 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-jvm-test/src/main/java/leakcanary/RepositoryRootHeapDumpDirectoryProvider.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import java.io.File
4 |
5 | class RepositoryRootHeapDumpDirectoryProvider(
6 | private val heapDumpDirectoryName: String
7 | ) : HeapDumpDirectoryProvider {
8 |
9 | override fun heapDumpDirectory() = File(projectRootDirectory(), heapDumpDirectoryName)
10 |
11 | private fun projectRootDirectory(): File {
12 | var currentDirectory = File("./")
13 | // Going through absolute path string otherwise parentFile returns null.
14 | currentDirectory = File(currentDirectory.absolutePath)
15 | while (".git" !in currentDirectory) {
16 | currentDirectory = currentDirectory.parentFile!!
17 | }
18 | return currentDirectory
19 | }
20 |
21 | private operator fun File.contains(filename: String): Boolean {
22 | return listFiles()?.any { it.name == filename } ?: false
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-test-core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id("com.vanniktech.maven.publish")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | api(projects.leakcanary.leakcanaryCore)
13 | api(projects.shark.shark)
14 | api(libs.junit)
15 |
16 | testImplementation(libs.assertjCore)
17 | }
18 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-test-core/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>StringTemplate:TestHeapDumpFileProvider.kt$TestHeapDumpFileProvider${classSimpleName}</ID>
6 | <ID>StringTemplate:TestHeapDumpFileProvider.kt$TestHeapDumpFileProvider${escapedMethodName}</ID>
7 | </CurrentIssues>
8 | </SmellBaseline>
9 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-test-core/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-test-core
2 | POM_NAME=LeakCanary shared test utils
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/leakcanary/leakcanary-test-core/src/main/java/leakcanary/TestHeapDumpFileProvider.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | class TestHeapDumpFileProvider(
4 | heapDumpDirectoryProvider: HeapDumpDirectoryProvider
5 | ) : HeapDumpFileProvider {
6 |
7 | private val delegate = DatetimeFormattedHeapDumpFileProvider(
8 | heapDumpDirectoryProvider = heapDumpDirectoryProvider,
9 | suffixProvider = {
10 | TestNameProvider.currentTestName()?.run {
11 | // JVM test method names can have spaces.
12 | val escapedMethodName = methodName.replace(' ', '-')
13 | "_${classSimpleName}-${escapedMethodName}"
14 | } ?: ""
15 | }
16 | )
17 |
18 | override fun newHeapDumpFile() = delegate.newHeapDumpFile()
19 | }
20 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-androidx/api/object-watcher-android-androidx.api:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/object-watcher/object-watcher-android-androidx/api/object-watcher-android-androidx.api
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-androidx/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.objectWatcher.objectWatcherAndroidCore)
9 |
10 | implementation(libs.kotlin.stdlib)
11 | // Optional dependency
12 | compileOnly(libs.androidX.fragment)
13 | }
14 |
15 | android {
16 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
17 | defaultConfig {
18 | minSdk = libs.versions.androidMinSdk.get().toInt()
19 | consumerProguardFiles("consumer-proguard-rules.pro")
20 | }
21 | buildFeatures.buildConfig = false
22 | namespace = "com.squareup.leakcanary.fragments.androidx"
23 | lint {
24 | checkOnly += "Interoperability"
25 | disable += "GoogleAppIndexingWarning"
26 | error += "ObsoleteSdkInt"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-androidx/consumer-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # AndroidXFragmentDestroyWatcher is loaded via reflection
2 | -keep class leakcanary.internal.AndroidXFragmentDestroyWatcher { *; }
3 | # ViewModelClearedWatcher reaches into ViewModelStore using reflection.
4 | -keep class androidx.lifecycle.ViewModelStore { *; }
5 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-androidx/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-object-watcher-android-androidx
2 | POM_NAME=LeakCanary Object Watcher for Android extension: Android X fragments support
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-androidx/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest>
3 | </manifest>
4 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.objectWatcher.objectWatcher)
9 | api(projects.leakcanary.leakcanaryAndroidUtils)
10 |
11 | implementation(libs.curtains)
12 | implementation(libs.kotlin.stdlib)
13 |
14 | testImplementation(libs.assertjCore)
15 | testImplementation(libs.junit)
16 | testImplementation(libs.kotlin.reflect)
17 | }
18 |
19 | android {
20 | resourcePrefix = "leak_canary_watcher_"
21 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
22 |
23 | defaultConfig {
24 | minSdk = libs.versions.androidMinSdk.get().toInt()
25 | consumerProguardFiles("consumer-proguard-rules.pro")
26 | }
27 |
28 | buildFeatures {
29 | buildConfig = false
30 | }
31 | namespace = "com.squareup.leakcanary.objectwatcher.core"
32 | lint {
33 | checkOnly += "Interoperability"
34 | disable += "GoogleAppIndexingWarning"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/consumer-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # KeyedWeakReference is looked up in the hprof file
2 | -keep class leakcanary.KeyedWeakReference { *; }
3 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>FinalNewline:Applications.kt$leakcanary.internal.Applications.kt</ID>
6 | <ID>FinalNewline:InstallableWatcher.kt$leakcanary.InstallableWatcher.kt</ID>
7 | <ID>FinalNewline:LeakCanaryDelegate.kt$leakcanary.internal.LeakCanaryDelegate.kt</ID>
8 | </CurrentIssues>
9 | </SmellBaseline>
10 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-object-watcher-android-core
2 | POM_NAME=LeakCanary Object Watcher for Android - Core
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest>
3 | </manifest>
4 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/src/main/java/leakcanary/InstallableWatcher.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | interface InstallableWatcher {
4 |
5 | fun install()
6 |
7 | fun uninstall()
8 | }
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/src/main/java/leakcanary/internal/Applications.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import android.app.Application
4 | import android.content.pm.ApplicationInfo
5 |
6 | internal val Application.isDebuggableBuild: Boolean
7 | get() = (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/src/main/java/leakcanary/internal/LeakCanaryDelegate.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import android.app.Application
4 | import leakcanary.OnObjectRetainedListener
5 |
6 | internal object LeakCanaryDelegate {
7 |
8 | @Suppress("UNCHECKED_CAST")
9 | val loadLeakCanary by lazy {
10 | try {
11 | val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
12 | leakCanaryListener.getDeclaredField("INSTANCE")
13 | .get(null) as (Application) -> Unit
14 | } catch (ignored: Throwable) {
15 | NoLeakCanary
16 | }
17 | }
18 |
19 | object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
20 | override fun invoke(application: Application) {
21 | }
22 |
23 | override fun onObjectRetained() {
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/src/main/java/leakcanary/internal/friendly/Friendly.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "NOTHING_TO_INLINE")
2 | @file:JvmName("leakcanary-object-watcher-android_Friendly")
3 |
4 | package leakcanary.internal.friendly
5 |
6 | internal inline val mainHandler
7 | get() = leakcanary.internal.mainHandler
8 |
9 | internal inline fun checkMainThread() = leakcanary.internal.checkMainThread()
10 |
11 | internal inline fun <reified T : Any> noOpDelegate(): T = leakcanary.internal.noOpDelegate()
12 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/src/main/res/values/leak_canary_public.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <public name="leak_canary_watcher_watch_dismissed_dialogs" type="bool"/>
4 | </resources>
5 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/src/main/res/values/watcher_bools.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <!-- Whether to automatically install in the main process on startup via a custom ContentProvider.
4 | If false, you need to call AppWatcher.manualInstall().
5 | -->
6 | <bool name="leak_canary_watcher_watch_dismissed_dialogs">false</bool>
7 | </resources>
8 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-core/src/test/java/leakcanary/AppWatcherTest.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import org.assertj.core.api.Assertions.assertThat
4 | import org.junit.Test
5 |
6 | class AppWatcherTest {
7 |
8 | @Test fun appWatcherLoads_notInstalled() {
9 | assertThat(AppWatcher.isInstalled)
10 | .describedAs("Ensure AppWatcher doesn't crash in JUnit tests")
11 | .isFalse()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-startup/api/object-watcher-android-startup.api:
--------------------------------------------------------------------------------
1 | public final class leakcanary/AppWatcherStartupInitializer : androidx/startup/Initializer {
2 | public fun <init> ()V
3 | public synthetic fun create (Landroid/content/Context;)Ljava/lang/Object;
4 | public fun create (Landroid/content/Context;)Lleakcanary/AppWatcherStartupInitializer;
5 | public fun dependencies ()Ljava/util/List;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-startup/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.objectWatcher.objectWatcherAndroidCore)
9 |
10 | implementation(libs.androidX.startup)
11 | }
12 |
13 | android {
14 | resourcePrefix = "leak_canary_watcher_"
15 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
16 | defaultConfig {
17 | minSdk = libs.versions.androidMinSdk.get().toInt()
18 | }
19 | buildFeatures.buildConfig = false
20 | namespace = "com.squareup.leakcanary.objectwatcher.startup"
21 | lint {
22 | checkOnly += "Interoperability"
23 | disable += "GoogleAppIndexingWarning"
24 | error += "ObsoleteSdkInt"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-startup/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-object-watcher-android-startup
2 | POM_NAME=AndroidX Startup config for leakcanary-object-watcher-android-core
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-startup/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 | xmlns:tools="http://schemas.android.com/tools">
4 | <application>
5 | <provider
6 | android:name="androidx.startup.InitializationProvider"
7 | android:authorities="${applicationId}.androidx-startup"
8 | android:exported="false"
9 | tools:node="merge">
10 |
11 | <meta-data
12 | android:name="leakcanary.AppWatcherStartupInitializer"
13 | android:value="androidx.startup"/>
14 | </provider>
15 |
16 | </application>
17 | </manifest>
18 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android-startup/src/main/java/leakcanary/AppWatcherStartupInitializer.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.startup.Initializer
6 |
7 | class AppWatcherStartupInitializer : Initializer<AppWatcherStartupInitializer> {
8 | override fun create(context: Context) = apply {
9 | val application = context.applicationContext as Application
10 | AppWatcher.manualInstall(application)
11 | }
12 | override fun dependencies() = emptyList<Class<out Initializer<*>>>()
13 | }
14 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android/api/object-watcher-android.api:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/object-watcher/object-watcher-android/api/object-watcher-android.api
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.objectWatcher.objectWatcherAndroidCore)
9 | }
10 |
11 | android {
12 | resourcePrefix = "leak_canary_watcher_"
13 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
14 |
15 | defaultConfig {
16 | minSdk = libs.versions.androidMinSdk.get().toInt()
17 | consumerProguardFiles("consumer-proguard-rules.pro")
18 | }
19 |
20 | buildFeatures {
21 | buildConfig = false
22 | }
23 | namespace = "com.squareup.leakcanary.objectwatcher"
24 | lint {
25 | checkOnly += "Interoperability"
26 | disable += "GoogleAppIndexingWarning"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android/consumer-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # A ContentProvider that gets created by Android on startup
2 | -keep class leakcanary.internal.MainProcessAppWatcherInstaller { <init>(); }
3 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>ParameterListWrapping:MainProcessAppWatcherInstaller.kt$MainProcessAppWatcherInstaller$( uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>? )</ID>
6 | </CurrentIssues>
7 | </SmellBaseline>
8 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-object-watcher-android
2 | POM_NAME=LeakCanary Object Watcher for Android - Auto installing
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest
3 | xmlns:android="http://schemas.android.com/apk/res/android">
4 |
5 | <application>
6 | <provider
7 | android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
8 | android:authorities="${applicationId}.leakcanary-installer"
9 | android:enabled="@bool/leak_canary_watcher_auto_install"
10 | android:exported="false"/>
11 | </application>
12 | </manifest>
13 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android/src/main/res/values/leak_canary_public.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <public name="leak_canary_watcher_auto_install" type="bool"/>
4 | </resources>
5 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher-android/src/main/res/values/watcher_bools.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <!-- Whether to automatically install in the main process on startup via a custom ContentProvider.
4 | If false, you need to call AppWatcher.manualInstall().
5 | -->
6 | <bool name="leak_canary_watcher_auto_install">true</bool>
7 | </resources>
8 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id("com.vanniktech.maven.publish")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | implementation(libs.kotlin.stdlib)
13 | api(projects.shark.sharkLog)
14 | api(projects.leakcanary.leakcanaryGc)
15 |
16 | testImplementation(libs.assertjCore)
17 | testImplementation(libs.junit)
18 | }
19 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>MultiLineIfElse:DeletableObjectReporter.kt$object : TrackedObjectReachability { override val isStronglyReachable: Boolean get() = false override val isRetained: Boolean get() = false }</ID>
6 | </CurrentIssues>
7 | </SmellBaseline>
8 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=leakcanary-object-watcher
2 | POM_NAME=LeakCanary Object Watcher
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher/src/main/java/leakcanary/Clock.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | /**
4 | * An interface to abstract the SystemClock.uptimeMillis() Android API in non Android artifacts.
5 | *
6 | * This is a functional interface with which you can create a [Clock] from a lambda.
7 | */
8 | @Deprecated("Use UptimeClock instead")
9 | fun interface Clock {
10 | /**
11 | * On Android VMs, this should return android.os.SystemClock.uptimeMillis().
12 | */
13 | fun uptimeMillis(): Long
14 | }
15 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher/src/main/java/leakcanary/DefaultDelayDeletableObjectReporter.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import kotlin.time.Duration
4 |
5 | class DefaultDelayDeletableObjectReporter(
6 | /**
7 | * A significant enough delay for the GC to get a chance to run and update reachability status.
8 | */
9 | private val defaultDelay: Duration,
10 | private val delayedReporter: DelayedDeletableObjectReporter
11 | ) : DeletableObjectReporter {
12 |
13 | override fun expectDeletionFor(
14 | target: Any,
15 | reason: String
16 | ): TrackedObjectReachability {
17 | return delayedReporter.expectDelayedDeletionFor(target, reason, defaultDelay)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher/src/main/java/leakcanary/DelayedExecutor.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import kotlin.time.Duration
4 |
5 | fun interface DelayedExecutor {
6 | fun executeWithDelay(delayUptime: Duration, runnable: Runnable)
7 | }
8 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher/src/main/java/leakcanary/OnObjectRetainedListener.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | /**
4 | * Listener used by [ReferenceQueueRetainedObjectTracker] to report retained objects.
5 | */
6 | fun interface OnObjectRetainedListener {
7 |
8 | /**
9 | * A tracked object became retained.
10 | */
11 | fun onObjectRetained()
12 | }
13 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher/src/main/java/leakcanary/TrackedObjectReachability.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | interface TrackedObjectReachability {
4 | /**
5 | * true if the tracked object is currently strongly reachable.
6 | */
7 | val isStronglyReachable: Boolean
8 |
9 | /**
10 | * Whether this object is eligible for automatic garbage collection.
11 | */
12 | val isDeletable: Boolean
13 | get() = !isStronglyReachable
14 |
15 | /**
16 | * true if the track object has been marked as retained and is currently strongly reachable.
17 | */
18 | val isRetained: Boolean
19 | }
20 |
--------------------------------------------------------------------------------
/object-watcher/object-watcher/src/main/java/leakcanary/UptimeClock.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import kotlin.time.Duration
4 |
5 | /**
6 | * An interface to abstract the clock to get the system uptime.
7 | */
8 | fun interface UptimeClock {
9 | /**
10 | * On JVMs this should return [System.nanoTime] as a [Duration].
11 | *
12 | * On Android VMs, this should return either [System.nanoTime] on Android 11 (when the method
13 | * was annotated with @CriticalNative) or [android.os.SystemClock.uptimeMillis()] before
14 | * Android 11.
15 | */
16 | fun uptime(): Duration
17 | }
18 |
--------------------------------------------------------------------------------
/plumber/plumber-android-core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.shark.sharkLog)
9 | api(projects.leakcanary.leakcanaryAndroidUtils)
10 |
11 | implementation(libs.kotlin.stdlib)
12 | implementation(libs.curtains)
13 | // Optional dependency
14 | compileOnly(libs.androidX.fragment)
15 | }
16 |
17 | android {
18 | resourcePrefix = "leak_canary_plumber"
19 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
20 | defaultConfig {
21 | minSdk = libs.versions.androidMinSdk.get().toInt()
22 | consumerProguardFiles("consumer-proguard-rules.pro")
23 | }
24 | buildFeatures.buildConfig = false
25 | namespace = "com.squareup.leakcanary.plumber.core"
26 | lint {
27 | checkOnly += "Interoperability"
28 | disable += "GoogleAppIndexingWarning"
29 | error += "ObsoleteSdkInt"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/plumber/plumber-android-core/consumer-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Enum values are referenced reflectively in EnumSet initialization
2 | -keepclassmembers,allowoptimization enum leakcanary.AndroidLeakFixes {
3 | public static **[] values();
4 | }
5 |
--------------------------------------------------------------------------------
/plumber/plumber-android-core/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>SafeCast:AndroidLeakFixes.kt$AndroidLeakFixes.Companion$if (it is HandlerThread) it else null</ID>
6 | </CurrentIssues>
7 | </SmellBaseline>
8 |
--------------------------------------------------------------------------------
/plumber/plumber-android-core/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=plumber-android-core
2 | POM_NAME=Plumber for Android - Core
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/plumber/plumber-android-core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest />
3 |
--------------------------------------------------------------------------------
/plumber/plumber-android-core/src/main/java/leakcanary/internal/FragmentExtensions.kt:
--------------------------------------------------------------------------------
1 | package leakcanary.internal
2 |
3 | import android.app.Activity
4 | import androidx.fragment.app.Fragment
5 | import androidx.fragment.app.FragmentActivity
6 | import androidx.fragment.app.FragmentManager
7 |
8 | private val hasAndroidXFragmentActivity: Boolean by lazy {
9 | try {
10 | Class.forName("androidx.fragment.app.FragmentActivity")
11 | true
12 | } catch (ignored: Throwable) {
13 | false
14 | }
15 | }
16 |
17 | internal fun Activity.onAndroidXFragmentViewDestroyed(block: () -> Unit) {
18 | if (!hasAndroidXFragmentActivity) {
19 | return
20 | }
21 | if (this is FragmentActivity) {
22 | supportFragmentManager.registerFragmentLifecycleCallbacks(
23 | object : FragmentManager.FragmentLifecycleCallbacks() {
24 | override fun onFragmentViewDestroyed(
25 | fm: FragmentManager,
26 | fragment: Fragment
27 | ) {
28 | block()
29 | }
30 | }, true
31 | )
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/plumber/plumber-android-core/src/main/java/leakcanary/internal/friendly/Friendly.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "NOTHING_TO_INLINE")
2 | @file:JvmName("plumber-android_Friendly")
3 |
4 | package leakcanary.internal.friendly
5 |
6 | import android.os.Handler
7 |
8 | internal inline fun checkMainThread() = leakcanary.internal.checkMainThread()
9 |
10 | internal inline fun <reified T : Any> noOpDelegate(): T = leakcanary.internal.noOpDelegate()
11 |
12 | internal inline val mainHandler: Handler
13 | get() = leakcanary.internal.mainHandler
14 |
15 | internal inline val isMainThread: Boolean
16 | get() = leakcanary.internal.isMainThread
17 |
--------------------------------------------------------------------------------
/plumber/plumber-android-startup/api/plumber-android-startup.api:
--------------------------------------------------------------------------------
1 | public final class leakcanary/PlumberStartupInitializer : androidx/startup/Initializer {
2 | public fun <init> ()V
3 | public synthetic fun create (Landroid/content/Context;)Ljava/lang/Object;
4 | public fun create (Landroid/content/Context;)Lleakcanary/PlumberStartupInitializer;
5 | public fun dependencies ()Ljava/util/List;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/plumber/plumber-android-startup/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.plumber.plumberAndroidCore)
9 |
10 | implementation(libs.kotlin.stdlib)
11 | implementation(libs.androidX.startup)
12 | }
13 |
14 | android {
15 | resourcePrefix = "leak_canary_plumber"
16 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
17 | defaultConfig {
18 | minSdk = libs.versions.androidMinSdk.get().toInt()
19 | }
20 | buildFeatures.buildConfig = false
21 | namespace = "com.squareup.leakcanary.plumber.startup"
22 | lint {
23 | checkOnly += "Interoperability"
24 | disable += "GoogleAppIndexingWarning"
25 | error += "ObsoleteSdkInt"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/plumber/plumber-android-startup/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=plumber-android-startup
2 | POM_NAME=AndroidX Startup config for plumber-android-core
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/plumber/plumber-android-startup/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 | xmlns:tools="http://schemas.android.com/tools">
4 | <application>
5 | <provider
6 | android:name="androidx.startup.InitializationProvider"
7 | android:authorities="${applicationId}.androidx-startup"
8 | android:exported="false"
9 | tools:node="merge">
10 |
11 | <meta-data
12 | android:name="leakcanary.PlumberStartupInitializer"
13 | android:value="androidx.startup"/>
14 | </provider>
15 |
16 | </application>
17 | </manifest>
18 |
--------------------------------------------------------------------------------
/plumber/plumber-android-startup/src/main/java/leakcanary/PlumberStartupInitializer.kt:
--------------------------------------------------------------------------------
1 | package leakcanary
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.startup.Initializer
6 |
7 | class PlumberStartupInitializer : Initializer<PlumberStartupInitializer> {
8 | override fun create(context: Context) = apply {
9 | val application = context.applicationContext as Application
10 | AndroidLeakFixes.applyFixes(application)
11 | }
12 | override fun dependencies() = emptyList<Class<out Initializer<*>>>()
13 | }
14 |
--------------------------------------------------------------------------------
/plumber/plumber-android/api/plumber-android.api:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/plumber/plumber-android/api/plumber-android.api
--------------------------------------------------------------------------------
/plumber/plumber-android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | id("com.vanniktech.maven.publish")
5 | }
6 |
7 | dependencies {
8 | api(projects.plumber.plumberAndroidCore)
9 |
10 | implementation(libs.kotlin.stdlib)
11 | }
12 |
13 | android {
14 | resourcePrefix = "leak_canary_plumber"
15 | compileSdk = libs.versions.androidCompileSdk.get().toInt()
16 | defaultConfig {
17 | minSdk = libs.versions.androidMinSdk.get().toInt()
18 | consumerProguardFiles("consumer-proguard-rules.pro")
19 | }
20 | buildFeatures.buildConfig = false
21 | namespace = "com.squareup.leakcanary.plumber"
22 | lint {
23 | checkOnly += "Interoperability"
24 | disable += "GoogleAppIndexingWarning"
25 | error += "ObsoleteSdkInt"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/plumber/plumber-android/consumer-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # A ContentProvider that gets created by Android on startup
2 | -keep class leakcanary.internal.PlumberInstaller { <init>(); }
3 |
--------------------------------------------------------------------------------
/plumber/plumber-android/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>ParameterListWrapping:PlumberInstaller.kt$PlumberInstaller$( uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>? )</ID>
6 | </CurrentIssues>
7 | </SmellBaseline>
8 |
--------------------------------------------------------------------------------
/plumber/plumber-android/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=plumber-android
2 | POM_NAME=Auto installer for plumber-android-core
3 | POM_PACKAGING=aar
4 |
--------------------------------------------------------------------------------
/plumber/plumber-android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3 | <application>
4 | <provider
5 | android:name="leakcanary.internal.PlumberInstaller"
6 | android:authorities="${applicationId}.plumber-installer"
7 | android:enabled="@bool/leak_canary_plumber_auto_install"
8 | android:exported="false" />
9 | </application>
10 | </manifest>
11 |
--------------------------------------------------------------------------------
/plumber/plumber-android/src/main/res/values/plumber_bools.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <resources>
3 | <bool name="leak_canary_plumber_auto_install">true</bool>
4 | </resources>
5 |
--------------------------------------------------------------------------------
/plumber/plumber-android/src/main/res/values/plumber_public.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2015 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <resources>
18 |
19 | <public name="leak_canary_plumber_auto_install" type="bool"/>
20 | </resources>
21 |
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>FinalNewline:LeakingService.kt$com.example.leakcanary.LeakingService.kt</ID>
6 | <ID>FinalNewline:LeakingSingleton.kt$com.example.leakcanary.LeakingSingleton.kt</ID>
7 | <ID>FinalNewline:LeakingThread.kt$com.example.leakcanary.LeakingThread.kt</ID>
8 | </CurrentIssues>
9 | </SmellBaseline>
10 |
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <!--
3 | ~ Copyright (C) 2018 Square, Inc.
4 | ~
5 | ~ Licensed under the Apache License, Version 2.0 (the "License");
6 | ~ you may not use this file except in compliance with the License.
7 | ~ You may obtain a copy of the License at
8 | ~
9 | ~ http://www.apache.org/licenses/LICENSE-2.0
10 | ~
11 | ~ Unless required by applicable law or agreed to in writing, software
12 | ~ distributed under the License is distributed on an "AS IS" BASIS,
13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ~ See the License for the specific language governing permissions and
15 | ~ limitations under the License.
16 | -->
17 | <manifest>
18 | </manifest>
19 |
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest
3 | xmlns:android="http://schemas.android.com/apk/res/android"
4 | xmlns:tools="http://schemas.android.com/tools">
5 | <application
6 | tools:replace="android:name"
7 | android:name=".DebugExampleApplication" />
8 | </manifest>
9 |
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/debug/java/com/example/leakcanary/DebugExampleApplication.kt:
--------------------------------------------------------------------------------
1 | package com.example.leakcanary
2 |
3 | import leakcanary.EventListener
4 | import leakcanary.EventListener.Event.HeapAnalysisDone
5 | import leakcanary.LeakCanary
6 | import org.leakcanary.internal.LeakUiAppClient
7 |
8 | class DebugExampleApplication : ExampleApplication() {
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 |
13 | // TODO We need to decide whether to show the activity icon based on whether
14 | // the app library is here (?). Though ideally the embedded activity is also a separate
15 | // optional module.
16 | LeakCanary.config = LeakCanary.config.run {
17 | copy(eventListeners = eventListeners + EventListener {
18 | // TODO Move this into an EventListener class, maybe the standard one
19 | // TODO Detect if app installed or not and delegate to std leakcanary if not.
20 | if (it is HeapAnalysisDone<*>) {
21 | LeakUiAppClient(this@DebugExampleApplication).sendHeapAnalysis(it.heapAnalysis)
22 | }
23 | })
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/main/java/com/example/leakcanary/LeakingService.kt:
--------------------------------------------------------------------------------
1 | package com.example.leakcanary
2 |
3 | import android.app.Service
4 | import android.content.Intent
5 | import android.os.IBinder
6 |
7 | class LeakingService : Service() {
8 |
9 | override fun onCreate() {
10 | super.onCreate()
11 | (application as ExampleApplication).leakedServices += this
12 | stopSelf()
13 | }
14 |
15 | override fun onBind(intent: Intent?): IBinder? {
16 | return null
17 | }
18 | }
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/main/java/com/example/leakcanary/LeakingSingleton.kt:
--------------------------------------------------------------------------------
1 | package com.example.leakcanary
2 |
3 | import android.view.View
4 |
5 | object LeakingSingleton {
6 | val leakedViews = mutableListOf<View>()
7 | }
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/main/java/com/example/leakcanary/LeakingThread.kt:
--------------------------------------------------------------------------------
1 | package com.example.leakcanary
2 |
3 | import android.view.View
4 |
5 | class LeakingThread : Thread() {
6 |
7 | val leakedViews = mutableListOf<View>()
8 |
9 | init {
10 | name = "Leaking thread"
11 | start()
12 | }
13 |
14 | override fun run() {
15 | synchronized(obj) {
16 | obj.wait()
17 | }
18 | }
19 |
20 | companion object {
21 | private val obj = Object()
22 | val thread = LeakingThread()
23 | }
24 | }
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/main/res/drawable/leak_canary_sample_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/samples/leakcanary-android-sample/src/main/res/drawable/leak_canary_sample_icon.png
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/samples/leakcanary-android-sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/samples/leakcanary-android-sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/samples/leakcanary-android-sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/samples/leakcanary-android-sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/samples/leakcanary-android-sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/leakcanary-android-sample/src/release/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="utf-8"?>
2 | <manifest
3 | xmlns:android="http://schemas.android.com/apk/res/android"
4 | xmlns:tools="http://schemas.android.com/tools">
5 | <application
6 | tools:replace="android:name"
7 | android:name=".ReleaseExampleApplication" />
8 | </manifest>
9 |
--------------------------------------------------------------------------------
/shark-cli.sh:
--------------------------------------------------------------------------------
1 | ./gradlew --quiet --no-configuration-cache :shark:shark-cli:installDist
2 | ./shark/shark-cli/build/install/shark-cli/bin/shark-cli "$@"
3 |
--------------------------------------------------------------------------------
/shark/shark-android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id("com.vanniktech.maven.publish")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | api(projects.shark.shark)
13 |
14 | implementation(libs.kotlin.stdlib)
15 |
16 | testImplementation(libs.assertjCore)
17 | testImplementation(libs.junit)
18 | testImplementation(libs.kotlinStatistics)
19 | testImplementation(libs.mockito)
20 | testImplementation(libs.mockitoKotlin)
21 | testImplementation(libs.okio2)
22 | testImplementation(projects.shark.sharkTest)
23 | testImplementation(projects.shark.sharkHprofTest)
24 | }
25 |
--------------------------------------------------------------------------------
/shark/shark-android/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=shark-android
2 | POM_NAME=Shark for Android heaps
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/shark/shark-android/src/main/java/shark/AndroidObjectGrowthDetector.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | fun ObjectGrowthDetector.Companion.forAndroidHeap(
4 | referenceMatchers: List<ReferenceMatcher> = AndroidObjectGrowthReferenceMatchers.defaults
5 | ): ObjectGrowthDetector {
6 | return ObjectGrowthDetector(
7 | gcRootProvider = MatchingGcRootProvider(referenceMatchers),
8 | referenceReaderFactory = AndroidReferenceReaderFactory(referenceMatchers)
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/shark/shark-android/src/main/java/shark/AndroidServices.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | object AndroidServices {
4 | val HeapGraph.aliveAndroidServiceObjectIds: List<Long>
5 | get() {
6 | return context.getOrPut(AndroidServices::class.java.name) {
7 | val activityThreadClass = findClassByName("android.app.ActivityThread")!!
8 | val currentActivityThread = activityThreadClass
9 | .readStaticField("sCurrentActivityThread")!!
10 | .valueAsInstance!!
11 |
12 | val mServices = currentActivityThread["android.app.ActivityThread", "mServices"]!!
13 | .valueAsInstance!!
14 |
15 | val servicesArray = mServices["android.util.ArrayMap", "mArray"]!!.valueAsObjectArray!!
16 |
17 | servicesArray.readElements()
18 | .filterIndexed { index, heapValue ->
19 | // ArrayMap<IBinder, Service>
20 | // even: key, odd: value
21 | index % 2 == 1
22 | && heapValue.isNonNullReference
23 | }
24 | .map { it.asNonNullObjectId!! }
25 | .toList()
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/shark/shark-android/src/main/java/shark/internal/friendly/Friendly.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
2 | @file:JvmName("shark-android_Friendly")
3 |
4 | package shark.internal.friendly
5 |
6 | import shark.AndroidNativeSizeMapper
7 | import shark.HeapGraph
8 |
9 | internal inline fun HeapGraph.mapNativeSizes() =
10 | AndroidNativeSizeMapper.mapNativeSizes(this)
11 |
--------------------------------------------------------------------------------
/shark/shark-android/src/main/resources/META-INF/proguard/shark.pro:
--------------------------------------------------------------------------------
1 | # Used during heap analysis to find resource id names
2 | -keep class shark.AndroidResourceIdNames { *; }
3 |
--------------------------------------------------------------------------------
/shark/shark-android/src/test/java/shark/Resources.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import java.io.File
4 |
5 | fun String.classpathFile(): File {
6 | val classLoader = Thread.currentThread()
7 | .contextClassLoader
8 | val url = classLoader.getResource(this)!!
9 | return File(url.path)
10 | }
--------------------------------------------------------------------------------
/shark/shark-android/src/test/resources/compose_leak.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/shark/shark-android/src/test/resources/compose_leak.hprof
--------------------------------------------------------------------------------
/shark/shark-android/src/test/resources/gc_root_in_non_primary_heap.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/shark/shark-android/src/test/resources/gc_root_in_non_primary_heap.hprof
--------------------------------------------------------------------------------
/shark/shark-android/src/test/resources/gcroot_unknown_object.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/shark/shark-android/src/test/resources/gcroot_unknown_object.hprof
--------------------------------------------------------------------------------
/shark/shark-android/src/test/resources/leak_asynctask_m.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/shark/shark-android/src/test/resources/leak_asynctask_m.hprof
--------------------------------------------------------------------------------
/shark/shark-android/src/test/resources/leak_asynctask_o.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/shark/shark-android/src/test/resources/leak_asynctask_o.hprof
--------------------------------------------------------------------------------
/shark/shark-android/src/test/resources/leak_asynctask_pre_m.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/shark/shark-android/src/test/resources/leak_asynctask_pre_m.hprof
--------------------------------------------------------------------------------
/shark/shark-android/src/test/resources/unloaded_classes-stripped.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/shark/shark-android/src/test/resources/unloaded_classes-stripped.hprof
--------------------------------------------------------------------------------
/shark/shark-cli/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>FinalNewline:DeobfuscateHprofCommand.kt$shark.DeobfuscateHprofCommand.kt</ID>
6 | <ID>FinalNewline:StripHprofCommand.kt$shark.StripHprofCommand.kt</ID>
7 | <ID>MaximumLineLength:DumpProcessCommand.kt$DumpProcessCommand.Companion$ </ID>
8 | <ID>MaximumLineLength:HeapGrowthCommand.kt$HeapGrowthCommand$ </ID>
9 | <ID>MaximumLineLength:StripHprofCommand.kt$StripHprofCommand$ </ID>
10 | <ID>MultiLineIfElse:InteractiveCommand.kt$InteractiveCommandquot;"</ID>
11 | <ID>NoConsecutiveBlankLines:Neo4JCommand.kt$ </ID>
12 | <ID>NoConsecutiveBlankLines:Neo4JCommand.kt$Neo4JCommand.Companion$ </ID>
13 | <ID>SpacingAroundParens:HeapGrowthCommand.kt$HeapGrowthCommand$(</ID>
14 | <ID>StringTemplate:InteractiveCommand.kt$InteractiveCommand${instanceCount}</ID>
15 | </CurrentIssues>
16 | </SmellBaseline>
17 |
--------------------------------------------------------------------------------
/shark/shark-cli/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=shark-cli
2 | POM_NAME=Shark Command Line Interface
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/shark/shark-cli/src/main/java/shark/DeobfuscateHprofCommand.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import com.github.ajalt.clikt.core.CliktCommand
4 | import com.github.ajalt.clikt.core.PrintMessage
5 | import shark.SharkCliCommand.Companion.retrieveHeapDumpFile
6 | import shark.SharkCliCommand.Companion.sharkCliParams
7 |
8 | class DeobfuscateHprofCommand : CliktCommand(
9 | name = "deobfuscate-hprof",
10 | help = "Deobfuscate the provided heap dump and generate a new \"-deobfuscated.hprof\" file."
11 | ) {
12 |
13 | override fun run() {
14 | val params = context.sharkCliParams
15 | val obfuscationMappingFile = params.obfuscationMappingPath
16 | ?: throw PrintMessage("Error: Missing obfuscation mapping file")
17 | val heapDumpFile = retrieveHeapDumpFile(params)
18 | SharkLog.d { "Deobfuscating heap dump $heapDumpFile" }
19 | val proguardMapping =
20 | ProguardMappingReader(obfuscationMappingFile.inputStream()).readProguardMapping()
21 | val deobfuscator = HprofDeobfuscator()
22 | val outputFile = deobfuscator.deobfuscate(proguardMapping, heapDumpFile)
23 | echo("Created deobfuscated hprof to $outputFile")
24 | }
25 | }
--------------------------------------------------------------------------------
/shark/shark-cli/src/main/java/shark/Main.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import com.github.ajalt.clikt.core.subcommands
4 |
5 | fun main(args: Array<String>) =
6 | SharkCliCommand().subcommands(
7 | InteractiveCommand(),
8 | AnalyzeCommand(),
9 | Neo4JCommand(),
10 | DumpProcessCommand(),
11 | StripHprofCommand(),
12 | DeobfuscateHprofCommand(),
13 | HeapGrowthCommand()
14 | ).main(args)
15 |
--------------------------------------------------------------------------------
/shark/shark-cli/src/main/java/shark/StripHprofCommand.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import com.github.ajalt.clikt.core.CliktCommand
4 | import shark.SharkCliCommand.Companion.retrieveHeapDumpFile
5 | import shark.SharkCliCommand.Companion.sharkCliParams
6 |
7 | class StripHprofCommand : CliktCommand(
8 | name = "strip-hprof",
9 | help = "Replace all primitive arrays from the provided heap dump with arrays of zeroes and generate a new \"-stripped.hprof\" file."
10 | ) {
11 |
12 | override fun run() {
13 | val heapDumpFile = retrieveHeapDumpFile(context.sharkCliParams)
14 | SharkLog.d { "Stripping primitive arrays in heap dump $heapDumpFile" }
15 | val stripper = HprofPrimitiveArrayStripper()
16 | val outputFile = stripper.stripPrimitiveArrays(heapDumpFile)
17 | echo("Created hprof with stripped primitive arrays to $outputFile")
18 | }
19 | }
--------------------------------------------------------------------------------
/shark/shark-graph/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id("com.vanniktech.maven.publish")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | api(projects.shark.sharkHprof)
13 | api(libs.androidX.collections)
14 |
15 | implementation(libs.kotlin.stdlib)
16 | implementation(libs.okio2)
17 |
18 | testImplementation(libs.assertjCore)
19 | testImplementation(libs.junit)
20 | testImplementation(projects.shark.sharkTest)
21 | testImplementation(projects.shark.sharkHprofTest)
22 | }
23 |
--------------------------------------------------------------------------------
/shark/shark-graph/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=shark-graph
2 | POM_NAME=Shark Graph
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/shark/shark-graph/src/main/java/shark/CloseableHeapGraph.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import java.io.Closeable
4 |
5 | /**
6 | * A [HeapGraph] that should be closed after being used.
7 | */
8 | interface CloseableHeapGraph : HeapGraph, Closeable
--------------------------------------------------------------------------------
/shark/shark-graph/src/main/java/shark/internal/aosp/ByteArrayComparator.kt:
--------------------------------------------------------------------------------
1 | package shark.internal.aosp
2 |
3 | internal fun interface ByteArrayComparator {
4 |
5 | /**
6 | * Indexes are divided by entrySize
7 | */
8 | fun compare(
9 | entrySize: Int,
10 | o1Array: ByteArray,
11 | o1Index: Int,
12 | o2Array: ByteArray,
13 | o2Index: Int
14 | ): Int
15 | }
16 |
--------------------------------------------------------------------------------
/shark/shark-graph/src/main/java/shark/internal/hppc/Tuples.kt:
--------------------------------------------------------------------------------
1 | package shark.internal.hppc
2 |
3 | /** Alternative to Pair<Long, Object> that doesn't box long.*/
4 | internal data class LongObjectPair<out B>(
5 | val first: Long,
6 | val second: B
7 | )
8 |
9 | /** Alternative to Pair<Int, Object> that doesn't box int.*/
10 | internal data class IntObjectPair<out B>(
11 | val first: Int,
12 | val second: B
13 | )
14 |
15 | /** Alternative to Pair<Long, Long> that doesn't box longs. */
16 | internal data class LongLongPair(
17 | val first: Long,
18 | val second: Long
19 | )
20 |
21 | internal infix fun <B> Long.to(that: B): LongObjectPair<B> = LongObjectPair(this, that)
22 |
23 | internal infix fun <B> Int.to(that: B): IntObjectPair<B> = IntObjectPair(this, that)
24 |
25 | internal infix fun Long.to(that: Long): LongLongPair = LongLongPair(this, that)
26 |
--------------------------------------------------------------------------------
/shark/shark-hprof-test/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | }
4 |
5 | java {
6 | sourceCompatibility = JavaVersion.VERSION_1_8
7 | targetCompatibility = JavaVersion.VERSION_1_8
8 | }
9 |
10 | dependencies {
11 | implementation(libs.kotlin.stdlib)
12 | implementation(libs.junit)
13 | implementation(libs.okio2)
14 |
15 | implementation(projects.shark.sharkHprof)
16 | }
17 |
--------------------------------------------------------------------------------
/shark/shark-hprof-test/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>FinalNewline:ProguardMappingHelper.kt$shark.ProguardMappingHelper.kt</ID>
6 | </CurrentIssues>
7 | </SmellBaseline>
8 |
--------------------------------------------------------------------------------
/shark/shark-hprof-test/src/main/kotlin/shark/ProguardMappingHelper.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | class ProguardMappingHelper(
4 | private val proguardMapping: ProguardMapping
5 | ) {
6 | fun clazz(
7 | className: Pair<String, String>,
8 | fieldsBlock: Class.() -> Unit = {}
9 | ) {
10 | val clazz = Class(className)
11 | fieldsBlock(clazz)
12 | proguardMapping.addMapping(clazz.nameMapping.second, clazz.nameMapping.first)
13 | clazz.fieldMappings.forEach { field ->
14 | proguardMapping.addMapping("${clazz.nameMapping.second}.${field.second}", field.first)
15 | }
16 | }
17 |
18 | inner class Class(val nameMapping: Pair<String, String>) {
19 | val fieldMappings = mutableSetOf<Pair<String, String>>()
20 | }
21 |
22 | fun Class.field(block: () -> Pair<String, String>) {
23 | fieldMappings.add(block())
24 | }
25 | }
26 |
27 | fun ProguardMapping.create(block: ProguardMappingHelper.() -> Unit): ProguardMapping {
28 | block(ProguardMappingHelper(this))
29 | return this
30 | }
--------------------------------------------------------------------------------
/shark/shark-hprof/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>FinalNewline:ConstantMemoryMetricsDualSourceProvider.kt$shark.ConstantMemoryMetricsDualSourceProvider.kt</ID>
6 | <ID>FinalNewline:GcRoot.kt$shark.GcRoot.kt</ID>
7 | <ID>FinalNewline:ProguardMappingReader.kt$shark.ProguardMappingReader.kt</ID>
8 | <ID>FinalNewline:ProguardMappingTest.kt$shark.ProguardMappingTest.kt</ID>
9 | <ID>FinalNewline:RandomAccessHprofReader.kt$shark.RandomAccessHprofReader.kt</ID>
10 | <ID>FinalNewline:ValueHolder.kt$shark.ValueHolder.kt</ID>
11 | <ID>NoConsecutiveBlankLines:ConstantMemoryMetricsDualSourceProvider.kt$ConstantMemoryMetricsDualSourceProvider$ </ID>
12 | <ID>NoConsecutiveBlankLines:FileSourceProvider.kt$ </ID>
13 | <ID>NoMultipleSpaces:HprofReaderPrimitiveArrayTest.kt$HprofReaderPrimitiveArrayTest$ </ID>
14 | <ID>NoUnusedImports:ThrowingCancelableFileSourceProvider.kt$shark.ThrowingCancelableFileSourceProvider.kt</ID>
15 | </CurrentIssues>
16 | </SmellBaseline>
17 |
--------------------------------------------------------------------------------
/shark/shark-hprof/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=shark-hprof
2 | POM_NAME=Shark Hprof
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/shark/shark-hprof/src/main/java/shark/ByteArraySourceProvider.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import java.io.IOException
4 | import okio.Buffer
5 | import okio.BufferedSource
6 |
7 | class ByteArraySourceProvider(private val byteArray: ByteArray) : DualSourceProvider {
8 | override fun openStreamingSource(): BufferedSource = Buffer().apply { write(byteArray) }
9 |
10 | override fun openRandomAccessSource(): RandomAccessSource {
11 | return object : RandomAccessSource {
12 |
13 | var closed = false
14 |
15 | override fun read(
16 | sink: Buffer,
17 | position: Long,
18 | byteCount: Long
19 | ): Long {
20 | if (closed) {
21 | throw IOException("Source closed")
22 | }
23 | val maxByteCount = byteCount.coerceAtMost(byteArray.size - position)
24 | sink.write(byteArray, position.toInt(), maxByteCount.toInt())
25 | return maxByteCount
26 | }
27 |
28 | override fun close() {
29 | closed = true
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/shark/shark-hprof/src/main/java/shark/DualSourceProvider.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Both a [StreamingSourceProvider] and a [RandomAccessSourceProvider]
5 | */
6 | interface DualSourceProvider : StreamingSourceProvider, RandomAccessSourceProvider
7 |
--------------------------------------------------------------------------------
/shark/shark-hprof/src/main/java/shark/HprofVersion.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Supported hprof versions
5 | */
6 | enum class HprofVersion(val versionString: String) {
7 | JDK1_2_BETA3("JAVA PROFILE 1.0"),
8 | JDK1_2_BETA4("JAVA PROFILE 1.0.1"),
9 | JDK_6("JAVA PROFILE 1.0.2"),
10 | ANDROID("JAVA PROFILE 1.0.3")
11 | }
12 |
--------------------------------------------------------------------------------
/shark/shark-hprof/src/main/java/shark/OnHprofRecordListener.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Listener passed in to [StreamingRecordReaderAdapter.readRecords], gets notified for each [HprofRecord]
5 | * found in the heap dump which types is in the set of the recordTypes parameter passed to
6 | * [StreamingRecordReaderAdapter.readRecords].
7 | */
8 | fun interface OnHprofRecordListener {
9 | fun onHprofRecord(
10 | /**
11 | * The position of the record in the underlying hprof file.
12 | */
13 | position: Long,
14 | record: HprofRecord
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/shark/shark-hprof/src/main/java/shark/OnHprofRecordTagListener.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Listener passed in to [StreamingHprofReader.readRecords], gets notified for each
5 | * [HprofRecordTag] found in the heap dump.
6 | *
7 | * Listener implementations are expected to read all bytes corresponding to a given tag from the
8 | * provided reader before returning.
9 | */
10 | fun interface OnHprofRecordTagListener {
11 | fun onHprofRecord(
12 | tag: HprofRecordTag,
13 | /**
14 | * Length of the record or -1 if there is the length is not known
15 | */
16 | length: Long,
17 | reader: HprofRecordReader
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/shark/shark-hprof/src/main/java/shark/PrimitiveType.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * A primitive type in the prof.
5 | */
6 | enum class PrimitiveType(
7 | /**
8 | * The hprof defined "basic type".
9 | */
10 | val hprofType: Int,
11 | /**
12 | * The size in bytes for each value of that type.
13 | */
14 | val byteSize: Int
15 | ) {
16 | BOOLEAN(4, 1),
17 | CHAR(5, 2),
18 | FLOAT(6, 4),
19 | DOUBLE(7, 8),
20 | BYTE(8, 1),
21 | SHORT(9, 2),
22 | INT(10, 4),
23 | LONG(11, 8);
24 |
25 | companion object {
26 | /**
27 | * The hprof defined "basic type" for references.
28 | */
29 | const val REFERENCE_HPROF_TYPE = 2
30 |
31 | val byteSizeByHprofType = values().associate { it.hprofType to it.byteSize }
32 |
33 | val primitiveTypeByHprofType = values().associateBy { it.hprofType }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/shark/shark-hprof/src/main/java/shark/RandomAccessSourceProvider.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Can open [RandomAccessSource] instances.
5 | */
6 | fun interface RandomAccessSourceProvider {
7 | fun openRandomAccessSource(): RandomAccessSource
8 | }
9 |
--------------------------------------------------------------------------------
/shark/shark-hprof/src/main/java/shark/StreamingSourceProvider.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import okio.BufferedSource
4 | import okio.Source
5 |
6 | /**
7 | * Can open [Source] instances.
8 | */
9 | fun interface StreamingSourceProvider {
10 | fun openStreamingSource(): BufferedSource
11 | }
12 |
--------------------------------------------------------------------------------
/shark/shark-hprof/src/main/java/shark/ValueHolder.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import shark.ValueHolder.ReferenceHolder
4 |
5 | /**
6 | * A value in the heap dump, which can be a [ReferenceHolder] or
7 | * a primitive type.
8 | */
9 | sealed class ValueHolder {
10 | data class ReferenceHolder(val value: Long) : ValueHolder() {
11 | val isNull
12 | get() = value == NULL_REFERENCE
13 | }
14 |
15 | data class BooleanHolder(val value: Boolean) : ValueHolder()
16 | data class CharHolder(val value: Char) : ValueHolder()
17 | data class FloatHolder(val value: Float) : ValueHolder()
18 | data class DoubleHolder(val value: Double) : ValueHolder()
19 | data class ByteHolder(val value: Byte) : ValueHolder()
20 | data class ShortHolder(val value: Short) : ValueHolder()
21 | data class IntHolder(val value: Int) : ValueHolder()
22 | data class LongHolder(val value: Long) : ValueHolder()
23 |
24 | companion object {
25 | const val NULL_REFERENCE = 0L
26 | }
27 | }
--------------------------------------------------------------------------------
/shark/shark-log/api/shark-log.api:
--------------------------------------------------------------------------------
1 | public final class shark/SharkLog {
2 | public static final field INSTANCE Lshark/SharkLog;
3 | public final fun d (Ljava/lang/Throwable;Lkotlin/jvm/functions/Function0;)V
4 | public final fun d (Lkotlin/jvm/functions/Function0;)V
5 | public final fun getLogger ()Lshark/SharkLog$Logger;
6 | public final fun setLogger (Lshark/SharkLog$Logger;)V
7 | }
8 |
9 | public abstract interface class shark/SharkLog$Logger {
10 | public abstract fun d (Ljava/lang/String;)V
11 | public abstract fun d (Ljava/lang/Throwable;Ljava/lang/String;)V
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/shark/shark-log/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id("com.vanniktech.maven.publish")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | implementation(libs.kotlin.stdlib)
13 |
14 | testImplementation(libs.assertjCore)
15 | testImplementation(libs.junit)
16 | }
17 |
--------------------------------------------------------------------------------
/shark/shark-log/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>FinalNewline:SharkLogTest.kt$shark.SharkLogTest.kt</ID>
6 | </CurrentIssues>
7 | </SmellBaseline>
8 |
--------------------------------------------------------------------------------
/shark/shark-log/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=shark-log
2 | POM_NAME=Shark Log
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/shark/shark-test/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | }
4 |
5 | java {
6 | sourceCompatibility = JavaVersion.VERSION_1_8
7 | targetCompatibility = JavaVersion.VERSION_1_8
8 | }
9 |
10 | dependencies {
11 | implementation(libs.kotlin.stdlib)
12 | implementation(libs.assertjCore)
13 | implementation(libs.junit)
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/shark/shark-test/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 | <?xml version='1.0' encoding='UTF-8'?>
2 | <SmellBaseline>
3 | <ManuallySuppressedIssues/>
4 | <CurrentIssues>
5 | <ID>FinalNewline:JvmTestHeapDumper.kt$shark.JvmTestHeapDumper.kt</ID>
6 | </CurrentIssues>
7 | </SmellBaseline>
8 |
--------------------------------------------------------------------------------
/shark/shark-test/src/main/kotlin/shark/HeapDumpRule.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import org.junit.rules.ExternalResource
4 | import org.junit.rules.TemporaryFolder
5 | import java.io.File
6 | import java.io.IOException
7 | import java.util.UUID
8 |
9 | class HeapDumpRule : ExternalResource() {
10 | private val temporaryFolder = TemporaryFolder()
11 |
12 | @Throws(Throwable::class)
13 | override fun before() {
14 | temporaryFolder.create()
15 | }
16 |
17 | override fun after() {
18 | temporaryFolder.delete()
19 | }
20 |
21 | @Throws(IOException::class)
22 | fun dumpHeap(): File {
23 | val hprof = File(temporaryFolder.root, "heapDump" + UUID.randomUUID() + ".hprof")
24 | JvmTestHeapDumper.dumpHeap(hprof.absolutePath)
25 | return hprof
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/shark/shark-test/src/main/kotlin/shark/JvmTestHeapDumper.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import com.sun.management.HotSpotDiagnosticMXBean
4 | import java.lang.management.ManagementFactory
5 |
6 | object JvmTestHeapDumper {
7 | private val hotspotMBean: HotSpotDiagnosticMXBean by lazy {
8 | val mBeanServer = ManagementFactory.getPlatformMBeanServer()
9 | ManagementFactory.newPlatformMXBeanProxy(
10 | mBeanServer,
11 | "com.sun.management:type=HotSpotDiagnostic",
12 | HotSpotDiagnosticMXBean::class.java
13 | )
14 | }
15 |
16 | fun dumpHeap(
17 | fileName: String
18 | ) {
19 | val live = true
20 | hotspotMBean.dumpHeap(fileName, live)
21 | }
22 | }
--------------------------------------------------------------------------------
/shark/shark/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm")
3 | id("com.vanniktech.maven.publish")
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 | }
10 |
11 | dependencies {
12 | api(projects.shark.sharkGraph)
13 |
14 | implementation(libs.coroutines.core)
15 | implementation(libs.kotlin.stdlib)
16 | implementation(libs.okio2)
17 |
18 | testImplementation(libs.assertjCore)
19 | testImplementation(libs.junit)
20 | testImplementation(projects.shark.sharkTest)
21 | testImplementation(projects.shark.sharkHprofTest)
22 | }
23 |
--------------------------------------------------------------------------------
/shark/shark/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=shark
2 | POM_NAME=Shark
3 | POM_PACKAGING=jar
4 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/ActualMatchingReferenceReaderFactory.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Creates [ReferenceReader] instances that will follow references from all [HeapObject]s,
5 | * applying matching rules provided by [referenceMatchers], and not creating any virtual reference.
6 | */
7 | class ActualMatchingReferenceReaderFactory(
8 | private val referenceMatchers: List<ReferenceMatcher>
9 | ) : ReferenceReader.Factory<HeapObject> {
10 | override fun createFor(heapGraph: HeapGraph): ReferenceReader<HeapObject> {
11 | return DelegatingObjectReferenceReader(
12 | classReferenceReader = ClassReferenceReader(heapGraph, referenceMatchers),
13 | instanceReferenceReader = ChainingInstanceReferenceReader(
14 | virtualRefReaders = listOf(JavaLocalReferenceReader(heapGraph, referenceMatchers)),
15 | flatteningInstanceReader = null,
16 | fieldRefReader = FieldInstanceReferenceReader(heapGraph, referenceMatchers)
17 | ), objectArrayReferenceReader = ObjectArrayReferenceReader()
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/AndroidObjectSizeCalculator.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import shark.DominatorTree.ObjectSizeCalculator
4 | import shark.internal.ShallowSizeCalculator
5 |
6 | class AndroidObjectSizeCalculator(graph: HeapGraph) : ObjectSizeCalculator {
7 |
8 | private val nativeSizes = AndroidNativeSizeMapper(graph).mapNativeSizes()
9 | private val shallowSizeCalculator = ShallowSizeCalculator(graph)
10 |
11 | override fun computeSize(objectId: Long): Int {
12 | val nativeSize = nativeSizes[objectId] ?: 0
13 | val shallowSize = shallowSizeCalculator.computeShallowSize(objectId)
14 | return nativeSize + shallowSize
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/AppSingletonInspector.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import shark.HeapObject.HeapInstance
4 |
5 | /**
6 | * Inspector that automatically marks instances of the provided class names as not leaking
7 | * because they're app wide singletons.
8 | */
9 | class AppSingletonInspector(private vararg val singletonClasses: String) : ObjectInspector {
10 | override fun inspect(
11 | reporter: ObjectReporter
12 | ) {
13 | if (reporter.heapObject is HeapInstance) {
14 | reporter.heapObject.instanceClass
15 | .classHierarchy
16 | .forEach { heapClass ->
17 | if (heapClass.name in singletonClasses) {
18 | reporter.notLeakingReasons += "${heapClass.name} is an app singleton"
19 | }
20 | }
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/DelegatingObjectReferenceReader.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import shark.HeapObject.HeapClass
4 | import shark.HeapObject.HeapInstance
5 | import shark.HeapObject.HeapObjectArray
6 | import shark.HeapObject.HeapPrimitiveArray
7 |
8 | internal class DelegatingObjectReferenceReader(
9 | private val classReferenceReader: ReferenceReader<HeapClass>,
10 | private val instanceReferenceReader: ReferenceReader<HeapInstance>,
11 | private val objectArrayReferenceReader: ReferenceReader<HeapObjectArray>,
12 | ) : ReferenceReader<HeapObject> {
13 | override fun read(source: HeapObject): Sequence<Reference> {
14 | return when(source) {
15 | is HeapClass -> classReferenceReader.read(source)
16 | is HeapInstance -> instanceReferenceReader.read(source)
17 | is HeapObjectArray -> objectArrayReferenceReader.read(source)
18 | is HeapPrimitiveArray -> emptySequence()
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/Dominators.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import java.io.Serializable
4 | import shark.ObjectDominators.DominatorNode
5 |
6 | class Dominators(val dominatorNodes: Map<Long, DominatorNode>) : Serializable
7 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/FilteringLeakingObjectFinder.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Finds the objects that are leaking by scanning all objects in the heap dump
5 | * and delegating the decision to a list of [FilteringLeakingObjectFinder.LeakingObjectFilter]
6 | */
7 | class FilteringLeakingObjectFinder(private val filters: List<LeakingObjectFilter>) :
8 | LeakingObjectFinder {
9 |
10 | /**
11 | * Filter to be passed to the [FilteringLeakingObjectFinder] constructor.
12 | */
13 | fun interface LeakingObjectFilter {
14 | /**
15 | * Returns whether the passed in [heapObject] is leaking. This should only return true
16 | * when we're 100% sure the passed in [heapObject] should not be in memory anymore.
17 | */
18 | fun isLeakingObject(heapObject: HeapObject): Boolean
19 | }
20 |
21 | override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> {
22 | return graph.objects
23 | .filter { heapObject ->
24 | filters.any { filter ->
25 | filter.isLeakingObject(heapObject)
26 | }
27 | }
28 | .map { it.objectId }
29 | .toSet()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/GcRootProvider.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | fun interface GcRootProvider {
4 | /**
5 | * Provides a sequence of GC Roots to traverse the graph from, ideally in a stable order.
6 | */
7 | fun provideGcRoots(graph: HeapGraph): Sequence<GcRootReference>
8 | }
9 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/GcRootReference.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | // TODO Revisit this API. It's more like a GC Root + some priority / tagging.
4 | class GcRootReference(
5 | val gcRoot: GcRoot,
6 | val isLowPriority: Boolean,
7 | val matchedLibraryLeak: LibraryLeakReferenceMatcher?,
8 | )
9 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/HeapAnalysisException.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import java.io.PrintWriter
4 | import java.io.StringWriter
5 |
6 | class HeapAnalysisException(cause: Throwable) : RuntimeException(cause) {
7 |
8 | override fun toString(): String {
9 | val stringWriter = StringWriter()
10 | cause!!.printStackTrace(PrintWriter(stringWriter))
11 | return stringWriter.toString()
12 | }
13 |
14 | companion object {
15 | private const val serialVersionUID: Long = -2522323377375290608
16 | }
17 | }
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/JvmObjectGrowthDetector.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | fun ObjectGrowthDetector.Companion.forJvmHeap(
4 | referenceMatchers: List<ReferenceMatcher> = JvmObjectGrowthReferenceMatchers.defaults
5 | ): ObjectGrowthDetector {
6 | return ObjectGrowthDetector(
7 | gcRootProvider = MatchingGcRootProvider(referenceMatchers),
8 | referenceReaderFactory = OpenJdkReferenceReaderFactory(referenceMatchers)
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/LeakNodeStatus.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * This class is kept to support backward compatible deserialization.
5 | */
6 | internal enum class LeakNodeStatus {
7 | NOT_LEAKING,
8 | LEAKING,
9 | UNKNOWN;
10 | }
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/LeakTracer.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | fun interface LeakTracer {
4 |
5 | // TODO What should we do about exceptions here? union error or exception?
6 | fun traceObjects(objectIds: Set<Long>): LeaksAndUnreachableObjects
7 |
8 | fun interface Factory {
9 | fun createFor(heapGraph: HeapGraph): LeakTracer
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/LeakingObjectFinder.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Finds the objects that are leaking, for which Shark will compute
5 | * leak traces.
6 | */
7 | fun interface LeakingObjectFinder {
8 |
9 | /**
10 | * For a given heap graph, returns a set of object ids for the objects that are leaking.
11 | */
12 | fun findLeakingObjectIds(graph: HeapGraph): Set<Long>
13 | }
14 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/LeaksAndUnreachableObjects.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | // TODO Name?
4 | data class LeaksAndUnreachableObjects(
5 | val applicationLeaks: List<ApplicationLeak>,
6 | val libraryLeaks: List<LibraryLeak>,
7 | val unreachableObjects: List<LeakTraceObject>
8 | )
9 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/MetadataExtractor.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata].
5 | */
6 | fun interface MetadataExtractor {
7 | fun extractMetadata(graph: HeapGraph): Map<String, String>
8 |
9 | companion object {
10 |
11 | /**
12 | * A no-op [MetadataExtractor]
13 | */
14 | val NO_OP = MetadataExtractor { emptyMap() }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/ObjectInspector.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * Provides LeakCanary with insights about objects (classes, instances and arrays) found in the
5 | * heap. [inspect] will be called for each object that LeakCanary wants to know more about.
6 | * The implementation can then use the provided [ObjectReporter] to provide insights for that
7 | * object.
8 | */
9 | fun interface ObjectInspector {
10 |
11 | /**
12 | * @see [ObjectInspector]
13 | */
14 | fun inspect(reporter: ObjectReporter)
15 | }
16 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/OpenJdkReferenceReaderFactory.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | class OpenJdkReferenceReaderFactory(
4 | private val referenceMatchers: List<ReferenceMatcher>
5 | ) : ReferenceReader.Factory<HeapObject> {
6 |
7 | private val virtualizingFactory = VirtualizingMatchingReferenceReaderFactory(
8 | referenceMatchers = referenceMatchers,
9 | virtualRefReadersFactory = { graph ->
10 | listOf(
11 | JavaLocalReferenceReader(graph, referenceMatchers),
12 | ) +
13 | OpenJdkInstanceRefReaders.values().mapNotNull { it.create(graph) } +
14 | ApacheHarmonyInstanceRefReaders.values().mapNotNull { it.create(graph) }
15 | }
16 | )
17 |
18 | override fun createFor(heapGraph: HeapGraph): ReferenceReader<HeapObject> {
19 | return virtualizingFactory.createFor(heapGraph)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/PathFindingResults.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import shark.internal.ReferencePathNode
4 |
5 | // TODO Class name
6 | class PathFindingResults(
7 | val pathsToLeakingObjects: List<ReferencePathNode>,
8 | val dominatorTree: DominatorTree?
9 | )
10 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/ReferenceLocationType.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | /**
4 | * TODO This is quite similar to the leaktrace equivalent
5 | */
6 | enum class ReferenceLocationType {
7 | INSTANCE_FIELD,
8 | STATIC_FIELD,
9 | LOCAL,
10 | ARRAY_ENTRY
11 | }
12 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/ReferenceReader.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | // TODO Reevaluate name. Maybe not "Reader"? Something about navigating?
4 | fun interface ReferenceReader<T : HeapObject> {
5 |
6 | /**
7 | * Returns the sequences of non null outgoing references from [source]. Outgoing refs
8 | * can be actual JVM references or they can be virtual references when simplifying known data
9 | * structures.
10 | *
11 | * Whenever possible, the returned sequence should be sorted in a way that ensures consistent
12 | * graph traversal across heap dumps.
13 | *
14 | * The returned sequence may contain several [Reference] with an identical
15 | * [Reference.valueObjectId].
16 | */
17 | fun read(source: T): Sequence<Reference>
18 |
19 | fun interface Factory<T : HeapObject> {
20 | fun createFor(heapGraph: HeapGraph): ReferenceReader<T>
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/ShortestPathFinder.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | // TODO Mention that this can also compute the dominator tree?
4 | // Ideally this would be split out, e.g. ObjectDominators uses this without
5 | // any target just to navigate the whole graph. But we do want to be able traverse the
6 | // whole thing in one go, and both find leaking objects + compute dominators.
7 | // Maybe there's a new "heap traversal" class that can navigate the whole heap starting from
8 | // gc roots and following references, and we plug in 2 things on it: the one for leaks
9 | // and the one for dominators. And both can say when to stop (dominators: never).
10 | fun interface ShortestPathFinder {
11 | fun findShortestPathsFromGcRoots(
12 | leakingObjectIds: Set<Long>
13 | ): PathFindingResults
14 |
15 | fun interface Factory {
16 | fun createFor(heapGraph: HeapGraph): ShortestPathFinder
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/internal/ByteStringCompat.java:
--------------------------------------------------------------------------------
1 | package shark.internal;
2 |
3 | import okio.ByteString;
4 |
5 | class ByteStringCompat {
6 |
7 | /**
8 | * This is deprecated (error) to invoke from Kotlin but invoking the Kotlin extension function
9 | * leads to improper bytecode that goes through the companion class.
10 | */
11 | static ByteString encodeUtf8(String string) {
12 | return ByteString.encodeUtf8(string);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/internal/IntIntPairUtils.kt:
--------------------------------------------------------------------------------
1 | package shark.internal
2 |
3 | @PublishedApi
4 | internal inline infix fun Int.packedWith(that: Int) =
5 | ((toLong()) shl 32) or (that.toLong() and 0xffffffffL)
6 |
7 | @PublishedApi
8 | internal inline val Long.unpackAsFirstInt: Int
9 | get() = (this shr 32).toInt()
10 |
11 | @PublishedApi
12 | internal inline val Long.unpackAsSecondInt: Int
13 | get() = (this and 0xFFFFFFFF).toInt()
14 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/internal/JavaFrames.kt:
--------------------------------------------------------------------------------
1 | package shark.internal
2 |
3 | import shark.GcRoot.JavaFrame
4 | import shark.HeapGraph
5 |
6 | internal object JavaFrames {
7 |
8 | private fun getJavaFramesByThreadSerialNumber(graph: HeapGraph) =
9 | graph.context.getOrPut(JavaFrames::class.java.name) {
10 | graph.gcRoots.asSequence().filterIsInstance<JavaFrame>().groupBy { javaFrame ->
11 | javaFrame.threadSerialNumber
12 | }
13 | }
14 |
15 | fun getByThreadObjectId(graph: HeapGraph, threadObjectId: Long): List<JavaFrame>? {
16 | val threadObject = ThreadObjects.getByThreadObjectId(graph, threadObjectId) ?: return null
17 | val javaFrameByThreadSerial = getJavaFramesByThreadSerialNumber(graph)
18 | return javaFrameByThreadSerial[threadObject.threadSerialNumber]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/internal/Strings.kt:
--------------------------------------------------------------------------------
1 | package shark.internal
2 |
3 | internal fun String.lastSegment(segmentingChar: Char): String {
4 | val separator = lastIndexOf(segmentingChar)
5 | return if (separator == -1) this else this.substring(separator + 1)
6 | }
7 |
8 | internal fun String.createSHA1Hash() = ByteStringCompat.encodeUtf8(this).sha1().hex()
9 |
--------------------------------------------------------------------------------
/shark/shark/src/main/java/shark/internal/ThreadObjects.kt:
--------------------------------------------------------------------------------
1 | package shark.internal
2 |
3 | import shark.GcRoot.ThreadObject
4 | import shark.HeapGraph
5 |
6 | internal object ThreadObjects {
7 |
8 | private fun getThreadObjectsByIdMap(graph: HeapGraph) = graph.context.getOrPut(ThreadObjects::class.java.name) {
9 | graph.gcRoots.asSequence().filterIsInstance<ThreadObject>().associateBy { it.id }
10 | }
11 |
12 | fun getThreadObjects(graph: HeapGraph) = getThreadObjectsByIdMap(graph).values
13 |
14 | fun getByThreadObjectId(graph: HeapGraph, objectId: Long): ThreadObject? {
15 | val threadObjectsById = getThreadObjectsByIdMap(graph)
16 | return threadObjectsById[objectId]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/shark/shark/src/test/java/shark/UnreachableObjectRenderingTest.kt:
--------------------------------------------------------------------------------
1 | package shark
2 |
3 | import org.assertj.core.api.Assertions.assertThat
4 | import org.junit.Test
5 |
6 | class UnreachableObjectRenderingTest {
7 |
8 | @Test fun `renders unreachable object`() {
9 | val heapDump = dump {
10 | "com.example.SomeClass" watchedInstance {
11 | }
12 | }
13 |
14 | val analysis = heapDump.checkForLeaks<HeapAnalysisSuccess>()
15 |
16 | analysis renders """
17 | com.example.SomeClass instance
18 | _ Leaking: YES (ObjectWatcher was watching this because its lifecycle has ended)
19 | _ key = 39efcc1a-67bf-2040-e7ab-3fc9f94731dc
20 | _ watchDurationMillis = 25000
21 | _ retainedDurationMillis = 10000
22 | """
23 | }
24 |
25 | private infix fun HeapAnalysisSuccess.renders(expectedString: String) {
26 | assertThat(unreachableObjects[0].toString()).isEqualTo(
27 | expectedString.trimIndent().replace('_', LeakTrace.ZERO_WIDTH_SPACE)
28 | )
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/shark/shark/src/test/resources/hashmap_api_25.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/shark/shark/src/test/resources/hashmap_api_25.hprof
--------------------------------------------------------------------------------
/shark/shark/src/test/resources/safe_iterable_map.hprof:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/square/leakcanary/22bef70ba5abdba5f65935c1a37227d01be598d3/shark/shark/src/test/resources/safe_iterable_map.hprof
--------------------------------------------------------------------------------