├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle ├── java-publish.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── systrace-gradle-plugin ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── groovy │ └── com │ │ └── geektime │ │ └── systrace │ │ ├── SystracePlugin.groovy │ │ ├── extension │ │ └── SystraceExtension.groovy │ │ └── transform │ │ ├── BaseProxyTransform.groovy │ │ └── SystemTraceTransform.groovy │ ├── java │ └── com │ │ └── geektime │ │ └── systrace │ │ ├── Log.java │ │ ├── MethodCollector.java │ │ ├── MethodTracer.java │ │ ├── ReflectUtil.java │ │ ├── TraceBuildConfig.java │ │ ├── TraceBuildConstants.java │ │ ├── Util.java │ │ ├── item │ │ └── TraceMethod.java │ │ └── retrace │ │ ├── MappingCollector.java │ │ ├── MappingProcessor.java │ │ ├── MappingReader.java │ │ └── MethodInfo.java │ └── resources │ └── META-INF │ └── gradle-plugins │ └── com.geektime.systrace-plugin.properties └── systrace-sample-android ├── .gitignore ├── app ├── .gitignore ├── blacklist │ └── blackMethodList.txt ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── matrix │ │ └── tencent │ │ └── com │ │ └── matrix_android │ │ └── ExampleInstrumentedTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── sample │ │ └── systrace │ │ ├── MainActivity.java │ │ ├── SampleApplication.java │ │ ├── TestIgnoreFile.java │ │ └── TraceTag.java │ └── res │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | **/build/ 6 | /captures 7 | **/.externalNativeBuild/ 8 | .idea/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chapter07 2 | 这个Sample是学习如何给代码加入Trace Tag, 大家可以将这个代码运用到自己的项目中,然后利用systrace查看结果 3 | 4 | 结果如下: 5 | 6 | ``` 7 | protected void onResume() { 8 | TraceTag.i("com.sample.systrace.MainActivity.onResume.()V"); 9 | super.onResume(); 10 | Log.i("MainActivity", "[onResume]"); 11 | TraceTag.o(); 12 | } 13 | ``` 14 | 15 | ## 操作步骤 16 | 操作步骤如下: 17 | 18 | 1. 使用Android Studio打开工程Chapter07 19 | 2. 运行gradle task ":systrace-gradle-plugin:buildAndPublishToLocalMaven" 编译plugin插件 20 | 3. 使用Android Studio单独打开工程systrace-sample-android 21 | 4. 编译app 22 | 23 | ## 注意事项 24 | 在systrace-sample-android工程中,需要注意以下几点: 25 | 26 | 1. 插桩代码会自动过滤短函数,过滤结果输出到`Chapter07/systrace-sample-android/app/build/systrace_output/Debug.ignoremethodmap`。 27 | 2. 我们也可以单独控制不插桩的白名单,配置文件位于`Chapter07/systrace-sample-android/app/blacklist/blackMethodList.txt`, 可以指定class或者包名。 28 | 3. 插桩后的class文件在目录`Chapter07/systrace-sample-android/app/build/systrace_output/classes`中查看。 29 | 30 | 然后运行应用,需要打开systrace 31 | ``` 32 | python $ANDROID_HOME/platform-tools/systrace/systrace.py gfx view wm am pm ss dalvik app sched -b 90960 -a com.sample.systrace -o test.log.html 33 | ``` -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | buildscript { 4 | repositories { 5 | mavenLocal() 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | // classpath 12 | classpath 'com.android.tools.build:gradle:3.2.1' 13 | classpath "com.github.dcendents:android-maven-gradle-plugin:1.5" 14 | 15 | } 16 | 17 | 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | mavenLocal() 23 | 24 | google() 25 | jcenter() 26 | } 27 | 28 | tasks.withType(Javadoc).all { 29 | enabled = false // TODO: 30 | options.setEncoding('UTF-8') 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 9 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | #Tue Jun 20 10:24:33 CST 2017 -------------------------------------------------------------------------------- /gradle/java-publish.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'maven-publish' 3 | 4 | 5 | 6 | def javadocJar = task("javadocJar", type: Jar, dependsOn: javadoc) { 7 | classifier = 'javadoc' 8 | from javadoc.destinationDir 9 | } 10 | def sourcesJar = task("sourcesJar", type: Jar) { 11 | classifier = 'sources' 12 | from sourceSets.main.java.srcDirs 13 | } 14 | 15 | publishing { 16 | publications { 17 | Component(MavenPublication) { 18 | from components.java 19 | groupId = group 20 | artifactId = POM_ARTIFACT_ID 21 | version = version 22 | 23 | artifact sourcesJar 24 | artifact javadocJar 25 | } 26 | } 27 | } 28 | 29 | 30 | task buildAndPublishToLocalMaven(type: Copy, dependsOn: ['build', 'publishToMavenLocal']) { 31 | group = 'geektime' 32 | 33 | // save artifacts files to artifacts folder 34 | from configurations.archives.allArtifacts.files 35 | into "${rootProject.buildDir}/outputs/artifacts/" 36 | rename { String fileName -> 37 | fileName.replace("release.aar", "${version}.aar") 38 | } 39 | 40 | doLast { 41 | println "* published to maven local: ${project.group}:${project.name}:${project.version}" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndroidAdvanceWithGeektime/Chapter07/1e5eed2555cad70fb6ee9d0c1f0e9aaddef54690/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':systrace-gradle-plugin' 2 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'java' 3 | 4 | 5 | sourceSets { 6 | main { 7 | groovy { 8 | srcDir '../systrace-gradle-plugin/src/main/groovy' 9 | } 10 | 11 | java { 12 | srcDir "../systrace-gradle-plugin/src/main/java" 13 | } 14 | 15 | resources { 16 | srcDir '../systrace-gradle-plugin/src/main/resources' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(include: ['*.jar'], dir: 'libs') 23 | compile gradleApi() 24 | compile localGroovy() 25 | compile group: 'org.ow2.asm', name: 'asm', version: '5.1' 26 | compile group: 'org.ow2.asm', name: 'asm-commons', version: '5.1' 27 | compile 'com.android.tools.build:gradle:2.1.0' 28 | compile 'com.android.tools.lint:lint:26.0.1' 29 | compile 'com.android.tools.lint:lint-api:26.0.1' 30 | compile 'com.android.tools.lint:lint-checks:26.0.1' 31 | } 32 | 33 | version 1.0 34 | group "com.geektime.systrace" 35 | 36 | apply from: rootProject.file('gradle/java-publish.gradle') 37 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=systrace-gradle-plugin 2 | POM_NAME=Systrace Gradle Plugin 3 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/groovy/com/geektime/systrace/SystracePlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.geektime.systrace 2 | 3 | 4 | import com.geektime.systrace.extension.SystraceExtension 5 | import com.geektime.systrace.transform.SystemTraceTransform 6 | import org.gradle.api.GradleException 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | 10 | /** 11 | * Created by zhangshaowen on 17/6/16. 12 | */ 13 | class SystracePlugin implements Plugin { 14 | private static final String TAG = "SystracePlugin" 15 | 16 | @Override 17 | void apply(Project project) { 18 | project.extensions.create("systrace", SystraceExtension) 19 | 20 | if (!project.plugins.hasPlugin('com.android.application')) { 21 | throw new GradleException('Systrace Plugin, Android Application plugin required') 22 | } 23 | 24 | project.afterEvaluate { 25 | def android = project.extensions.android 26 | def configuration = project.systrace 27 | android.applicationVariants.all { variant -> 28 | 29 | String output = configuration.output 30 | if (Util.isNullOrNil(output)) { 31 | configuration.output = project.getBuildDir().getAbsolutePath() + File.separator + "systrace_output" 32 | Log.i(TAG, "set Systrace output file to " + configuration.output) 33 | } 34 | 35 | Log.i(TAG, "Trace enable is %s", configuration.enable) 36 | if (configuration.enable) { 37 | SystemTraceTransform.inject(project, variant) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/groovy/com/geektime/systrace/extension/SystraceExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.geektime.systrace.extension 2 | 3 | class SystraceExtension { 4 | boolean enable 5 | String baseMethodMapFile 6 | String blackListFile 7 | String output 8 | 9 | 10 | SystraceExtension() { 11 | enable = true 12 | baseMethodMapFile = "" 13 | blackListFile = "" 14 | output = "" 15 | 16 | } 17 | 18 | @Override 19 | String toString() { 20 | """| enable = ${enable} 21 | | baseMethodMapFile = ${baseMethodMapFile} 22 | | blackListFile = ${blackListFile} 23 | | output = ${output} 24 | """.stripMargin() 25 | } 26 | } -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/groovy/com/geektime/systrace/transform/BaseProxyTransform.groovy: -------------------------------------------------------------------------------- 1 | package com.geektime.systrace.transform 2 | 3 | import com.android.build.api.transform.DirectoryInput 4 | import com.android.build.api.transform.QualifiedContent 5 | import com.android.build.api.transform.Status 6 | import com.android.build.api.transform.Transform 7 | import com.google.common.base.Charsets 8 | import com.google.common.hash.Hashing 9 | import com.geektime.systrace.ReflectUtil 10 | 11 | import java.lang.reflect.Field 12 | 13 | public abstract class BaseProxyTransform extends Transform { 14 | protected final Transform origTransform 15 | 16 | public BaseProxyTransform(Transform origTransform) { 17 | this.origTransform = origTransform 18 | } 19 | 20 | @Override 21 | Set getInputTypes() { 22 | return origTransform.getInputTypes() 23 | } 24 | 25 | @Override 26 | Set getScopes() { 27 | return origTransform.getScopes() 28 | } 29 | 30 | @Override 31 | boolean isIncremental() { 32 | return origTransform.isIncremental() 33 | } 34 | 35 | protected String getUniqueJarName(File jarFile) { 36 | final String origJarName = jarFile.getName() 37 | final String hashing = Hashing.sha1().hashString(jarFile.getPath(), Charsets.UTF_16LE).toString() 38 | final int dotPos = origJarName.lastIndexOf('.') 39 | if (dotPos < 0) { 40 | return "${origJarName}_${hashing}" 41 | } else { 42 | final String nameWithoutDotExt = origJarName.substring(0, dotPos) 43 | final String dotExt = origJarName.substring(dotPos) 44 | return "${nameWithoutDotExt}_${hashing}${dotExt}" 45 | } 46 | } 47 | 48 | protected void replaceFile(QualifiedContent input, File newFile) { 49 | final Field fileField = ReflectUtil.getDeclaredFieldRecursive(input.getClass(), 'file') 50 | fileField.set(input, newFile) 51 | } 52 | 53 | protected void replaceChangedFile(DirectoryInput dirInput, Map changedFiles) { 54 | final Field changedFilesField = ReflectUtil.getDeclaredFieldRecursive(dirInput.getClass(), 'changedFiles') 55 | changedFilesField.set(dirInput, changedFiles) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/groovy/com/geektime/systrace/transform/SystemTraceTransform.groovy: -------------------------------------------------------------------------------- 1 | package com.geektime.systrace.transform 2 | 3 | import com.android.build.api.transform.* 4 | import com.android.build.gradle.internal.pipeline.TransformTask 5 | import com.geektime.systrace.Log 6 | import com.geektime.systrace.Util 7 | 8 | import com.geektime.systrace.MethodCollector 9 | import com.geektime.systrace.MethodTracer 10 | import com.geektime.systrace.TraceBuildConfig 11 | import com.geektime.systrace.item.TraceMethod 12 | import com.geektime.systrace.retrace.MappingReader 13 | import com.geektime.systrace.retrace.MappingCollector 14 | import org.gradle.api.Project 15 | import org.gradle.api.Task 16 | import org.gradle.api.execution.TaskExecutionGraph 17 | import org.gradle.api.execution.TaskExecutionGraphListener 18 | 19 | import java.lang.reflect.Field 20 | 21 | 22 | public class SystemTraceTransform extends BaseProxyTransform { 23 | 24 | Transform origTransform 25 | Project project 26 | def variant 27 | 28 | SystemTraceTransform(Project project, def variant, Transform origTransform) { 29 | super(origTransform) 30 | this.origTransform = origTransform 31 | this.variant = variant 32 | this.project = project 33 | } 34 | 35 | public static void inject(Project project, def variant) { 36 | 37 | String hackTransformTaskName = getTransformTaskName( 38 | "", 39 | "",variant.name 40 | ) 41 | 42 | String hackTransformTaskNameForWrapper = getTransformTaskName( 43 | "", 44 | "Builder",variant.name 45 | ) 46 | 47 | project.logger.info("prepare inject dex transform :" + hackTransformTaskName +" hackTransformTaskNameForWrapper:"+hackTransformTaskNameForWrapper) 48 | 49 | project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() { 50 | @Override 51 | public void graphPopulated(TaskExecutionGraph taskGraph) { 52 | for (Task task : taskGraph.getAllTasks()) { 53 | if ((task.name.equalsIgnoreCase(hackTransformTaskName) || task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper)) 54 | && !(((TransformTask) task).getTransform() instanceof SystemTraceTransform)) { 55 | project.logger.warn("find dex transform. transform class: " + task.transform.getClass() + " . task name: " + task.name) 56 | project.logger.info("variant name: " + variant.name) 57 | Field field = TransformTask.class.getDeclaredField("transform") 58 | field.setAccessible(true) 59 | field.set(task, new SystemTraceTransform(project, variant, task.transform)) 60 | project.logger.warn("transform class after hook: " + task.transform.getClass()) 61 | break 62 | } 63 | } 64 | } 65 | }) 66 | } 67 | 68 | @Override 69 | public String getName() { 70 | return "SystemTraceTransform" 71 | } 72 | 73 | @Override 74 | public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { 75 | long start = System.currentTimeMillis() 76 | final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental() 77 | final File rootOutput = new File(project.systrace.output, "classes/${getName()}/") 78 | if (!rootOutput.exists()) { 79 | rootOutput.mkdirs() 80 | } 81 | final TraceBuildConfig traceConfig = initConfig() 82 | Log.i("Systrace." + getName(), "[transform] isIncremental:%s rootOutput:%s", isIncremental, rootOutput.getAbsolutePath()) 83 | final MappingCollector mappingCollector = new MappingCollector() 84 | File mappingFile = new File(traceConfig.getMappingPath()); 85 | if (mappingFile.exists() && mappingFile.isFile()) { 86 | MappingReader mappingReader = new MappingReader(mappingFile); 87 | mappingReader.read(mappingCollector) 88 | } 89 | 90 | Map jarInputMap = new HashMap<>() 91 | Map scrInputMap = new HashMap<>() 92 | 93 | transformInvocation.inputs.each { TransformInput input -> 94 | input.directoryInputs.each { DirectoryInput dirInput -> 95 | collectAndIdentifyDir(scrInputMap, dirInput, rootOutput, isIncremental) 96 | } 97 | input.jarInputs.each { JarInput jarInput -> 98 | if (jarInput.getStatus() != Status.REMOVED) { 99 | collectAndIdentifyJar(jarInputMap, scrInputMap, jarInput, rootOutput, isIncremental) 100 | } 101 | } 102 | } 103 | 104 | MethodCollector methodCollector = new MethodCollector(traceConfig, mappingCollector) 105 | HashMap collectedMethodMap = methodCollector.collect(scrInputMap.keySet().toList(), jarInputMap.keySet().toList()) 106 | MethodTracer methodTracer = new MethodTracer(traceConfig, collectedMethodMap, methodCollector.getCollectedClassExtendMap()) 107 | methodTracer.trace(scrInputMap, jarInputMap) 108 | origTransform.transform(transformInvocation) 109 | Log.i("Systrace." + getName(), "[transform] cost time: %dms", System.currentTimeMillis() - start) 110 | } 111 | 112 | private void collectAndIdentifyDir(Map dirInputMap, DirectoryInput input, File rootOutput, boolean isIncremental) { 113 | final File dirInput = input.file 114 | final File dirOutput = new File(rootOutput, input.file.getName()) 115 | if (!dirOutput.exists()) { 116 | dirOutput.mkdirs() 117 | } 118 | if (isIncremental) { 119 | if (!dirInput.exists()) { 120 | dirOutput.deleteDir() 121 | } else { 122 | final Map obfuscatedChangedFiles = new HashMap<>() 123 | final String rootInputFullPath = dirInput.getAbsolutePath() 124 | final String rootOutputFullPath = dirOutput.getAbsolutePath() 125 | input.changedFiles.each { Map.Entry entry -> 126 | final File changedFileInput = entry.getKey() 127 | final String changedFileInputFullPath = changedFileInput.getAbsolutePath() 128 | final File changedFileOutput = new File( 129 | changedFileInputFullPath.replace(rootInputFullPath, rootOutputFullPath) 130 | ) 131 | final Status status = entry.getValue() 132 | switch (status) { 133 | case Status.NOTCHANGED: 134 | break 135 | case Status.ADDED: 136 | case Status.CHANGED: 137 | dirInputMap.put(changedFileInput, changedFileOutput) 138 | break 139 | case Status.REMOVED: 140 | changedFileOutput.delete() 141 | break 142 | } 143 | obfuscatedChangedFiles.put(changedFileOutput, status) 144 | } 145 | replaceChangedFile(input, obfuscatedChangedFiles) 146 | } 147 | } else { 148 | dirInputMap.put(dirInput, dirOutput) 149 | } 150 | replaceFile(input, dirOutput) 151 | } 152 | 153 | private void collectAndIdentifyJar(Map jarInputMaps, Map dirInputMaps, JarInput input, File rootOutput, boolean isIncremental) { 154 | final File jarInput = input.file 155 | final File jarOutput = new File(rootOutput, getUniqueJarName(jarInput)) 156 | if (Util.isRealZipOrJar(jarInput)) { 157 | switch (input.status) { 158 | case Status.NOTCHANGED: 159 | if (isIncremental) { 160 | break 161 | } 162 | case Status.ADDED: 163 | case Status.CHANGED: 164 | jarInputMaps.put(jarInput, jarOutput) 165 | break 166 | case Status.REMOVED: 167 | break 168 | } 169 | } else { 170 | // Special case for WeChat AutoDex. Its rootInput jar file is actually 171 | // a txt file contains path list. 172 | BufferedReader br = null 173 | BufferedWriter bw = null 174 | try { 175 | br = new BufferedReader(new InputStreamReader(new FileInputStream(jarInput))) 176 | bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(jarOutput))) 177 | String realJarInputFullPath 178 | while ((realJarInputFullPath = br.readLine()) != null) { 179 | // src jar. 180 | final File realJarInput = new File(realJarInputFullPath) 181 | // dest jar, moved to extraguard intermediate output dir. 182 | final File realJarOutput = new File(rootOutput, getUniqueJarName(realJarInput)) 183 | 184 | if (realJarInput.exists() && Util.isRealZipOrJar(realJarInput)) { 185 | jarInputMaps.put(realJarInput, realJarOutput) 186 | } else { 187 | realJarOutput.delete() 188 | if (realJarInput.exists() && realJarInput.isDirectory()) { 189 | realJarOutput = new File(rootOutput, realJarInput.getName()) 190 | if (!realJarOutput.exists()) { 191 | realJarOutput.mkdirs() 192 | } 193 | dirInputMaps.put(realJarInput, realJarOutput) 194 | } 195 | 196 | } 197 | // write real output full path to the fake jar at rootOutput. 198 | final String realJarOutputFullPath = realJarOutput.getAbsolutePath() 199 | bw.writeLine(realJarOutputFullPath) 200 | } 201 | } catch (FileNotFoundException e) { 202 | Log.e("Systrace." + getName(), "FileNotFoundException:%s", e.toString()) 203 | } finally { 204 | Util.closeQuietly(br) 205 | Util.closeQuietly(bw) 206 | } 207 | jarInput.delete() // delete raw inputList 208 | } 209 | 210 | replaceFile(input, jarOutput) 211 | } 212 | 213 | 214 | static 215 | private String getTransformTaskName(String customDexTransformName, String wrappSuffix, String buildTypeSuffix) { 216 | if(customDexTransformName != null && customDexTransformName.length() > 0) { 217 | return customDexTransformName+"For${buildTypeSuffix}" 218 | } 219 | return "transformClassesWithDex${wrappSuffix}For${buildTypeSuffix}" 220 | } 221 | 222 | private TraceBuildConfig initConfig() { 223 | def configuration = project.systrace 224 | def variantName = variant.name.capitalize() 225 | def mappingFilePath = "" 226 | if (variant.getBuildType().isMinifyEnabled()) { 227 | mappingFilePath = variant.mappingFile.getAbsolutePath() 228 | } 229 | TraceBuildConfig traceConfig = new TraceBuildConfig.Builder() 230 | .setPackageName(variant.applicationId) 231 | .setBaseMethodMap(configuration.baseMethodMapFile) 232 | .setMethodMapDir(configuration.output + "/${variantName}.methodmap") 233 | .setIgnoreMethodMapDir(configuration.output + "/${variantName}.ignoremethodmap") 234 | .setBlackListFile(configuration.blackListFile) 235 | .setMappingPath(mappingFilePath) 236 | .build() 237 | project.logger.info("TraceConfig: " + traceConfig.toString()) 238 | return traceConfig 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/Log.java: -------------------------------------------------------------------------------- 1 | package com.geektime.systrace; 2 | 3 | 4 | import java.io.PrintWriter; 5 | import java.io.StringWriter; 6 | 7 | public class Log { 8 | 9 | private static LogImp debugLog = new LogImp() { 10 | 11 | @Override 12 | public void v(final String tag, final String msg, final Object... obj) { 13 | String log = obj == null ? msg : String.format(msg, obj); 14 | System.out.println(String.format("[VERBOSE][%s]%s", tag, log)); 15 | } 16 | 17 | @Override 18 | public void i(final String tag, final String msg, final Object... obj) { 19 | String log = obj == null ? msg : String.format(msg, obj); 20 | System.out.println(String.format("[INFO][%s]%s", tag, log)); 21 | } 22 | 23 | @Override 24 | public void d(final String tag, final String msg, final Object... obj) { 25 | String log = obj == null ? msg : String.format(msg, obj); 26 | System.out.println(String.format("[DEBUG][%s]%s", tag, log)); 27 | } 28 | 29 | @Override 30 | public void w(final String tag, final String msg, final Object... obj) { 31 | String log = obj == null ? msg : String.format(msg, obj); 32 | System.out.println(String.format("[WARN][%s]%s", tag, log)); 33 | } 34 | 35 | @Override 36 | public void e(final String tag, final String msg, final Object... obj) { 37 | String log = obj == null ? msg : String.format(msg, obj); 38 | System.out.println(String.format("[ERROR][%s]%s", tag, log)); 39 | } 40 | 41 | @Override 42 | public void printErrStackTrace(String tag, Throwable tr, String format, Object... obj) { 43 | String log = obj == null ? format : String.format(format, obj); 44 | if (log == null) { 45 | log = ""; 46 | } 47 | StringWriter sw = new StringWriter(); 48 | PrintWriter pw = new PrintWriter(sw); 49 | tr.printStackTrace(pw); 50 | log += " " + sw.toString(); 51 | System.out.println(String.format("[ERROR][%s]%s", tag, log)); 52 | } 53 | }; 54 | 55 | private static LogImp logImp = debugLog; 56 | 57 | private Log() { 58 | } 59 | 60 | public static void setLogImp(LogImp imp) { 61 | logImp = imp; 62 | } 63 | 64 | public static LogImp getImpl() { 65 | return logImp; 66 | } 67 | 68 | public static void v(final String tag, final String msg, final Object... obj) { 69 | if (logImp != null) { 70 | logImp.v(tag, msg, obj); 71 | } 72 | } 73 | 74 | public static void e(final String tag, final String msg, final Object... obj) { 75 | if (logImp != null) { 76 | logImp.e(tag, msg, obj); 77 | } 78 | } 79 | 80 | public static void w(final String tag, final String msg, final Object... obj) { 81 | if (logImp != null) { 82 | logImp.w(tag, msg, obj); 83 | } 84 | } 85 | 86 | public static void i(final String tag, final String msg, final Object... obj) { 87 | if (logImp != null) { 88 | logImp.i(tag, msg, obj); 89 | } 90 | } 91 | 92 | public static void d(final String tag, final String msg, final Object... obj) { 93 | if (logImp != null) { 94 | logImp.d(tag, msg, obj); 95 | } 96 | } 97 | 98 | public static void printErrStackTrace(String tag, Throwable tr, final String format, final Object... obj) { 99 | if (logImp != null) { 100 | logImp.printErrStackTrace(tag, tr, format, obj); 101 | } 102 | } 103 | 104 | public interface LogImp { 105 | 106 | void v(final String tag, final String msg, final Object... obj); 107 | 108 | void i(final String tag, final String msg, final Object... obj); 109 | 110 | void w(final String tag, final String msg, final Object... obj); 111 | 112 | void d(final String tag, final String msg, final Object... obj); 113 | 114 | void e(final String tag, final String msg, final Object... obj); 115 | 116 | void printErrStackTrace(String tag, Throwable tr, final String format, final Object... obj); 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/MethodCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace; 18 | 19 | import com.geektime.systrace.item.TraceMethod; 20 | import com.geektime.systrace.retrace.MappingCollector; 21 | 22 | import org.objectweb.asm.ClassReader; 23 | import org.objectweb.asm.ClassVisitor; 24 | import org.objectweb.asm.ClassWriter; 25 | import org.objectweb.asm.MethodVisitor; 26 | import org.objectweb.asm.Opcodes; 27 | import org.objectweb.asm.tree.AbstractInsnNode; 28 | import org.objectweb.asm.tree.MethodNode; 29 | 30 | import java.io.File; 31 | import java.io.FileInputStream; 32 | import java.io.FileOutputStream; 33 | import java.io.InputStream; 34 | import java.io.OutputStreamWriter; 35 | import java.io.PrintWriter; 36 | import java.io.Writer; 37 | import java.util.ArrayList; 38 | import java.util.Collections; 39 | import java.util.Comparator; 40 | import java.util.Enumeration; 41 | import java.util.HashMap; 42 | import java.util.List; 43 | import java.util.ListIterator; 44 | import java.util.Map; 45 | import java.util.Scanner; 46 | import java.util.concurrent.atomic.AtomicInteger; 47 | import java.util.zip.ZipEntry; 48 | import java.util.zip.ZipFile; 49 | 50 | 51 | public class MethodCollector { 52 | 53 | private static final String TAG = "Matrix.MethodCollector"; 54 | private final HashMap mCollectedMethodMap; 55 | private final HashMap mCollectedIgnoreMethodMap; 56 | private final HashMap mCollectedBlackMethodMap; 57 | 58 | private final HashMap mCollectedClassExtendMap; 59 | 60 | private final TraceBuildConfig mTraceConfig; 61 | private final AtomicInteger mMethodId = new AtomicInteger(0); 62 | private final MappingCollector mMappingCollector; 63 | private int mIncrementCount, mIgnoreCount; 64 | 65 | public MethodCollector(TraceBuildConfig config, MappingCollector mappingCollector) { 66 | this.mCollectedMethodMap = new HashMap<>(); 67 | this.mCollectedClassExtendMap = new HashMap<>(); 68 | this.mCollectedIgnoreMethodMap = new HashMap<>(); 69 | this.mCollectedBlackMethodMap = new HashMap<>(); 70 | this.mTraceConfig = config; 71 | this.mMappingCollector = mappingCollector; 72 | } 73 | 74 | public HashMap getCollectedClassExtendMap() { 75 | return mCollectedClassExtendMap; 76 | } 77 | 78 | public HashMap collect(List srcFolderList, List dependencyJarList) { 79 | mTraceConfig.parseBlackFile(mMappingCollector); 80 | 81 | File originMethodMapFile = new File(mTraceConfig.getBaseMethodMap()); 82 | getMethodFromBaseMethod(originMethodMapFile); 83 | Log.i(TAG, "[collect] %s method from %s", mCollectedMethodMap.size(), mTraceConfig.getBaseMethodMap()); 84 | retraceMethodMap(mMappingCollector, mCollectedMethodMap); 85 | 86 | collectMethodFromSrc(srcFolderList, true); 87 | collectMethodFromJar(dependencyJarList, true); 88 | collectMethodFromSrc(srcFolderList, false); 89 | collectMethodFromJar(dependencyJarList, false); 90 | Log.i(TAG, "[collect] incrementCount:%s ignoreMethodCount:%s", mIncrementCount, mIgnoreCount); 91 | 92 | saveCollectedMethod(mMappingCollector); 93 | saveIgnoreCollectedMethod(mMappingCollector); 94 | 95 | return mCollectedMethodMap; 96 | 97 | } 98 | 99 | private void retraceMethodMap(MappingCollector processor, HashMap methodMap) { 100 | if (null == processor || null == methodMap) { 101 | return; 102 | } 103 | HashMap retraceMethodMap = new HashMap<>(methodMap.size()); 104 | for (Map.Entry entry : methodMap.entrySet()) { 105 | TraceMethod traceMethod = entry.getValue(); 106 | traceMethod.proguard(processor); 107 | retraceMethodMap.put(traceMethod.getMethodName(), traceMethod); 108 | } 109 | methodMap.clear(); 110 | methodMap.putAll(retraceMethodMap); 111 | retraceMethodMap.clear(); 112 | 113 | } 114 | 115 | private void saveIgnoreCollectedMethod(MappingCollector mappingCollector) { 116 | File methodMapFile = new File(mTraceConfig.getIgnoreMethodMapFile()); 117 | if (!methodMapFile.getParentFile().exists()) { 118 | methodMapFile.getParentFile().mkdirs(); 119 | } 120 | List ignoreMethodList = new ArrayList<>(); 121 | ignoreMethodList.addAll(mCollectedIgnoreMethodMap.values()); 122 | Log.i(TAG, "[saveIgnoreCollectedMethod] size:%s path:%s", mCollectedIgnoreMethodMap.size(), methodMapFile.getAbsolutePath()); 123 | 124 | List blackMethodList = new ArrayList<>(); 125 | blackMethodList.addAll(mCollectedBlackMethodMap.values()); 126 | Log.i(TAG, "[saveIgnoreBlackMethod] size:%s path:%s", mCollectedBlackMethodMap.size(), methodMapFile.getAbsolutePath()); 127 | 128 | Collections.sort(ignoreMethodList, new Comparator() { 129 | @Override 130 | public int compare(TraceMethod o1, TraceMethod o2) { 131 | return o1.className.compareTo(o2.className); 132 | } 133 | }); 134 | 135 | Collections.sort(blackMethodList, new Comparator() { 136 | @Override 137 | public int compare(TraceMethod o1, TraceMethod o2) { 138 | return o1.className.compareTo(o2.className); 139 | } 140 | }); 141 | 142 | PrintWriter pw = null; 143 | try { 144 | FileOutputStream fileOutputStream = new FileOutputStream(methodMapFile, false); 145 | Writer w = new OutputStreamWriter(fileOutputStream, "UTF-8"); 146 | pw = new PrintWriter(w); 147 | pw.println("ignore methods:"); 148 | for (TraceMethod traceMethod : ignoreMethodList) { 149 | traceMethod.revert(mappingCollector); 150 | pw.println(traceMethod.toIgnoreString()); 151 | } 152 | pw.println(""); 153 | pw.println("black methods:"); 154 | for (TraceMethod traceMethod : blackMethodList) { 155 | traceMethod.revert(mappingCollector); 156 | pw.println(traceMethod.toIgnoreString()); 157 | } 158 | 159 | } catch (Exception e) { 160 | Log.e(TAG, "write method map Exception:%s", e.getMessage()); 161 | e.printStackTrace(); 162 | } finally { 163 | if (pw != null) { 164 | pw.flush(); 165 | pw.close(); 166 | } 167 | } 168 | } 169 | 170 | 171 | private void saveCollectedMethod(MappingCollector mappingCollector) { 172 | File methodMapFile = new File(mTraceConfig.getMethodMapFile()); 173 | if (!methodMapFile.getParentFile().exists()) { 174 | methodMapFile.getParentFile().mkdirs(); 175 | } 176 | List methodList = new ArrayList<>(); 177 | methodList.addAll(mCollectedMethodMap.values()); 178 | Log.i(TAG, "[saveCollectedMethod] size:%s path:%s", mCollectedMethodMap.size(), methodMapFile.getAbsolutePath()); 179 | 180 | Collections.sort(methodList, new Comparator() { 181 | @Override 182 | public int compare(TraceMethod o1, TraceMethod o2) { 183 | return o1.id - o2.id; 184 | } 185 | }); 186 | 187 | PrintWriter pw = null; 188 | try { 189 | FileOutputStream fileOutputStream = new FileOutputStream(methodMapFile, false); 190 | Writer w = new OutputStreamWriter(fileOutputStream, "UTF-8"); 191 | pw = new PrintWriter(w); 192 | for (TraceMethod traceMethod : methodList) { 193 | traceMethod.revert(mappingCollector); 194 | pw.println(traceMethod.toString()); 195 | } 196 | } catch (Exception e) { 197 | Log.e(TAG, "write method map Exception:%s", e.getMessage()); 198 | e.printStackTrace(); 199 | } finally { 200 | if (pw != null) { 201 | pw.flush(); 202 | pw.close(); 203 | } 204 | } 205 | } 206 | 207 | 208 | private void collectMethodFromSrc(List srcFolderList, boolean isSingle) { 209 | if (null != srcFolderList) { 210 | for (File srcFile : srcFolderList) { 211 | innerCollectMethodFromSrc(srcFile, isSingle); 212 | } 213 | } 214 | } 215 | 216 | 217 | private void collectMethodFromJar(List dependencyJarList, boolean isSingle) { 218 | if (null != dependencyJarList) { 219 | for (File jarFile : dependencyJarList) { 220 | innerCollectMethodFromJar(jarFile, isSingle); 221 | } 222 | } 223 | 224 | } 225 | 226 | private void getMethodFromBaseMethod(File baseMethodFile) { 227 | if (!baseMethodFile.exists()) { 228 | Log.w(TAG, "[getMethodFromBaseMethod] not exist!%s", baseMethodFile.getAbsolutePath()); 229 | return; 230 | } 231 | Scanner fileReader = null; 232 | try { 233 | fileReader = new Scanner(baseMethodFile, "UTF-8"); 234 | while (fileReader.hasNext()) { 235 | String nextLine = fileReader.nextLine(); 236 | if (!Util.isNullOrNil(nextLine)) { 237 | nextLine = nextLine.trim(); 238 | if (nextLine.startsWith("#")) { 239 | Log.i("[getMethodFromBaseMethod] comment %s", nextLine); 240 | continue; 241 | } 242 | String[] fields = nextLine.split(","); 243 | TraceMethod traceMethod = new TraceMethod(); 244 | traceMethod.id = Integer.parseInt(fields[0]); 245 | traceMethod.accessFlag = Integer.parseInt(fields[1]); 246 | String[] methodField = fields[2].split(" "); 247 | traceMethod.className = methodField[0].replace("/", "."); 248 | traceMethod.methodName = methodField[1]; 249 | if (methodField.length > 2) { 250 | traceMethod.desc = methodField[2].replace("/", "."); 251 | } 252 | if (mMethodId.get() < traceMethod.id) { 253 | mMethodId.set(traceMethod.id); 254 | } 255 | if (mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector)) { 256 | mCollectedMethodMap.put(traceMethod.getMethodName(), traceMethod); 257 | } 258 | } 259 | } 260 | } catch (Exception e) { 261 | Log.e(TAG, "[getMethodFromBaseMethod] err!"); 262 | } finally { 263 | if (fileReader != null) { 264 | fileReader.close(); 265 | } 266 | } 267 | } 268 | 269 | 270 | private void innerCollectMethodFromSrc(File srcFile, boolean isSingle) { 271 | ArrayList classFileList = new ArrayList<>(); 272 | if (srcFile.isDirectory()) { 273 | listClassFiles(classFileList, srcFile); 274 | } else { 275 | classFileList.add(srcFile); 276 | } 277 | 278 | for (File classFile : classFileList) { 279 | InputStream is = null; 280 | try { 281 | is = new FileInputStream(classFile); 282 | ClassReader classReader = new ClassReader(is); 283 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 284 | ClassVisitor visitor; 285 | if (isSingle) { 286 | visitor = new SingleTraceClassAdapter(Opcodes.ASM5, classWriter); 287 | } else { 288 | visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter); 289 | } 290 | classReader.accept(visitor, 0); 291 | } catch (Exception e) { 292 | e.printStackTrace(); 293 | } finally { 294 | try { 295 | is.close(); 296 | } catch (Exception e) { 297 | // ignore 298 | } 299 | } 300 | } 301 | } 302 | 303 | private void innerCollectMethodFromJar(File fromJar, boolean isSingle) { 304 | ZipFile zipFile = null; 305 | 306 | try { 307 | zipFile = new ZipFile(fromJar); 308 | Enumeration enumeration = zipFile.entries(); 309 | while (enumeration.hasMoreElements()) { 310 | ZipEntry zipEntry = enumeration.nextElement(); 311 | String zipEntryName = zipEntry.getName(); 312 | if (mTraceConfig.isNeedTraceClass(zipEntryName)) { 313 | InputStream inputStream = zipFile.getInputStream(zipEntry); 314 | ClassReader classReader = new ClassReader(inputStream); 315 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 316 | ClassVisitor visitor; 317 | if (isSingle) { 318 | visitor = new SingleTraceClassAdapter(Opcodes.ASM5, classWriter); 319 | } else { 320 | visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter); 321 | } 322 | classReader.accept(visitor, 0); 323 | } 324 | } 325 | } catch (Exception e) { 326 | e.printStackTrace(); 327 | } finally { 328 | try { 329 | zipFile.close(); 330 | } catch (Exception e) { 331 | Log.e(TAG, "close stream err! fromJar:%s", fromJar.getAbsolutePath()); 332 | } 333 | } 334 | } 335 | 336 | 337 | private void listClassFiles(ArrayList classFiles, File folder) { 338 | File[] files = folder.listFiles(); 339 | if (null == files) { 340 | Log.e(TAG, "[listClassFiles] files is null! %s", folder.getAbsolutePath()); 341 | return; 342 | } 343 | for (File file : files) { 344 | if (file == null) { 345 | continue; 346 | } 347 | if (file.isDirectory()) { 348 | listClassFiles(classFiles, file); 349 | } else { 350 | if (null != file && file.isFile() && mTraceConfig.isNeedTraceClass(file.getName())) { 351 | classFiles.add(file); 352 | } 353 | 354 | } 355 | } 356 | } 357 | 358 | private class SingleTraceClassAdapter extends ClassVisitor { 359 | 360 | SingleTraceClassAdapter(int i, ClassVisitor classVisitor) { 361 | super(i, classVisitor); 362 | } 363 | 364 | @Override 365 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 366 | super.visit(version, access, name, signature, superName, interfaces); 367 | mCollectedClassExtendMap.put(name, superName); 368 | } 369 | 370 | } 371 | 372 | 373 | private class TraceClassAdapter extends ClassVisitor { 374 | private String className; 375 | private boolean isABSClass = false; 376 | 377 | TraceClassAdapter(int i, ClassVisitor classVisitor) { 378 | super(i, classVisitor); 379 | } 380 | 381 | @Override 382 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 383 | super.visit(version, access, name, signature, superName, interfaces); 384 | this.className = name; 385 | if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) { 386 | this.isABSClass = true; 387 | } 388 | mCollectedClassExtendMap.put(className, superName); 389 | 390 | } 391 | 392 | @Override 393 | public MethodVisitor visitMethod(int access, String name, String desc, 394 | String signature, String[] exceptions) { 395 | if (isABSClass) { 396 | return super.visitMethod(access, name, desc, signature, exceptions); 397 | } else { 398 | return new CollectMethodNode(className, access, name, desc, signature, exceptions); 399 | } 400 | } 401 | 402 | @Override 403 | public void visitEnd() { 404 | super.visitEnd(); 405 | // collect Activity#onWindowFocusChange 406 | } 407 | 408 | } 409 | 410 | private class CollectMethodNode extends MethodNode { 411 | private String className; 412 | private boolean isConstructor; 413 | 414 | 415 | CollectMethodNode(String className, int access, String name, String desc, 416 | String signature, String[] exceptions) { 417 | super(Opcodes.ASM5, access, name, desc, signature, exceptions); 418 | this.className = className; 419 | } 420 | 421 | @Override 422 | public void visitEnd() { 423 | super.visitEnd(); 424 | TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc); 425 | 426 | if ("".equals(name) /*|| "".equals(name)*/) { 427 | isConstructor = true; 428 | } 429 | // filter simple methods 430 | if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod()) 431 | && mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector)) { 432 | mIgnoreCount++; 433 | mCollectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod); 434 | return; 435 | } 436 | 437 | if (mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector) && !mCollectedMethodMap.containsKey(traceMethod.getMethodName())) { 438 | traceMethod.id = mMethodId.incrementAndGet(); 439 | mCollectedMethodMap.put(traceMethod.getMethodName(), traceMethod); 440 | mIncrementCount++; 441 | } else if (!mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector) 442 | && !mCollectedBlackMethodMap.containsKey(traceMethod.className)) { 443 | mIgnoreCount++; 444 | mCollectedBlackMethodMap.put(traceMethod.getMethodName(), traceMethod); 445 | } 446 | 447 | } 448 | 449 | private boolean isGetSetMethod() { 450 | // complex method 451 | // if (!isConstructor && instructions.size() > 20) { 452 | // return false; 453 | // } 454 | int ignoreCount = 0; 455 | ListIterator iterator = instructions.iterator(); 456 | while (iterator.hasNext()) { 457 | AbstractInsnNode insnNode = iterator.next(); 458 | int opcode = insnNode.getOpcode(); 459 | if (-1 == opcode) { 460 | continue; 461 | } 462 | if (opcode != Opcodes.GETFIELD 463 | && opcode != Opcodes.GETSTATIC 464 | && opcode != Opcodes.H_GETFIELD 465 | && opcode != Opcodes.H_GETSTATIC 466 | 467 | && opcode != Opcodes.RETURN 468 | && opcode != Opcodes.ARETURN 469 | && opcode != Opcodes.DRETURN 470 | && opcode != Opcodes.FRETURN 471 | && opcode != Opcodes.LRETURN 472 | && opcode != Opcodes.IRETURN 473 | 474 | && opcode != Opcodes.PUTFIELD 475 | && opcode != Opcodes.PUTSTATIC 476 | && opcode != Opcodes.H_PUTFIELD 477 | && opcode != Opcodes.H_PUTSTATIC 478 | && opcode > Opcodes.SALOAD) { 479 | if (isConstructor && opcode == Opcodes.INVOKESPECIAL) { 480 | ignoreCount++; 481 | if (ignoreCount > 1) { 482 | // Log.e(TAG, "[ignore] classname %s, name %s", className, name); 483 | return false; 484 | } 485 | continue; 486 | } 487 | return false; 488 | } 489 | } 490 | return true; 491 | } 492 | 493 | private boolean isSingleMethod() { 494 | ListIterator iterator = instructions.iterator(); 495 | while (iterator.hasNext()) { 496 | AbstractInsnNode insnNode = iterator.next(); 497 | int opcode = insnNode.getOpcode(); 498 | if (-1 == opcode) { 499 | continue; 500 | } else if (Opcodes.INVOKEVIRTUAL <= opcode && opcode <= Opcodes.INVOKEDYNAMIC) { 501 | return false; 502 | } 503 | } 504 | return true; 505 | } 506 | 507 | 508 | private boolean isEmptyMethod() { 509 | ListIterator iterator = instructions.iterator(); 510 | while (iterator.hasNext()) { 511 | AbstractInsnNode insnNode = iterator.next(); 512 | int opcode = insnNode.getOpcode(); 513 | if (-1 == opcode) { 514 | continue; 515 | } else { 516 | return false; 517 | } 518 | } 519 | return true; 520 | } 521 | 522 | } 523 | 524 | } 525 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/MethodTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace; 18 | 19 | import java.io.ByteArrayInputStream; 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.InputStream; 24 | import java.util.ArrayList; 25 | import java.util.Enumeration; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | import java.util.concurrent.atomic.AtomicInteger; 29 | import java.util.zip.ZipEntry; 30 | import java.util.zip.ZipFile; 31 | import java.util.zip.ZipOutputStream; 32 | 33 | import com.geektime.systrace.item.TraceMethod; 34 | import org.objectweb.asm.ClassReader; 35 | import org.objectweb.asm.ClassVisitor; 36 | import org.objectweb.asm.ClassWriter; 37 | import org.objectweb.asm.MethodVisitor; 38 | import org.objectweb.asm.Opcodes; 39 | import org.objectweb.asm.commons.AdviceAdapter; 40 | 41 | /** 42 | * Created by caichongyang on 2017/6/4. 43 | *

44 | * This class hooks all collected methods in oder to trace method in/out. 45 | *

46 | */ 47 | 48 | public class MethodTracer { 49 | 50 | private static final String TAG = "Matrix.MethodTracer"; 51 | private static AtomicInteger traceMethodCount = new AtomicInteger(); 52 | private final TraceBuildConfig mTraceConfig; 53 | private final HashMap mCollectedMethodMap; 54 | private final HashMap mCollectedClassExtendMap; 55 | 56 | MethodTracer(TraceBuildConfig config, HashMap collectedMap, HashMap collectedClassExtendMap) { 57 | this.mTraceConfig = config; 58 | this.mCollectedClassExtendMap = collectedClassExtendMap; 59 | this.mCollectedMethodMap = collectedMap; 60 | } 61 | 62 | 63 | public void trace(Map srcFolderList, Map dependencyJarList) { 64 | traceMethodFromSrc(srcFolderList); 65 | traceMethodFromJar(dependencyJarList); 66 | } 67 | 68 | private void traceMethodFromSrc(Map srcMap) { 69 | if (null != srcMap) { 70 | for (Map.Entry entry : srcMap.entrySet()) { 71 | innerTraceMethodFromSrc(entry.getKey(), entry.getValue()); 72 | } 73 | } 74 | } 75 | 76 | private void traceMethodFromJar(Map dependencyMap) { 77 | if (null != dependencyMap) { 78 | for (Map.Entry entry : dependencyMap.entrySet()) { 79 | innerTraceMethodFromJar(entry.getKey(), entry.getValue()); 80 | } 81 | } 82 | } 83 | 84 | private void innerTraceMethodFromSrc(File input, File output) { 85 | 86 | ArrayList classFileList = new ArrayList<>(); 87 | if (input.isDirectory()) { 88 | listClassFiles(classFileList, input); 89 | } else { 90 | classFileList.add(input); 91 | } 92 | 93 | for (File classFile : classFileList) { 94 | InputStream is = null; 95 | FileOutputStream os = null; 96 | try { 97 | final String changedFileInputFullPath = classFile.getAbsolutePath(); 98 | final File changedFileOutput = new File(changedFileInputFullPath.replace(input.getAbsolutePath(), output.getAbsolutePath())); 99 | if (!changedFileOutput.exists()) { 100 | changedFileOutput.getParentFile().mkdirs(); 101 | } 102 | changedFileOutput.createNewFile(); 103 | 104 | if (mTraceConfig.isNeedTraceClass(classFile.getName())) { 105 | is = new FileInputStream(classFile); 106 | ClassReader classReader = new ClassReader(is); 107 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 108 | ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter); 109 | classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES); 110 | is.close(); 111 | 112 | if (output.isDirectory()) { 113 | os = new FileOutputStream(changedFileOutput); 114 | } else { 115 | os = new FileOutputStream(output); 116 | } 117 | os.write(classWriter.toByteArray()); 118 | os.close(); 119 | } else { 120 | Util.copyFileUsingStream(classFile, changedFileOutput); 121 | } 122 | } catch (Exception e) { 123 | e.printStackTrace(); 124 | } finally { 125 | try { 126 | is.close(); 127 | os.close(); 128 | } catch (Exception e) { 129 | // ignore 130 | } 131 | } 132 | } 133 | } 134 | 135 | private void innerTraceMethodFromJar(File input, File output) { 136 | ZipOutputStream zipOutputStream = null; 137 | ZipFile zipFile = null; 138 | try { 139 | zipOutputStream = new ZipOutputStream(new FileOutputStream(output)); 140 | zipFile = new ZipFile(input); 141 | Enumeration enumeration = zipFile.entries(); 142 | while (enumeration.hasMoreElements()) { 143 | ZipEntry zipEntry = enumeration.nextElement(); 144 | String zipEntryName = zipEntry.getName(); 145 | if (mTraceConfig.isNeedTraceClass(zipEntryName)) { 146 | InputStream inputStream = zipFile.getInputStream(zipEntry); 147 | ClassReader classReader = new ClassReader(inputStream); 148 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 149 | ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter); 150 | classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES); 151 | byte[] data = classWriter.toByteArray(); 152 | InputStream byteArrayInputStream = new ByteArrayInputStream(data); 153 | ZipEntry newZipEntry = new ZipEntry(zipEntryName); 154 | Util.addZipEntry(zipOutputStream, newZipEntry, byteArrayInputStream); 155 | } else { 156 | InputStream inputStream = zipFile.getInputStream(zipEntry); 157 | ZipEntry newZipEntry = new ZipEntry(zipEntryName); 158 | Util.addZipEntry(zipOutputStream, newZipEntry, inputStream); 159 | } 160 | } 161 | } catch (Exception e) { 162 | Log.e(TAG, "[traceMethodFromJar] err! %s", output.getAbsolutePath()); 163 | } finally { 164 | try { 165 | if (zipOutputStream != null) { 166 | zipOutputStream.finish(); 167 | zipOutputStream.flush(); 168 | zipOutputStream.close(); 169 | } 170 | if (zipFile != null) { 171 | zipFile.close(); 172 | } 173 | } catch (Exception e) { 174 | Log.e(TAG, "close stream err!"); 175 | } 176 | } 177 | } 178 | 179 | private void listClassFiles(ArrayList classFiles, File folder) { 180 | File[] files = folder.listFiles(); 181 | if (null == files) { 182 | Log.e(TAG, "[listClassFiles] files is null! %s", folder.getAbsolutePath()); 183 | return; 184 | } 185 | for (File file : files) { 186 | if (file == null) { 187 | continue; 188 | } 189 | if (file.isDirectory()) { 190 | listClassFiles(classFiles, file); 191 | } else { 192 | if (null != file && file.isFile()) { 193 | classFiles.add(file); 194 | } 195 | 196 | } 197 | } 198 | } 199 | 200 | private class TraceClassAdapter extends ClassVisitor { 201 | 202 | private String className; 203 | private boolean isABSClass = false; 204 | private boolean isMethodBeatClass = false; 205 | 206 | TraceClassAdapter(int i, ClassVisitor classVisitor) { 207 | super(i, classVisitor); 208 | } 209 | 210 | @Override 211 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 212 | super.visit(version, access, name, signature, superName, interfaces); 213 | this.className = name; 214 | if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) { 215 | this.isABSClass = true; 216 | } 217 | if (mTraceConfig.isMethodBeatClass(className, mCollectedClassExtendMap)) { 218 | isMethodBeatClass = true; 219 | } 220 | } 221 | 222 | @Override 223 | public MethodVisitor visitMethod(int access, String name, String desc, 224 | String signature, String[] exceptions) { 225 | if (isABSClass) { 226 | return super.visitMethod(access, name, desc, signature, exceptions); 227 | } else { 228 | MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); 229 | return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className, 230 | isMethodBeatClass); 231 | } 232 | } 233 | 234 | 235 | @Override 236 | public void visitEnd() { 237 | super.visitEnd(); 238 | } 239 | } 240 | 241 | private class TraceMethodAdapter extends AdviceAdapter { 242 | 243 | private final String methodName; 244 | private final String name; 245 | private final String className; 246 | private final boolean isMethodBeatClass; 247 | 248 | protected TraceMethodAdapter(int api, MethodVisitor mv, int access, String name, String desc, String className, 249 | boolean isMethodBeatClass) { 250 | super(api, mv, access, name, desc); 251 | TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc); 252 | this.methodName = traceMethod.getMethodName(); 253 | this.isMethodBeatClass = isMethodBeatClass; 254 | this.className = className; 255 | this.name = name; 256 | } 257 | 258 | @Override 259 | protected void onMethodEnter() { 260 | TraceMethod traceMethod = mCollectedMethodMap.get(methodName); 261 | if (traceMethod != null) { 262 | traceMethodCount.incrementAndGet(); 263 | String sectionName = methodName; 264 | int length = sectionName.length(); 265 | if (length > TraceBuildConstants.MAX_SECTION_NAME_LEN) { 266 | // 先去掉参数 267 | int parmIndex = sectionName.indexOf('('); 268 | sectionName = sectionName.substring(0, parmIndex); 269 | // 如果依然更大,直接裁剪 270 | length = sectionName.length(); 271 | if (length > TraceBuildConstants.MAX_SECTION_NAME_LEN) { 272 | sectionName = sectionName.substring(length - TraceBuildConstants.MAX_SECTION_NAME_LEN); 273 | } 274 | } 275 | mv.visitLdcInsn(sectionName); 276 | mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_METHOD_BEAT_CLASS, "i", "(Ljava/lang/String;)V", false); 277 | } 278 | } 279 | 280 | @Override 281 | protected void onMethodExit(int opcode) { 282 | //if (isMethodBeatClass && ("").equals(name)) { 283 | // StringBuffer stringBuffer = new StringBuffer(); 284 | // 285 | // stringBuffer.deleteCharAt(stringBuffer.length() - 1); 286 | // mv.visitLdcInsn(stringBuffer.toString()); 287 | // mv.visitFieldInsn(Opcodes.PUTSTATIC, className, TraceBuildConstants.MATRIX_TRACE_APPLICATION_CREATE_FILED, "Ljava/lang/String;"); 288 | //} 289 | TraceMethod traceMethod = mCollectedMethodMap.get(methodName); 290 | if (traceMethod != null) { 291 | 292 | traceMethodCount.incrementAndGet(); 293 | //mv.visitLdcInsn(traceMethod.id); 294 | mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_METHOD_BEAT_CLASS, "o", "()V", false); 295 | } 296 | } 297 | } 298 | 299 | 300 | 301 | 302 | } 303 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/ReflectUtil.java: -------------------------------------------------------------------------------- 1 | package com.geektime.systrace; 2 | 3 | 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | 7 | public final class ReflectUtil { 8 | public static Field getDeclaredFieldRecursive(Object clazz, String fieldName) throws NoSuchFieldException, ClassNotFoundException { 9 | Class realClazz = null; 10 | if (clazz instanceof String) { 11 | realClazz = Class.forName((String) clazz); 12 | } else if (clazz instanceof Class) { 13 | realClazz = (Class) clazz; 14 | } else { 15 | throw new IllegalArgumentException("Illegal clazz type: " + clazz.getClass()); 16 | } 17 | Class currClazz = realClazz; 18 | while (true) { 19 | try { 20 | Field field = currClazz.getDeclaredField(fieldName); 21 | field.setAccessible(true); 22 | return field; 23 | } catch (NoSuchFieldException e) { 24 | if (currClazz.equals(Object.class)) { 25 | throw e; 26 | } 27 | currClazz = currClazz.getSuperclass(); 28 | } 29 | } 30 | } 31 | 32 | public static Method getDeclaredMethodRecursive(Object clazz, String methodName, Class... argTypes) throws NoSuchMethodException, ClassNotFoundException { 33 | Class realClazz = null; 34 | if (clazz instanceof String) { 35 | realClazz = Class.forName((String) clazz); 36 | } else if (clazz instanceof Class) { 37 | realClazz = (Class) clazz; 38 | } else { 39 | throw new IllegalArgumentException("Illegal clazz type: " + clazz.getClass()); 40 | } 41 | Class currClazz = realClazz; 42 | while (true) { 43 | try { 44 | Method method = currClazz.getDeclaredMethod(methodName); 45 | method.setAccessible(true); 46 | return method; 47 | } catch (NoSuchMethodException e) { 48 | if (currClazz.equals(Object.class)) { 49 | throw e; 50 | } 51 | currClazz = currClazz.getSuperclass(); 52 | } 53 | } 54 | } 55 | 56 | private ReflectUtil() { 57 | throw new UnsupportedOperationException(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/TraceBuildConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace; 18 | 19 | import com.geektime.systrace.retrace.MappingCollector; 20 | 21 | import java.io.File; 22 | import java.util.HashMap; 23 | import java.util.HashSet; 24 | 25 | /** 26 | * Created by caichongyang on 2017/6/20. 27 | */ 28 | public class TraceBuildConfig { 29 | private static final String TAG = "Matrix.TraceBuildConfig"; 30 | private final String mPackageName; 31 | private final String mMappingPath; 32 | private final String mBaseMethodMap; 33 | private final String mMethodMapFile; 34 | private final String mIgnoreMethodMapFile; 35 | 36 | private final String mBlackListDir; 37 | private final HashSet mBlackClassMap; 38 | private final HashSet mBlackPackageMap; 39 | 40 | public TraceBuildConfig(String packageName, String mappingPath, String baseMethodMap, String methodMapFile, String ignoreMethodMapFile, String blackListFile) { 41 | mPackageName = packageName; 42 | mMappingPath = mappingPath; 43 | mBaseMethodMap = baseMethodMap; 44 | mMethodMapFile = methodMapFile; 45 | mIgnoreMethodMapFile = ignoreMethodMapFile; 46 | mBlackListDir = blackListFile; 47 | mBlackClassMap = new HashSet(); 48 | mBlackPackageMap = new HashSet(); 49 | } 50 | 51 | public String getPackageName() { 52 | return mPackageName; 53 | } 54 | 55 | public String getMappingPath() { 56 | return mMappingPath; 57 | } 58 | 59 | public String getBaseMethodMap() { 60 | return mBaseMethodMap; 61 | } 62 | 63 | public String getMethodMapFile() { 64 | return mMethodMapFile; 65 | } 66 | 67 | public String getBlackListFile() { 68 | return mBlackListDir; 69 | } 70 | 71 | 72 | public String getIgnoreMethodMapFile() { 73 | return mIgnoreMethodMapFile; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return "\n" + "PackageName:" + mPackageName + "\n" + "MappingPath:" + mMappingPath + "\n" + "MethodMapFile:" + mMethodMapFile; 79 | } 80 | 81 | /** 82 | * whether it need to trace by class filename 83 | * @param fileName 84 | * @return 85 | */ 86 | public boolean isNeedTraceClass(String fileName) { 87 | boolean isNeed = true; 88 | if (fileName.endsWith(".class")) { 89 | for (String unTraceCls : TraceBuildConstants.UN_TRACE_CLASS) { 90 | if (fileName.contains(unTraceCls)) { 91 | isNeed = false; 92 | break; 93 | } 94 | } 95 | } else { 96 | isNeed = false; 97 | } 98 | return isNeed; 99 | } 100 | 101 | /** 102 | * whether it need to trace. 103 | * if this class in collected set,it return true. 104 | * @param clsName 105 | * @param mappingCollector 106 | * @return 107 | */ 108 | public boolean isNeedTrace(String clsName, MappingCollector mappingCollector) { 109 | boolean isNeed = true; 110 | if (mBlackClassMap.contains(clsName)) { 111 | isNeed = false; 112 | } else { 113 | if (null != mappingCollector) { 114 | clsName = mappingCollector.originalClassName(clsName, clsName); 115 | } 116 | for (String packageName : mBlackPackageMap) { 117 | if (clsName.startsWith(packageName.replaceAll("/", "."))) { 118 | isNeed = false; 119 | break; 120 | } 121 | } 122 | } 123 | return isNeed; 124 | } 125 | 126 | public boolean isMethodBeatClass(String className, HashMap mCollectedClassExtendMap) { 127 | className = className.replace(".", "/"); 128 | boolean isApplication = className.equals(TraceBuildConstants.MATRIX_TRACE_METHOD_BEAT_CLASS); 129 | if (isApplication) { 130 | return true; 131 | } else if (mCollectedClassExtendMap.containsKey(className)) { 132 | return mCollectedClassExtendMap.get(className).equals(TraceBuildConstants.MATRIX_TRACE_METHOD_BEAT_CLASS); 133 | } else { 134 | return false; 135 | } 136 | } 137 | 138 | /** 139 | * parse the BlackFile in order to pass some class/method 140 | * @param processor 141 | */ 142 | public void parseBlackFile(MappingCollector processor) { 143 | File blackConfigFile = new File(mBlackListDir); 144 | if (!blackConfigFile.exists()) { 145 | Log.w(TAG, "black config file not exist %s", blackConfigFile.getAbsoluteFile()); 146 | } 147 | String blackStr = TraceBuildConstants.DEFAULT_BLACK_TRACE + Util.readFileAsString(blackConfigFile.getAbsolutePath()); 148 | 149 | String[] blackArray = blackStr.split("\n"); 150 | 151 | if (blackArray != null) { 152 | for (String black : blackArray) { 153 | black = black.trim().replace("/", "."); 154 | if (black.length() == 0) { 155 | continue; 156 | } 157 | if (black.startsWith("#")) { 158 | Log.i(TAG, "[parseBlackFile] comment:%s", black); 159 | continue; 160 | } 161 | if (black.startsWith("[")) { 162 | continue; 163 | } 164 | 165 | if (black.startsWith("-keepclass ")) { 166 | black = black.replace("-keepclass ", ""); 167 | mBlackClassMap.add(processor.proguardClassName(black, black)); 168 | 169 | } else if (black.startsWith("-keeppackage ")) { 170 | black = black.replace("-keeppackage ", ""); 171 | mBlackPackageMap.add(black); 172 | } 173 | 174 | } 175 | } 176 | 177 | Log.i(TAG, "[parseBlackFile] BlackClassMap size:%s BlackPrefixMap size:%s", mBlackClassMap.size(), mBlackPackageMap.size()); 178 | } 179 | 180 | public static class Builder { 181 | 182 | public String mPackageName; 183 | public String mMappingPath; 184 | public String mBaseMethodMap; 185 | public String mMethodMapFile; 186 | public String mIgnoreMethodMapFile; 187 | public String mBlackListFile; 188 | 189 | public Builder setPackageName(String packageName) { 190 | mPackageName = packageName; 191 | return this; 192 | } 193 | 194 | public Builder setMappingPath(String mappingPath) { 195 | mMappingPath = mappingPath; 196 | return this; 197 | } 198 | 199 | public Builder setBaseMethodMap(String baseMethodMap) { 200 | mBaseMethodMap = baseMethodMap; 201 | return this; 202 | } 203 | 204 | public Builder setMethodMapDir(String methodMapDir) { 205 | mMethodMapFile = methodMapDir; 206 | return this; 207 | } 208 | 209 | public Builder setIgnoreMethodMapDir(String methodMapDir) { 210 | mIgnoreMethodMapFile = methodMapDir; 211 | return this; 212 | } 213 | 214 | public Builder setBlackListFile(String blackListFile) { 215 | mBlackListFile = blackListFile; 216 | return this; 217 | } 218 | 219 | public TraceBuildConfig build() { 220 | return new TraceBuildConfig(mPackageName, mMappingPath, mBaseMethodMap, mMethodMapFile, mIgnoreMethodMapFile, mBlackListFile); 221 | } 222 | 223 | } 224 | 225 | 226 | } 227 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/TraceBuildConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace; 18 | 19 | /** 20 | * Created by caichongyang on 2017/6/20. 21 | */ 22 | public class TraceBuildConstants { 23 | 24 | public final static int MAX_SECTION_NAME_LEN = 127; 25 | 26 | public final static String MATRIX_TRACE_METHOD_BEAT_CLASS = "com/sample/systrace/TraceTag"; 27 | public static final String[] UN_TRACE_CLASS = {"R.class", "R$", "Manifest", "BuildConfig"}; 28 | public final static String DEFAULT_BLACK_TRACE = 29 | "[package]\n" 30 | + "-keepclass com/sample/systrace/TraceTag\n"; 31 | } 32 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace; 18 | 19 | import java.io.Closeable; 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.InputStreamReader; 26 | import java.io.Reader; 27 | import java.lang.reflect.Field; 28 | import java.util.zip.ZipEntry; 29 | import java.util.zip.ZipException; 30 | import java.util.zip.ZipFile; 31 | import java.util.zip.ZipOutputStream; 32 | 33 | 34 | public final class Util { 35 | private static final String TAG = "Util"; 36 | public static final int BUFFER_SIZE = 16384; 37 | 38 | private Util() { 39 | } 40 | 41 | public static void addZipEntry(ZipOutputStream zipOutputStream, ZipEntry zipEntry, InputStream inputStream) throws Exception { 42 | try { 43 | zipOutputStream.putNextEntry(zipEntry); 44 | byte[] buffer = new byte[BUFFER_SIZE]; 45 | int length = -1; 46 | while ((length = inputStream.read(buffer, 0, buffer.length)) != -1) { 47 | zipOutputStream.write(buffer, 0, length); 48 | zipOutputStream.flush(); 49 | } 50 | } catch (ZipException e) { 51 | Log.e(TAG, "addZipEntry err!"); 52 | } finally { 53 | closeQuietly(inputStream); 54 | 55 | zipOutputStream.closeEntry(); 56 | } 57 | } 58 | 59 | public static boolean isNullOrNil(String str) { 60 | return str == null || str.isEmpty(); 61 | } 62 | 63 | public static Field getDeclaredFieldRecursive(Object clazz, String fieldName) throws NoSuchFieldException, ClassNotFoundException { 64 | Class realClazz = null; 65 | if (clazz instanceof String) { 66 | realClazz = Class.forName((String) clazz); 67 | } else if (clazz instanceof Class) { 68 | realClazz = (Class) clazz; 69 | } else { 70 | throw new IllegalArgumentException("Illegal clazz type: " + clazz.getClass()); 71 | } 72 | Class currClazz = realClazz; 73 | while (true) { 74 | try { 75 | Field field = currClazz.getDeclaredField(fieldName); 76 | field.setAccessible(true); 77 | return field; 78 | } catch (NoSuchFieldException e) { 79 | if (currClazz.equals(Object.class)) { 80 | throw e; 81 | } 82 | currClazz = currClazz.getSuperclass(); 83 | } 84 | } 85 | } 86 | 87 | public static boolean isRealZipOrJar(File input) { 88 | ZipFile zf = null; 89 | try { 90 | zf = new ZipFile(input); 91 | return true; 92 | } catch (Exception e) { 93 | return false; 94 | } finally { 95 | Util.closeQuietly(zf); 96 | } 97 | } 98 | 99 | 100 | /** 101 | * Close {@code target} quietly. 102 | * 103 | * @param obj 104 | * Object to be closed. 105 | */ 106 | public static void closeQuietly(Object obj) { 107 | if (obj == null) { 108 | return; 109 | } 110 | if (obj instanceof Closeable) { 111 | try { 112 | ((Closeable) obj).close(); 113 | } catch (Throwable ignored) { 114 | // ignore 115 | } 116 | } else if (obj instanceof AutoCloseable) { 117 | try { 118 | ((AutoCloseable) obj).close(); 119 | } catch (Throwable ignored) { 120 | // ignore 121 | } 122 | } else if (obj instanceof ZipFile) { 123 | try { 124 | ((ZipFile) obj).close(); 125 | } catch (Throwable ignored) { 126 | // ignore 127 | } 128 | } else { 129 | throw new IllegalArgumentException("obj " + obj + " is not closeable"); 130 | } 131 | } 132 | 133 | public static String readFileAsString(String filePath) { 134 | StringBuffer fileData = new StringBuffer(); 135 | Reader fileReader = null; 136 | InputStream inputStream = null; 137 | try { 138 | inputStream = new FileInputStream(filePath); 139 | fileReader = new InputStreamReader(inputStream, "UTF-8"); 140 | char[] buf = new char[BUFFER_SIZE]; 141 | int numRead = 0; 142 | while ((numRead = fileReader.read(buf)) != -1) { 143 | String readData = String.valueOf(buf, 0, numRead); 144 | fileData.append(readData); 145 | } 146 | } catch (Exception e) { 147 | Log.e(TAG, "file op readFileAsString e type:%s, e msg:%s, filePath:%s", 148 | e.getClass().getSimpleName(), e.getMessage(), filePath); 149 | 150 | } finally { 151 | try { 152 | closeQuietly(fileReader); 153 | closeQuietly(inputStream); 154 | } catch (Exception e) { 155 | Log.e(TAG, "file op readFileAsString close e type:%s, e msg:%s, filePath:%s", 156 | e.getClass().getSimpleName(), e.getMessage(), filePath); 157 | } 158 | } 159 | return fileData.toString(); 160 | } 161 | 162 | public static void copyFileUsingStream(File source, File dest) throws IOException { 163 | FileInputStream is = null; 164 | FileOutputStream os = null; 165 | File parent = dest.getParentFile(); 166 | if (parent != null && (!parent.exists())) { 167 | parent.mkdirs(); 168 | } 169 | try { 170 | is = new FileInputStream(source); 171 | os = new FileOutputStream(dest, false); 172 | 173 | byte[] buffer = new byte[BUFFER_SIZE]; 174 | int length; 175 | while ((length = is.read(buffer)) > 0) { 176 | os.write(buffer, 0, length); 177 | } 178 | } finally { 179 | closeQuietly(is); 180 | closeQuietly(os); 181 | } 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/item/TraceMethod.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace.item; 18 | 19 | 20 | import com.geektime.systrace.Util; 21 | import com.geektime.systrace.retrace.MappingCollector; 22 | import com.geektime.systrace.retrace.MethodInfo; 23 | 24 | import org.objectweb.asm.Opcodes; 25 | import org.objectweb.asm.Type; 26 | 27 | /** 28 | * Created by caichongyang on 2017/6/3. 29 | */ 30 | public class TraceMethod { 31 | private static final String TAG = "Matrix.TraceMethod"; 32 | public int id; 33 | public int accessFlag; 34 | public String className; 35 | public String methodName; 36 | public String desc; 37 | 38 | public static TraceMethod create(int id, int accessFlag, String className, String methodName, String desc) { 39 | TraceMethod traceMethod = new TraceMethod(); 40 | traceMethod.id = id; 41 | traceMethod.accessFlag = accessFlag; 42 | traceMethod.className = className.replace("/", "."); 43 | traceMethod.methodName = methodName; 44 | traceMethod.desc = desc.replace("/", "."); 45 | return traceMethod; 46 | } 47 | 48 | public String getMethodName() { 49 | if (desc == null || isNativeMethod()) { 50 | return this.className + "." + this.methodName; 51 | } else { 52 | return this.className + "." + this.methodName + "." + desc; 53 | } 54 | } 55 | 56 | /** 57 | * proguard -> original 58 | * 59 | * @param processor 60 | */ 61 | public void revert(MappingCollector processor) { 62 | if (null == processor) { 63 | return; 64 | } 65 | MethodInfo methodInfo = processor.originalMethodInfo(className, methodName, desc); 66 | this.methodName = methodInfo.originalName; 67 | this.desc = methodInfo.desc; 68 | this.className = processor.originalClassName(className, className); 69 | } 70 | 71 | /** 72 | * original -> proguard 73 | * 74 | * @param processor 75 | */ 76 | public void proguard(MappingCollector processor) { 77 | if (null == processor) { 78 | return; 79 | } 80 | MethodInfo methodInfo = processor.obfuscatedMethodInfo(className, methodName, desc); 81 | this.methodName = methodInfo.originalName; 82 | this.desc = methodInfo.desc; 83 | this.className = processor.proguardClassName(className, className); 84 | } 85 | 86 | public String getReturn() { 87 | if (Util.isNullOrNil(desc)) { 88 | return null; 89 | } 90 | return Type.getReturnType(desc).toString(); 91 | } 92 | 93 | 94 | @Override 95 | public String toString() { 96 | if (desc == null || isNativeMethod()) { 97 | return id + "," + accessFlag + "," + className + " " + methodName; 98 | } else { 99 | return id + "," + accessFlag + "," + className + " " + methodName + " " + desc; 100 | } 101 | } 102 | 103 | public String toIgnoreString() { 104 | if (desc == null || isNativeMethod()) { 105 | return className + " " + methodName; 106 | } else { 107 | return className + " " + methodName + " " + desc; 108 | } 109 | } 110 | 111 | public boolean isNativeMethod() { 112 | return (accessFlag & Opcodes.ACC_NATIVE) != 0; 113 | } 114 | 115 | @Override 116 | public boolean equals(Object obj) { 117 | if (obj instanceof TraceMethod) { 118 | TraceMethod tm = (TraceMethod) obj; 119 | return tm.getMethodName().equals(getMethodName()); 120 | } else { 121 | return false; 122 | } 123 | } 124 | 125 | @Override 126 | public int hashCode() { 127 | return super.hashCode(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/retrace/MappingCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace.retrace; 18 | 19 | import org.objectweb.asm.Type; 20 | 21 | import java.util.HashMap; 22 | import java.util.Iterator; 23 | import java.util.LinkedHashSet; 24 | import java.util.Map; 25 | import java.util.Set; 26 | 27 | /** 28 | * Created by caichongyang on 2017/8/3. 29 | */ 30 | public class MappingCollector implements MappingProcessor { 31 | private final static String TAG = "MappingCollector"; 32 | private final static int DEFAULT_CAPACITY = 2000; 33 | public HashMap mObfuscatedRawClassMap = new HashMap<>(DEFAULT_CAPACITY); 34 | public HashMap mRawObfuscatedClassMap = new HashMap<>(DEFAULT_CAPACITY); 35 | private final Map>> mObfuscatedClassMethodMap = new HashMap<>(); 36 | private final Map>> mOriginalClassMethodMap = new HashMap<>(); 37 | 38 | @Override 39 | public boolean processClassMapping(String className, String newClassName) { 40 | this.mObfuscatedRawClassMap.put(newClassName, className); 41 | this.mRawObfuscatedClassMap.put(className, newClassName); 42 | return true; 43 | } 44 | 45 | @Override 46 | public void processMethodMapping(String className, String methodReturnType, String methodName, String methodArguments, String newClassName, String newMethodName) { 47 | newClassName = mRawObfuscatedClassMap.get(className); 48 | Map> methodMap = mObfuscatedClassMethodMap.get(newClassName); 49 | if (methodMap == null) { 50 | methodMap = new HashMap<>(); 51 | mObfuscatedClassMethodMap.put(newClassName, methodMap); 52 | } 53 | Set methodSet = methodMap.get(newMethodName); 54 | if (methodSet == null) { 55 | methodSet = new LinkedHashSet<>(); 56 | methodMap.put(newMethodName, methodSet); 57 | } 58 | methodSet.add(new MethodInfo(className, methodReturnType, methodName, methodArguments)); 59 | 60 | 61 | Map> methodMap2 = mOriginalClassMethodMap.get(className); 62 | if (methodMap2 == null) { 63 | methodMap2 = new HashMap<>(); 64 | mOriginalClassMethodMap.put(className, methodMap2); 65 | } 66 | Set methodSet2 = methodMap2.get(methodName); 67 | if (methodSet2 == null) { 68 | methodSet2 = new LinkedHashSet<>(); 69 | methodMap2.put(methodName, methodSet2); 70 | } 71 | methodSet2.add(new MethodInfo(newClassName, methodReturnType, newMethodName, methodArguments)); 72 | 73 | } 74 | 75 | public String originalClassName(String proguardClassName, String defaultClassName) { 76 | if (mObfuscatedRawClassMap.containsKey(proguardClassName)) { 77 | return mObfuscatedRawClassMap.get(proguardClassName); 78 | } else { 79 | return defaultClassName; 80 | } 81 | } 82 | 83 | public String proguardClassName(String originalClassName, String defaultClassName) { 84 | if (mRawObfuscatedClassMap.containsKey(originalClassName)) { 85 | return mRawObfuscatedClassMap.get(originalClassName); 86 | } else { 87 | return defaultClassName; 88 | } 89 | } 90 | 91 | /** 92 | * get original method info 93 | * 94 | * @param obfuscatedClassName 95 | * @param obfuscatedMethodName 96 | * @param obfuscatedMethodDesc 97 | * @return 98 | */ 99 | public MethodInfo originalMethodInfo(String obfuscatedClassName, String obfuscatedMethodName, String obfuscatedMethodDesc) { 100 | DescInfo descInfo = parseMethodDesc(obfuscatedMethodDesc, false); 101 | 102 | // obfuscated name -> original method names. 103 | Map> methodMap = mObfuscatedClassMethodMap.get(obfuscatedClassName); 104 | if (methodMap != null) { 105 | Set methodSet = methodMap.get(obfuscatedMethodName); 106 | if (methodSet != null) { 107 | // Find all matching methods. 108 | Iterator methodInfoIterator = methodSet.iterator(); 109 | while (methodInfoIterator.hasNext()) { 110 | MethodInfo methodInfo = methodInfoIterator.next(); 111 | if (methodInfo.matches(descInfo.returnType, descInfo.arguments)) { 112 | MethodInfo newMethodInfo = new MethodInfo(methodInfo); 113 | newMethodInfo.setDesc(descInfo.desc); 114 | return newMethodInfo; 115 | } 116 | } 117 | } 118 | } 119 | 120 | MethodInfo defaultMethodInfo = MethodInfo.deFault(); 121 | defaultMethodInfo.setDesc(descInfo.desc); 122 | defaultMethodInfo.setOriginalName(obfuscatedMethodName); 123 | return defaultMethodInfo; 124 | } 125 | 126 | /** 127 | * get obfuscated method info 128 | * 129 | * @param originalClassName 130 | * @param originalMethodName 131 | * @param originalMethodDesc 132 | * @return 133 | */ 134 | public MethodInfo obfuscatedMethodInfo(String originalClassName, String originalMethodName, String originalMethodDesc) { 135 | DescInfo descInfo = parseMethodDesc(originalMethodDesc, true); 136 | 137 | // Class name -> obfuscated method names. 138 | Map> methodMap = mOriginalClassMethodMap.get(originalClassName); 139 | if (methodMap != null) { 140 | Set methodSet = methodMap.get(originalMethodName); 141 | if (null != methodSet) { 142 | // Find all matching methods. 143 | Iterator methodInfoIterator = methodSet.iterator(); 144 | while (methodInfoIterator.hasNext()) { 145 | MethodInfo methodInfo = methodInfoIterator.next(); 146 | MethodInfo newMethodInfo = new MethodInfo(methodInfo); 147 | obfuscatedMethodInfo(newMethodInfo); 148 | if (newMethodInfo.matches(descInfo.returnType, descInfo.arguments)) { 149 | newMethodInfo.setDesc(descInfo.desc); 150 | return newMethodInfo; 151 | } 152 | } 153 | } 154 | } 155 | MethodInfo defaultMethodInfo = MethodInfo.deFault(); 156 | defaultMethodInfo.setDesc(descInfo.desc); 157 | defaultMethodInfo.setOriginalName(originalMethodName); 158 | return defaultMethodInfo; 159 | } 160 | 161 | private void obfuscatedMethodInfo(MethodInfo methodInfo) { 162 | String methodArguments = methodInfo.getOriginalArguments(); 163 | String[] args = methodArguments.split(","); 164 | StringBuffer stringBuffer = new StringBuffer(); 165 | for (String str : args) { 166 | String key = str.replace("[", "").replace("]", ""); 167 | if (mRawObfuscatedClassMap.containsKey(key)) { 168 | stringBuffer.append(str.replace(key, mRawObfuscatedClassMap.get(key))); 169 | } else { 170 | stringBuffer.append(str); 171 | } 172 | stringBuffer.append(','); 173 | } 174 | if (stringBuffer.length() > 0) { 175 | stringBuffer.deleteCharAt(stringBuffer.length() - 1); 176 | } 177 | String methodReturnType = methodInfo.getOriginalType(); 178 | String key = methodReturnType.replace("[", "").replace("]", ""); 179 | if (mRawObfuscatedClassMap.containsKey(key)) { 180 | methodReturnType = methodReturnType.replace(key, mRawObfuscatedClassMap.get(key)); 181 | } 182 | methodInfo.setOriginalArguments(stringBuffer.toString()); 183 | methodInfo.setOriginalType(methodReturnType); 184 | } 185 | 186 | /** 187 | * parse method desc 188 | * 189 | * @param desc 190 | * @param isRawToObfuscated 191 | * @return 192 | */ 193 | private DescInfo parseMethodDesc(String desc, boolean isRawToObfuscated) { 194 | DescInfo descInfo = new DescInfo(); 195 | Type[] argsObj = Type.getArgumentTypes(desc); 196 | StringBuffer argumentsBuffer = new StringBuffer(); 197 | StringBuffer descBuffer = new StringBuffer(); 198 | descBuffer.append('('); 199 | for (Type type : argsObj) { 200 | String key = type.getClassName().replace("[", "").replace("]", ""); 201 | if (isRawToObfuscated) { 202 | if (mRawObfuscatedClassMap.containsKey(key)) { 203 | argumentsBuffer.append(type.getClassName().replace(key, mRawObfuscatedClassMap.get(key))); 204 | descBuffer.append(type.toString().replace(key, mRawObfuscatedClassMap.get(key))); 205 | } else { 206 | argumentsBuffer.append(type.getClassName()); 207 | descBuffer.append(type.toString()); 208 | } 209 | } else { 210 | if (mObfuscatedRawClassMap.containsKey(key)) { 211 | argumentsBuffer.append(type.getClassName().replace(key, mObfuscatedRawClassMap.get(key))); 212 | descBuffer.append(type.toString().replace(key, mObfuscatedRawClassMap.get(key))); 213 | } else { 214 | argumentsBuffer.append(type.getClassName()); 215 | descBuffer.append(type.toString()); 216 | } 217 | } 218 | argumentsBuffer.append(','); 219 | } 220 | descBuffer.append(')'); 221 | 222 | Type returnObj; 223 | try { 224 | returnObj = Type.getReturnType(desc); 225 | } catch (ArrayIndexOutOfBoundsException e) { 226 | returnObj = Type.getReturnType(desc + ";"); 227 | } 228 | if (isRawToObfuscated) { 229 | String key = returnObj.getClassName().replace("[", "").replace("]", ""); 230 | if (mRawObfuscatedClassMap.containsKey(key)) { 231 | descInfo.setReturnType(returnObj.getClassName().replace(key, mRawObfuscatedClassMap.get(key))); 232 | descBuffer.append(returnObj.toString().replace(key, mRawObfuscatedClassMap.get(key))); 233 | } else { 234 | descInfo.setReturnType(returnObj.getClassName()); 235 | descBuffer.append(returnObj.toString()); 236 | } 237 | } else { 238 | String key = returnObj.getClassName().replace("[", "").replace("]", ""); 239 | if (mObfuscatedRawClassMap.containsKey(key)) { 240 | descInfo.setReturnType(returnObj.getClassName().replace(key, mObfuscatedRawClassMap.get(key))); 241 | descBuffer.append(returnObj.toString().replace(key, mObfuscatedRawClassMap.get(key))); 242 | } else { 243 | descInfo.setReturnType(returnObj.getClassName()); 244 | descBuffer.append(returnObj.toString()); 245 | } 246 | } 247 | 248 | // delete last , 249 | if (argumentsBuffer.length() > 0) { 250 | argumentsBuffer.deleteCharAt(argumentsBuffer.length() - 1); 251 | } 252 | descInfo.setArguments(argumentsBuffer.toString()); 253 | 254 | descInfo.setDesc(descBuffer.toString()); 255 | return descInfo; 256 | } 257 | 258 | /** 259 | * about method desc info 260 | */ 261 | private static class DescInfo { 262 | private String desc; 263 | private String arguments; 264 | private String returnType; 265 | 266 | public void setArguments(String arguments) { 267 | this.arguments = arguments; 268 | } 269 | 270 | public void setReturnType(String returnType) { 271 | this.returnType = returnType; 272 | } 273 | 274 | public void setDesc(String desc) { 275 | this.desc = desc; 276 | } 277 | } 278 | 279 | } 280 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/retrace/MappingProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace.retrace; 18 | 19 | /** 20 | * Created by caichongyang on 2017/6/3. 21 | */ 22 | public interface MappingProcessor { 23 | /** 24 | * mapping the class name. 25 | * 26 | * @param className the original class name. 27 | * @param newClassName the new class name. 28 | * @return whether the processor is interested in receiving mappings of the class members of 29 | * this class. 30 | */ 31 | boolean processClassMapping(String className, 32 | String newClassName); 33 | 34 | /** 35 | * mapping the method name. 36 | * 37 | * @param className the original class name. 38 | * @param methodReturnType the original external method return type. 39 | * @param methodName the original external method name. 40 | * @param methodArguments the original external method arguments. 41 | * @param newClassName the new class name. 42 | * @param newMethodName the new method name. 43 | */ 44 | void processMethodMapping(String className, 45 | String methodReturnType, 46 | String methodName, 47 | String methodArguments, 48 | String newClassName, 49 | String newMethodName); 50 | } 51 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/retrace/MappingReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace.retrace; 18 | 19 | import com.geektime.systrace.Log; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.File; 23 | import java.io.FileReader; 24 | import java.io.IOException; 25 | import java.io.LineNumberReader; 26 | 27 | /** 28 | * Created by caichongyang on 2017/6/3. 29 | */ 30 | public class MappingReader { 31 | private final static String TAG = "MappingReader"; 32 | private final static String SPLIT = ":"; 33 | private final static String SPACE = " "; 34 | private final static String ARROW = "->"; 35 | private final static String LEFT_PUNC = "("; 36 | private final static String RIGHT_PUNC = ")"; 37 | private final static String DOT = "."; 38 | private final File proguardMappingFile; 39 | 40 | public MappingReader(File proguardMappingFile) { 41 | this.proguardMappingFile = proguardMappingFile; 42 | } 43 | 44 | /** 45 | * Reads the mapping file 46 | */ 47 | public void read(MappingProcessor mappingProcessor) throws IOException { 48 | LineNumberReader reader = new LineNumberReader(new BufferedReader(new FileReader(proguardMappingFile))); 49 | try { 50 | String className = null; 51 | // Read the class and class member mappings. 52 | while (true) { 53 | String line = reader.readLine(); 54 | if (line == null) { 55 | break; 56 | } 57 | line = line.trim(); 58 | if (!line.startsWith("#")) { 59 | // a class mapping 60 | if (line.endsWith(SPLIT)) { 61 | className = parseClassMapping(line, mappingProcessor); 62 | } else if (className != null) { // a class member mapping 63 | parseClassMemberMapping(className, line, mappingProcessor); 64 | } 65 | } else { 66 | Log.i(TAG, "comment:# %s", line); 67 | } 68 | } 69 | } catch (IOException err) { 70 | throw new IOException("Can't read mapping file", err); 71 | } finally { 72 | try { 73 | reader.close(); 74 | } catch (IOException ex) { 75 | // do nothing 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * @param line read content 82 | * @param mappingProcessor 83 | * @return 84 | */ 85 | private String parseClassMapping(String line, MappingProcessor mappingProcessor) { 86 | 87 | int leftIndex = line.indexOf(ARROW); 88 | if (leftIndex < 0) { 89 | return null; 90 | } 91 | int offset = 2; 92 | int rightIndex = line.indexOf(SPLIT, leftIndex + offset); 93 | if (rightIndex < 0) { 94 | return null; 95 | } 96 | 97 | // trim the elements. 98 | String className = line.substring(0, leftIndex).trim(); 99 | String newClassName = line.substring(leftIndex + offset, rightIndex).trim(); 100 | 101 | // Process this class name mapping. 102 | boolean ret = mappingProcessor.processClassMapping(className, newClassName); 103 | 104 | return ret ? className : null; 105 | } 106 | 107 | /** 108 | * Parses the a class member mapping 109 | * 110 | * @param className 111 | * @param line 112 | * @param mappingProcessor parse line such as 113 | * ___ ___ -> ___ 114 | * ___:___:___ ___(___) -> ___ 115 | * ___:___:___ ___(___):___ -> ___ 116 | * ___:___:___ ___(___):___:___ -> ___ 117 | */ 118 | private void parseClassMemberMapping(String className, String line, MappingProcessor mappingProcessor) { 119 | int leftIndex1 = line.indexOf(SPLIT); 120 | int leftIndex2 = leftIndex1 < 0 ? -1 : line.indexOf(SPLIT, leftIndex1 + 1); 121 | int spaceIndex = line.indexOf(SPACE, leftIndex2 + 2); 122 | int argIndex1 = line.indexOf(LEFT_PUNC, spaceIndex + 1); 123 | int argIndex2 = argIndex1 < 0 ? -1 : line.indexOf(RIGHT_PUNC, argIndex1 + 1); 124 | int leftIndex3 = argIndex2 < 0 ? -1 : line.indexOf(SPLIT, argIndex2 + 1); 125 | int leftIndex4 = leftIndex3 < 0 ? -1 : line.indexOf(SPLIT, leftIndex3 + 1); 126 | int rightIndex = line.indexOf(ARROW, (leftIndex4 >= 0 ? leftIndex4 : leftIndex3 >= 0 127 | ? leftIndex3 : argIndex2 >= 0 ? argIndex2 : spaceIndex) + 1); 128 | if (spaceIndex < 0 || rightIndex < 0) { 129 | return; 130 | } 131 | 132 | // trim the elements. 133 | String type = line.substring(leftIndex2 + 1, spaceIndex).trim(); 134 | String name = line.substring(spaceIndex + 1, argIndex1 >= 0 ? argIndex1 : rightIndex).trim(); 135 | String newName = line.substring(rightIndex + 2).trim(); 136 | 137 | String newClassName = className; 138 | int dotIndex = name.lastIndexOf(DOT); 139 | if (dotIndex >= 0) { 140 | className = name.substring(0, dotIndex); 141 | name = name.substring(dotIndex + 1); 142 | } 143 | 144 | // parse class member mapping. 145 | if (type.length() > 0 && name.length() > 0 && newName.length() > 0 && argIndex2 >= 0) { 146 | String arguments = line.substring(argIndex1 + 1, argIndex2).trim(); 147 | mappingProcessor.processMethodMapping(className, type, name, arguments, newClassName, newName); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/java/com/geektime/systrace/retrace/MethodInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.geektime.systrace.retrace; 18 | 19 | /** 20 | * Created by caichongyang on 2017/6/3. 21 | */ 22 | public class MethodInfo { 23 | 24 | private final String originalClassName; 25 | 26 | public String originalType; 27 | public String originalArguments; 28 | public String originalName; 29 | public String desc; 30 | 31 | public MethodInfo(String originalClassName, 32 | String originalType, 33 | String originalName, 34 | String originalArguments) { 35 | 36 | this.originalType = originalType; 37 | this.originalArguments = originalArguments; 38 | this.originalClassName = originalClassName; 39 | this.originalName = originalName; 40 | } 41 | 42 | public MethodInfo(MethodInfo methodInfo) { 43 | this.originalType = methodInfo.getOriginalType(); 44 | this.originalArguments = methodInfo.getOriginalArguments(); 45 | this.originalClassName = methodInfo.getOriginalClassName(); 46 | this.originalName = methodInfo.getOriginalName(); 47 | this.desc = methodInfo.getDesc(); 48 | } 49 | 50 | public static MethodInfo deFault() { 51 | return new MethodInfo("", "", "", ""); 52 | } 53 | 54 | public boolean matches(String originalType, String originalArguments) { 55 | boolean bool = (originalType == null || originalType.equals(this.originalType)) 56 | && (originalArguments == null || originalArguments.equals(this.originalArguments)); 57 | return bool; 58 | } 59 | 60 | public String getOriginalClassName() { 61 | return originalClassName; 62 | } 63 | 64 | public String getOriginalType() { 65 | return originalType; 66 | } 67 | 68 | public String getOriginalName() { 69 | return originalName; 70 | } 71 | 72 | public String getOriginalArguments() { 73 | return originalArguments; 74 | } 75 | 76 | public String getDesc() { 77 | return desc; 78 | } 79 | 80 | public void setDesc(String desc) { 81 | this.desc = desc; 82 | } 83 | 84 | public void setOriginalName(String originalName) { 85 | this.originalName = originalName; 86 | } 87 | 88 | public void setOriginalArguments(String originalArguments) { 89 | this.originalArguments = originalArguments; 90 | } 91 | 92 | public void setOriginalType(String originalType) { 93 | this.originalType = originalType; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /systrace-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.geektime.systrace-plugin.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.geektime.systrace.SystracePlugin -------------------------------------------------------------------------------- /systrace-sample-android/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /build 3 | -------------------------------------------------------------------------------- /systrace-sample-android/app/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /build 3 | -------------------------------------------------------------------------------- /systrace-sample-android/app/blacklist/blackMethodList.txt: -------------------------------------------------------------------------------- 1 | [package] 2 | # 可以将某个class加入白名单中 3 | -keepclass com/sample/systrace/TestIgnoreFile 4 | 5 | # 可以将某个package加入白名单中 6 | #-keeppackage com/sample/systrace/ -------------------------------------------------------------------------------- /systrace-sample-android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | 4 | android { 5 | compileSdkVersion 26 6 | buildToolsVersion "28.0.3" 7 | 8 | defaultConfig { 9 | applicationId "com.sample.systrace" 10 | minSdkVersion 16 11 | targetSdkVersion 26 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | } 16 | 17 | 18 | dependencies { 19 | implementation fileTree(dir: 'libs', include: ['*.jar']) 20 | implementation 'com.android.support:appcompat-v7:26.0.0' 21 | 22 | } 23 | 24 | apply plugin: 'com.geektime.systrace-plugin' 25 | systrace { 26 | enable = true 27 | baseMethodMapFile = "${project.buildDir}/systrace_output/Debug.methodmap" 28 | blackListFile = "${project.projectDir}/blacklist/blackMethodList.txt" 29 | 30 | } 31 | -------------------------------------------------------------------------------- /systrace-sample-android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/zhangshaowen/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -printconfiguration configuration.txt 19 | 20 | -keepattributes SourceFile,LineNumberTable 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /systrace-sample-android/app/src/androidTest/java/matrix/tencent/com/matrix_android/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package matrix.tencent.com.matrix_android; 18 | 19 | import android.content.Context; 20 | import android.support.test.InstrumentationRegistry; 21 | import android.support.test.runner.AndroidJUnit4; 22 | 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | 26 | import static org.junit.Assert.*; 27 | 28 | /** 29 | * Instrumentation test, which will execute on an Android device. 30 | * 31 | * @see Testing documentation 32 | */ 33 | @RunWith(AndroidJUnit4.class) 34 | public class ExampleInstrumentedTest { 35 | @Test 36 | public void useAppContext() throws Exception { 37 | // Context of the app under test. 38 | Context appContext = InstrumentationRegistry.getTargetContext(); 39 | 40 | assertEquals("matrix.tencent.com.matrix_android", appContext.getPackageName()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /systrace-sample-android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /systrace-sample-android/app/src/main/java/com/sample/systrace/MainActivity.java: -------------------------------------------------------------------------------- 1 | 2 | package com.sample.systrace; 3 | 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.Button; 9 | 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | private static final String TAG = "MainActivity"; 13 | 14 | @Override 15 | protected void onResume() { 16 | super.onResume(); 17 | Log.i(TAG, "[onResume]"); 18 | } 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | Button button = findViewById(R.id.test_gc); 25 | button.setOnClickListener(new View.OnClickListener() { 26 | @Override 27 | public void onClick(final View v) { 28 | TestIgnoreFile.TestGc(); 29 | } 30 | }); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /systrace-sample-android/app/src/main/java/com/sample/systrace/SampleApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sample.systrace; 18 | 19 | import android.app.Application; 20 | import android.content.Context; 21 | 22 | public class SampleApplication extends Application { 23 | 24 | private static Context sContext; 25 | 26 | @Override 27 | public void onCreate() { 28 | super.onCreate(); 29 | sContext = this; 30 | } 31 | 32 | public static Context getContext(){ 33 | return sContext; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /systrace-sample-android/app/src/main/java/com/sample/systrace/TestIgnoreFile.java: -------------------------------------------------------------------------------- 1 | package com.sample.systrace; 2 | 3 | public class TestIgnoreFile { 4 | public static void TestGc() { 5 | for (int i = 0; i < 10000; i ++) { 6 | TestInner(); 7 | } 8 | } 9 | 10 | private static void TestInner() { 11 | int[] test = new int[10000]; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /systrace-sample-android/app/src/main/java/com/sample/systrace/TraceTag.java: -------------------------------------------------------------------------------- 1 | package com.sample.systrace; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import android.os.Trace; 6 | 7 | 8 | 9 | public class TraceTag { 10 | 11 | private static final String TAG = "TraceTag"; 12 | 13 | /** 14 | * hook method when it's called in. 15 | * 16 | */ 17 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) 18 | public static void i(String name) { 19 | Trace.beginSection(name); 20 | } 21 | 22 | /** 23 | * hook method when it's called out. 24 | */ 25 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) 26 | public static void o() { 27 | Trace.endSection(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /systrace-sample-android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |