├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── encodings.xml ├── gradle.xml ├── misc.xml └── runConfigurations.xml ├── README.md ├── README_en.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── cn │ │ │ └── cxzheng │ │ │ └── asmtraceman │ │ │ └── ExampleInstrumentedTest.kt │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── cn │ │ │ │ └── cxzheng │ │ │ │ └── asmtraceman │ │ │ │ ├── MainActivity.kt │ │ │ │ └── test │ │ │ │ └── RandomTest.kt │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── test │ │ └── java │ │ └── cn │ │ └── cxzheng │ │ └── asmtraceman │ │ └── ExampleUnitTest.kt └── traceconfig.txt ├── asplugin └── methodtraceman-plugin.jar ├── aspluginicon.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── endwork.png ├── log_brower.png ├── log_detail.png ├── log_important.png ├── mtm-logprint.png ├── result.png ├── startui.png └── startwork.png ├── methodtraceman.png ├── settings.gradle ├── tracemanplugin ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── groovy │ └── cn │ │ └── cxzheng │ │ └── tracemanplugin │ │ ├── TraceManConfig.groovy │ │ ├── TraceManPlugin.groovy │ │ └── TraceManTransform.groovy │ ├── java │ └── cn │ │ └── cxzheng │ │ └── tracemanplugin │ │ ├── Config.kt │ │ ├── MethodFilter.kt │ │ ├── TraceClassVisitor.kt │ │ ├── TraceMethod.kt │ │ ├── TraceMethodVisitor.kt │ │ └── Utils.kt │ └── resources │ └── META-INF │ └── gradle-plugins │ └── cn.cxzheng.asmtraceman.properties ├── tracemanui-noop ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── cxzheng │ │ └── tracemanui │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── cxzheng │ │ │ └── tracemanui │ │ │ ├── MethodTraceServerManager.kt │ │ │ └── TraceMan.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── cn │ └── cxzheng │ └── tracemanui │ └── ExampleUnitTest.kt ├── tracemanui ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── cxzheng │ │ └── tracemanui │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── methodtraceman │ │ │ ├── asset-manifest.json │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ ├── precache-manifest.44014e4246f8f0b6384a70c0b6ff3b12.js │ │ │ ├── service-worker.js │ │ │ └── static │ │ │ ├── css │ │ │ ├── 2.d19c8617.chunk.css │ │ │ ├── 2.d19c8617.chunk.css.map │ │ │ ├── main.d8239b71.chunk.css │ │ │ └── main.d8239b71.chunk.css.map │ │ │ └── js │ │ │ ├── 2.8c8f6235.chunk.js │ │ │ ├── 2.8c8f6235.chunk.js.map │ │ │ ├── main.031207ea.chunk.js │ │ │ ├── main.031207ea.chunk.js.map │ │ │ ├── runtime~main.a8a9905a.js │ │ │ └── runtime~main.a8a9905a.js.map │ ├── java │ │ └── cn │ │ │ └── cxzheng │ │ │ └── tracemanui │ │ │ ├── MethodTraceServerManager.kt │ │ │ ├── TraceMan.java │ │ │ ├── TraceManProvider.kt │ │ │ ├── TraceManServer.kt │ │ │ ├── consumer │ │ │ └── DataConsumer.kt │ │ │ ├── handler │ │ │ ├── HttpRequestHandler.kt │ │ │ ├── IHttpRequestHandler.kt │ │ │ ├── IWebScoketHandler.kt │ │ │ └── WebScoketHandler.kt │ │ │ ├── model │ │ │ └── Message.kt │ │ │ ├── producer │ │ │ ├── BaseProducer.kt │ │ │ ├── DataProducer.kt │ │ │ └── module │ │ │ │ ├── DebugBaseInfo.kt │ │ │ │ ├── appInfo │ │ │ │ ├── AppInfo.kt │ │ │ │ └── AppInfoProducer.kt │ │ │ │ └── methodcost │ │ │ │ ├── MethodCostHelper.kt │ │ │ │ ├── MethodCostProducer.kt │ │ │ │ └── MethodInfo.kt │ │ │ └── utils │ │ │ ├── AppUtil.kt │ │ │ ├── JsonUtil.kt │ │ │ └── LogUtil.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── cn │ └── cxzheng │ └── tracemanui │ └── ExampleUnitTest.java └── 问题排障.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/caches/build_file_checksums.ser 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | .DS_Store 10 | /build 11 | /captures 12 | .externalNativeBuild 13 | /repo 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 28 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MethodTraceMan 2 | 3 |

4 | 中文    5 | English 6 |

7 | 8 | 用于快速找到高耗时方法,定位解决Android App卡顿问题。通过gradle plugin+ASM实现可配置范围的方法插桩来统计所有方法的耗时,并在浏览器提供友好的界面展示,支持耗时筛选、线程筛选、方法名筛选等。 9 | 10 | 11 | ## 预览 12 | 13 | 14 | 15 | ### 整个项目包括三部分: 16 | 17 | 1. 方法耗时数据收集部分:通过gradle plugin+ASM在编译时期对所有方法进行插桩收集方法耗时数据,并进行处理 18 | 2. 方法耗时数据展示部分:在浏览器上展示方法耗时数据,并支持耗时筛选、线程筛选、方法名搜索等功能 19 | 3. AndroidStduio插件:用于方便在AndroidStduio顶部栏上快速打开方法耗时数据展示部分【即上面说的第二部分】 20 | 21 | ### 实现及原理 22 | 详见我的博客:[App流畅度优化:利用字节码插桩实现一个快速排查高耗时方法的工具](https://juejin.im/post/6844903975142047758) 23 | 24 | 25 | ## QuickStart 26 | 27 | ### Step1 集成与配置 28 | 29 | #### root project `build.gradle` 30 | 31 | ```groovy 32 | buildscript { 33 | repositories { 34 | google() 35 | jcenter() 36 | maven { url 'https://jitpack.io' } 37 | maven { url "https://plugins.gradle.org/m2/" } 38 | } 39 | dependencies { 40 | classpath "gradle.plugin.cn.cxzheng.methodTracePlugin:tracemanplugin:1.0.4" 41 | } 42 | } 43 | 44 | allprojects { 45 | repositories { 46 | google() 47 | jcenter() 48 | maven { url 'https://jitpack.io' } 49 | maven { url "https://plugins.gradle.org/m2/" } 50 | } 51 | } 52 | ``` 53 | 54 | #### app module project `build.gradle` 55 | 56 | ```groovy 57 | dependencies { 58 | debugImplementation 'com.github.zhengcx:MethodTraceMan:1.0.7' 59 | releaseImplementation 'com.github.zhengcx:MethodTraceMan:1.0.5-noop' 60 | } 61 | 62 | apply plugin: "cn.cxzheng.asmtraceman" 63 | traceMan { 64 | open = true //这里如果设置为false,则会关闭插桩 65 | logTraceInfo = false //这里设置为true时可以在log日志里看到所有被插桩的类和方法 66 | traceConfigFile = "${project.projectDir}/traceconfig.txt" 67 | } 68 | ``` 69 | release包下依赖的是noop包,里面不会做任何操作,也不会增加包大小。 70 | 71 | 72 | #### 在app module的根目录下创建一个名叫`traceconfig.txt`的配置文件,并在里面对插桩范围进行配置 73 | 下面是配置示例: 74 | ```txt 75 | #配置需插桩的包,如果为空,则默认所有文件都进行插桩(config the package need to trace,If they are empty, all files are traced by default.) 76 | -tracepackage cn/cxzheng/asmtraceman 77 | 78 | #在需插桩的包下设置无需插桩的包(Setting up traceless packages under packages that require trace) 79 | #-keeppackage cn/cxzheng/asmtraceman/test 80 | 81 | #在需插桩的包下设置无需插桩的类(Setting up traceless classes under packages that require trace) 82 | #-keepclass cn/cxzheng/asmtraceman/MainActivity 83 | 84 | #插桩代码所在类,这里固定配置为:cn/cxzheng/tracemanui/TraceMan(Fixed configuration here: cn/cxzheng/tracemanui/TraceMan) 85 | -beatclass cn/cxzheng/tracemanui/TraceMan 86 | ``` 87 | 88 | **注意:** -tracepackage 后面是需要改成你自己项目中想配置插桩范围的包名,以斜杆分割如cn/cxzheng/asmtraceman,错误示范:cn.cxzheng.asmtraceman 89 | 90 | #### 在AndroidManifest.xml中检查是否开启了网络权限,如果没有的话,请开启网络权限 91 | ```xml 92 | 93 | 94 | 95 | ``` 96 | 97 | #### 最后,Rebuild项目并运行安装你的app,所有方法就会进行耗时插桩 98 | 99 | 100 | ### Step2 安装AndroidStduio辅助插件: MethodTraceMan 101 | 102 | 这个插件的主要功能是可以在AndroidStduio上快速方便的打开methodtraceman的UI界面,这个插件已上传AndroidStduio的插件仓库,你可以通过在AndroidStduio插件库中搜索`MethodTraceMan`来安装这个插件,当然在项目的aspluin目录下也提供了该插件的jar包,具体如何安装AndroidStduio插件,这里就不细说了,可以在网上搜索,安装好之后重启AndroidStduio,就可以在顶部栏看到MethodTraceMan插件的黄色灯泡💡图标了,集成和安装到这里就介绍完毕了,下面我会介绍MethodTraceMan如何使用。 103 | 104 | 安装后重启,图如下: 105 | 106 | **注意:** 如果重启AndroidStduio后在顶部栏没发现小灯泡图标,请检查AndroidStduio顶部栏View->Toolbar是否勾选上。 107 | 108 | 109 | 110 | ### Step3 使用介绍 111 | 完成上面两步后,确保已完成: 112 | 113 | 1. 按要求集成后,Rebuild&Run 启动你的App 114 | 2. AndroidStduio上已安装好MethodTraceMan插件 115 | 116 | 接下来会介绍如何使用MethodTraceMan来方便的排查高耗时的方法: 117 | 118 | #### 1.将手机通过Usb连接电脑,确保连接成功。 119 | 120 | #### 2.启动APP后,此时点击AndroidStduio顶部栏的MethodTraceMan灯泡小图标,则会在浏览器上打开MethodTraceMan的UI界面如下: 121 | 122 | 123 | 124 | 125 | #### 3.点击“开始方法耗时统计按钮”,然后开始随意操作你的app 126 | 127 | 128 | #### 4.操作完app后,点击“结束方法耗时统计”按钮,此时会输出所有方法的耗时统计,你可以进行耗时筛选、线程筛选、方法名搜索等进行筛查 129 | 130 | 131 | 132 | 133 | 134 | ## 问题排障 135 | 136 | 日志排障详见:问题排障 137 | 138 | **注意事项:** 139 | * 请不要同时打开两个集成了此项目的App,会导致耗时数据无法传送到浏览器的UI界面 140 | * 请不要同时连接两个手机,会导致浏览器打开界面失败 141 | * 集成进自己的项目的话,请务必记得将traceconfig.txt中 -tracepackage配置成自己想插桩的包范围 142 | * 如果重启AndroidStduio后在顶部栏没发现小灯泡图标,请检查AndroidStduio顶部栏View->Toolbar是否勾选上 143 | 144 | **若升级AndroidStduio4.x后安装此plugin报错提示:Plugin is incompatible (supported only in IntelliJ IDEA).** 145 | 146 | 解决方案: 147 | 148 | 1.cd到AndroidStduio安装目录下的plugins文件夹下,如/Users/xxx/Library/ApplicationSupport/Google/AndroidStudio4.x/plugins 149 | 150 | 2.删除此文件夹下的methodtraceman-plugin.jar 151 | 152 | 3.重启AndroidStduio,重新安装此插件即可。 153 | 154 | 155 | 156 | ## End 欢迎Star/Fork/Issue. 157 | 158 | **集成及使用过程中有任何问题或者建议,欢迎与我进行交流,谢谢** 159 | 160 | 161 | 162 | ## License 163 | 164 | Apache2.0. 165 | 166 | 167 | ## About Me 168 | 169 | - 博客: [舒大飞](https://juejin.im/user/2313028194537566/posts) 170 | - Github: [DavidSu](https://github.com/zhengcx) 171 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # MethodTraceMan 2 | 3 |

4 | 中文    5 | English 6 |

7 | 8 | A tool for discovering high-time-consuming methods for Android App.Used to quickly find high-time-consuming methods, locate and solve Android App jank problem. Through gradle plugin + ASM, instrumentation of all methods during compilation, and provide friendly interface display in browser, support time-consuming filtering, thread filtering, method name filtering, etc. 9 | 10 | 11 | ## Overview 12 | 13 | 14 | 15 | ### The entire project consists of three parts: 16 | 17 | 1. Method time-consuming data collection part 18 | 2. Method time-consuming data display part 19 | 3. AndroidStduio auxiliary plugin 20 | 21 | ## QuickStart 22 | 23 | ### Step1 Dependencies and Config 24 | 25 | #### root project `build.gradle` 26 | 27 | ```groovy 28 | buildscript { 29 | repositories { 30 | google() 31 | jcenter() 32 | maven { url 'https://jitpack.io' } 33 | maven { url "https://plugins.gradle.org/m2/" } 34 | } 35 | dependencies { 36 | classpath "gradle.plugin.cn.cxzheng.methodTracePlugin:tracemanplugin:1.0.4" 37 | } 38 | } 39 | 40 | allprojects { 41 | repositories { 42 | google() 43 | jcenter() 44 | maven { url 'https://jitpack.io' } 45 | maven { url "https://plugins.gradle.org/m2/" } 46 | } 47 | } 48 | ``` 49 | 50 | #### app module project `build.gradle` 51 | 52 | ```groovy 53 | dependencies { 54 | debugImplementation 'com.github.zhengcx:MethodTraceMan:1.0.7' 55 | releaseImplementation 'com.github.zhengcx:MethodTraceMan:1.0.5-noop' 56 | } 57 | 58 | apply plugin: "cn.cxzheng.asmtraceman" 59 | traceMan { 60 | open = true 61 | traceConfigFile = "${project.projectDir}/traceconfig.txt" 62 | } 63 | ``` 64 | 65 | #### Create a file called traceconfig.txt under the root directory of app module. 66 | Configuration code instrumentation range,example: 67 | 68 | ```txt 69 | #config the package need to trace,If they are empty, all files are traced by default. 70 | -tracepackage cn/cxzheng/asmtraceman 71 | 72 | #Setting up traceless packages under packages that require trace 73 | -keeppackage cn/cxzheng/asmtraceman/test 74 | 75 | #Setting up traceless classes under packages that require trace 76 | -keepclass cn/cxzheng/asmtraceman/MainActivity 77 | 78 | #Fixed configuration here: cn/cxzheng/tracemanui/TraceMan 79 | -beatclass cn/cxzheng/tracemanui/TraceMan 80 | ``` 81 | 82 | #### then Rebuild project,all methods will be time-consuming trace. 83 | 84 | 85 | ### Step2 Install Android Stduio assist plugins. 86 | The main function of this plugin is to quickly and easily open the UI display interface of methodtraceman.This plugin has been uploaded to Android Stduio plug-in repository. You can search for`MethodTraceMan`to install it. How to install Android Stduio plug-in is not detailed here, but you can search online.After installed plugin and restart AndroidStduio, you can see the yellow light bulb icon of the MethodTraceMan plugin in the top bar. 87 | 88 | 89 | 90 | 91 | ### Step3 How to Use. 92 | After completing the above two steps, make sure you have completed: 93 | 94 | 1. Rebuild&Run launches your app after integration as required. 95 | 2. MethodTraceMan plugin is installed on AndroidStduio. 96 | 97 | Next, we'll show you how to use MethodTraceMan to easily troubleshoot time-consuming methods: 98 | 99 | #### 1.Connect your phone to your computer via Usb to ensure a successful connection. 100 | 101 | #### 2.After launching the app, click on the MethodTraceMan light bulb icon in the top bar of AndroidStduio, and the UI of MethodTraceMan will be opened in the browser as follows: 102 | 103 | 104 | 105 | 106 | #### 3.Click the "Start Method Time Statistics" button and start free to operate your app. 107 | 108 | 109 | #### 4.After operating the app, click the “End method time-consuming statistics” button, and the time-consuming statistics of all methods will be output. You can perform time-consuming filtering, thread filtering, method name search, etc. for screening. 110 | 111 | 112 | 113 | 114 | 115 | ### End Welcome to Star/Fork/Issue. 116 | 117 | ## License 118 | 119 | Apache2.0. 120 | 121 | 122 | ## About Me 123 | 124 | - Github: [DavidSu](https://github.com/zhengcx) 125 | - blog: [舒大飞](https://juejin.im/user/5a6d2293518825734a74ed4c/posts) 126 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "cn.cxzheng.asmtraceman" 11 | minSdkVersion 15 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | javaCompileOptions { 17 | annotationProcessorOptions { 18 | includeCompileClasspath true 19 | } 20 | } 21 | } 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation fileTree(dir: 'libs', include: ['*.jar']) 32 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 33 | implementation 'com.android.support:appcompat-v7:28.0.0' 34 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 37 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 38 | 39 | debugImplementation 'com.github.zhengcx:MethodTraceMan:1.0.7' 40 | releaseImplementation 'com.github.zhengcx:MethodTraceMan:1.0.5-noop' 41 | 42 | // releaseImplementation project(':tracemanui-noop') 43 | // implementation project(':tracemanui') 44 | 45 | } 46 | 47 | apply plugin: "cn.cxzheng.asmtraceman" 48 | 49 | traceMan { 50 | open = true 51 | logTraceInfo = true 52 | traceConfigFile = "${project.projectDir}/traceconfig.txt" 53 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/cxzheng/asmtraceman/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.asmtraceman 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("cn.cxzheng.asmtraceman", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/cn/cxzheng/asmtraceman/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.asmtraceman 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.widget.Toast 6 | import cn.cxzheng.asmtraceman.test.RandomTest 7 | import cn.cxzheng.tracemanui.MethodTraceServerManager 8 | import kotlinx.android.synthetic.main.activity_main.* 9 | import java.util.* 10 | 11 | class MainActivity : AppCompatActivity() { 12 | 13 | private val randomTest by lazy { RandomTest() } 14 | private val random by lazy { Random() } 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | MethodTraceServerManager.logLevel = MethodTraceServerManager.MTM_LOG_DETAIL 18 | 19 | setContentView(R.layout.activity_main) 20 | 21 | 22 | btn_click.setOnClickListener { 23 | 24 | val randomMill = rand(0, 30) 25 | Toast.makeText(this, "已执行随机耗时方法", Toast.LENGTH_SHORT).show() 26 | randomTest.randomSleep(randomMill.toLong()) 27 | } 28 | 29 | } 30 | 31 | private fun rand(from: Int, to: Int): Int { 32 | return random.nextInt(to - from) + from 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/cn/cxzheng/asmtraceman/test/RandomTest.kt: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.asmtraceman.test 2 | 3 | import android.os.SystemClock 4 | 5 | /** 6 | * Create by cxzheng on 2019/8/28 7 | */ 8 | class RandomTest { 9 | 10 | fun randomSleep(timeMills: Long) { 11 | SystemClock.sleep(timeMills) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | \n\n \n\n \n\n\n { this._filterMethodTag(value) }}\n enterButton />\n\n \n \n
\n (\n\n \n
\n
\n
\n {itemData.isMainThread ? '[主线程]' : '[非主线程]'}\n {itemData.name}\n
\n
\n \n {itemData.costTime}ms\n
\n\n\n
\n
\n \n )\n } />\n
\n\n \n );\n }\n}\n\nexport default MethodCostLayout;","import React from 'react';\nimport { Layout } from 'antd';\nimport { Route } from 'react-router-dom'\nimport MethodCostLayout from '../components/MethodCostLayout'\n\nconst { Content, Footer } = Layout;\n\nconst RightContent = (props) => {\n\n return (\n \n \n
\n \n
\n
\n
CopyRight©2019 DavidSu. All rights reserved.
\n
\n )\n}\n\nexport default RightContent\n\n","import React, { Component } from 'react';\nimport { Tag } from \"antd\";\nimport store from \"../store/index\";\n\nlet colors = ['magenta', 'blue', 'green', 'orange', 'lime', 'cyan']\nclass TagViews extends Component {\n constructor(props) {\n super(props);\n let { AppInfo } = store.getState().methodReducer\n this.state = {\n tags: AppInfo\n }\n\n this.unsubscribe = store.subscribe(this._refreshData)\n\n }\n\n _refreshData = () => {\n let { AppInfo } = store.getState().methodReducer\n this.setState({\n tags: AppInfo\n })\n }\n\n componentWillUnmount() {\n this.unsubscribe()\n }\n\n getTagViews = () => {\n let tags = this.state.tags\n let tagViews = []\n let count = 0\n if (tags) {\n tags.forEach(element => {\n let color = colors[count % colors.length]\n tagViews.push(\n \n {element}\n \n )\n\n count++\n });\n }\n\n return tagViews\n }\n\n render() {\n return (\n
\n {this.getTagViews()}\n
\n );\n }\n}\n\nexport default TagViews;","import React, { Component } from 'react';\nimport websocketEngine from './websocket/WebscoketEngine'\nimport Mock from './Mock'\nimport { Layout, message } from 'antd';\nimport LeftSider from './widget/LeftSider'\nimport { BrowserRouter } from 'react-router-dom'\n\nimport RightContent from './widget/RightContent'\nimport store from \"./store/index\"\nimport { updateAppInfo, updateMethodCostInfo } from \"./store/actionCreators\";\nimport TagViews from './widget/TagViews';\nimport Bus from './event/eventBus'\n\nconst { Header } = Layout;\n\nclass App extends Component {\n constructor(props) {\n super(props);\n this._onReceiveMessage = this._onReceiveMessage.bind(this);\n this.mock = new Mock();\n }\n\n componentDidMount() {\n websocketEngine.setReceiveMessageCallback(this._onReceiveMessage);\n websocketEngine.start(function (evt) {\n websocketEngine.sendMessage('{\"moduleName\": \"OnlineMessage\"}')\n });\n\n // this._testData()\n\n this._registerEventBus()\n }\n\n _registerEventBus = () => {\n Bus.addListener('sendMsgToClient', (msg) => {\n console.log(msg);\n try {\n websocketEngine.start(function (evt) {\n websocketEngine.sendMessage(msg)\n });\n } catch (error) {\n message.error('MethodTraceMan与手机客户端未连接完毕,请稍后再试!')\n }\n });\n\n }\n\n componentWillUnmount() {\n Bus.removeAllListeners()\n }\n _testData = () => {\n setTimeout(() => {\n this._onReceiveMessage(\"appInfo\", [\"机型:samsung SM-G9700\", \"系统版本:28\", \"包名:ctrip.english.debug\", \"App版本:7.1.0\", \"UID:E451995153\", \"ClientID:37002118410181244263\"]\n )\n }, 3000)\n\n setTimeout(() => {\n this._onReceiveMessage(\"methodCost\", [{\n \"costTime\": 1.0,\n \"endPos\": 3.0,\n \"isMainThread\": true,\n \"name\": \"com.ctrip.ibu.hotel.module.main.MainSearchInfoHelper.getInstance.()Lcom.ctrip.ibu.hotel.module.main.MainSearchInfoHelper;\",\n \"startPos\": 0.0\n }, {\n \"costTime\": 15.0,\n \"endPos\": 45.0,\n \"isMainThread\": true,\n \"name\": \"com.ctrip.ibu.hotel.module.main.HotelMainActivity.lambda$gotoHotelList$11.(IILandroid.content.Intent;)V\",\n \"startPos\": 6.0\n }, {\n \"costTime\": 14.0,\n \"endPos\": 44.0,\n \"isMainThread\": true,\n \"name\": \"com.ctrip.ibu.hotel.module.main.HotelMainPresenter.onHotelsActivityResult.(Landroid.content.Intent;)V\",\n \"startPos\": 7.0\n }, {\n \"costTime\": 1.0,\n \"endPos\": 19.0,\n \"isMainThread\": true,\n \"name\": \"com.ctrip.ibu.hotel.module.main.MainSearchInfoHelper.getAdultNum.()I\",\n \"startPos\": 18.0\n }, {\n \"costTime\": 6.0,\n \"endPos\": 41.0,\n \"isMainThread\": true,\n \"name\": \"com.ctrip.ibu.hotel.module.main.MainSearchInfoHelper.onRoomGuestChange.(IILjava.util.List;)V\",\n \"startPos\": 30.0\n },\n {\n \"costTime\": 1.0,\n \"endPos\": 3.0,\n \"isMainThread\": true,\n \"name\": \"com.ctrip.ibu.hotel.module.main.MainSearchInfoHelper.getInstance.()Lcom.ctrip.ibu.hotel.module.main.MainSearchInfoHelper;\",\n \"startPos\": 0.0\n }, {\n \"costTime\": 15.0,\n \"endPos\": 45.0,\n \"isMainThread\": true,\n \"name\": \"com.ctrip.ibu.hotel.module.main.HotelMainActivity.lambda$gotoHotelList$11.(IILandroid.content.Intent;)V\",\n \"startPos\": 6.0\n }])\n }, 5000)\n }\n\n\n _onReceiveMessage(moduleName, payload) {\n switch (moduleName) {\n case \"appInfo\":\n store.dispatch(updateAppInfo(payload.appInfo))\n break;\n case \"methodCost\":\n store.dispatch(updateMethodCostInfo(payload))\n break;\n default:\n break;\n }\n\n\n }\n\n\n render() {\n return (\n \n
\n MethodTraceMan.\n \n
\n \n \n \n \n \n \n
\n );\n }\n}\n\nexport default App;\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.1/8 is considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl)\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport * as serviceWorker from './serviceWorker';\n\nReactDOM.render(, document.getElementById('root'));\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /tracemanui/src/main/assets/methodtraceman/static/js/runtime~main.a8a9905a.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c() 34 | 35 | @JvmField 36 | var isActiveTraceMan = false 37 | 38 | const val MTM_LOG_IMPORTANT = 1 39 | const val MTM_LOG_DETAIL = 2 40 | 41 | var logLevel = MTM_LOG_IMPORTANT 42 | 43 | init { 44 | dataModules[APPINFO] = AppInfoProducer() 45 | dataModules[METHODCOST] = MethodCostProducer() 46 | } 47 | 48 | 49 | fun getModule(name: String): T { 50 | return dataModules[name] as T 51 | } 52 | 53 | 54 | /** 55 | * 开启服务 56 | */ 57 | @Synchronized 58 | @JvmOverloads 59 | fun startService( 60 | context: Context, 61 | port: Int = DEBUG_SERVER_PORT 62 | ) { 63 | if (isServerRunning) { 64 | return 65 | } 66 | 67 | isServerRunning = true 68 | 69 | debugServer = TraceManServer(port) 70 | 71 | setServerCallback(context) 72 | 73 | dataConsumer = DataConsumer(debugServer!!) 74 | 75 | debugServer?.start() 76 | 77 | dataConsumer?.observe() 78 | 79 | LogUtil.i("MethodTraceMan Server is running") 80 | LogUtil.i("http://${getIPAddress(context)}:$port/index.html") 81 | 82 | } 83 | 84 | 85 | private fun setServerCallback(context: Context) { 86 | val httpRequestHandler = HttpRequestHandler(context, "methodtraceman") 87 | val webSocketHandler = WebScoketHandler() 88 | 89 | debugServer?.serverCallback = object : TraceManServer.ServerCallback { 90 | override fun onHttpRequest( 91 | request: AsyncHttpServerRequest, 92 | response: AsyncHttpServerResponse 93 | ) { 94 | checkThread() 95 | try { 96 | val map = httpRequestHandler.handle(request.path) 97 | response.send(map["mimeType"], map["payload"]) 98 | 99 | } catch (throwable: Throwable) { 100 | LogUtil.e(throwable.toString()) 101 | } 102 | } 103 | 104 | override fun onWebSocketRequest(webSocket: WebSocket, messageFromClient: String) { 105 | checkThread() 106 | webSocketHandler.handle(webSocket, messageFromClient) 107 | } 108 | } 109 | } 110 | 111 | 112 | /** 113 | * 关闭服务 114 | */ 115 | @Synchronized 116 | fun stopService() { 117 | debugServer?.stop() 118 | debugServer = null 119 | 120 | 121 | dataConsumer?.clearObserve() 122 | dataConsumer = null 123 | 124 | isServerRunning = false 125 | LogUtil.i("MethodTraceMan Server Stopped.") 126 | } 127 | 128 | private fun isInMainThread(): Boolean { 129 | return Looper.myLooper() == Looper.getMainLooper() 130 | } 131 | 132 | private fun checkThread() { 133 | if (isInMainThread()) { 134 | throw IllegalStateException("trace man service must execute on work thread!") 135 | } 136 | } 137 | 138 | private fun getIPAddress(context: Context): String { 139 | @SuppressLint("WifiManagerPotentialLeak") 140 | val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager 141 | val ipAddress = wifiManager?.connectionInfo?.ipAddress ?: 0 142 | @SuppressLint("DefaultLocale") 143 | val formattedIpAddress = String.format( 144 | "%d.%d.%d.%d", 145 | ipAddress and 0xff, 146 | ipAddress shr 8 and 0xff, 147 | ipAddress shr 16 and 0xff, 148 | ipAddress shr 24 and 0xff 149 | ) 150 | return formattedIpAddress 151 | } 152 | 153 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/TraceMan.java: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.tracemanui; 2 | 3 | import android.os.Build; 4 | import android.os.Looper; 5 | import android.os.Trace; 6 | import android.support.annotation.RequiresApi; 7 | 8 | import com.ctrip.ibu.hotel.debug.server.producer.module.methodcost.MethodInfo; 9 | 10 | import java.util.ArrayList; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | 14 | import cn.cxzheng.tracemanui.utils.LogUtil; 15 | 16 | 17 | /** 18 | * Create by cxzheng on 2019/8/26 19 | * 全方法插桩内容 20 | */ 21 | public class TraceMan { 22 | 23 | private static List methodList = new LinkedList<>(); 24 | 25 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 26 | public static void start(String name) { 27 | if (isOpenTraceMethod()) { 28 | Trace.beginSection(name); 29 | synchronized (methodList) { 30 | methodList.add(new Entity(name, System.currentTimeMillis(), true, isInMainThread())); 31 | } 32 | } 33 | } 34 | 35 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) 36 | public static void end(String name) { 37 | if (isOpenTraceMethod()) { 38 | LogUtil.detail("执行了方法:" + name); 39 | Trace.endSection(); 40 | synchronized (methodList) { 41 | methodList.add(new Entity(name, System.currentTimeMillis(), false, isInMainThread())); 42 | } 43 | } 44 | } 45 | 46 | 47 | public static void startCollectMethodCost() { 48 | resetTraceManData(); 49 | } 50 | 51 | public static List endCollectMethodCost() { 52 | List resultList = obtainMethodCostData(); 53 | resetTraceManData(); 54 | return resultList; 55 | } 56 | 57 | 58 | public static void resetTraceManData() { 59 | synchronized (methodList) { 60 | methodList.clear(); 61 | } 62 | } 63 | 64 | /** 65 | * 处理插桩数据,按顺序获取所有方法耗时 66 | */ 67 | public static List obtainMethodCostData() { 68 | synchronized (methodList) { 69 | List resultList = new ArrayList(); 70 | for (int i = 0; i < methodList.size(); i++) { 71 | Entity startEntity = methodList.get(i); 72 | if (!startEntity.isStart) { 73 | continue; 74 | } 75 | startEntity.pos = i; 76 | Entity endEntity = findEndEntity(startEntity.name, i + 1); 77 | 78 | if (startEntity != null && endEntity != null && endEntity.time - startEntity.time > 0) { 79 | resultList.add(createMethodInfo(startEntity, endEntity)); 80 | } 81 | } 82 | return resultList; 83 | } 84 | } 85 | 86 | /** 87 | * 找到方法对应的结束点 88 | * 89 | * @param name 90 | * @param startPos 91 | * @return 92 | */ 93 | private static Entity findEndEntity(String name, int startPos) { 94 | int sameCount = 1; 95 | for (int i = startPos; i < methodList.size(); i++) { 96 | Entity endEntity = methodList.get(i); 97 | if (endEntity.name.equals(name)) { 98 | if (endEntity.isStart) { 99 | sameCount++; 100 | } else { 101 | sameCount--; 102 | } 103 | if (sameCount == 0 && !endEntity.isStart) { 104 | endEntity.pos = i; 105 | return endEntity; 106 | } 107 | } 108 | } 109 | return null; 110 | } 111 | 112 | private static MethodInfo createMethodInfo(Entity startEntity, Entity endEntity) { 113 | return new MethodInfo(startEntity.name, 114 | endEntity.time - startEntity.time, startEntity.pos, endEntity.pos, startEntity.isMainThread); 115 | } 116 | 117 | 118 | private static boolean isInMainThread() { 119 | return Looper.myLooper() == Looper.getMainLooper(); 120 | } 121 | 122 | private static boolean isOpenTraceMethod() { 123 | return MethodTraceServerManager.isActiveTraceMan; 124 | } 125 | 126 | static class Entity { 127 | public String name; 128 | public Long time; 129 | public boolean isStart; 130 | public int pos; 131 | public boolean isMainThread; 132 | 133 | public Entity(String name, Long time, boolean isStart, boolean isMainThread) { 134 | this.name = name; 135 | this.time = time; 136 | this.isStart = isStart; 137 | this.isMainThread = isMainThread; 138 | } 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/TraceManProvider.kt: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.tracemanui 2 | 3 | import android.content.ContentProvider 4 | import android.content.ContentValues 5 | import android.database.Cursor 6 | import android.net.Uri 7 | 8 | /** 9 | * Create by cxzheng on 2019-12-08 10 | */ 11 | class TraceManProvider : ContentProvider() { 12 | override fun insert(uri: Uri, values: ContentValues?): Uri? { 13 | return null 14 | } 15 | 16 | override fun query( 17 | uri: Uri, 18 | projection: Array?, 19 | selection: String?, 20 | selectionArgs: Array?, 21 | sortOrder: String? 22 | ): Cursor? { 23 | return null 24 | } 25 | 26 | override fun update( 27 | uri: Uri, 28 | values: ContentValues?, 29 | selection: String?, 30 | selectionArgs: Array? 31 | ): Int { 32 | return 0 33 | } 34 | 35 | override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { 36 | return 0 37 | } 38 | 39 | override fun getType(uri: Uri): String? { 40 | return null 41 | } 42 | 43 | override fun onCreate(): Boolean { 44 | MethodTraceServerManager.startService(context) 45 | return true 46 | } 47 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/TraceManServer.kt: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.tracemanui 2 | 3 | import android.util.Log 4 | import cn.cxzheng.tracemanui.utils.LogUtil 5 | import com.koushikdutta.async.AsyncServer 6 | import com.koushikdutta.async.callback.CompletedCallback 7 | import com.koushikdutta.async.http.WebSocket 8 | import com.koushikdutta.async.http.server.AsyncHttpServer 9 | import com.koushikdutta.async.http.server.AsyncHttpServerRequest 10 | import com.koushikdutta.async.http.server.AsyncHttpServerResponse 11 | import java.util.* 12 | 13 | /** 14 | * Create by cxzheng on 2019/7/6 15 | * Server 16 | */ 17 | class TraceManServer { 18 | 19 | private var port: Int = 0 20 | private var server: AsyncHttpServer 21 | private var webSockets: MutableList 22 | var serverCallback: ServerCallback? = null 23 | 24 | 25 | constructor(port: Int) { 26 | this.port = port 27 | server = AsyncHttpServer() 28 | webSockets = Collections.synchronizedList(ArrayList()) 29 | 30 | //webScoket请求监听回调 31 | server.websocket("/refresh") { webSocket, _ -> 32 | webSockets.add(webSocket) 33 | 34 | webSocket.closedCallback = CompletedCallback { webSockets.remove(webSocket) } 35 | 36 | webSocket.stringCallback = WebSocket.StringCallback { s -> 37 | serverCallback?.onWebSocketRequest(webSocket, s) 38 | } 39 | } 40 | 41 | //Http请求监听回调 42 | server.get("/.*[(.html)|(.css)|(.js)|(.png)|(.jpg)|(.jpeg)|(.ico)]") { request, response -> 43 | serverCallback?.onHttpRequest(request, response) 44 | LogUtil.detail("接收到浏览器请求UI界面") 45 | } 46 | } 47 | 48 | /** 49 | * 启动服务 50 | */ 51 | fun start() { 52 | server.listen(port) 53 | } 54 | 55 | /** 56 | * 关闭服务 57 | */ 58 | fun stop() { 59 | server.stop() 60 | AsyncServer.getDefault().stop() 61 | } 62 | 63 | /** 64 | * 发送数据 65 | */ 66 | fun sendMessage(message: String) { 67 | val wss = webSockets.toTypedArray() 68 | wss.forEach { 69 | if (it.isOpen) { 70 | it.send(message) 71 | } 72 | } 73 | } 74 | 75 | 76 | interface ServerCallback { 77 | fun onHttpRequest(request: AsyncHttpServerRequest, response: AsyncHttpServerResponse) 78 | 79 | fun onWebSocketRequest(webSocket: WebSocket, messageFromClient: String) 80 | } 81 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/consumer/DataConsumer.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.consumer 2 | 3 | import android.util.Log 4 | import cn.cxzheng.tracemanui.MethodTraceServerManager 5 | import cn.cxzheng.tracemanui.MethodTraceServerManager.APPINFO 6 | import cn.cxzheng.tracemanui.MethodTraceServerManager.METHODCOST 7 | import cn.cxzheng.tracemanui.TraceManServer 8 | import cn.cxzheng.tracemanui.utils.LogUtil 9 | import com.ctrip.ibu.hotel.debug.server.model.Message 10 | import com.ctrip.ibu.hotel.debug.server.producer.module.appInfo.AppInfoProducer 11 | import com.ctrip.ibu.hotel.debug.server.producer.module.methodcost.MethodCostProducer 12 | import io.reactivex.disposables.CompositeDisposable 13 | import io.reactivex.disposables.Disposable 14 | import io.reactivex.schedulers.Schedulers 15 | 16 | /** 17 | * Create by cxzheng on 2019/7/7 18 | * 数据消费 19 | */ 20 | class DataConsumer(var server: TraceManServer) { 21 | 22 | private var compositeDisposables: CompositeDisposable = CompositeDisposable() 23 | 24 | 25 | fun observe() { 26 | 27 | compositeDisposables.addAll( 28 | appInfoConsumer(), 29 | methodCostConsumer() 30 | ) 31 | 32 | } 33 | 34 | private fun methodCostConsumer(): Disposable? { 35 | return MethodTraceServerManager.getModule(METHODCOST).subject() 36 | .subscribeOn(Schedulers.computation()) 37 | .observeOn(Schedulers.computation()) 38 | .subscribe({ 39 | val messageEntity = Message(METHODCOST, it) 40 | val message = messageEntity.toString() 41 | server.sendMessage(message) 42 | 43 | //Log输出 44 | LogUtil.i("已向浏览器发送${it.size}条方法耗时信息") 45 | it.forEach { methodInfo -> 46 | val threadText = if (methodInfo.isMainThread) "[主线程]" else "[非主线程]" 47 | LogUtil.detail("方法耗时详情:" + methodInfo.name + " " + methodInfo.costTime + "ms" + " " + threadText) 48 | } 49 | LogUtil.detail(message) 50 | }, { 51 | LogUtil.detail(it.message) 52 | }) 53 | } 54 | 55 | 56 | private fun appInfoConsumer(): Disposable? { 57 | return MethodTraceServerManager.getModule(APPINFO).subject() 58 | .subscribeOn(Schedulers.computation()) 59 | .observeOn(Schedulers.computation()) 60 | .subscribe({ 61 | val messageEntity = Message(APPINFO, it) 62 | val message = messageEntity.toString() 63 | server.sendMessage(message) 64 | LogUtil.detail(message) 65 | }, { 66 | LogUtil.detail(it.message) 67 | }) 68 | } 69 | 70 | fun clearObserve() { 71 | compositeDisposables.dispose() 72 | } 73 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/handler/HttpRequestHandler.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.handler 2 | 3 | import android.content.Context 4 | import android.content.res.AssetManager 5 | import android.text.TextUtils 6 | import java.io.BufferedReader 7 | import java.io.InputStream 8 | import java.io.InputStreamReader 9 | import java.nio.charset.Charset 10 | 11 | /** 12 | * Create by cxzheng on 2019/7/7 13 | */ 14 | class HttpRequestHandler : IHttpRequestHandler { 15 | 16 | private var assetManager: AssetManager 17 | private var fileName: String 18 | 19 | constructor(context: Context, fileName: String) { 20 | assetManager = context.resources.assets 21 | this.fileName = fileName 22 | } 23 | 24 | override fun handle(path: String): Map { 25 | // Log.i(DEBUG_SERVER_TAG, path) 26 | var resultPath: String? = null 27 | if (path.startsWith("/")) { 28 | resultPath = path.substring(1) 29 | } 30 | if (TextUtils.isEmpty(resultPath)) { 31 | resultPath = "debugbridge/index.html" 32 | } 33 | val resourcePath = "$fileName/$resultPath" 34 | 35 | return mapOf("mimeType" to getMimeType(resourcePath), "payload" to getPayload(resourcePath)) 36 | } 37 | 38 | 39 | /** 40 | * 资源类型 41 | */ 42 | private fun getMimeType(fileName: String): String? { 43 | return when { 44 | TextUtils.isEmpty(fileName) -> null 45 | fileName.endsWith(".html") -> "text/html;charset=utf-8" 46 | fileName.endsWith(".js") -> "application/javascript;charset=utf-8" 47 | fileName.endsWith(".css") -> "text/css;charset=utf-8" 48 | else -> "application/octet-stream" 49 | } 50 | } 51 | 52 | /** 53 | * 获取资源 54 | */ 55 | private fun getPayload(fileName: String): String { 56 | var input: InputStream? = null 57 | try { 58 | input = assetManager.open(fileName) 59 | 60 | val sb = StringBuilder() 61 | val br = BufferedReader(InputStreamReader(input, Charset.forName("UTF-8"))) 62 | var line: String? = br.readLine() 63 | while (line != null) { 64 | sb.append("\n") 65 | sb.append(line) 66 | line = br.readLine() 67 | } 68 | return sb.toString() 69 | } finally { 70 | try { 71 | input?.close() 72 | } catch (e: Throwable) { 73 | } 74 | 75 | } 76 | } 77 | 78 | 79 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/handler/IHttpRequestHandler.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.handler 2 | 3 | /** 4 | * Create by cxzheng on 2019/7/7 5 | */ 6 | interface IHttpRequestHandler { 7 | 8 | fun handle(path: String): Map 9 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/handler/IWebScoketHandler.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.handler 2 | 3 | import com.koushikdutta.async.http.WebSocket 4 | 5 | /** 6 | * Create by cxzheng on 2019/7/7 7 | */ 8 | interface IWebScoketHandler { 9 | 10 | fun handle(webScoket: WebSocket, message: String?) 11 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/handler/WebScoketHandler.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.handler 2 | 3 | import cn.cxzheng.tracemanui.MethodTraceServerManager.isActiveTraceMan 4 | import cn.cxzheng.tracemanui.utils.LogUtil 5 | import com.ctrip.ibu.hotel.debug.server.producer.DataProducer 6 | import com.ctrip.ibu.hotel.debug.server.producer.module.appInfo.AppInfo 7 | import com.ctrip.ibu.hotel.debug.server.producer.module.methodcost.MethodCostHelper 8 | import com.koushikdutta.async.http.WebSocket 9 | import org.json.JSONObject 10 | 11 | /** 12 | * Create by cxzheng on 2019/7/7 13 | */ 14 | class WebScoketHandler : IWebScoketHandler { 15 | 16 | override fun handle(webScoket: WebSocket, message: String?) { 17 | val obj = JSONObject(message) 18 | val moduleName = obj["moduleName"] 19 | 20 | when (moduleName) { 21 | "OnlineMessage" -> { 22 | LogUtil.i("接收到消息:传输设备基本信息") 23 | DataProducer.producerAppInfo(AppInfo()) 24 | } 25 | "StartMethodCost" -> { 26 | LogUtil.i("接收到消息:开始方法耗时统计") 27 | isActiveTraceMan = true 28 | MethodCostHelper.startMethodCost() 29 | } 30 | "EndMethodCost" -> { 31 | LogUtil.i("接收到消息:结束方法耗时统计") 32 | isActiveTraceMan = false 33 | MethodCostHelper.endMethodCost() 34 | } 35 | 36 | } 37 | 38 | 39 | LogUtil.detail("接到浏览器消息内容:$message") 40 | } 41 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/model/Message.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.model 2 | 3 | import cn.cxzheng.tracemanui.utils.JsonUtil 4 | 5 | /** 6 | * Create by cxzheng on 2019/7/8 7 | * 客户端传输的消息结构 8 | */ 9 | class Message { 10 | 11 | val SUCCESS = 1 12 | val DEFAULT_FAIL = 0 13 | 14 | var code: Int = 0 15 | var message: String 16 | var data: DataWithName? = null 17 | 18 | 19 | constructor(errorMessage: String) { 20 | this.code = DEFAULT_FAIL 21 | this.message = errorMessage 22 | this.data = null 23 | } 24 | 25 | constructor(moduleName: String, data: Any) { 26 | this.code = SUCCESS 27 | this.message = "success" 28 | this.data = DataWithName(moduleName, data) 29 | } 30 | 31 | class DataWithName(var moduleName: String, var payload: Any) 32 | 33 | override fun toString(): String { 34 | return JsonUtil.toJson(this, false) 35 | } 36 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/producer/BaseProducer.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.producer 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.subjects.PublishSubject 5 | import io.reactivex.subjects.Subject 6 | 7 | /** 8 | * Create by cxzheng on 2019/7/7 9 | */ 10 | open class BaseProducer { 11 | 12 | private var mSubject: Subject = PublishSubject.create() 13 | 14 | 15 | fun produce(data: T) { 16 | mSubject.onNext(data) 17 | } 18 | 19 | fun subject(): Observable { 20 | return mSubject 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/producer/DataProducer.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.producer 2 | 3 | import cn.cxzheng.tracemanui.MethodTraceServerManager 4 | import cn.cxzheng.tracemanui.MethodTraceServerManager.APPINFO 5 | import cn.cxzheng.tracemanui.MethodTraceServerManager.METHODCOST 6 | import cn.cxzheng.tracemanui.MethodTraceServerManager.isActiveTraceMan 7 | import com.ctrip.ibu.hotel.debug.server.producer.module.appInfo.AppInfo 8 | import com.ctrip.ibu.hotel.debug.server.producer.module.appInfo.AppInfoProducer 9 | import com.ctrip.ibu.hotel.debug.server.producer.module.methodcost.MethodCostProducer 10 | import com.ctrip.ibu.hotel.debug.server.producer.module.methodcost.MethodInfo 11 | 12 | /** 13 | * Create by cxzheng on 2019/7/23 14 | */ 15 | class DataProducer { 16 | 17 | companion object { 18 | 19 | fun producerAppInfo(appInfo: AppInfo) { 20 | MethodTraceServerManager.getModule(APPINFO).produce(appInfo) 21 | } 22 | 23 | fun producerMethodCostInfo(methodCostInfo: List) { 24 | MethodTraceServerManager.getModule(METHODCOST) 25 | .produce(methodCostInfo) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/producer/module/DebugBaseInfo.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.producer.module 2 | 3 | import org.joda.time.DateTime 4 | 5 | 6 | /** 7 | * Create by cxzheng on 2019/7/27 8 | */ 9 | open class DebugBaseInfo { 10 | 11 | var timeStamp = DateTime.now().toString("yyyy-MM-dd HH:mm:ss") 12 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/producer/module/appInfo/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.producer.module.appInfo 2 | 3 | import android.os.Build 4 | import cn.cxzheng.tracemanui.utils.AppUtil 5 | import com.ctrip.ibu.hotel.debug.server.producer.module.DebugBaseInfo 6 | 7 | /** 8 | * Create by cxzheng on 2019/7/27 9 | */ 10 | class AppInfo : DebugBaseInfo() { 11 | 12 | val appInfo = ArrayList().apply { 13 | add("机型:${Build.BRAND} ${Build.MODEL}") 14 | add("系统版本:${Build.VERSION.SDK_INT}") 15 | // add("包名:${Build.)}") 16 | // add("App版本:${AppUtil.getVersionCode()}") 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/producer/module/appInfo/AppInfoProducer.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.producer.module.appInfo 2 | 3 | import com.ctrip.ibu.hotel.debug.server.producer.BaseProducer 4 | 5 | /** 6 | * Create by cxzheng on 2019/7/27 7 | */ 8 | class AppInfoProducer : BaseProducer() { 9 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/producer/module/methodcost/MethodCostHelper.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.producer.module.methodcost 2 | 3 | import cn.cxzheng.tracemanui.TraceMan 4 | import com.ctrip.ibu.hotel.debug.server.producer.DataProducer 5 | import io.reactivex.Observable 6 | import io.reactivex.android.schedulers.AndroidSchedulers 7 | import io.reactivex.schedulers.Schedulers 8 | 9 | /** 10 | * Create by cxzheng on 2019/9/9 11 | */ 12 | class MethodCostHelper { 13 | 14 | companion object { 15 | 16 | fun startMethodCost() { 17 | TraceMan.startCollectMethodCost() 18 | } 19 | 20 | 21 | fun endMethodCost() { 22 | val methodCostInfo = TraceMan.endCollectMethodCost() 23 | methodCostInfo?.let { 24 | Observable.create> { 25 | it.onNext(methodCostInfo) 26 | it.onComplete() 27 | 28 | }.subscribeOn(Schedulers.computation()) 29 | .observeOn(AndroidSchedulers.mainThread()) 30 | .subscribe({ 31 | DataProducer.producerMethodCostInfo(it) 32 | }, { 33 | it.printStackTrace() 34 | }) 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/producer/module/methodcost/MethodCostProducer.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.producer.module.methodcost 2 | 3 | import com.ctrip.ibu.hotel.debug.server.producer.BaseProducer 4 | 5 | /** 6 | * Create by cxzheng on 2019/9/8 7 | */ 8 | class MethodCostProducer : BaseProducer>() { 9 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/producer/module/methodcost/MethodInfo.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.ibu.hotel.debug.server.producer.module.methodcost 2 | 3 | /** 4 | * Create by cxzheng on 2019/9/9 5 | */ 6 | class MethodInfo(var name: String?, var costTime: Long, var startPos: Int, var endPos: Int, var isMainThread: Boolean) -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/utils/AppUtil.kt: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.tracemanui.utils 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageInfo 5 | 6 | class AppUtil { 7 | 8 | companion object { 9 | @Synchronized 10 | fun getAppName(context: Context): String? { 11 | try { 12 | val labelRes = getPackageInfo(context).applicationInfo.labelRes 13 | return context.resources.getString(labelRes) 14 | } catch (e: Exception) { 15 | e.printStackTrace() 16 | } 17 | return null 18 | } 19 | 20 | @Synchronized 21 | fun getVersionName(context: Context): String? { 22 | try { 23 | return getPackageInfo(context).versionName 24 | } catch (e: Exception) { 25 | e.printStackTrace() 26 | } 27 | return null 28 | } 29 | 30 | 31 | @Synchronized 32 | fun getVersionCode(context: Context): Int { 33 | try { 34 | return getPackageInfo(context).versionCode 35 | } catch (e: Exception) { 36 | e.printStackTrace() 37 | } 38 | return 0 39 | } 40 | 41 | 42 | @Synchronized 43 | fun getPackageName(context: Context): String? { 44 | try { 45 | return getPackageInfo(context).packageName 46 | } catch (e: Exception) { 47 | e.printStackTrace() 48 | } 49 | return null 50 | } 51 | 52 | private fun getPackageInfo(context: Context): PackageInfo { 53 | return context.packageManager.getPackageInfo( 54 | context.packageName, 0 55 | ) 56 | } 57 | } 58 | 59 | 60 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/utils/JsonUtil.kt: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.tracemanui.utils 2 | 3 | import com.google.gson.GsonBuilder 4 | 5 | /** 6 | * Create by cxzheng on 2019/9/13 7 | */ 8 | class JsonUtil { 9 | 10 | companion object { 11 | fun toJson(any: Any?, excludeFieldsWithoutExposeAnnotation: Boolean): String { 12 | return any?.let { 13 | createGsonBuilder(excludeFieldsWithoutExposeAnnotation).create().toJson(any) 14 | } ?: "" 15 | } 16 | 17 | private fun createGsonBuilder(excludeFieldsWithoutExposeAnnotation: Boolean): GsonBuilder { 18 | return GsonBuilder().apply { 19 | if (excludeFieldsWithoutExposeAnnotation) { 20 | excludeFieldsWithoutExposeAnnotation() 21 | } 22 | disableHtmlEscaping() 23 | } 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /tracemanui/src/main/java/cn/cxzheng/tracemanui/utils/LogUtil.kt: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.tracemanui.utils 2 | 3 | import android.util.Log 4 | import cn.cxzheng.tracemanui.MethodTraceServerManager 5 | import cn.cxzheng.tracemanui.MethodTraceServerManager.DEBUG_SERVER_TAG 6 | import cn.cxzheng.tracemanui.MethodTraceServerManager.MTM_LOG_DETAIL 7 | 8 | /** 9 | * Create by cxzheng on 2019-11-09 10 | */ 11 | class LogUtil { 12 | 13 | companion object { 14 | 15 | 16 | @JvmStatic 17 | fun detail(message: String?) { 18 | if (MethodTraceServerManager.logLevel == MTM_LOG_DETAIL) { 19 | Log.i(DEBUG_SERVER_TAG, message) 20 | } 21 | } 22 | 23 | @JvmStatic 24 | fun i(message: String?) { 25 | Log.i(DEBUG_SERVER_TAG, message) 26 | } 27 | 28 | 29 | @JvmStatic 30 | fun e(message: String?) { 31 | Log.e(DEBUG_SERVER_TAG, message) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /tracemanui/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | tracemanui 3 | 4 | -------------------------------------------------------------------------------- /tracemanui/src/test/java/cn/cxzheng/tracemanui/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cn.cxzheng.tracemanui; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /问题排障.md: -------------------------------------------------------------------------------- 1 | # MethodTraceMan问题排障 2 | 当集成完MethodTraceMan项目后,Rebuild项目并启动app后,遇到没有成功输出方法耗时数据时,`第一步请先阅读以下的几个注意事项`,看是否有存在问题: 3 | * 请不要同时打开两个集成了此项目的App,会导致耗时数据无法传送到浏览器的UI界面 4 | * 请不要同时连接两个手机,会导致浏览器打开界面失败 5 | * 集成进自己的项目的话,请务必记得将traceconfig.txt中 -tracepackage配置成自己想插桩的包范围 6 | * 如果重启AndroidStduio后在顶部栏没发现小灯泡图标,请检查AndroidStduio顶部栏View->Toolbar是否勾选上 7 | 8 | 9 | 当上面的注意事项都排查过没有问题,依然无法成功输出数据时,则需要`启动项目内部的日志来排查问题`了,整个项目主要包括:插桩、数据处理、发送数据、接收数据、展示数据等几个过程,所以我们在这几个重要过程都添加了详细的日志,遇到问题可以首先通过以下几个配置来打开详细的日志输出: 10 | 11 | ## 打开详细日志输出配置 12 | 13 | 1.build.gradle中打开`logTraceInfo`开关,则会在Rebuild过程中输出所有被插桩的类和方法 14 | 15 | ```groovy 16 | apply plugin: "cn.cxzheng.asmtraceman" 17 | traceMan { 18 | open = true //这里如果设置为false,则会关闭插桩 19 | logTraceInfo = true //这里设置为true时可以在log日志里看到所有被插桩的类和方法 20 | traceConfigFile = "${project.projectDir}/traceconfig.txt" 21 | } 22 | ``` 23 | 24 | 2.初始化MethodTraceMan项目时,如Application或者MainActivity的onCreate()中添加如下代码,设置日志输出级别为`详细`,如下配置即可: 25 | 26 | ```kotlin 27 | MethodTraceServerManager.logLevel = MethodTraceServerManager.MTM_LOG_DETAIL 28 | ``` 29 | 30 | ## 排障 31 | 32 | 1.首先通过观察Build下的编译日志来看看是否插桩成功(会输出所有被成功插桩的类和方法) 33 | 34 | 35 | 36 | 2.在Logcat下筛选关键字`MethodTraceMan`,观察各个流程的详细日志输出,看看哪个流程有问题,最后结束耗时统计的时候是否发送了耗时数据给浏览器等 37 | 38 | 39 | 40 | 3.如果通过第二步的排查确定已经将数据正确的发送给了浏览器,那么最后一步就是排查浏览器是否正确的接收到了,打开浏览器的检查->Console查看输出即可。 41 | 42 | --------------------------------------------------------------------------------