├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── README.md ├── androiddevmetrics-plugin ├── build.gradle └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── frogermcs │ │ │ └── androiddevmetrics │ │ │ └── weaving │ │ │ └── plugin │ │ │ └── AndroidDevMetricsPlugin.kt │ └── resources │ │ └── META-INF │ │ └── gradle-plugins │ │ └── com.frogermcs.androiddevmetrics.properties │ └── test │ └── kotlin │ └── com │ └── frogermcs │ └── androiddevmetrics │ └── weaving │ └── plugin │ └── AndroidDevMetricsPluginTest.kt ├── androiddevmetrics-runtime-noop ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── frogermcs │ └── androiddevmetrics │ └── AndroidDevMetrics.java ├── androiddevmetrics-runtime ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── frogermcs │ │ └── androiddevmetrics │ │ ├── AndroidDevMetrics.java │ │ ├── aspect │ │ ├── ActivityLifecycleAnalyzer.java │ │ └── Dagger2GraphAnalyzer.java │ │ └── internal │ │ ├── ActivityMetricDescription.java │ │ ├── MethodsTracingManager.java │ │ ├── MetricDescription.java │ │ ├── MetricDescriptionTreeItem.java │ │ ├── metrics │ │ ├── ActivityLaunchMetrics.java │ │ ├── ActivityLifecycleMetrics.java │ │ ├── ChoreographerMetrics.java │ │ ├── InitManager.java │ │ ├── OnMetricsDataListener.java │ │ └── model │ │ │ ├── FpsDropMetric.java │ │ │ └── InitMetric.java │ │ ├── ui │ │ ├── ExpandableActivitiesMetricsListAdapter.java │ │ ├── ExpandableMetricsListAdapter.java │ │ ├── MetricsActivity.java │ │ ├── dialog │ │ │ ├── ActivitiesMethodsPickerDialog.java │ │ │ ├── EmulatorIsNotSupportedDialog.java │ │ │ ├── MethodsTracingFinishedDialog.java │ │ │ └── PermissionNotGrantedDialog.java │ │ ├── fragment │ │ │ ├── ActivitiesMetricsFragment.java │ │ │ └── Dagger2MetricsFragment.java │ │ └── interceptor │ │ │ ├── DefaultInterceptor.java │ │ │ └── UIInterceptor.java │ │ └── utils │ │ └── Utils.java │ └── res │ ├── color │ └── adm_tab_font_color.xml │ ├── drawable-xhdpi │ ├── ic_timeline_white_18dp.png │ ├── ic_triangle_down.png │ └── ic_triangle_up.png │ ├── drawable │ └── ic_metrics_group_indicator.xml │ ├── layout │ ├── adm_activity_metrics.xml │ ├── adm_fragment_activities_metrics.xml │ ├── adm_fragment_dagger2_metrics.xml │ ├── adm_list_item_activity_metrics_description.xml │ ├── adm_list_item_activity_metrics_header.xml │ ├── adm_list_item_metrics_description.xml │ └── adm_list_item_metrics_header.xml │ ├── mipmap-hdpi │ └── __ic_launcher_adm.png │ ├── mipmap-mdpi │ └── __ic_launcher_adm.png │ ├── mipmap-xhdpi │ └── __ic_launcher_adm.png │ ├── mipmap-xxhdpi │ └── __ic_launcher_adm.png │ ├── mipmap-xxxhdpi │ └── __ic_launcher_adm.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── art ├── activities_metrics.png ├── dagger2_metrics.png ├── dagger2metrics-notification.png └── dagger2metrics.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/seeds.txt 24 | proguard/unused.txt 25 | 26 | # Mac 27 | .DS_Store 28 | .idea 29 | *.iml 30 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "GithubClient-example"] 2 | path = GithubClient-example 3 | url = https://github.com/frogermcs/githubclient.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | Version 0.7 *(2019-04-26)* 5 | -------------------------- 6 | 7 | * Maintenance: fixed Gradle warnings (Kudos AndrewHeyO, erykrutkowski!) 8 | * Maintenance: Groovy plugin rewritten to Kotlin (Kudos skrzyneckik!) 9 | 10 | Version 0.6 *(2018-02-12)* 11 | ---------------------------- 12 | 13 | * Maintenance: Updated dependencies 14 | * Fix: Support for notifications on Android 26 15 | 16 | Version 0.5 *(2017-04-05)* 17 | ---------------------------- 18 | 19 | * New: Allow custom extension to the library using Interceptor like pattern. (Thanks Amulya Khare)! 20 | 21 | Version 0.4 *(2015-03-21)* 22 | ---------------------------- 23 | 24 | * New: Activities lifecycle methods tracing 25 | 26 | Version 0.3.1 *(2015-03-06)* 27 | ---------------------------- 28 | 29 | * Update: Use Class.getName() instead of Class.getSimpleName() because of performance issues in anonymous classes 30 | * Update: Don't measure Dagger 2 Producers monitor injections 31 | * Update: Gradle config cleanup, added no-op lib version for release build variant 32 | 33 | Version 0.3 *(2015-03-01)* 34 | ---------------------------- 35 | 36 | * **Project renamed to AndroidDevMetrics** 37 | 38 | * New: Metrics for Activity lifecycle (measuring execution time of onCreate, onStart, onResume and layout) 39 | * New: Metrics for frame drops 40 | * Fix: NullPointerException for @Nullable 41 | 42 | Version 0.2.1 *(2015-02-02)* 43 | ---------------------------- 44 | 45 | * Fix: Overriding stats for non-singleton dependencies 46 | * Fix: ListView group indicator style 47 | 48 | 49 | Version 0.2.0 *(2016-01-30)* 50 | ---------------------------- 51 | 52 | Initial release. 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidDevMetrics 2 | (formerly dagger2metrics) 3 | 4 | Performance metrics library for Android development. 5 | 6 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-AndroidDevMetrics-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/3120) 7 | 8 | The problem with performance is that it often decreases slowly so in day-by-day development it's hard to notice that our app (or Activity or any other view) launches 50ms longer. And another 150ms longer, and another 100ms... 9 | 10 | With **AndroidDevMetrics** you will be able to see how performant are the most common operations like object initialization (in Dagger 2 graph), or Activity lifecycle methods (`onCreate()`, `onStart()`, `onResume()`). 11 | 12 | It won't show you exact reason of performance issues or bottlenecks (yet!) but it can point out where you should start looking first. 13 | 14 | AndroidDevMetrics currently includes: 15 | 16 | * Activity lifecycle metrics - metrics for lifecycle methods execution (`onCreate()`, `onStart()`, `onResume()`) 17 | * Activity lifecycle methods tracing without app recompiling 18 | * Frame rate drops - metrics for fps drops for each of screens (activity) 19 | * Dagger 2 metrics - metrics for objects initialization in Dagger 2 20 | 21 | ![screenshot1.png](https://raw.githubusercontent.com/frogermcs/androiddevmetrics/master/art/activities_metrics.png) 22 | 23 | ![screenshot.png](https://raw.githubusercontent.com/frogermcs/androiddevmetrics/master/art/dagger2_metrics.png) 24 | 25 | ## Getting started 26 | 27 | Script below shows how to enable all available metrics. 28 | 29 | In your `build.gradle`: 30 | 31 | ```gradle 32 | buildscript { 33 | repositories { 34 | jcenter() 35 | } 36 | 37 | dependencies { 38 | classpath 'com.frogermcs.androiddevmetrics:androiddevmetrics-plugin:0.7' 39 | } 40 | } 41 | 42 | apply plugin: 'com.android.application' 43 | apply plugin: 'com.frogermcs.androiddevmetrics' 44 | ``` 45 | 46 | In your `Application` class: 47 | 48 | ```java 49 | public class ExampleApplication extends Application { 50 | 51 | @Override 52 | public void onCreate() { 53 | super.onCreate(); 54 | //Use it only in debug builds 55 | if (BuildConfig.DEBUG) { 56 | AndroidDevMetrics.initWith(this); 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | ## How does it work? 63 | 64 | Detailed description how it works under the hood can be found on wiki pages: 65 | 66 | * [Activity lifecycle and frame drops metrics](https://github.com/frogermcs/AndroidDevMetrics/wiki/Activity-lifecycle-metrics) 67 | * [Activity lifecycle methods tracing](http://frogermcs.github.io/androiddevmetrics-activity-lifecycle-methods-tracing/) 68 | * [Dagger 2 metrics](https://github.com/frogermcs/AndroidDevMetrics/wiki/Dagger-2-metrics) 69 | 70 | ## I found performance issue, what should I do next? 71 | 72 | There is no silver bullet for performance issues but here are a couple steps which can help you with potential bugs hunting. 73 | 74 | If measured time of object initialization or method execution looks suspicious you should definitely give a try to [TraceView](http://developer.android.com/tools/debugging/debugging-tracing.html). This tool logs method execution over time and shows execution data, per-thread timelines, and call stacks. Practical example of TraceView usage can be found in this blog post: [Measuring Dagger 2 graph creation performance](http://frogermcs.github.io/dagger-graph-creation-performance/]). 75 | 76 | --- 77 | 78 | If it seems that layout or view can be a reason of performance issue you should start with those links from official Android documentation: 79 | 80 | * http://developer.android.com/training/improving-layouts/index.html 81 | * http://developer.android.com/training/improving-layouts/optimizing-layout.html 82 | 83 | --- 84 | 85 | Finally, if you want to understand where most of performance issues come from, here *is a collection of videos focused entirely on helping developers write faster, more performant Android Applications.* 86 | 87 | * [Android Performance Patterns](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE) 88 | 89 | ## Example app 90 | 91 | You can check [GithubClient](https://github.com/frogermcs/githubclient) - example Android app which shows how to use Dagger 2. Most recent version uses **AndroidDevMetrics** for measuring performance. 92 | 93 | ## Building AndroidDevMetrics 94 | 95 | Build AndroidDevMetrics plugin with [`./gradlew clean build`]. The tests can be run with 96 | `./gradlew clean test`. To install the plugin in your local maven repository (usually located at 97 | `~/.m2/repository`) use `./gradlew clean install`. You can change `VERSION_NAME` value in `gradle.properties` 98 | to easily recognise your version. 99 | 100 | ## License 101 | 102 | Copyright 2016 Miroslaw Stanek 103 | 104 | Licensed under the Apache License, Version 2.0 (the "License"); 105 | you may not use this file except in compliance with the License. 106 | You may obtain a copy of the License at 107 | 108 | http://www.apache.org/licenses/LICENSE-2.0 109 | 110 | Unless required by applicable law or agreed to in writing, software 111 | distributed under the License is distributed on an "AS IS" BASIS, 112 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 113 | See the License for the specific language governing permissions and 114 | limitations under the License. 115 | -------------------------------------------------------------------------------- /androiddevmetrics-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | apply plugin: 'com.bmuschko.nexus' 3 | apply plugin: 'com.jfrog.bintray' 4 | 5 | targetCompatibility = JavaVersion.VERSION_1_7 6 | sourceCompatibility = JavaVersion.VERSION_1_7 7 | 8 | repositories { 9 | jcenter() 10 | mavenLocal() 11 | google() 12 | } 13 | 14 | dependencies { 15 | implementation gradleApi() 16 | implementation localGroovy() 17 | implementation 'com.android.tools.build:gradle:3.3.2' 18 | implementation 'org.aspectj:aspectjtools:1.8.8' 19 | implementation 'org.aspectj:aspectjrt:1.8.8' 20 | implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.21" 21 | testImplementation "junit:junit:4.12" 22 | } 23 | 24 | group = GROUP 25 | version = VERSION_NAME 26 | 27 | modifyPom { 28 | project { 29 | name "AndroidDevMetrics Plugin" 30 | description POM_DESCRIPTION 31 | url POM_URL 32 | artifactId "androiddevmetrics-plugin" 33 | groupId GROUP 34 | 35 | scm { 36 | url POM_SCM_URL 37 | connection POM_SCM_CONNECTION 38 | developerConnection POM_SCM_DEV_CONNECTION 39 | } 40 | 41 | licenses { 42 | license { 43 | name POM_LICENCE_NAME 44 | url POM_LICENCE_URL 45 | distribution POM_LICENCE_DIST 46 | } 47 | } 48 | 49 | developers { 50 | developer { 51 | id POM_DEVELOPER_ID 52 | name POM_DEVELOPER_NAME 53 | email POM_DEVELOPER_EMAIL 54 | } 55 | } 56 | } 57 | } 58 | 59 | extraArchive { 60 | sources = true 61 | tests = true 62 | javadoc = true 63 | } 64 | 65 | // Bintray 66 | bintray { 67 | // Global gradle.properties 68 | user = project.hasProperty('BINTRAY_USER') ? project.getProperty('BINTRAY_USER') : "" 69 | key = project.hasProperty('BINTRAY_KEY') ? project.getProperty('BINTRAY_KEY') : "" 70 | 71 | configurations = ['archives'] 72 | pkg { 73 | repo = 'maven' 74 | name = 'androiddevmetrics-plugin' 75 | desc = POM_DESCRIPTION 76 | websiteUrl = POM_URL 77 | vcsUrl = GIT_URL 78 | licenses = ["Apache-2.0"] 79 | publish = true 80 | publicDownloadNumbers = true 81 | version { 82 | desc = POM_DESCRIPTION 83 | gpg { 84 | sign = true 85 | passphrase = project.hasProperty('BINTRAY_GPG_PASSWORD') ? project.getProperty('BINTRAY_GPG_PASSWORD') : "" 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /androiddevmetrics-plugin/src/main/kotlin/com/frogermcs/androiddevmetrics/weaving/plugin/AndroidDevMetricsPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.weaving.plugin 2 | 3 | import com.android.build.gradle.* 4 | import com.android.build.gradle.api.BaseVariant 5 | import org.aspectj.bridge.IMessage 6 | import org.aspectj.bridge.MessageHandler 7 | import org.aspectj.tools.ajc.Main 8 | import org.gradle.api.DomainObjectSet 9 | import org.gradle.api.Plugin 10 | import org.gradle.api.Project 11 | import org.gradle.api.tasks.compile.AbstractCompile 12 | import java.io.File 13 | 14 | class AndroidDevMetricsPlugin: Plugin { 15 | 16 | override fun apply(project: Project) { 17 | with(project) { 18 | val hasApp = project.plugins.withType(AppPlugin::class.java).isNotEmpty() 19 | val hasLib = project.plugins.withType(LibraryPlugin::class.java).isNotEmpty() 20 | if (!hasApp && !hasLib) { 21 | throw IllegalStateException("'android' or 'android-library' plugin required.") 22 | } 23 | 24 | //TODO can we do it as closure? 25 | project.dependencies.add("releaseCompile", "com.frogermcs.androiddevmetrics:androiddevmetrics-runtime-noop:0.7") 26 | project.dependencies.add("debugCompile", "com.frogermcs.androiddevmetrics:androiddevmetrics-runtime:0.7") 27 | project.dependencies.add("debugCompile", "org.aspectj:aspectjrt:1.8.8") 28 | project.dependencies.add("compile", "com.android.support:support-v4:26.1.0") 29 | 30 | val log = project.logger 31 | val variants: DomainObjectSet 32 | if (hasApp) { 33 | variants = (project.extensions.getByName("android") as AppExtension).applicationVariants as DomainObjectSet 34 | } else { 35 | variants = (project.extensions.getByName("android") as LibraryExtension).libraryVariants as DomainObjectSet 36 | } 37 | 38 | variants.all { variant -> 39 | if (!variant.buildType.isDebuggable) { 40 | log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.") 41 | return@all 42 | } 43 | 44 | val javaCompiler = variant.javaCompileProvider.get() as AbstractCompile 45 | javaCompiler.doLast({ 46 | val args = arrayOf( 47 | "-showWeaveInfo", 48 | "-1.5", 49 | "-inpath", javaCompiler.destinationDir.toString(), 50 | "-aspectpath", javaCompiler.classpath.asPath, 51 | "-d", javaCompiler.destinationDir.toString(), 52 | "-classpath", javaCompiler.classpath.asPath, 53 | "-bootclasspath", (project.extensions.getByName("android") as BaseExtension).bootClasspath.joinToString(File.pathSeparator) 54 | ) 55 | log.debug("ajc args: %s".format(args)) 56 | 57 | val handler = MessageHandler(true) 58 | Main().run(args, handler) 59 | 60 | handler.getMessages(null, true).forEach { 61 | when(it.kind) { 62 | IMessage.ABORT, IMessage.ERROR, IMessage.FAIL -> log.error(it.message, it.thrown) 63 | IMessage.WARNING -> log.warn(it.message, it.thrown) 64 | IMessage.INFO -> log.info(it.message, it.thrown) 65 | IMessage.DEBUG -> log.debug(it.message, it.thrown) 66 | } 67 | } 68 | }) 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /androiddevmetrics-plugin/src/main/resources/META-INF/gradle-plugins/com.frogermcs.androiddevmetrics.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.frogermcs.androiddevmetrics.weaving.plugin.AndroidDevMetricsPlugin 2 | -------------------------------------------------------------------------------- /androiddevmetrics-plugin/src/test/kotlin/com/frogermcs/androiddevmetrics/weaving/plugin/AndroidDevMetricsPluginTest.kt: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.weaving.plugin 2 | 3 | import com.android.build.gradle.AppPlugin 4 | import com.android.build.gradle.LibraryPlugin 5 | import org.junit.Assert.assertNotNull 6 | import org.gradle.api.GradleException 7 | import org.gradle.testfixtures.ProjectBuilder 8 | import org.junit.Test 9 | 10 | class AndroidDevMetricsPluginTest { 11 | 12 | @Test(expected = GradleException::class) 13 | fun `plugin should throw exception when 'android' or 'android-library' plugin unavailable`() { 14 | val project = ProjectBuilder.builder().build() 15 | 16 | project.pluginManager.apply(AndroidDevMetricsPlugin::class.java) 17 | } 18 | 19 | @Test 20 | fun `plugin should work with 'android' plugin`() { 21 | val project = ProjectBuilder.builder().build() 22 | 23 | with(project) { 24 | pluginManager.apply(AppPlugin::class.java) 25 | pluginManager.apply(AndroidDevMetricsPlugin::class.java) 26 | } 27 | } 28 | 29 | @Test 30 | fun `plugin should work with 'android-library' plugin`() { 31 | val project = ProjectBuilder.builder().build() 32 | 33 | with(project) { 34 | pluginManager.apply(LibraryPlugin::class.java) 35 | pluginManager.apply(AndroidDevMetricsPlugin::class.java) 36 | } 37 | } 38 | 39 | @Test 40 | fun `plugin should add dependencies when applied`() { 41 | val project = ProjectBuilder.builder().build() 42 | 43 | with(project) { 44 | pluginManager.apply(AppPlugin::class.java) 45 | pluginManager.apply(AndroidDevMetricsPlugin::class.java) 46 | 47 | assertNotNull(project.dependencies.components.all { println(it.id) }) 48 | 49 | //TODO 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /androiddevmetrics-runtime-noop/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime-noop/build.gradle: -------------------------------------------------------------------------------- 1 | import org.aspectj.bridge.IMessage 2 | import org.aspectj.bridge.MessageHandler 3 | import org.aspectj.tools.ajc.Main 4 | 5 | apply plugin: 'com.android.library' 6 | apply plugin: 'com.jfrog.bintray' 7 | apply plugin: 'com.github.dcendents.android-maven' 8 | 9 | buildscript { 10 | repositories { 11 | mavenCentral() 12 | jcenter() 13 | google() 14 | } 15 | dependencies { 16 | classpath 'org.aspectj:aspectjtools:1.8.8' 17 | } 18 | } 19 | 20 | repositories { 21 | maven { url 'https://dl.bintray.com/frogermcs/maven/' } 22 | } 23 | 24 | dependencies { } 25 | 26 | android { 27 | compileSdkVersion = 28 28 | 29 | defaultConfig { 30 | minSdkVersion 14 31 | targetSdkVersion 28 32 | } 33 | 34 | 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_7 37 | targetCompatibility JavaVersion.VERSION_1_7 38 | } 39 | 40 | buildTypes { 41 | release { 42 | minifyEnabled false 43 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 44 | } 45 | } 46 | } 47 | 48 | android.libraryVariants.all { variant -> 49 | JavaCompile javaCompile = variant.javaCompileProvider.get() 50 | javaCompile.doLast { 51 | String[] args = ["-showWeaveInfo", 52 | "-1.5", 53 | "-inpath", javaCompile.destinationDir.toString(), 54 | "-aspectpath", javaCompile.classpath.asPath, 55 | "-d", javaCompile.destinationDir.toString(), 56 | "-classpath", javaCompile.classpath.asPath] 57 | 58 | MessageHandler handler = new MessageHandler(true); 59 | new Main().run(args, handler) 60 | 61 | def log = project.logger 62 | for (IMessage message : handler.getMessages(null, true)) { 63 | switch (message.getKind()) { 64 | case IMessage.ABORT: 65 | case IMessage.ERROR: 66 | case IMessage.FAIL: 67 | log.error message.message, message.thrown 68 | break; 69 | case IMessage.WARNING: 70 | case IMessage.INFO: 71 | log.info message.message, message.thrown 72 | break; 73 | case IMessage.DEBUG: 74 | log.debug message.message, message.thrown 75 | break; 76 | } 77 | } 78 | } 79 | } 80 | 81 | group = GROUP 82 | version = VERSION_NAME 83 | 84 | install { 85 | repositories.mavenInstaller { 86 | pom { 87 | project { 88 | packaging 'aar' 89 | groupId GROUP 90 | artifactId "androiddevmetrics-runtime-noop" 91 | 92 | name "AndroidDevMetrics Runtime" 93 | description POM_DESCRIPTION 94 | url POM_URL 95 | 96 | licenses { 97 | license { 98 | name POM_LICENCE_NAME 99 | url POM_LICENCE_URL 100 | Distribution POM_LICENCE_DIST 101 | } 102 | } 103 | 104 | developers { 105 | developer { 106 | id POM_DEVELOPER_ID 107 | name POM_DEVELOPER_NAME 108 | email POM_DEVELOPER_EMAIL 109 | } 110 | } 111 | 112 | scm { 113 | url POM_SCM_URL 114 | connection POM_SCM_CONNECTION 115 | developerConnection POM_SCM_DEV_CONNECTION 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | // Bintray 123 | bintray { 124 | user = project.hasProperty('BINTRAY_USER') ? project.getProperty('BINTRAY_USER') : "" 125 | key = project.hasProperty('BINTRAY_KEY') ? project.getProperty('BINTRAY_KEY') : "" 126 | 127 | configurations = ['archives'] 128 | pkg { 129 | repo = 'maven' 130 | name = 'androiddevmetrics-runtime-noop' 131 | desc = POM_DESCRIPTION 132 | websiteUrl = POM_URL 133 | vcsUrl = GIT_URL 134 | licenses = ["Apache-2.0"] 135 | publish = true 136 | publicDownloadNumbers = true 137 | version { 138 | desc = POM_DESCRIPTION 139 | gpg { 140 | sign = true 141 | passphrase = project.hasProperty('BINTRAY_GPG_PASSWORD') ? project.getProperty('BINTRAY_GPG_PASSWORD') : "" 142 | } 143 | } 144 | } 145 | } 146 | 147 | task sourcesJar(type: Jar) { 148 | from android.sourceSets.main.java.srcDirs 149 | classifier = 'sources' 150 | } 151 | 152 | task javadoc(type: Javadoc) { 153 | source = android.sourceSets.main.java.srcDirs 154 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 155 | } 156 | 157 | task javadocJar(type: Jar, dependsOn: javadoc) { 158 | classifier = 'javadoc' 159 | from javadoc.destinationDir 160 | } 161 | 162 | artifacts { 163 | archives javadocJar 164 | archives sourcesJar 165 | } -------------------------------------------------------------------------------- /androiddevmetrics-runtime-noop/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime-noop/src/main/java/com/frogermcs/androiddevmetrics/AndroidDevMetrics.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Created by williamwebb on 3/2/16. 7 | */ 8 | // TODO: generate at compiletime to match really impl signature 9 | public class AndroidDevMetrics { 10 | 11 | private int dagger2WarningLevel1, dagger2WarningLevel2, dagger2WarningLevel3; 12 | 13 | static final AndroidDevMetrics singleton = new AndroidDevMetrics(null); 14 | 15 | /** stub **/ 16 | public static AndroidDevMetrics initWith(Context context) { 17 | return singleton; 18 | } 19 | 20 | /** stub **/ 21 | public static AndroidDevMetrics initWith(Builder builder) { 22 | return singleton; 23 | } 24 | 25 | /** stub **/ 26 | public static AndroidDevMetrics initWith(AndroidDevMetrics androidDevMetrics) { 27 | return singleton; 28 | } 29 | 30 | /** stub **/ 31 | private static void setAndroidDevMetrics(AndroidDevMetrics androidDevMetrics) { } 32 | 33 | /** stub **/ 34 | public static AndroidDevMetrics singleton() { 35 | return singleton; 36 | } 37 | 38 | AndroidDevMetrics(Context context) { } 39 | 40 | /** stub **/ 41 | public int dagger2WarningLevel1() { 42 | return dagger2WarningLevel1; 43 | } 44 | 45 | /** stub **/ 46 | public int dagger2WarningLevel2() { 47 | return dagger2WarningLevel2; 48 | } 49 | 50 | /** stub **/ 51 | public int dagger2WarningLevel3() { 52 | return dagger2WarningLevel3; 53 | } 54 | 55 | /** stub **/ 56 | private void setupMetrics() { } 57 | 58 | /** stub **/ 59 | private void showNotification() { } 60 | 61 | /** stub **/ 62 | public static class Builder { 63 | 64 | /** stub **/ 65 | public Builder(Context context) { } 66 | 67 | public Builder dagger2WarningLevelsMs(int warning1, int warning2, int warning3) { 68 | return this; 69 | } 70 | 71 | /** stub **/ 72 | public Builder frameDropsLimits(int measureIntervalMillis, double maxFpsForFrameDrop) { 73 | return this; 74 | } 75 | 76 | /** stub **/ 77 | public Builder enableActivityMetrics(boolean enable) { 78 | return this; 79 | } 80 | 81 | /** stub **/ 82 | public Builder showNotification(boolean show) { 83 | return this; 84 | } 85 | 86 | /** stub **/ 87 | public Builder enableDagger2Metrics(boolean enable) { 88 | return this; 89 | } 90 | 91 | /** stub **/ 92 | public Builder autoCancelNotification(boolean autoCancelNotification) { 93 | return this; 94 | } 95 | 96 | public Builder addUIInterceptor(Object interceptor) { 97 | return this; 98 | } 99 | 100 | /** stub **/ 101 | public AndroidDevMetrics build() { 102 | return new AndroidDevMetrics(null); 103 | } 104 | } 105 | 106 | } 107 | 108 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/build.gradle: -------------------------------------------------------------------------------- 1 | import org.aspectj.bridge.IMessage 2 | import org.aspectj.bridge.MessageHandler 3 | import org.aspectj.tools.ajc.Main 4 | 5 | apply plugin: 'com.android.library' 6 | apply plugin: 'com.jfrog.bintray' 7 | apply plugin: 'com.github.dcendents.android-maven' 8 | 9 | buildscript { 10 | repositories { 11 | google() 12 | jcenter() 13 | mavenCentral() 14 | } 15 | dependencies { 16 | classpath 'org.aspectj:aspectjtools:1.8.8' 17 | } 18 | } 19 | 20 | repositories { 21 | google() 22 | maven { url 'https://dl.bintray.com/frogermcs/maven/' } 23 | } 24 | 25 | dependencies { 26 | implementation 'org.aspectj:aspectjrt:1.8.8' 27 | implementation 'com.android.support:support-compat:28.0.0' 28 | implementation 'com.android.support:appcompat-v7:28.0.0' 29 | } 30 | 31 | android { 32 | compileSdkVersion = 28 33 | 34 | defaultConfig { 35 | minSdkVersion 14 36 | targetSdkVersion 28 37 | } 38 | 39 | 40 | compileOptions { 41 | sourceCompatibility JavaVersion.VERSION_1_7 42 | targetCompatibility JavaVersion.VERSION_1_7 43 | } 44 | 45 | buildTypes { 46 | release { 47 | minifyEnabled false 48 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 49 | } 50 | } 51 | } 52 | 53 | android.libraryVariants.all { variant -> 54 | JavaCompile javaCompile = variant.javaCompileProvider.get() 55 | javaCompile.doLast { 56 | String[] args = ["-showWeaveInfo", 57 | "-1.5", 58 | "-inpath", javaCompile.destinationDir.toString(), 59 | "-aspectpath", javaCompile.classpath.asPath, 60 | "-d", javaCompile.destinationDir.toString(), 61 | "-classpath", javaCompile.classpath.asPath] 62 | 63 | MessageHandler handler = new MessageHandler(true); 64 | new Main().run(args, handler) 65 | 66 | def log = project.logger 67 | for (IMessage message : handler.getMessages(null, true)) { 68 | switch (message.getKind()) { 69 | case IMessage.ABORT: 70 | case IMessage.ERROR: 71 | case IMessage.FAIL: 72 | log.error message.message, message.thrown 73 | break; 74 | case IMessage.WARNING: 75 | case IMessage.INFO: 76 | log.info message.message, message.thrown 77 | break; 78 | case IMessage.DEBUG: 79 | log.debug message.message, message.thrown 80 | break; 81 | } 82 | } 83 | } 84 | } 85 | 86 | group = GROUP 87 | version = VERSION_NAME 88 | 89 | install { 90 | repositories.mavenInstaller { 91 | pom { 92 | project { 93 | packaging 'aar' 94 | groupId GROUP 95 | artifactId "androiddevmetrics-runtime" 96 | 97 | name "AndroidDevMetrics Runtime" 98 | description POM_DESCRIPTION 99 | url POM_URL 100 | 101 | licenses { 102 | license { 103 | name POM_LICENCE_NAME 104 | url POM_LICENCE_URL 105 | Distribution POM_LICENCE_DIST 106 | } 107 | } 108 | 109 | developers { 110 | developer { 111 | id POM_DEVELOPER_ID 112 | name POM_DEVELOPER_NAME 113 | email POM_DEVELOPER_EMAIL 114 | } 115 | } 116 | 117 | scm { 118 | url POM_SCM_URL 119 | connection POM_SCM_CONNECTION 120 | developerConnection POM_SCM_DEV_CONNECTION 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | // Bintray 128 | bintray { 129 | // Global gradle.properties 130 | user = project.hasProperty('BINTRAY_USER') ? project.getProperty('BINTRAY_USER') : "" 131 | key = project.hasProperty('BINTRAY_KEY') ? project.getProperty('BINTRAY_KEY') : "" 132 | 133 | configurations = ['archives'] 134 | pkg { 135 | repo = 'maven' 136 | name = 'androiddevmetrics-runtime' 137 | desc = POM_DESCRIPTION 138 | websiteUrl = POM_URL 139 | vcsUrl = GIT_URL 140 | licenses = ["Apache-2.0"] 141 | publish = true 142 | publicDownloadNumbers = true 143 | version { 144 | desc = POM_DESCRIPTION 145 | gpg { 146 | sign = true 147 | passphrase = project.hasProperty('BINTRAY_GPG_PASSWORD') ? project.getProperty('BINTRAY_GPG_PASSWORD') : "" 148 | } 149 | } 150 | } 151 | } 152 | 153 | task sourcesJar(type: Jar) { 154 | from android.sourceSets.main.java.srcDirs 155 | classifier = 'sources' 156 | } 157 | 158 | task javadoc(type: Javadoc) { 159 | source = android.sourceSets.main.java.srcDirs 160 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 161 | } 162 | 163 | task javadocJar(type: Jar, dependsOn: javadoc) { 164 | classifier = 'javadoc' 165 | from javadoc.destinationDir 166 | } 167 | 168 | artifacts { 169 | archives javadocJar 170 | archives sourcesJar 171 | } -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/AndroidDevMetrics.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics; 2 | 3 | import android.app.Application; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.app.PendingIntent; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.os.Build; 10 | import android.support.v4.app.NotificationCompat; 11 | 12 | import com.frogermcs.androiddevmetrics.aspect.ActivityLifecycleAnalyzer; 13 | import com.frogermcs.androiddevmetrics.aspect.Dagger2GraphAnalyzer; 14 | import com.frogermcs.androiddevmetrics.internal.MethodsTracingManager; 15 | import com.frogermcs.androiddevmetrics.internal.metrics.ActivityLaunchMetrics; 16 | import com.frogermcs.androiddevmetrics.internal.metrics.ChoreographerMetrics; 17 | import com.frogermcs.androiddevmetrics.internal.metrics.InitManager; 18 | import com.frogermcs.androiddevmetrics.internal.ui.MetricsActivity; 19 | import com.frogermcs.androiddevmetrics.internal.ui.interceptor.DefaultInterceptor; 20 | import com.frogermcs.androiddevmetrics.internal.ui.interceptor.UIInterceptor; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | 26 | /** 27 | * Created by Miroslaw Stanek on 25.01.2016. 28 | */ 29 | public class AndroidDevMetrics { 30 | private static int DAGGER2_WARNING_1_LIMIT_MILLIS = 30; 31 | private static int DAGGER2_WARNING_2_LIMIT_MILLIS = 50; 32 | private static int DAGGER2_WARNING_3_LIMIT_MILLIS = 100; 33 | 34 | private static int FRAME_DROPS_DEFAULT_INTERVAL_MS = 500; 35 | private static double FRAME_DROPS_FPS_LIMIT = 40; 36 | 37 | static volatile AndroidDevMetrics singleton; 38 | 39 | private Context context; 40 | private int dagger2WarningLevel1, dagger2WarningLevel2, dagger2WarningLevel3; 41 | private boolean enableAcitivtyMetrics; 42 | private boolean showNotification; 43 | private boolean autoCancelNotification; 44 | private boolean enableDagger2Metrics; 45 | private int intervalMillis; 46 | private double maxFpsForFrameDrop; 47 | private List interceptors; 48 | 49 | /** 50 | * Enable Activity and Dagger 2 metrics 51 | */ 52 | public static AndroidDevMetrics initWith(Context context) { 53 | Builder androidDevMetricsBuilder = new Builder(context) 54 | .enableActivityMetrics(true) 55 | .enableDagger2Metrics(true) 56 | .showNotification(true) 57 | .autoCancelNotification(false); 58 | return initWith(androidDevMetricsBuilder); 59 | } 60 | 61 | public static AndroidDevMetrics initWith(Builder builder) { 62 | return initWith(builder.build()); 63 | } 64 | 65 | public static AndroidDevMetrics initWith(AndroidDevMetrics androidDevMetrics) { 66 | if (singleton == null) { 67 | synchronized (AndroidDevMetrics.class) { 68 | if (singleton == null) { 69 | setAndroidDevMetrics(androidDevMetrics); 70 | } 71 | } 72 | } 73 | 74 | return singleton; 75 | } 76 | 77 | private static void setAndroidDevMetrics(AndroidDevMetrics androidDevMetrics) { 78 | singleton = androidDevMetrics; 79 | singleton.setupMetrics(); 80 | } 81 | 82 | public static AndroidDevMetrics singleton() { 83 | if (singleton == null) { 84 | throw new IllegalStateException("Must Initialize Dagger2Metrics before using singleton()"); 85 | } else { 86 | return singleton; 87 | } 88 | } 89 | 90 | AndroidDevMetrics(Context context) { 91 | this.context = context; 92 | } 93 | 94 | public int dagger2WarningLevel1() { 95 | return dagger2WarningLevel1; 96 | } 97 | 98 | public int dagger2WarningLevel2() { 99 | return dagger2WarningLevel2; 100 | } 101 | 102 | public int dagger2WarningLevel3() { 103 | return dagger2WarningLevel3; 104 | } 105 | 106 | private void setupMetrics() { 107 | Dagger2GraphAnalyzer.setEnabled(enableDagger2Metrics); 108 | InitManager.getInstance().initializedMetrics.clear(); 109 | 110 | ActivityLifecycleAnalyzer.setEnabled(true); 111 | if (enableAcitivtyMetrics) { 112 | MethodsTracingManager.getInstance().init(context); 113 | ActivityLaunchMetrics activityLaunchMetrics = ActivityLaunchMetrics.getInstance(); 114 | ((Application) context.getApplicationContext()).registerActivityLifecycleCallbacks(activityLaunchMetrics); 115 | ChoreographerMetrics.getInstance().setMaxFpsForFrameDrop(maxFpsForFrameDrop); 116 | ChoreographerMetrics.getInstance().setIntervalMillis(intervalMillis); 117 | ChoreographerMetrics.getInstance().start(); 118 | } 119 | 120 | if (showNotification) { 121 | showNotification(); 122 | } 123 | } 124 | 125 | private void showNotification() { 126 | NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 127 | 128 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 129 | int importance = NotificationManager.IMPORTANCE_HIGH; 130 | String notificationChannel = "AndroidDevMetrics"; 131 | NotificationChannel mChannel = mNotificationManager.getNotificationChannel(notificationChannel); 132 | if (mChannel == null) { 133 | mChannel = new NotificationChannel(notificationChannel, notificationChannel, importance); 134 | mChannel.setDescription(notificationChannel); 135 | mChannel.enableVibration(true); 136 | mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400}); 137 | mNotificationManager.createNotificationChannel(mChannel); 138 | } 139 | 140 | NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, notificationChannel) 141 | .setSmallIcon(R.drawable.ic_timeline_white_18dp) 142 | .setContentTitle(context.getString(R.string.adm_name)) 143 | .setContentText("Click to see current metrics") 144 | .setAutoCancel(autoCancelNotification); 145 | 146 | Intent resultIntent = new Intent(context, MetricsActivity.class); 147 | PendingIntent resultPendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); 148 | mBuilder.setContentIntent(resultPendingIntent); 149 | mNotificationManager.notify("AndroidDevMetrics".hashCode(), mBuilder.build()); 150 | } else { 151 | NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context) 152 | .setSmallIcon(R.drawable.ic_timeline_white_18dp) 153 | .setContentTitle(context.getString(R.string.adm_name)) 154 | .setContentText("Click to see current metrics") 155 | .setAutoCancel(autoCancelNotification); 156 | 157 | Intent resultIntent = new Intent(context, MetricsActivity.class); 158 | PendingIntent resultPendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); 159 | mBuilder.setContentIntent(resultPendingIntent); 160 | mNotificationManager.notify("AndroidDevMetrics".hashCode(), mBuilder.build()); 161 | } 162 | } 163 | 164 | public List interceptors() { 165 | return interceptors; 166 | } 167 | 168 | public static class Builder { 169 | private final Context context; 170 | private int dagger2WarningLevel1 = DAGGER2_WARNING_1_LIMIT_MILLIS; 171 | private int dagger2WarningLevel2 = DAGGER2_WARNING_2_LIMIT_MILLIS; 172 | private int dagger2WarningLevel3 = DAGGER2_WARNING_3_LIMIT_MILLIS; 173 | private boolean enableAcitivtyMetrics = true; 174 | private boolean enableDagger2Metrics = true; 175 | private boolean showNotification = true; 176 | private boolean autoCancelNotification = false; 177 | private int intervalMillis = FRAME_DROPS_DEFAULT_INTERVAL_MS; 178 | private double maxFpsForFrameDrop = FRAME_DROPS_FPS_LIMIT; 179 | private List interceptors; 180 | 181 | public Builder(Context context) { 182 | if (context == null) { 183 | throw new IllegalArgumentException("Context must not be null."); 184 | } else { 185 | this.context = context.getApplicationContext(); 186 | this.interceptors = new ArrayList<>(); 187 | this.interceptors.add(new DefaultInterceptor()); 188 | } 189 | } 190 | 191 | public Builder dagger2WarningLevelsMs(int warning1, int warning2, int warning3) { 192 | if (warning1 > warning2 || warning2 > warning3) { 193 | throw new IllegalArgumentException("Warning levels should be ascending"); 194 | } else { 195 | this.dagger2WarningLevel1 = warning1; 196 | this.dagger2WarningLevel2 = warning2; 197 | this.dagger2WarningLevel3 = warning3; 198 | } 199 | 200 | return this; 201 | } 202 | 203 | public Builder frameDropsLimits(int measureIntervalMillis, double maxFpsForFrameDrop) { 204 | if (maxFpsForFrameDrop > 60) { 205 | throw new IllegalArgumentException("Max fps cannot be bigger than 60fps"); 206 | } 207 | 208 | this.intervalMillis = measureIntervalMillis; 209 | this.maxFpsForFrameDrop = maxFpsForFrameDrop; 210 | return this; 211 | } 212 | 213 | public Builder enableActivityMetrics(boolean enable) { 214 | this.enableAcitivtyMetrics = enable; 215 | return this; 216 | } 217 | 218 | public Builder showNotification(boolean show) { 219 | this.showNotification = show; 220 | return this; 221 | } 222 | 223 | public Builder enableDagger2Metrics(boolean enable) { 224 | this.enableDagger2Metrics = enable; 225 | return this; 226 | } 227 | 228 | public Builder autoCancelNotification(boolean autoCancelNotification) { 229 | this.autoCancelNotification = autoCancelNotification; 230 | return this; 231 | } 232 | 233 | public Builder addUIInterceptor(UIInterceptor interceptor) { 234 | this.interceptors.add(interceptor); 235 | return this; 236 | } 237 | 238 | public AndroidDevMetrics build() { 239 | AndroidDevMetrics androidDevMetrics = new AndroidDevMetrics(context); 240 | androidDevMetrics.dagger2WarningLevel1 = this.dagger2WarningLevel1; 241 | androidDevMetrics.dagger2WarningLevel2 = this.dagger2WarningLevel2; 242 | androidDevMetrics.dagger2WarningLevel3 = this.dagger2WarningLevel3; 243 | androidDevMetrics.enableAcitivtyMetrics = this.enableAcitivtyMetrics; 244 | androidDevMetrics.showNotification = this.showNotification; 245 | androidDevMetrics.enableDagger2Metrics = this.enableDagger2Metrics; 246 | androidDevMetrics.maxFpsForFrameDrop = this.maxFpsForFrameDrop; 247 | androidDevMetrics.intervalMillis = this.intervalMillis; 248 | androidDevMetrics.autoCancelNotification = this.autoCancelNotification; 249 | androidDevMetrics.interceptors = this.interceptors; 250 | return androidDevMetrics; 251 | } 252 | } 253 | 254 | 255 | } 256 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/aspect/ActivityLifecycleAnalyzer.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.aspect; 2 | 3 | import android.app.Activity; 4 | import android.os.Debug; 5 | import android.os.Environment; 6 | import android.support.v4.app.FragmentActivity; 7 | 8 | import com.frogermcs.androiddevmetrics.internal.MethodsTracingManager; 9 | import com.frogermcs.androiddevmetrics.internal.metrics.ActivityLifecycleMetrics; 10 | 11 | import org.aspectj.lang.ProceedingJoinPoint; 12 | import org.aspectj.lang.annotation.Around; 13 | import org.aspectj.lang.annotation.Aspect; 14 | import org.aspectj.lang.annotation.Pointcut; 15 | import org.aspectj.lang.reflect.MethodSignature; 16 | 17 | /** 18 | * Created by Miroslaw Stanek on 28.02.2016. 19 | */ 20 | @Aspect 21 | public class ActivityLifecycleAnalyzer { 22 | private static volatile boolean enabled = true; 23 | 24 | public static void setEnabled(boolean enabled) { 25 | ActivityLifecycleAnalyzer.enabled = enabled; 26 | } 27 | 28 | public static boolean isEnabled() { 29 | return enabled; 30 | } 31 | 32 | public static final String METHOD_ON_CREATE = "onCreate"; 33 | public static final String METHOD_ON_START = "onStart"; 34 | public static final String METHOD_ON_RESUME = "onResume"; 35 | 36 | @Pointcut("execution(void *.onCreate(..)) && this(android.app.Activity+)") 37 | public void onCreateMethod() { 38 | } 39 | 40 | @Pointcut("execution(void *.onStart(..)) && this(android.app.Activity+)") 41 | public void onStartMethod() { 42 | } 43 | 44 | @Pointcut("execution(void *.onResume(..)) && this(android.app.Activity+)") 45 | public void onResumeMethod() { 46 | } 47 | 48 | @Around("onCreateMethod() || onStartMethod() || onResumeMethod()") 49 | public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable { 50 | if (!enabled) return joinPoint.proceed(); 51 | 52 | MethodSignature signature = (MethodSignature) joinPoint.getSignature(); 53 | String methodName = signature.getMethod().getName(); 54 | 55 | final Object result; 56 | if (METHOD_ON_RESUME.equals(methodName)) { 57 | ActivityLifecycleMetrics.getInstance().logPreOnResume((Activity) joinPoint.getTarget()); 58 | result = executeWithTracingIfEnabled(joinPoint, methodName); 59 | ActivityLifecycleMetrics.getInstance().logPostOnResume((Activity) joinPoint.getTarget()); 60 | } else if (METHOD_ON_START.equals(methodName)) { 61 | ActivityLifecycleMetrics.getInstance().logPreOnStart((Activity) joinPoint.getTarget()); 62 | result = executeWithTracingIfEnabled(joinPoint, methodName); 63 | ActivityLifecycleMetrics.getInstance().logPostOnStart((Activity) joinPoint.getTarget()); 64 | } else if (METHOD_ON_CREATE.equals(methodName)) { 65 | ActivityLifecycleMetrics.getInstance().logPreOnCreate((Activity) joinPoint.getTarget()); 66 | result = executeWithTracingIfEnabled(joinPoint, methodName); 67 | ActivityLifecycleMetrics.getInstance().logPostOnCreate((Activity) joinPoint.getTarget()); 68 | } else { 69 | result = null; 70 | } 71 | 72 | return result; 73 | } 74 | 75 | private Object executeWithTracingIfEnabled(ProceedingJoinPoint joinPoint, String methodName) throws Throwable { 76 | final String targetName = joinPoint.getTarget().getClass().getName(); 77 | if (MethodsTracingManager.getInstance().shouldTraceMethod(targetName, methodName)) { 78 | MethodsTracingManager.getInstance().disableMethodTracing(targetName, methodName); 79 | String traceName = "/sdcard/" + joinPoint.getTarget().getClass().getSimpleName() + methodName + ".trace"; 80 | MethodsTracingManager.getInstance().addTracedMethod(traceName); 81 | 82 | Debug.startMethodTracing(traceName); 83 | Object result = joinPoint.proceed(); 84 | Debug.stopMethodTracing(); 85 | 86 | return result; 87 | } else { 88 | return joinPoint.proceed(); 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/aspect/Dagger2GraphAnalyzer.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.aspect; 2 | 3 | import com.frogermcs.androiddevmetrics.internal.metrics.InitManager; 4 | 5 | import org.aspectj.lang.ProceedingJoinPoint; 6 | import org.aspectj.lang.annotation.Around; 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.aspectj.lang.annotation.Pointcut; 9 | import org.aspectj.lang.reflect.CodeSignature; 10 | import org.aspectj.lang.reflect.ConstructorSignature; 11 | import org.aspectj.lang.reflect.MethodSignature; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | 15 | @Aspect 16 | public class Dagger2GraphAnalyzer { 17 | private static volatile boolean enabled = false; 18 | 19 | public static void setEnabled(boolean enabled) { 20 | Dagger2GraphAnalyzer.enabled = enabled; 21 | } 22 | 23 | public static boolean isEnabled() { 24 | return enabled; 25 | } 26 | 27 | @Pointcut("within(@dagger.Module *)") 28 | public void withinAnnotatedClass() { 29 | } 30 | 31 | @Pointcut("execution(@javax.inject.Inject *.new(..))") 32 | public void injectConstructor() { 33 | } 34 | 35 | //Exclude methods from *_MonitoringModule (Dagger 2 producers) 36 | @Pointcut("!execution(* defaultSetOfFactories())") 37 | public void exceprDefaultSetOfFactories() { 38 | } 39 | 40 | @Pointcut("!execution(dagger.producers.monitoring.ProductionComponentMonitor *(..))") 41 | public void exceptProductionComponentMonitor() { 42 | } 43 | 44 | @Pointcut("execution(@dagger.Provides * *(..)) &&" + 45 | " withinAnnotatedClass() &&" + 46 | " exceptProductionComponentMonitor()" + 47 | " && exceprDefaultSetOfFactories()") 48 | public void providesMethod() { 49 | } 50 | 51 | @Around("providesMethod() || injectConstructor()") 52 | public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable { 53 | long start = System.nanoTime(); 54 | Object result = joinPoint.proceed(); 55 | long stop = System.nanoTime(); 56 | long took = TimeUnit.NANOSECONDS.toMillis(stop - start); 57 | 58 | if (!enabled) { 59 | return result; 60 | } 61 | 62 | CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature(); 63 | Class cls = codeSignature.getDeclaringType(); 64 | 65 | if (codeSignature instanceof ConstructorSignature) { 66 | InitManager.getInstance().addInitMetric(cls, joinPoint.getArgs(), took); 67 | } 68 | 69 | if (isMethodWithReturnType(codeSignature)) { 70 | if (result != null) { 71 | InitManager.getInstance().addInitMetric(result.getClass(), joinPoint.getArgs(), took); 72 | } 73 | } 74 | 75 | return result; 76 | } 77 | 78 | private boolean isMethodWithReturnType(CodeSignature codeSignature) { 79 | return codeSignature instanceof MethodSignature && ((MethodSignature) codeSignature).getReturnType() != void.class; 80 | } 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ActivityMetricDescription.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal; 2 | 3 | import com.frogermcs.androiddevmetrics.aspect.ActivityLifecycleAnalyzer; 4 | import com.frogermcs.androiddevmetrics.internal.metrics.ActivityLifecycleMetrics; 5 | import com.frogermcs.androiddevmetrics.internal.metrics.model.FpsDropMetric; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by Miroslaw Stanek on 22.02.2016. 12 | */ 13 | public class ActivityMetricDescription { 14 | 15 | public String activityName; 16 | public String activitySimpleName; 17 | public int instancesCount; 18 | public int frameDropsCount; 19 | public float fpsDropsSum; 20 | public long activityLayoutTimeMillis; 21 | public long activityCreateMillis; 22 | public long activityStartMillis; 23 | public long activityResumeMillis; 24 | public boolean isLauncherActivity; 25 | public boolean hasOnCreateImplemented; 26 | public boolean hasOnStartImplemented; 27 | public boolean hasOnResumeImplemented; 28 | 29 | public static ActivityMetricDescription initFrom(ActivityLifecycleMetrics.ActivityLifecycleMetric activityMetrics) { 30 | ActivityMetricDescription activityMetricDescription = new ActivityMetricDescription(); 31 | activityMetricDescription.activityName = activityMetrics.activityClass.getName(); 32 | activityMetricDescription.activitySimpleName = activityMetrics.activityClass.getSimpleName(); 33 | activityMetricDescription.instancesCount = 1; 34 | activityMetricDescription.frameDropsCount = 0; 35 | activityMetricDescription.fpsDropsSum = 0; 36 | activityMetricDescription.isLauncherActivity = activityMetrics.isFirstActivity; 37 | activityMetricDescription.activityLayoutTimeMillis = activityMetrics.visibleTimeMillis(); 38 | activityMetricDescription.activityCreateMillis = activityMetrics.createTimeMillis(); 39 | activityMetricDescription.activityStartMillis = activityMetrics.startTimeMillis(); 40 | activityMetricDescription.activityResumeMillis = activityMetrics.resumeTimeMillis(); 41 | activityMetricDescription.hasOnCreateImplemented = activityMetrics.hasOnCreateImplemented; 42 | activityMetricDescription.hasOnStartImplemented = activityMetrics.hasOnStartImplemented; 43 | activityMetricDescription.hasOnResumeImplemented = activityMetrics.hasOnResumeImplemented; 44 | return activityMetricDescription; 45 | } 46 | 47 | public void updateWith(ActivityLifecycleMetrics.ActivityLifecycleMetric activityMetrics) { 48 | activityLayoutTimeMillis = ((activityLayoutTimeMillis * instancesCount) + activityMetrics.visibleTimeMillis()) / (instancesCount + 1); 49 | activityCreateMillis = ((activityCreateMillis * instancesCount) + activityMetrics.createTimeMillis()) / (instancesCount + 1); 50 | activityStartMillis = ((activityStartMillis * instancesCount) + activityMetrics.startTimeMillis()) / (instancesCount + 1); 51 | activityResumeMillis = ((activityResumeMillis * instancesCount) + activityMetrics.resumeTimeMillis()) / (instancesCount + 1); 52 | instancesCount++; 53 | } 54 | 55 | public void updateWith(FpsDropMetric fpsDropMetric) { 56 | frameDropsCount += fpsDropMetric.dropsCount; 57 | fpsDropsSum += fpsDropMetric.averageFps * fpsDropMetric.dropsCount; 58 | } 59 | 60 | public long getOverallTimeMillis() { 61 | return activityLayoutTimeMillis + activityCreateMillis + activityStartMillis + activityResumeMillis; 62 | } 63 | 64 | public float getAverageFps() { 65 | return fpsDropsSum / frameDropsCount; 66 | } 67 | 68 | public String[] getImplementedMethods() { 69 | List implementedMethods = new ArrayList<>(); 70 | if (hasOnCreateImplemented) implementedMethods.add(ActivityLifecycleAnalyzer.METHOD_ON_CREATE); 71 | if (hasOnStartImplemented) implementedMethods.add(ActivityLifecycleAnalyzer.METHOD_ON_START); 72 | if (hasOnResumeImplemented) implementedMethods.add(ActivityLifecycleAnalyzer.METHOD_ON_RESUME); 73 | 74 | String[] implementedMethodsArray = new String[implementedMethods.size()]; 75 | implementedMethods.toArray(implementedMethodsArray); 76 | return implementedMethodsArray; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/MethodsTracingManager.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.support.annotation.NonNull; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | /** 13 | * Created by Miroslaw Stanek on 17.03.2016. 14 | */ 15 | public class MethodsTracingManager { 16 | 17 | private static final String PREFS_KEY_SCHEDULED_METHODS = "PREFS_KEY_SCHEDULED_METHODS"; 18 | 19 | private static class Holder { 20 | static final MethodsTracingManager INSTANCE = new MethodsTracingManager(); 21 | } 22 | 23 | public static MethodsTracingManager getInstance() { 24 | return Holder.INSTANCE; 25 | } 26 | 27 | private boolean isInitialized = false; 28 | private SharedPreferences sharedPreferences; 29 | private Set scheduledMethods; 30 | 31 | private Set tracedMethods; 32 | 33 | public void init(Context context) { 34 | this.isInitialized = true; 35 | sharedPreferences = context.getSharedPreferences("ADM_PREFS", Context.MODE_PRIVATE); 36 | scheduledMethods = sharedPreferences.getStringSet(PREFS_KEY_SCHEDULED_METHODS, new HashSet()); 37 | tracedMethods = new HashSet<>(); 38 | } 39 | 40 | public void scheduleMethodTracing(String activityName, String method) { 41 | checkInitialized(); 42 | 43 | scheduledMethods.add(methodKey(activityName, method)); 44 | updatePrefs(); 45 | } 46 | 47 | public void disableMethodTracing(String activityName, String method) { 48 | checkInitialized(); 49 | 50 | scheduledMethods.remove(methodKey(activityName, method)); 51 | updatePrefs(); 52 | } 53 | 54 | public void addTracedMethod(String methodName) { 55 | tracedMethods.add(methodName); 56 | } 57 | 58 | public String[] getTracedMethods() { 59 | if (tracedMethods.size() == 0) return null; 60 | 61 | String[] methodsArray = new String[tracedMethods.size()]; 62 | tracedMethods.toArray(methodsArray); 63 | tracedMethods.clear(); 64 | return methodsArray; 65 | } 66 | 67 | public void clearTracedMethods() { 68 | tracedMethods.clear(); 69 | } 70 | 71 | @NonNull 72 | private String methodKey(String activityName, String method) { 73 | return activityName + "|" + method; 74 | } 75 | 76 | private void updatePrefs() { 77 | sharedPreferences.edit() 78 | .clear() 79 | .putStringSet(PREFS_KEY_SCHEDULED_METHODS, scheduledMethods) 80 | .commit(); 81 | } 82 | 83 | public boolean shouldTraceMethod(String activityName, String method) { 84 | checkInitialized(); 85 | 86 | return scheduledMethods.contains(methodKey(activityName, method)); 87 | } 88 | 89 | private void checkInitialized() { 90 | if (!isInitialized) throw new RuntimeException("MethodsTracingManager must be initialized by init(..)"); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/MetricDescription.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal; 2 | 3 | import com.frogermcs.androiddevmetrics.internal.metrics.model.InitMetric; 4 | import com.frogermcs.androiddevmetrics.AndroidDevMetrics; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | /** 11 | * Created by Miroslaw Stanek on 25.01.2016. 12 | */ 13 | public class MetricDescription extends MetricDescriptionTreeItem { 14 | public final List descriptionTreeItems = new ArrayList<>(); 15 | 16 | public String className; 17 | public String formattedInitTime; 18 | 19 | public MetricDescription() { 20 | } 21 | 22 | public static MetricDescription InitFromMetric(InitMetric initMetric) { 23 | MetricDescription metricDescription = new MetricDescription(); 24 | metricDescription.className = initMetric.getSimpleClassName(); 25 | metricDescription.formatInitTime(initMetric.getTotalInitTime(), 26 | initMetric.getInitTimeWithoutArgs(), 27 | initMetric.getThreadName()); 28 | metricDescription.initDescriptionsTree(initMetric.args, 0, ""); 29 | return metricDescription; 30 | } 31 | 32 | private void formatInitTime(long overallInitTime, long noArgsInitTime, String threadName) { 33 | StringBuilder sb = new StringBuilder("Initialization: "); 34 | sb.append(noArgsInitTime); 35 | sb.append("ms, "); 36 | sb.append("with args: "); 37 | sb.append(overallInitTime); 38 | sb.append("ms"); 39 | sb.append(" (" + threadName + ")"); 40 | formattedInitTime = sb.toString(); 41 | warningLevel = getWarningLevel(noArgsInitTime); 42 | } 43 | 44 | private void initDescriptionsTree(Set initMetrics, int depthLevel, String prev) { 45 | if (depthLevel == 0 && initMetrics.size() == 0) { 46 | descriptionTreeItems.add(new MetricDescriptionTreeItem("No args or args initialized before", 0)); 47 | } 48 | int size = initMetrics.size(); 49 | int count = 0; 50 | for (InitMetric initMetric : initMetrics) { 51 | final long initTimeWithoutArgs = initMetric.getInitTimeWithoutArgs(); 52 | final long totalInitTime = initMetric.getTotalInitTime(); 53 | final int warningLevel = getWarningLevel(initTimeWithoutArgs); 54 | 55 | String depthStr = prev + "\u2502" + space(1); 56 | String edgeChar = "\u251c"; 57 | String secondRowChar = "\u2502"; 58 | if (count == size - 1) { 59 | edgeChar = "\u2514"; 60 | secondRowChar = space(1); 61 | depthStr = prev + space(2); 62 | } 63 | 64 | StringBuilder sb = new StringBuilder(prev); 65 | sb.append(edgeChar + "\u2500\u25CF "); 66 | sb.append(initMetric.getSimpleClassName()); 67 | sb.append("
"); 68 | sb.append(prev + secondRowChar + space(1)); 69 | sb.append(initTimeWithoutArgs); 70 | sb.append("ms, with args: "); 71 | sb.append(totalInitTime); 72 | sb.append("ms"); 73 | sb.append(" (" + initMetric.threadName + ")"); 74 | descriptionTreeItems.add(new MetricDescriptionTreeItem(sb.toString(), warningLevel)); 75 | if (initMetric.args.size() > 0) { 76 | initDescriptionsTree(initMetric.args, depthLevel + 1, depthStr); 77 | } 78 | 79 | count++; 80 | } 81 | } 82 | 83 | private int getWarningLevel(long initTimeWithoutArgs) { 84 | if (initTimeWithoutArgs < AndroidDevMetrics.singleton().dagger2WarningLevel1()) { 85 | return 0; 86 | } else if (initTimeWithoutArgs < AndroidDevMetrics.singleton().dagger2WarningLevel2()) { 87 | return 1; 88 | } else if (initTimeWithoutArgs < AndroidDevMetrics.singleton().dagger2WarningLevel3()) { 89 | return 2; 90 | } else { 91 | return 3; 92 | } 93 | } 94 | 95 | private String space(int count) { 96 | String spaceChar = "\u2000\u2006"; 97 | for (int i = 0; i < count - 1; i++) { 98 | spaceChar += spaceChar; 99 | } 100 | return spaceChar; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/MetricDescriptionTreeItem.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal; 2 | 3 | /** 4 | * Created by Miroslaw Stanek on 30.01.2016. 5 | */ 6 | public class MetricDescriptionTreeItem { 7 | public String description; 8 | public int warningLevel; 9 | 10 | public MetricDescriptionTreeItem() { 11 | } 12 | 13 | public MetricDescriptionTreeItem(String description) { 14 | this(description, 0); 15 | } 16 | 17 | public MetricDescriptionTreeItem(String description, int warningLevel) { 18 | this.description = description; 19 | this.warningLevel = warningLevel; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/metrics/ActivityLaunchMetrics.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.metrics; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | 7 | import com.frogermcs.androiddevmetrics.internal.MethodsTracingManager; 8 | import com.frogermcs.androiddevmetrics.internal.ui.dialog.MethodsTracingFinishedDialog; 9 | 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | /** 14 | * Created by Miroslaw Stanek on 06.02.2016. 15 | */ 16 | public class ActivityLaunchMetrics implements Application.ActivityLifecycleCallbacks { 17 | 18 | private static class Holder { 19 | static final ActivityLaunchMetrics INSTANCE = new ActivityLaunchMetrics(); 20 | } 21 | 22 | public static ActivityLaunchMetrics getInstance() { 23 | return Holder.INSTANCE; 24 | } 25 | 26 | public final Set dataListeners = new HashSet<>(); 27 | 28 | private String currentActivityName = "not_set"; 29 | private MethodsTracingManager methodsTracingManager; 30 | 31 | ActivityLaunchMetrics() { 32 | methodsTracingManager = MethodsTracingManager.getInstance(); 33 | } 34 | 35 | @Override 36 | public void onActivityCreated(final Activity activity, Bundle savedInstanceState) { 37 | ActivityLifecycleMetrics.getInstance().logPostOnCreate(activity); 38 | currentActivityName = activity.getClass().getSimpleName(); 39 | } 40 | 41 | @Override 42 | public void onActivityStarted(Activity activity) { 43 | ActivityLifecycleMetrics.getInstance().logPostOnStart(activity); 44 | } 45 | 46 | @Override 47 | public void onActivityResumed(Activity activity) { 48 | ActivityLifecycleMetrics.getInstance().logPostOnResume(activity); 49 | final String[] tracedMethods = methodsTracingManager.getTracedMethods(); 50 | if (tracedMethods != null) { 51 | MethodsTracingFinishedDialog dialog = MethodsTracingFinishedDialog.newInstance(tracedMethods); 52 | dialog.show(activity.getFragmentManager(), MethodsTracingFinishedDialog.TAG); 53 | methodsTracingManager.clearTracedMethods(); 54 | } 55 | } 56 | 57 | @Override 58 | public void onActivityPaused(Activity activity) { 59 | ActivityLifecycleMetrics.getInstance().logOnPaused(activity); 60 | } 61 | 62 | @Override 63 | public void onActivityStopped(Activity activity) { 64 | ActivityLifecycleMetrics.getInstance().logOnStopped(activity); 65 | } 66 | 67 | @Override 68 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 69 | } 70 | 71 | @Override 72 | public void onActivityDestroyed(Activity activity) { 73 | ActivityLifecycleMetrics.getInstance().logOnDestroyed(activity); 74 | } 75 | 76 | public void addMetricsDataListener(OnMetricsDataListener onDataListener) { 77 | dataListeners.add(onDataListener); 78 | } 79 | 80 | public void removeMetricsDataListener(OnMetricsDataListener onDataListener) { 81 | dataListeners.remove(onDataListener); 82 | } 83 | 84 | public String getCurrentActivityName() { 85 | return currentActivityName; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/metrics/ActivityLifecycleMetrics.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.metrics; 2 | 3 | import android.app.Activity; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.view.WindowCallbackWrapper; 6 | import android.view.Window; 7 | 8 | import com.frogermcs.androiddevmetrics.internal.ActivityMetricDescription; 9 | import com.frogermcs.androiddevmetrics.internal.metrics.model.FpsDropMetric; 10 | import com.frogermcs.androiddevmetrics.internal.ui.MetricsActivity; 11 | 12 | import java.util.ArrayList; 13 | import java.util.LinkedHashMap; 14 | import java.util.List; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | /** 18 | * Created by Miroslaw Stanek on 28.02.2016. 19 | */ 20 | public class ActivityLifecycleMetrics { 21 | private static class Holder { 22 | static final ActivityLifecycleMetrics INSTANCE = new ActivityLifecycleMetrics(); 23 | } 24 | 25 | public static ActivityLifecycleMetrics getInstance() { 26 | return Holder.INSTANCE; 27 | } 28 | 29 | public LinkedHashMap activityLifecycleMetricsMap = new LinkedHashMap<>(); 30 | 31 | public void logPreOnCreate(Activity activity) { 32 | if (isIgnoredActivity(activity)) return; 33 | 34 | int activityHash = activity.hashCode(); 35 | ActivityLifecycleMetric activityLifecycleMetric = activityLifecycleMetricsMap.get(activityHash); 36 | if (activityLifecycleMetric == null) { 37 | activityLifecycleMetric = setupNewActivityLifecycleMetric(activity, activityHash); 38 | } 39 | 40 | activityLifecycleMetric.state = ActivityLifecycleMetric.STATE_PRE_CREATED; 41 | activityLifecycleMetric.hasOnCreateImplemented = true; 42 | } 43 | 44 | public void logPostOnCreate(Activity activity) { 45 | if (isIgnoredActivity(activity)) return; 46 | 47 | int activityHash = activity.hashCode(); 48 | ActivityLifecycleMetric activityLifecycleMetric = activityLifecycleMetricsMap.get(activityHash); 49 | if (activityLifecycleMetric == null) { 50 | activityLifecycleMetric = setupNewActivityLifecycleMetric(activity, activityHash); 51 | } 52 | 53 | activityLifecycleMetric.postCreateNanoTime = System.nanoTime(); 54 | activityLifecycleMetric.state = ActivityLifecycleMetric.STATE_POST_CREATED; 55 | 56 | Window window = activity.getWindow(); 57 | window.setCallback(new MetricWindowCallbackWrapper(window.getCallback(), activityLifecycleMetric)); 58 | } 59 | 60 | @NonNull 61 | private ActivityLifecycleMetric setupNewActivityLifecycleMetric(Activity activity, int activityHash) { 62 | ActivityLifecycleMetric activityLifecycleMetric; 63 | activityLifecycleMetric = new ActivityLifecycleMetric(); 64 | activityLifecycleMetric.activityClass = activity.getClass(); 65 | activityLifecycleMetric.preCreateNanoTime = System.nanoTime(); 66 | activityLifecycleMetric.isFirstActivity = activityLifecycleMetricsMap.size() == 0; 67 | activityLifecycleMetricsMap.put(activityHash, activityLifecycleMetric); 68 | return activityLifecycleMetric; 69 | } 70 | 71 | public void logPreOnStart(Activity activity) { 72 | if (isIgnoredActivity(activity)) return; 73 | 74 | int activityHash = activity.hashCode(); 75 | ActivityLifecycleMetric activityLifecycleMetric = activityLifecycleMetricsMap.get(activityHash); 76 | if (activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_POST_CREATED) { 77 | activityLifecycleMetric.preStartNanoTime = System.nanoTime(); 78 | activityLifecycleMetric.state = ActivityLifecycleMetric.STATE_PRE_STARTED; 79 | activityLifecycleMetric.hasOnStartImplemented = true; 80 | } 81 | } 82 | 83 | public void logPostOnStart(Activity activity) { 84 | if (isIgnoredActivity(activity)) return; 85 | 86 | int activityHash = activity.hashCode(); 87 | ActivityLifecycleMetric activityLifecycleMetric = activityLifecycleMetricsMap.get(activityHash); 88 | if (activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_POST_CREATED 89 | || activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_PRE_STARTED 90 | || activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_POST_STARTED) { 91 | if (activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_POST_CREATED) { 92 | activityLifecycleMetric.preStartNanoTime = System.nanoTime(); 93 | } 94 | activityLifecycleMetric.postStartNanoTime = System.nanoTime(); 95 | activityLifecycleMetric.state = ActivityLifecycleMetric.STATE_POST_STARTED; 96 | } 97 | } 98 | 99 | public void logPreOnResume(Activity activity) { 100 | if (isIgnoredActivity(activity)) return; 101 | 102 | int activityHash = activity.hashCode(); 103 | ActivityLifecycleMetric activityLifecycleMetric = activityLifecycleMetricsMap.get(activityHash); 104 | if (activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_POST_STARTED) { 105 | activityLifecycleMetric.preResumeNanoTime = System.nanoTime(); 106 | activityLifecycleMetric.state = ActivityLifecycleMetric.STATE_PRE_RESUMED; 107 | activityLifecycleMetric.hasOnResumeImplemented = true; 108 | } 109 | } 110 | 111 | public void logPostOnResume(Activity activity) { 112 | if (isIgnoredActivity(activity)) return; 113 | 114 | int activityHash = activity.hashCode(); 115 | ActivityLifecycleMetric activityLifecycleMetric = activityLifecycleMetricsMap.get(activityHash); 116 | if (activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_POST_STARTED 117 | || activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_PRE_RESUMED 118 | || activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_POST_RESUMED) { 119 | if (activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_POST_STARTED) { 120 | activityLifecycleMetric.preResumeNanoTime = System.nanoTime(); 121 | } 122 | activityLifecycleMetric.postResumeNanoTime = System.nanoTime(); 123 | activityLifecycleMetric.state = ActivityLifecycleMetric.STATE_POST_RESUMED; 124 | } 125 | } 126 | 127 | public void logOnPaused(Activity activity) { 128 | if (isIgnoredActivity(activity)) return; 129 | 130 | updateActivityMetricState(activity, ActivityLifecycleMetric.STATE_PAUSED); 131 | } 132 | 133 | public void logOnStopped(Activity activity) { 134 | if (isIgnoredActivity(activity)) return; 135 | 136 | updateActivityMetricState(activity, ActivityLifecycleMetric.STATE_STOPPED); 137 | } 138 | 139 | public void logOnDestroyed(Activity activity) { 140 | if (isIgnoredActivity(activity)) return; 141 | 142 | updateActivityMetricState(activity, ActivityLifecycleMetric.STATE_DESTROYED); 143 | } 144 | 145 | private void updateActivityMetricState(Activity activity, int state) { 146 | int activityHash = activity.hashCode(); 147 | ActivityLifecycleMetric activityLifecycleMetric = activityLifecycleMetricsMap.get(activityHash); 148 | if (activityLifecycleMetric != null) { 149 | activityLifecycleMetric.state = state; 150 | } 151 | } 152 | 153 | public boolean isIgnoredActivity(Activity activity) { 154 | return activity instanceof MetricsActivity; 155 | } 156 | 157 | public List getListOfMetricDescriptions() { 158 | LinkedHashMap activityMetricDescriptions = new LinkedHashMap<>(); 159 | 160 | for (ActivityLifecycleMetric activityLifecycleMetric : activityLifecycleMetricsMap.values()) { 161 | ActivityMetricDescription activityMetricDescription = activityMetricDescriptions.get(activityLifecycleMetric.activityClass.getSimpleName()); 162 | if (activityMetricDescription == null) { 163 | activityMetricDescription = ActivityMetricDescription.initFrom(activityLifecycleMetric); 164 | activityMetricDescriptions.put(activityLifecycleMetric.activityClass.getSimpleName(), activityMetricDescription); 165 | } else { 166 | activityMetricDescription.updateWith(activityLifecycleMetric); 167 | } 168 | } 169 | 170 | ChoreographerMetrics.getInstance().collectDropsIfAny(); 171 | for (FpsDropMetric fpsDropMetric : ChoreographerMetrics.getInstance().dropMetricsList) { 172 | ActivityMetricDescription activityMetricDescription = activityMetricDescriptions.get(fpsDropMetric.activityName); 173 | if (activityMetricDescription != null) { 174 | activityMetricDescription.updateWith(fpsDropMetric); 175 | } 176 | } 177 | 178 | return new ArrayList<>(activityMetricDescriptions.values()); 179 | } 180 | 181 | public static class ActivityLifecycleMetric { 182 | public static final int STATE_NEW = 0; 183 | public static final int STATE_PRE_CREATED = 1; 184 | public static final int STATE_POST_CREATED = 2; 185 | public static final int STATE_PRE_STARTED = 3; 186 | public static final int STATE_POST_STARTED = 4; 187 | public static final int STATE_PRE_RESUMED = 5; 188 | public static final int STATE_POST_RESUMED = 6; 189 | public static final int STATE_VISIBLE = 7; 190 | public static final int STATE_PAUSED = 8; 191 | public static final int STATE_STOPPED = 9; 192 | public static final int STATE_DESTROYED = 10; 193 | 194 | public Class activityClass; 195 | public long preCreateNanoTime; 196 | public long postCreateNanoTime; 197 | public long preStartNanoTime; 198 | public long postStartNanoTime; 199 | public long preResumeNanoTime; 200 | public long postResumeNanoTime; 201 | public long viewVisibleToUserNanoTime; 202 | public int state = STATE_NEW; 203 | public boolean isFirstActivity = false; 204 | public boolean hasOnCreateImplemented = false; 205 | public boolean hasOnStartImplemented = false; 206 | public boolean hasOnResumeImplemented = false; 207 | 208 | public long createTimeMillis() { 209 | if (state > STATE_POST_CREATED) { 210 | return TimeUnit.NANOSECONDS.toMillis(postCreateNanoTime - preCreateNanoTime); 211 | } else { 212 | return -1; 213 | } 214 | } 215 | 216 | public long startTimeMillis() { 217 | if (state > STATE_POST_STARTED) { 218 | return TimeUnit.NANOSECONDS.toMillis(postStartNanoTime - preStartNanoTime); 219 | } else { 220 | return -1; 221 | } 222 | } 223 | 224 | public long resumeTimeMillis() { 225 | if (state > STATE_POST_RESUMED) { 226 | return TimeUnit.NANOSECONDS.toMillis(postResumeNanoTime - preResumeNanoTime); 227 | } else { 228 | return -1; 229 | } 230 | } 231 | 232 | public long visibleTimeMillis() { 233 | if (state >= STATE_VISIBLE) { 234 | return TimeUnit.NANOSECONDS.toMillis(viewVisibleToUserNanoTime - postResumeNanoTime); 235 | } else { 236 | return -1; 237 | } 238 | } 239 | 240 | @Override 241 | public String toString() { 242 | return "ActivityLifecycleMetric{" + 243 | "activityClass=" + activityClass + 244 | ",\nCreateTimeMillis=" + createTimeMillis() + 245 | ",\nStartTimeMillis=" + startTimeMillis() + 246 | ",\nResumeTimeMillis=" + resumeTimeMillis() + 247 | ",\nviewVisibleToUserNanoTime=" + visibleTimeMillis() + 248 | ",\nstate=" + state + 249 | '}'; 250 | } 251 | } 252 | 253 | public static class MetricWindowCallbackWrapper extends WindowCallbackWrapper { 254 | 255 | private ActivityLifecycleMetric activityLifecycleMetric; 256 | 257 | public MetricWindowCallbackWrapper(Window.Callback wrapped, ActivityLifecycleMetric activityLifecycleMetric) { 258 | super(wrapped); 259 | this.activityLifecycleMetric = activityLifecycleMetric; 260 | } 261 | 262 | @Override 263 | public void onWindowFocusChanged(boolean hasFocus) { 264 | super.onWindowFocusChanged(hasFocus); 265 | if (activityLifecycleMetric.state == ActivityLifecycleMetric.STATE_POST_RESUMED && hasFocus) { 266 | activityLifecycleMetric.viewVisibleToUserNanoTime = System.nanoTime(); 267 | activityLifecycleMetric.state = ActivityLifecycleMetric.STATE_VISIBLE; 268 | } 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/metrics/ChoreographerMetrics.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.metrics; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import android.view.Choreographer; 6 | 7 | import com.frogermcs.androiddevmetrics.internal.metrics.model.FpsDropMetric; 8 | 9 | import java.util.ArrayList; 10 | import java.util.HashSet; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | import java.util.Set; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | * Created by Miroslaw Stanek on 16.02.2016. 18 | */ 19 | public class ChoreographerMetrics { 20 | 21 | private static class Holder { 22 | static final ChoreographerMetrics INSTANCE = new ChoreographerMetrics(); 23 | } 24 | 25 | public static ChoreographerMetrics getInstance() { 26 | return Holder.INSTANCE; 27 | } 28 | 29 | public final List dropMetricsList = new ArrayList<>(); 30 | public final Set dataListeners = new HashSet<>(); 31 | 32 | private Choreographer choreographer; 33 | 34 | private long frameStartTime = 0; 35 | private int framesRendered = 0; 36 | 37 | private int intervalMillis; 38 | private double maxFpsForFrameDrop; 39 | 40 | private int dropIntervalMillis = 1000 * 10; 41 | private long firstDropRegistered = 0; 42 | 43 | private List tempDrops = new LinkedList<>(); 44 | private String currentActivityName; 45 | private FrameDropsMetrics frameDropsMetrics; 46 | 47 | ChoreographerMetrics() { 48 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 49 | choreographer = Choreographer.getInstance(); 50 | frameDropsMetrics = new FrameDropsMetrics(); 51 | } 52 | } 53 | 54 | public void start() { 55 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 56 | choreographer.postFrameCallback(frameDropsMetrics); 57 | } 58 | } 59 | 60 | public void stop() { 61 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 62 | frameStartTime = 0; 63 | framesRendered = 0; 64 | choreographer.removeFrameCallback(frameDropsMetrics); 65 | } 66 | } 67 | 68 | public void setIntervalMillis(int interval) { 69 | this.intervalMillis = interval; 70 | } 71 | 72 | public void setMaxFpsForFrameDrop(double fps) { 73 | this.maxFpsForFrameDrop = fps; 74 | } 75 | 76 | private void onDropRegistered(double fps, long registeredTime) { 77 | String activityName = ActivityLaunchMetrics.getInstance().getCurrentActivityName(); 78 | tempDrops.add(fps); 79 | if (firstDropRegistered == 0) { 80 | firstDropRegistered = registeredTime; 81 | currentActivityName = activityName; 82 | } 83 | 84 | long dropTimeSpan = registeredTime - firstDropRegistered; 85 | if (dropTimeSpan > dropIntervalMillis || !activityName.equals(currentActivityName)) { 86 | collectDropsIfAny(); 87 | currentActivityName = activityName; 88 | } 89 | 90 | } 91 | 92 | public void collectDropsIfAny() { 93 | if (tempDrops.size() > 0) { 94 | FpsDropMetric dropMetric = FpsDropMetric.fromDrops(tempDrops, currentActivityName); 95 | firstDropRegistered = 0; 96 | broadcastNewDropMetrics(dropMetric); 97 | dropMetricsList.add(dropMetric); 98 | tempDrops.clear(); 99 | } 100 | } 101 | 102 | private void broadcastNewDropMetrics(FpsDropMetric dropMetric) { 103 | for (OnMetricsDataListener dataListener : dataListeners) { 104 | dataListener.onFrameDropRegistered(dropMetric); 105 | } 106 | } 107 | 108 | public void addMetricsDataListener(OnMetricsDataListener onDataListener) { 109 | dataListeners.add(onDataListener); 110 | } 111 | 112 | public void removeMetricsDataListener(OnMetricsDataListener onDataListener) { 113 | dataListeners.remove(onDataListener); 114 | } 115 | 116 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 117 | private class FrameDropsMetrics implements Choreographer.FrameCallback { 118 | @Override 119 | public void doFrame(long frameTimeNanos) { 120 | long currentTimeMillis = TimeUnit.NANOSECONDS.toMillis(frameTimeNanos); 121 | 122 | if (frameStartTime > 0) { 123 | final long timeSpan = currentTimeMillis - frameStartTime; 124 | framesRendered++; 125 | 126 | if (timeSpan > intervalMillis) { 127 | double fps = framesRendered * 1000 / (double) timeSpan; 128 | frameStartTime = currentTimeMillis; 129 | framesRendered = 0; 130 | if (fps < maxFpsForFrameDrop) { 131 | onDropRegistered(fps, currentTimeMillis); 132 | } 133 | } 134 | 135 | if (firstDropRegistered > 0) { 136 | long dropIntervalTimeSpan = currentTimeMillis - firstDropRegistered; 137 | if (dropIntervalTimeSpan > dropIntervalMillis) { 138 | collectDropsIfAny(); 139 | } 140 | } 141 | } else { 142 | frameStartTime = currentTimeMillis; 143 | } 144 | 145 | choreographer.postFrameCallback(this); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/metrics/InitManager.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.metrics; 2 | 3 | import com.frogermcs.androiddevmetrics.internal.MetricDescription; 4 | import com.frogermcs.androiddevmetrics.internal.metrics.model.InitMetric; 5 | import com.frogermcs.androiddevmetrics.internal.ui.interceptor.UIInterceptor; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashSet; 9 | import java.util.LinkedHashMap; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | /** 14 | * Created by Miroslaw Stanek on 24.01.2016. 15 | */ 16 | public class InitManager { 17 | private static class Holder { 18 | static final InitManager INSTANCE = new InitManager(); 19 | } 20 | 21 | public static InitManager getInstance() { 22 | return Holder.INSTANCE; 23 | } 24 | 25 | public final Set dataListeners = new HashSet<>(); 26 | 27 | public final LinkedHashMap initializedMetrics = new LinkedHashMap<>(); 28 | public final LinkedHashMap initCounter = new LinkedHashMap<>(); 29 | 30 | public void addInitMetric(Class initializedClass, Object[] args, long initTimeMillis) { 31 | InitMetric initMetric = new InitMetric(); 32 | initMetric.initTimeMillis = initTimeMillis; 33 | initMetric.cls = initializedClass; 34 | initMetric.threadName = Thread.currentThread().getName(); 35 | initMetric.traceElements = Thread.currentThread().getStackTrace(); 36 | 37 | String simpleName = initializedClass.getName(); 38 | if (!initializedMetrics.containsKey(simpleName)) { 39 | putInitMetric(simpleName, initMetric); 40 | int argsLength = args.length; 41 | for (int i = 0; i < argsLength; i++) { 42 | if (args[i] == null) continue; 43 | String argClassSimpleName = args[i].getClass().getName(); 44 | InitMetric argMethics = initializedMetrics.get(argClassSimpleName); 45 | if (argMethics != null) { 46 | initMetric.args.add(argMethics); 47 | initializedMetrics.remove(argClassSimpleName); 48 | } 49 | } 50 | initMetric.instanceNo = 0; 51 | initCounter.put(simpleName, 0); 52 | } else { 53 | int counterVal = initCounter.get(simpleName) + 1; 54 | initCounter.put(simpleName, counterVal); 55 | initMetric.instanceNo = counterVal; 56 | putInitMetric(simpleName + "#" + counterVal, initMetric); 57 | } 58 | } 59 | 60 | private void putInitMetric(String key, InitMetric initMetric) { 61 | initializedMetrics.put(key, initMetric); 62 | for (OnMetricsDataListener dataListener : dataListeners) { 63 | dataListener.onInitNewMetricRecorded(initMetric); 64 | } 65 | } 66 | 67 | public List getListOfMetricDescriptions(UIInterceptor interceptor) { 68 | List metricDescriptions = new ArrayList<>(); 69 | List displayList = interceptor.intercept(new ArrayList<>(InitManager.getInstance().initializedMetrics.values())); 70 | for (InitMetric initMetric : displayList) { 71 | metricDescriptions.add(MetricDescription.InitFromMetric(initMetric)); 72 | } 73 | return metricDescriptions; 74 | } 75 | 76 | public void addMetricsDataListener(OnMetricsDataListener onDataListener) { 77 | dataListeners.add(onDataListener); 78 | } 79 | 80 | public void removeMetricsDataListener(OnMetricsDataListener onDataListener) { 81 | dataListeners.remove(onDataListener); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/metrics/OnMetricsDataListener.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.metrics; 2 | 3 | import com.frogermcs.androiddevmetrics.internal.metrics.model.FpsDropMetric; 4 | import com.frogermcs.androiddevmetrics.internal.metrics.model.InitMetric; 5 | 6 | /** 7 | * Created by Miroslaw Stanek on 06.02.2016. 8 | */ 9 | public interface OnMetricsDataListener { 10 | void onInitNewMetricRecorded(InitMetric initMetric); 11 | void onFrameDropRegistered(FpsDropMetric fpsDropMetric); 12 | } 13 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/metrics/model/FpsDropMetric.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.metrics.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by Miroslaw Stanek on 17.02.2016. 7 | */ 8 | public class FpsDropMetric { 9 | public double averageFps; 10 | public int dropsCount; 11 | public String activityName; 12 | 13 | public static FpsDropMetric oneDrop(double fps) { 14 | FpsDropMetric fpsDropMetric = new FpsDropMetric(); 15 | fpsDropMetric.averageFps = fps; 16 | fpsDropMetric.dropsCount = 1; 17 | return fpsDropMetric; 18 | } 19 | 20 | public static FpsDropMetric fromDrops(List drops, String currentActivityName) { 21 | double averageFps = 0; 22 | int size = drops.size(); 23 | for (int i = 0; i < size; i++) { 24 | averageFps += drops.get(i); 25 | } 26 | averageFps /= size; 27 | 28 | FpsDropMetric fpsDropMetric = new FpsDropMetric(); 29 | fpsDropMetric.averageFps = averageFps; 30 | fpsDropMetric.dropsCount = drops.size(); 31 | fpsDropMetric.activityName = currentActivityName; 32 | return fpsDropMetric; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "FpsDropMetric{" + 38 | "averageFps=" + averageFps + 39 | ", dropsCount=" + dropsCount + 40 | '}'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/metrics/model/InitMetric.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.metrics.model; 2 | 3 | import java.lang.reflect.Proxy; 4 | import java.util.Arrays; 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | /** 9 | * Created by Miroslaw Stanek on 23.01.2016. 10 | */ 11 | public class InitMetric { 12 | 13 | public Class cls; 14 | public long initTimeMillis = 0; 15 | public int instanceNo = 0; 16 | public String threadName = ""; 17 | public Set args = new HashSet<>(); 18 | public StackTraceElement[] traceElements; 19 | 20 | public long getTotalInitTime() { 21 | long total = initTimeMillis; 22 | for (InitMetric initMetric : args) { 23 | total += initMetric.getTotalInitTime(); 24 | } 25 | return total; 26 | } 27 | 28 | public long getInitTimeWithoutArgs() { 29 | return initTimeMillis; 30 | } 31 | 32 | public String getSimpleClassName() { 33 | String className; 34 | if (Proxy.isProxyClass(cls)) { 35 | final Class[] interfaces = cls.getInterfaces(); 36 | if (interfaces.length == 1) { 37 | className = interfaces[0].getName(); 38 | } else { 39 | className = Arrays.asList(interfaces).toString(); 40 | } 41 | } else { 42 | className = cls.getName(); 43 | } 44 | 45 | int dot = className.lastIndexOf('.'); 46 | if (dot != -1) { 47 | className = className.substring(dot + 1); 48 | } 49 | 50 | if (instanceNo > 0) { 51 | return className + "#" + Integer.toString(instanceNo); 52 | } 53 | return className; 54 | } 55 | 56 | public String getThreadName() { 57 | return threadName; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | if (Proxy.isProxyClass(cls)) { 63 | return "InitMetric{" + 64 | "initTimeMillis=" + initTimeMillis + 65 | ", cls=" + Arrays.asList(cls.getInterfaces()) + 66 | ", args=" + args + 67 | '}'; 68 | } else { 69 | return "InitMetric{" + 70 | "initTimeMillis=" + initTimeMillis + 71 | ", cls=" + cls.getName() + 72 | ", args=" + args + 73 | '}'; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/ExpandableActivitiesMetricsListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.BaseExpandableListAdapter; 7 | import android.widget.Button; 8 | import android.widget.TextView; 9 | 10 | import com.frogermcs.androiddevmetrics.R; 11 | import com.frogermcs.androiddevmetrics.internal.ActivityMetricDescription; 12 | import com.frogermcs.androiddevmetrics.internal.ui.fragment.ActivitiesMetricsFragment; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by Miroslaw Stanek on 30.01.2016. 19 | */ 20 | public class ExpandableActivitiesMetricsListAdapter extends BaseExpandableListAdapter { 21 | 22 | private final List metricDescriptionList = new ArrayList<>(); 23 | 24 | private ActivitiesMetricsFragment activitiesMetricsFragment; 25 | 26 | public ExpandableActivitiesMetricsListAdapter(ActivitiesMetricsFragment activitiesMetricsFragment) { 27 | this.activitiesMetricsFragment = activitiesMetricsFragment; 28 | } 29 | 30 | public void updateMetrics(List metricDescriptions) { 31 | metricDescriptionList.clear(); 32 | metricDescriptionList.addAll(metricDescriptions); 33 | } 34 | 35 | @Override 36 | public int getGroupCount() { 37 | return metricDescriptionList.size(); 38 | } 39 | 40 | @Override 41 | public int getChildrenCount(int groupPosition) { 42 | return 1; 43 | } 44 | 45 | @Override 46 | public ActivityMetricDescription getGroup(int groupPosition) { 47 | return metricDescriptionList.get(groupPosition); 48 | } 49 | 50 | @Override 51 | public ActivityMetricDescription getChild(int groupPosition, int childPosition) { 52 | return metricDescriptionList.get(groupPosition); 53 | } 54 | 55 | @Override 56 | public long getGroupId(int groupPosition) { 57 | return 0; 58 | } 59 | 60 | @Override 61 | public long getChildId(int groupPosition, int childPosition) { 62 | return 0; 63 | } 64 | 65 | @Override 66 | public boolean hasStableIds() { 67 | return false; 68 | } 69 | 70 | @Override 71 | public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { 72 | HeaderViewHolder viewHolder; 73 | if (convertView == null) { 74 | convertView = LayoutInflater.from(parent.getContext()).inflate(com.frogermcs.androiddevmetrics.R.layout.adm_list_item_activity_metrics_header, parent, false); 75 | viewHolder = new HeaderViewHolder(convertView); 76 | convertView.setTag(viewHolder); 77 | } else { 78 | viewHolder = (HeaderViewHolder) convertView.getTag(); 79 | } 80 | 81 | ActivityMetricDescription activityMetricDescription = getGroup(groupPosition); 82 | viewHolder.bindView(activityMetricDescription); 83 | return convertView; 84 | } 85 | 86 | @Override 87 | public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { 88 | DescriptionViewHolder viewHolder; 89 | if (convertView == null) { 90 | convertView = LayoutInflater.from(parent.getContext()).inflate(com.frogermcs.androiddevmetrics.R.layout.adm_list_item_activity_metrics_description, parent, false); 91 | viewHolder = new DescriptionViewHolder(convertView); 92 | convertView.setTag(viewHolder); 93 | } else { 94 | viewHolder = (DescriptionViewHolder) convertView.getTag(); 95 | } 96 | 97 | ActivityMetricDescription activityMetricDescription = getChild(groupPosition, childPosition); 98 | viewHolder.bindView(activityMetricDescription); 99 | return convertView; 100 | } 101 | 102 | @Override 103 | public boolean isChildSelectable(int groupPosition, int childPosition) { 104 | return false; 105 | } 106 | 107 | @Override 108 | public void onGroupCollapsed(int groupPosition) { 109 | super.onGroupCollapsed(groupPosition); 110 | } 111 | 112 | @Override 113 | public void onGroupExpanded(int groupPosition) { 114 | super.onGroupExpanded(groupPosition); 115 | } 116 | 117 | private class HeaderViewHolder { 118 | View root; 119 | TextView tvActivityName; 120 | TextView tvActivityDescription; 121 | 122 | public HeaderViewHolder(View view) { 123 | this.root = view; 124 | tvActivityName = (TextView) view.findViewById(com.frogermcs.androiddevmetrics.R.id.tvActivityName); 125 | tvActivityDescription = (TextView) view.findViewById(com.frogermcs.androiddevmetrics.R.id.tvActivityDescription); 126 | } 127 | 128 | public void bindView(ActivityMetricDescription activityMetricDescription) { 129 | tvActivityName.setText(activityMetricDescription.activitySimpleName); 130 | String frameDrops; 131 | if (activityMetricDescription.frameDropsCount == 0) { 132 | frameDrops = "No frame drops"; 133 | } else { 134 | frameDrops = String.format("Dropped frames %d", activityMetricDescription.frameDropsCount); 135 | } 136 | 137 | String subtitle = String.format("%s%s | %s", 138 | activityMetricDescription.isLauncherActivity ? "App launcher | " : "", 139 | frameDrops, 140 | "instances " + activityMetricDescription.instancesCount 141 | ); 142 | tvActivityDescription.setText(subtitle); 143 | } 144 | } 145 | 146 | private class DescriptionViewHolder { 147 | TextView tvOnCreateTime; 148 | TextView tvOnStartTime; 149 | TextView tvOnResumeTime; 150 | TextView tvOnLayoutTime; 151 | TextView tvOverallTime; 152 | TextView tvInstancesCount; 153 | TextView tvFrameDrops; 154 | Button btnScheduleMethodTracing; 155 | 156 | public DescriptionViewHolder(View view) { 157 | tvInstancesCount = (TextView) view.findViewById(R.id.tvInstancesCount); 158 | tvFrameDrops = (TextView) view.findViewById(R.id.tvFrameDrops); 159 | tvOnCreateTime = (TextView) view.findViewById(R.id.tvOnCreateTime); 160 | tvOnStartTime = (TextView) view.findViewById(R.id.tvOnStartTime); 161 | tvOnResumeTime = (TextView) view.findViewById(R.id.tvOnResumeTime); 162 | tvOnLayoutTime = (TextView) view.findViewById(R.id.tvOnLayoutTime); 163 | tvOverallTime = (TextView) view.findViewById(R.id.tvOverallTime); 164 | btnScheduleMethodTracing = (Button) view.findViewById(R.id.btnScheduleMethodTracing); 165 | btnScheduleMethodTracing.setOnClickListener(new View.OnClickListener() { 166 | @Override 167 | public void onClick(View v) { 168 | activitiesMetricsFragment.scheduleTracingForActivity(((ActivityMetricDescription)v.getTag())); 169 | } 170 | }); 171 | } 172 | 173 | public void bindView(ActivityMetricDescription activityMetricDescription) { 174 | tvInstancesCount.setText(Integer.toString(activityMetricDescription.instancesCount)); 175 | if (activityMetricDescription.frameDropsCount == 0) { 176 | tvFrameDrops.setText("No frame drops!"); 177 | } else { 178 | tvFrameDrops.setText(String.format("%d/%.2f fps", activityMetricDescription.frameDropsCount, activityMetricDescription.getAverageFps())); 179 | } 180 | tvOverallTime.setText(activityMetricDescription.getOverallTimeMillis() + "ms"); 181 | tvOnCreateTime.setText(activityMetricDescription.activityCreateMillis + "ms"); 182 | tvOnStartTime.setText(activityMetricDescription.activityStartMillis + "ms"); 183 | tvOnResumeTime.setText(activityMetricDescription.activityResumeMillis + "ms"); 184 | tvOnLayoutTime.setText(activityMetricDescription.activityLayoutTimeMillis + "ms"); 185 | btnScheduleMethodTracing.setTag(activityMetricDescription); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/ExpandableMetricsListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui; 2 | 3 | import android.content.res.Resources; 4 | import android.text.Html; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.BaseExpandableListAdapter; 9 | import android.widget.TextView; 10 | 11 | import com.frogermcs.androiddevmetrics.R; 12 | import com.frogermcs.androiddevmetrics.internal.MetricDescription; 13 | import com.frogermcs.androiddevmetrics.internal.MetricDescriptionTreeItem; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by Miroslaw Stanek on 30.01.2016. 20 | */ 21 | public class ExpandableMetricsListAdapter extends BaseExpandableListAdapter { 22 | 23 | private final List metricDescriptionList = new ArrayList<>(); 24 | 25 | public void updateMetrics(List metricDescriptions) { 26 | metricDescriptionList.clear(); 27 | metricDescriptionList.addAll(metricDescriptions); 28 | } 29 | 30 | @Override 31 | public int getGroupCount() { 32 | return metricDescriptionList.size(); 33 | } 34 | 35 | @Override 36 | public int getChildrenCount(int groupPosition) { 37 | return metricDescriptionList.get(groupPosition).descriptionTreeItems.size(); 38 | } 39 | 40 | @Override 41 | public MetricDescription getGroup(int groupPosition) { 42 | return metricDescriptionList.get(groupPosition); 43 | } 44 | 45 | @Override 46 | public MetricDescriptionTreeItem getChild(int groupPosition, int childPosition) { 47 | return metricDescriptionList.get(groupPosition).descriptionTreeItems.get(childPosition); 48 | } 49 | 50 | @Override 51 | public long getGroupId(int groupPosition) { 52 | return 0; 53 | } 54 | 55 | @Override 56 | public long getChildId(int groupPosition, int childPosition) { 57 | return 0; 58 | } 59 | 60 | @Override 61 | public boolean hasStableIds() { 62 | return false; 63 | } 64 | 65 | @Override 66 | public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { 67 | HeaderViewHolder viewHolder; 68 | if (convertView == null) { 69 | convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.adm_list_item_metrics_header, parent, false); 70 | viewHolder = new HeaderViewHolder(convertView); 71 | convertView.setTag(viewHolder); 72 | } else { 73 | viewHolder = (HeaderViewHolder) convertView.getTag(); 74 | } 75 | 76 | MetricDescription metricDescription = getGroup(groupPosition); 77 | viewHolder.bindView(metricDescription); 78 | return convertView; 79 | } 80 | 81 | @Override 82 | public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { 83 | DescriptionViewHolder viewHolder; 84 | if (convertView == null) { 85 | convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.adm_list_item_metrics_description, parent, false); 86 | viewHolder = new DescriptionViewHolder(convertView); 87 | convertView.setTag(viewHolder); 88 | } else { 89 | viewHolder = (DescriptionViewHolder) convertView.getTag(); 90 | } 91 | 92 | MetricDescriptionTreeItem metricDescription = getChild(groupPosition, childPosition); 93 | viewHolder.bindView(metricDescription); 94 | return convertView; 95 | } 96 | 97 | @Override 98 | public boolean isChildSelectable(int groupPosition, int childPosition) { 99 | return false; 100 | } 101 | 102 | @Override 103 | public void onGroupCollapsed(int groupPosition) { 104 | super.onGroupCollapsed(groupPosition); 105 | } 106 | 107 | @Override 108 | public void onGroupExpanded(int groupPosition) { 109 | super.onGroupExpanded(groupPosition); 110 | } 111 | 112 | private class HeaderViewHolder { 113 | View root; 114 | TextView tvClassName; 115 | TextView tvInitTime; 116 | 117 | public HeaderViewHolder(View view) { 118 | this.root = view; 119 | tvClassName = (TextView) view.findViewById(R.id.tvClassName); 120 | tvInitTime = (TextView) view.findViewById(R.id.tvInitTime); 121 | } 122 | 123 | public void bindView(MetricDescription metricDescription) { 124 | tvClassName.setText(metricDescription.className); 125 | tvInitTime.setText(Html.fromHtml(metricDescription.formattedInitTime)); 126 | 127 | final Resources resources = tvClassName.getContext().getResources(); 128 | if (metricDescription.warningLevel == 1) { 129 | root.setBackgroundResource(R.color.d2m_bg_warning_1); 130 | tvClassName.setTextColor(resources.getColor(R.color.d2m_font_warning_1_and_2)); 131 | tvInitTime.setTextColor(resources.getColor(R.color.d2m_font_warning_1_and_2)); 132 | } else if (metricDescription.warningLevel == 2) { 133 | root.setBackgroundResource(R.color.d2m_bg_warning_2); 134 | tvClassName.setTextColor(resources.getColor(R.color.d2m_font_warning_1_and_2)); 135 | tvInitTime.setTextColor(resources.getColor(R.color.d2m_font_warning_1_and_2)); 136 | } else if (metricDescription.warningLevel == 3) { 137 | root.setBackgroundResource(R.color.d2m_bg_warning_3); 138 | tvClassName.setTextColor(resources.getColor(R.color.d2m_font_warning_3)); 139 | tvInitTime.setTextColor(resources.getColor(R.color.d2m_font_warning_3)); 140 | } else { 141 | root.setBackgroundResource(R.color.d2m_transparent); 142 | tvInitTime.setTextColor(resources.getColor(R.color.d2m_font_default_description)); 143 | tvClassName.setTextColor(resources.getColor(R.color.d2m_font_default_title)); 144 | } 145 | } 146 | } 147 | 148 | private class DescriptionViewHolder { 149 | TextView tvTreeDescription; 150 | 151 | public DescriptionViewHolder(View view) { 152 | tvTreeDescription = (TextView) view.findViewById(R.id.tvTreeDescription); 153 | } 154 | 155 | public void bindView(MetricDescriptionTreeItem metricDescription) { 156 | final Resources resources = tvTreeDescription.getContext().getResources(); 157 | tvTreeDescription.setText(Html.fromHtml(metricDescription.description)); 158 | if (metricDescription.warningLevel == 1) { 159 | tvTreeDescription.setBackgroundResource(R.color.d2m_bg_warning_1); 160 | tvTreeDescription.setTextColor(resources.getColor(R.color.d2m_font_warning_1_and_2)); 161 | } else if (metricDescription.warningLevel == 2) { 162 | tvTreeDescription.setBackgroundResource(R.color.d2m_bg_warning_2); 163 | tvTreeDescription.setTextColor(resources.getColor(R.color.d2m_font_warning_1_and_2)); 164 | } else if (metricDescription.warningLevel == 3) { 165 | tvTreeDescription.setBackgroundResource(R.color.d2m_bg_warning_3); 166 | tvTreeDescription.setTextColor(resources.getColor(R.color.d2m_font_warning_3)); 167 | } else { 168 | tvTreeDescription.setBackgroundResource(R.color.d2m_transparent); 169 | tvTreeDescription.setTextColor(resources.getColor(R.color.d2m_font_default_description)); 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/MetricsActivity.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentActivity; 6 | import android.support.v4.app.FragmentPagerAdapter; 7 | import android.support.v4.view.ViewPager; 8 | import android.view.View; 9 | import android.widget.Button; 10 | 11 | import com.frogermcs.androiddevmetrics.R; 12 | import com.frogermcs.androiddevmetrics.internal.ui.fragment.ActivitiesMetricsFragment; 13 | import com.frogermcs.androiddevmetrics.internal.ui.fragment.Dagger2MetricsFragment; 14 | 15 | /** 16 | * Created by Miroslaw Stanek on 25.01.2016. 17 | */ 18 | public class MetricsActivity extends FragmentActivity { 19 | 20 | private Button btnActivities; 21 | private Button btnDagger2; 22 | private ViewPager vpMetrics; 23 | 24 | private ActivitiesMetricsFragment activitiesMetricsFragment; 25 | private Dagger2MetricsFragment dagger2MetricsFragment; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.adm_activity_metrics); 31 | btnActivities = (Button) findViewById(R.id.btnActivities); 32 | btnDagger2 = (Button) findViewById(R.id.btnDagger2); 33 | vpMetrics = (ViewPager) findViewById(R.id.vpMetrics); 34 | 35 | activitiesMetricsFragment = new ActivitiesMetricsFragment(); 36 | dagger2MetricsFragment = new Dagger2MetricsFragment(); 37 | 38 | FragmentPagerAdapter fragmentPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) { 39 | @Override 40 | public Fragment getItem(int position) { 41 | if (position == 0) { 42 | return activitiesMetricsFragment; 43 | } else if (position == 1) { 44 | return dagger2MetricsFragment; 45 | } 46 | 47 | return null; 48 | } 49 | 50 | @Override 51 | public int getCount() { 52 | return 2; 53 | } 54 | }; 55 | vpMetrics.setAdapter(fragmentPagerAdapter); 56 | vpMetrics.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 57 | @Override 58 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 59 | 60 | } 61 | 62 | @Override 63 | public void onPageSelected(int position) { 64 | if (position == 0) { 65 | activitiesPageSelected(); 66 | } else if (position == 1) { 67 | daggerPageSelected(); 68 | } 69 | } 70 | 71 | @Override 72 | public void onPageScrollStateChanged(int state) { 73 | 74 | } 75 | }); 76 | 77 | btnActivities.setOnClickListener(new View.OnClickListener() { 78 | @Override 79 | public void onClick(View v) { 80 | vpMetrics.setCurrentItem(0); 81 | } 82 | }); 83 | btnDagger2.setOnClickListener(new View.OnClickListener() { 84 | @Override 85 | public void onClick(View v) { 86 | vpMetrics.setCurrentItem(1); 87 | } 88 | }); 89 | 90 | activitiesPageSelected(); 91 | } 92 | 93 | private void activitiesPageSelected() { 94 | btnActivities.setSelected(true); 95 | btnDagger2.setSelected(false); 96 | } 97 | 98 | private void daggerPageSelected() { 99 | btnActivities.setSelected(false); 100 | btnDagger2.setSelected(true); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/dialog/ActivitiesMethodsPickerDialog.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui.dialog; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Dialog; 5 | import android.content.DialogInterface; 6 | import android.os.Bundle; 7 | import android.support.v4.app.DialogFragment; 8 | 9 | import com.frogermcs.androiddevmetrics.aspect.ActivityLifecycleAnalyzer; 10 | import com.frogermcs.androiddevmetrics.internal.ActivityMetricDescription; 11 | import com.frogermcs.androiddevmetrics.internal.MethodsTracingManager; 12 | 13 | /** 14 | * Created by Miroslaw Stanek on 17.03.2016. 15 | */ 16 | public class ActivitiesMethodsPickerDialog extends DialogFragment { 17 | public static final String TAG = "ActivitiesMethodsPickerDialog"; 18 | 19 | private static final String ARG_ACTIVITY_NAME = "ARG_ACTIVITY_NAME"; 20 | private static final String ARG_IMPLEMENTED_METHODS = "ARG_IMPLEMENTED_METHODS"; 21 | 22 | String[] items; 23 | boolean[] enabledItems; 24 | private String activityName; 25 | 26 | public static ActivitiesMethodsPickerDialog newInstance(ActivityMetricDescription activityMetricDescription) { 27 | ActivitiesMethodsPickerDialog dialog = new ActivitiesMethodsPickerDialog(); 28 | Bundle args = new Bundle(); 29 | args.putString(ARG_ACTIVITY_NAME, activityMetricDescription.activityName); 30 | args.putStringArray(ARG_IMPLEMENTED_METHODS, activityMetricDescription.getImplementedMethods()); 31 | dialog.setArguments(args); 32 | return dialog; 33 | } 34 | 35 | @Override 36 | public void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | activityName = getArguments().getString(ARG_ACTIVITY_NAME); 39 | items = getArguments().getStringArray(ARG_IMPLEMENTED_METHODS); 40 | enabledItems = new boolean[]{ 41 | MethodsTracingManager.getInstance().shouldTraceMethod(activityName, ActivityLifecycleAnalyzer.METHOD_ON_CREATE), 42 | MethodsTracingManager.getInstance().shouldTraceMethod(activityName, ActivityLifecycleAnalyzer.METHOD_ON_START), 43 | MethodsTracingManager.getInstance().shouldTraceMethod(activityName, ActivityLifecycleAnalyzer.METHOD_ON_RESUME) 44 | }; 45 | } 46 | 47 | @Override 48 | public Dialog onCreateDialog(Bundle savedInstanceState) { 49 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 50 | builder.setTitle("Pick methods (implemented only)") 51 | .setMultiChoiceItems(items, enabledItems, new DialogInterface.OnMultiChoiceClickListener() { 52 | @Override 53 | public void onClick(DialogInterface dialog, int which, boolean isChecked) { 54 | enabledItems[which] = isChecked; 55 | if (isChecked) { 56 | MethodsTracingManager.getInstance().scheduleMethodTracing(activityName, items[which]); 57 | } else { 58 | MethodsTracingManager.getInstance().disableMethodTracing(activityName, items[which]); 59 | } 60 | } 61 | }) 62 | .setPositiveButton("OK", null); 63 | 64 | return builder.create(); 65 | } 66 | } -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/dialog/EmulatorIsNotSupportedDialog.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui.dialog; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Dialog; 5 | import android.os.Bundle; 6 | import android.support.v4.app.DialogFragment; 7 | 8 | /** 9 | * Created by Miroslaw Stanek on 17.03.2016. 10 | */ 11 | public class EmulatorIsNotSupportedDialog extends DialogFragment { 12 | public static final String TAG = "EmulatorIsNotSupportedDialog"; 13 | 14 | @Override 15 | public Dialog onCreateDialog(Bundle savedInstanceState) { 16 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 17 | builder.setTitle("Emulator not supported") 18 | .setMessage("Currently methods tracing works only on real devices.") 19 | .setPositiveButton("OK", null); 20 | 21 | return builder.create(); 22 | } 23 | } -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/dialog/MethodsTracingFinishedDialog.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui.dialog; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Dialog; 5 | import android.app.DialogFragment; 6 | import android.os.Bundle; 7 | import android.text.Html; 8 | 9 | import java.util.Locale; 10 | 11 | /** 12 | * Created by Miroslaw Stanek on 17.03.2016. 13 | */ 14 | public class MethodsTracingFinishedDialog extends DialogFragment { 15 | public static final String TAG = "MethodsTracingFinishedDialog"; 16 | 17 | private static final String ARG_TRACED_METHODS = "ARG_TRACED_METHODS"; 18 | 19 | private String[] items; 20 | private String formattedCommands; 21 | 22 | public static MethodsTracingFinishedDialog newInstance(String[] methods) { 23 | MethodsTracingFinishedDialog f = new MethodsTracingFinishedDialog(); 24 | Bundle args = new Bundle(); 25 | args.putStringArray(ARG_TRACED_METHODS, methods); 26 | f.setArguments(args); 27 | return f; 28 | } 29 | 30 | @Override 31 | public void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | items = getArguments().getStringArray(ARG_TRACED_METHODS); 34 | 35 | String cmd = "$ adb pull %s
"; 36 | formattedCommands = ""; 37 | 38 | for (String method : items) { 39 | formattedCommands += String.format(Locale.ENGLISH, cmd, method); 40 | } 41 | } 42 | 43 | @Override 44 | public Dialog onCreateDialog(Bundle savedInstanceState) { 45 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 46 | builder.setTitle("Tracing finished") 47 | .setMessage(Html.fromHtml("Tracing is finished. Plug your device and type in terminal:

" + 48 | formattedCommands + "

" + 49 | "Then drag and drop file(s) to Android Studio.") 50 | 51 | ) 52 | .setPositiveButton("OK", null); 53 | 54 | return builder.create(); 55 | } 56 | } -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/dialog/PermissionNotGrantedDialog.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui.dialog; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Dialog; 5 | import android.os.Bundle; 6 | import android.support.v4.app.DialogFragment; 7 | 8 | /** 9 | * Created by Miroslaw Stanek on 17.03.2016. 10 | */ 11 | public class PermissionNotGrantedDialog extends DialogFragment { 12 | public static final String TAG = "PermissionNotGrantedDialog"; 13 | 14 | @Override 15 | public Dialog onCreateDialog(Bundle savedInstanceState) { 16 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 17 | builder.setTitle("Grant permission") 18 | .setMessage("To use method tracing feature you have to grant \"WRITE_EXTERNAL_STORAGE\" permission. Go to device settings and enable it.") 19 | .setPositiveButton("OK", null); 20 | 21 | return builder.create(); 22 | } 23 | } -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/fragment/ActivitiesMetricsFragment.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui.fragment; 2 | 3 | import android.Manifest; 4 | import android.content.pm.PackageManager; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v4.content.ContextCompat; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.ExpandableListView; 13 | import android.widget.TextView; 14 | 15 | import com.frogermcs.androiddevmetrics.R; 16 | import com.frogermcs.androiddevmetrics.aspect.ActivityLifecycleAnalyzer; 17 | import com.frogermcs.androiddevmetrics.internal.ActivityMetricDescription; 18 | import com.frogermcs.androiddevmetrics.internal.metrics.ActivityLifecycleMetrics; 19 | import com.frogermcs.androiddevmetrics.internal.ui.ExpandableActivitiesMetricsListAdapter; 20 | import com.frogermcs.androiddevmetrics.internal.ui.dialog.ActivitiesMethodsPickerDialog; 21 | import com.frogermcs.androiddevmetrics.internal.ui.dialog.EmulatorIsNotSupportedDialog; 22 | import com.frogermcs.androiddevmetrics.internal.ui.dialog.PermissionNotGrantedDialog; 23 | import com.frogermcs.androiddevmetrics.internal.utils.Utils; 24 | 25 | /** 26 | * Created by Miroslaw Stanek on 22.02.2016. 27 | */ 28 | public class ActivitiesMetricsFragment extends Fragment { 29 | 30 | private ExpandableListView lvActivitiesMetrics; 31 | private TextView tvEmpty; 32 | 33 | @Nullable 34 | @Override 35 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 36 | View view = inflater.inflate(R.layout.adm_fragment_activities_metrics, container, false); 37 | lvActivitiesMetrics = (ExpandableListView) view.findViewById(R.id.lvActivitiesMetrics); 38 | tvEmpty = (TextView) view.findViewById(R.id.tvEmpty); 39 | return view; 40 | } 41 | 42 | @Override 43 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 44 | super.onViewCreated(view, savedInstanceState); 45 | ExpandableActivitiesMetricsListAdapter adapter = new ExpandableActivitiesMetricsListAdapter(this); 46 | lvActivitiesMetrics.setAdapter(adapter); 47 | adapter.updateMetrics(ActivityLifecycleMetrics.getInstance().getListOfMetricDescriptions()); 48 | 49 | if (!ActivityLifecycleAnalyzer.isEnabled()) { 50 | tvEmpty.setVisibility(View.VISIBLE); 51 | tvEmpty.setText("Activities lifecycle metrics disabled"); 52 | lvActivitiesMetrics.setVisibility(View.GONE); 53 | } else if (adapter.getGroupCount() == 0) { 54 | tvEmpty.setVisibility(View.VISIBLE); 55 | lvActivitiesMetrics.setVisibility(View.GONE); 56 | tvEmpty.setText("No collected data"); 57 | } else { 58 | tvEmpty.setVisibility(View.GONE); 59 | lvActivitiesMetrics.setVisibility(View.VISIBLE); 60 | } 61 | } 62 | 63 | public void scheduleTracingForActivity(ActivityMetricDescription activityMetricDescription) { 64 | if (Utils.isEmulator()) { 65 | EmulatorIsNotSupportedDialog dialog = new EmulatorIsNotSupportedDialog(); 66 | dialog.show(getFragmentManager(), EmulatorIsNotSupportedDialog.TAG); 67 | return; 68 | } 69 | 70 | int permissionCheck = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE); 71 | if (permissionCheck == PackageManager.PERMISSION_GRANTED) { 72 | ActivitiesMethodsPickerDialog dialog = ActivitiesMethodsPickerDialog.newInstance(activityMetricDescription); 73 | dialog.show(getFragmentManager(), ActivitiesMethodsPickerDialog.TAG); 74 | } else { 75 | PermissionNotGrantedDialog dialog = new PermissionNotGrantedDialog(); 76 | dialog.show(getFragmentManager(), PermissionNotGrantedDialog.TAG); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/fragment/Dagger2MetricsFragment.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui.fragment; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.DialogInterface; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Button; 12 | import android.widget.ExpandableListView; 13 | import android.widget.TextView; 14 | 15 | import com.frogermcs.androiddevmetrics.AndroidDevMetrics; 16 | import com.frogermcs.androiddevmetrics.R; 17 | import com.frogermcs.androiddevmetrics.aspect.Dagger2GraphAnalyzer; 18 | import com.frogermcs.androiddevmetrics.internal.metrics.InitManager; 19 | import com.frogermcs.androiddevmetrics.internal.ui.ExpandableMetricsListAdapter; 20 | import com.frogermcs.androiddevmetrics.internal.ui.interceptor.UIInterceptor; 21 | 22 | import java.util.List; 23 | 24 | /** 25 | * Created by Miroslaw Stanek on 25.01.2016. 26 | */ 27 | public class Dagger2MetricsFragment extends Fragment { 28 | 29 | private ExpandableListView lvMetrics; 30 | private TextView tvEmpty; 31 | private Button btnMenu; 32 | private List interceptorList; 33 | private ExpandableMetricsListAdapter adapter; 34 | 35 | @Nullable 36 | @Override 37 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 38 | final View view = inflater.inflate(R.layout.adm_fragment_dagger2_metrics, container, false); 39 | lvMetrics = (ExpandableListView) view.findViewById(R.id.lvMetrics); 40 | tvEmpty = (TextView) view.findViewById(R.id.tvEmpty); 41 | interceptorList = AndroidDevMetrics.singleton().interceptors(); 42 | if (interceptorList.size() > 1) { 43 | btnMenu = (Button) view.findViewById(R.id.btnMenu); 44 | btnMenu.setVisibility(View.VISIBLE); 45 | btnMenu.setText(interceptorList.get(0).getName()); 46 | btnMenu.setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | showUIInterceptorMenu(); 50 | } 51 | }); 52 | } 53 | return view; 54 | } 55 | 56 | @Override 57 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 58 | super.onViewCreated(view, savedInstanceState); 59 | 60 | adapter = new ExpandableMetricsListAdapter(); 61 | lvMetrics.setAdapter(adapter); 62 | adapter.updateMetrics(InitManager.getInstance().getListOfMetricDescriptions(interceptorList.get(0))); 63 | 64 | if (!Dagger2GraphAnalyzer.isEnabled()) { 65 | tvEmpty.setVisibility(View.VISIBLE); 66 | tvEmpty.setText("Dagger 2 metrics disabled"); 67 | lvMetrics.setVisibility(View.GONE); 68 | } else if (adapter.getGroupCount() == 0) { 69 | tvEmpty.setVisibility(View.VISIBLE); 70 | lvMetrics.setVisibility(View.GONE); 71 | tvEmpty.setText("No collected data"); 72 | } else { 73 | tvEmpty.setVisibility(View.GONE); 74 | lvMetrics.setVisibility(View.VISIBLE); 75 | } 76 | } 77 | 78 | private void showUIInterceptorMenu() { 79 | String[] transformerNames = new String[interceptorList.size()]; 80 | for (int i = 0; i < interceptorList.size(); i++) { 81 | transformerNames[i] = interceptorList.get(i).getName(); 82 | } 83 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 84 | builder.setTitle("Settings") 85 | .setItems(transformerNames, new DialogInterface.OnClickListener() { 86 | public void onClick(DialogInterface dialog, int which) { 87 | btnMenu.setText(interceptorList.get(which).getName()); 88 | adapter.updateMetrics(InitManager.getInstance().getListOfMetricDescriptions(interceptorList.get(which))); 89 | adapter.notifyDataSetChanged(); 90 | } 91 | }); 92 | builder.create().show(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/interceptor/DefaultInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui.interceptor; 2 | 3 | import com.frogermcs.androiddevmetrics.internal.metrics.model.InitMetric; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author amulya 9 | * @since 13 Jan 2017, 12:58 PM. 10 | */ 11 | 12 | public final class DefaultInterceptor implements UIInterceptor { 13 | 14 | @Override 15 | public List intercept(List metrics) { 16 | return metrics; 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return "Default"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/ui/interceptor/UIInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.ui.interceptor; 2 | 3 | import com.frogermcs.androiddevmetrics.internal.metrics.model.InitMetric; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author amulya 9 | * @since 13 Jan 2017, 12:51 PM. 10 | */ 11 | 12 | public interface UIInterceptor { 13 | 14 | List intercept(List metrics); 15 | 16 | String getName(); 17 | } 18 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/java/com/frogermcs/androiddevmetrics/internal/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.androiddevmetrics.internal.utils; 2 | 3 | import android.os.Build; 4 | 5 | /** 6 | * Created by Miroslaw Stanek on 21.03.2016. 7 | */ 8 | public class Utils { 9 | public static boolean isEmulator() { 10 | return Build.FINGERPRINT.startsWith("generic") 11 | || Build.FINGERPRINT.startsWith("unknown") 12 | || Build.MODEL.contains("google_sdk") 13 | || Build.MODEL.contains("Emulator") 14 | || Build.MODEL.contains("Android SDK built for x86") 15 | || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) 16 | || "google_sdk".equals(Build.PRODUCT); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/res/color/adm_tab_font_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/res/drawable-xhdpi/ic_timeline_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frogermcs/AndroidDevMetrics/10dee8ac2dd8e0f7b635a8d89669ad334ea4f8bc/androiddevmetrics-runtime/src/main/res/drawable-xhdpi/ic_timeline_white_18dp.png -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/res/drawable-xhdpi/ic_triangle_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frogermcs/AndroidDevMetrics/10dee8ac2dd8e0f7b635a8d89669ad334ea4f8bc/androiddevmetrics-runtime/src/main/res/drawable-xhdpi/ic_triangle_down.png -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/res/drawable-xhdpi/ic_triangle_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frogermcs/AndroidDevMetrics/10dee8ac2dd8e0f7b635a8d89669ad334ea4f8bc/androiddevmetrics-runtime/src/main/res/drawable-xhdpi/ic_triangle_up.png -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/res/drawable/ic_metrics_group_indicator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androiddevmetrics-runtime/src/main/res/layout/adm_activity_metrics.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 17 | 18 | 26 | 27 | 28 | 32 | 33 |