├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README_EN.md ├── build.gradle ├── buildSrc └── build.gradle ├── debug.sh ├── docs ├── droidassist.dtd ├── pic1.png └── wiki.md ├── droidassist.dtd ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── plugin ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro ├── src │ └── main │ │ ├── groovy │ │ └── com │ │ │ └── didichuxing │ │ │ └── tools │ │ │ └── droidassist │ │ │ ├── DroidAssistConfiguration.groovy │ │ │ ├── DroidAssistContext.groovy │ │ │ ├── DroidAssistExecutor.groovy │ │ │ ├── DroidAssistExtension.groovy │ │ │ ├── DroidAssistPlugin.groovy │ │ │ ├── DroidAssistTransform.groovy │ │ │ ├── ex │ │ │ ├── DroidAssistBadStatementException.java │ │ │ ├── DroidAssistBadTypeException.java │ │ │ ├── DroidAssistException.java │ │ │ └── DroidAssistNotFoundException.java │ │ │ ├── spec │ │ │ ├── ClassFilterSpec.java │ │ │ └── SourceSpec.java │ │ │ ├── tasks │ │ │ ├── DirInputTask.groovy │ │ │ ├── InputTask.groovy │ │ │ └── JarInputTask.groovy │ │ │ ├── transform │ │ │ ├── ExprExecTransformer.java │ │ │ ├── SourceTargetTransformer.java │ │ │ ├── Transformer.java │ │ │ ├── around │ │ │ │ ├── AroundTransformer.java │ │ │ │ ├── ConstructorCallAroundTransformer.java │ │ │ │ ├── ConstructorExecutionAroundTransformer.java │ │ │ │ ├── FieldAccessAroundTransformer.java │ │ │ │ ├── InitializerExecutionAroundTransformer.java │ │ │ │ ├── MethodCallAroundTransformer.java │ │ │ │ └── MethodExecutionAroundTransformer.java │ │ │ ├── enhance │ │ │ │ ├── ConstructorCallTimingTransformer.java │ │ │ │ ├── ConstructorCallTryCatchTransformer.java │ │ │ │ ├── ConstructorExecutionTimingTransformer.java │ │ │ │ ├── ConstructorExecutionTryCatchTransformer.java │ │ │ │ ├── InitializerExecutionTimingTransformer.java │ │ │ │ ├── InitializerExecutionTryCatchTransformer.java │ │ │ │ ├── MethodCallTimingTransformer.java │ │ │ │ ├── MethodCallTryCatchTransformer.java │ │ │ │ ├── MethodExecutionTimingTransformer.java │ │ │ │ ├── MethodExecutionTryCatchTransformer.java │ │ │ │ ├── ReparentClassTransformer.java │ │ │ │ ├── TimingTransformer.java │ │ │ │ └── TryCatchTransformer.java │ │ │ ├── insert │ │ │ │ ├── ConstructorCallInsertTransformer.java │ │ │ │ ├── ConstructorExecutionInsertTransformer.java │ │ │ │ ├── FieldAccessInsertTransformer.java │ │ │ │ ├── InitializerExecutionInsertTransformer.java │ │ │ │ ├── InsertTransformer.java │ │ │ │ ├── MethodCallInsertTransformer.java │ │ │ │ └── MethodExecutionInsertTransformer.java │ │ │ └── replace │ │ │ │ ├── ConstructorCallReplaceTransformer.java │ │ │ │ ├── ConstructorExecutionReplaceTransformer.java │ │ │ │ ├── FieldAccessReplaceTransformer.java │ │ │ │ ├── InitializerExecutionReplaceTransformer.java │ │ │ │ ├── MethodCallReplaceTransformer.java │ │ │ │ ├── MethodExecutionReplaceTransformer.java │ │ │ │ └── ReplaceTransformer.java │ │ │ └── util │ │ │ ├── ClassUtils.java │ │ │ ├── GradleUtils.groovy │ │ │ ├── IOUtils.groovy │ │ │ ├── Logger.groovy │ │ │ ├── TimingLogger.groovy │ │ │ ├── WorkerExecutor.groovy │ │ │ └── ZipUtils.groovy │ │ └── resources │ │ └── META-INF │ │ └── gradle-plugins │ │ └── com.didichuxing.tools.droidassist.properties └── upload.gradle ├── sample ├── .gitignore ├── build.gradle ├── droidassist.xml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── didichuxing │ │ └── tools │ │ └── droidassist │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── didichuxing │ │ │ └── tools │ │ │ └── test │ │ │ ├── Child.java │ │ │ ├── ExampleSpec.java │ │ │ ├── IInterface.java │ │ │ ├── LogUtils.java │ │ │ ├── MainActivity.java │ │ │ ├── Parent.java │ │ │ ├── ReParent.java │ │ │ ├── TestAnnotation.java │ │ │ ├── TestAnnotation2.java │ │ │ └── TestAnnotation3.java │ └── res │ │ └── values │ │ ├── colors.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── didichuxing │ └── tools │ └── droidassist │ └── JavaUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | /output/ 9 | /.idea/ 10 | /buildSrc/build/ 11 | /buildSrc/.gradle/ 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guideline 2 | 3 | Thanks for considering to contribute this project. All issues and pull requests are highly appreciated. 4 | 5 | ## Pull Requests 6 | 7 | Before sending pull request to this project, please read and follow guidelines below. 8 | 9 | 1. Branch: We only accept pull request on `dev` branch. 10 | 2. Coding style: Follow the coding style used in DroidAssist. 11 | 3. Commit message: Use English and be aware of your spell. 12 | 4. Test: Make sure to test your code. 13 | 14 | Add device mode, API version, related log, screenshots and other related information in your pull request if possible. 15 | 16 | NOTE: We assume all your contribution can be licensed under the [Apache License 2.0](./LICENSE). 17 | 18 | ## Issues 19 | 20 | We love clearly described issues. :) 21 | 22 | Following information can help us to resolve the issue faster. 23 | 24 | * Device mode and hardware information. 25 | * API version. 26 | * Logs. 27 | * Screenshots. 28 | * Steps to reproduce the issue. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 android-apm 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # DroidAssist ![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat) ![Release Version](https://img.shields.io/badge/release-1.1.1-blue.svg) 3 | ---------- 4 | `DroidAssist` 是一个轻量级的 Android 字节码编辑插件,基于 `Javassist` 对字节码操作,根据 xml 配置处理 class 文件,以达到对 class 文件进行动态修改的效果。和其他 AOP 方案不同,DroidAssist 提供了一种更加轻量,简单易用,无侵入,可配置化的字节码操作方式,你不需要 Java 字节码的相关知识,只需要在 Xml 插件配置中添加简单的 Java 代码即可实现类似 AOP 的功能,同时不需要引入其他额外的依赖。 5 | 6 | > [**Javassist**: A Java bytecode engineering toolkit since 1999](http://www.Javassist.org/ "Java bytecode engineering toolkit since 1999") 7 | 8 | [English](README_EN.md) 9 | 10 | ## 功能 11 | - **替换**:把指定位置代码替换为指定代码 12 | - **插入**:在指定位置的前后插入指定代码 13 | - **环绕**:在指定位置环绕插入指定代码 14 | - **增强**: 15 | - **TryCatch** 对指定代码添加 try catch 代码 16 | - **Timing** 对指定代码添加耗时统计代码 17 | 18 | ## 特点 19 | * 灵活的配置化方式,使得一个配置就可以处理项目中所有的 class 文件。 20 | * 丰富的字节码处理功能,针对 Android 移动端的特点提供了例如代码替换,添加try catch,方法耗时等功能。 21 | * 简单易用,只需要依赖一个插件,处理过程以及处理后的代码中也不需要添加额外的依赖。 22 | * 处理速度较快,只占用较少的编译时间。 23 | 24 | ## 使用指南 25 | 26 | DroidAssist 适用于 `Android Studio` 工程 `application model` 或者 `library model`,使用 DroidAssist 需要接入 DroidAssist 插件并编写专有配置文件。 27 | 28 | 在 root project 的 `build.gradle` 里添加: 29 | 30 | ```groovy 31 | dependencies { 32 | classpath "com.didichuxing.tools:droidassist:1.1.1" 33 | } 34 | ``` 35 | 36 | 在需要处理的 model project 的 build.gradle 里添加: 37 | 38 | ```groovy 39 | apply plugin: 'com.didichuxing.tools.droidassist' 40 | droidAssistOptions { 41 | config file("droidassist.xml"),file("droidassist2.xml") //插件配置文件(必选配置,支持多配置文件) 42 | } 43 | ``` 44 | 45 | 其他配置: 46 | * `enable` 如果需要停用 DroidAssist 插件功能,可以添加 `enable false` 以停用插件 (可选配置) 47 | * `logLevel` 日志输出等级:`0` 关闭日志输出,`1` 输出日志到控制台 `2` 输出日志到文件 `3` 输出日志到控制台以及日志 (可选配置) 48 | * `logDir` 日志输出目录,当日志输出到文件时,默认的输出目录是当前 `model` 的 `build/outputs/logs` 目录 (可选配置) 49 | 50 | ## 示例 51 | 52 | 下面例子将把项目中所有使用系统 `android.util.Log` 类进行 `DEBUG` 日志输出的代码替换为自定义的日志输出类,以方便对线上日志进行策略化,动态化管理。 53 | ```xml 54 | 55 | 56 | 57 | int android.util.Log.d(java.lang.String,java.lang.String) 58 | 59 | 60 | $_=com.didichuxing.tools.test.LogUtils.log($1,$2); 61 | 62 | 63 | 64 | ``` 65 | 66 | 处理前的class: 67 | 68 | ```java 69 | public class MainActivity extends Activity { 70 | public static final String TAG = "MainActivity"; 71 | 72 | @Override 73 | protected void onCreate(Bundle savedInstanceState) { 74 | super.onCreate(savedInstanceState); 75 | Log.d(TAG, "MainActivity onCreate"); 76 | } 77 | } 78 | ``` 79 | 80 | 处理后的 class: 81 | 82 | ```java 83 | public class MainActivity extends Activity { 84 | public static final String TAG = "MainActivity"; 85 | 86 | protected void onCreate(Bundle savedInstanceState) { 87 | super.onCreate(savedInstanceState); 88 | String var2 = "MainActivity"; 89 | String var3 = "MainActivity onCreate"; 90 | int var4 = LogUtils.log(var2, var3); // The target method using custom log method. 91 | } 92 | } 93 | ``` 94 | 95 | ## 完整文档 96 | 完整开发文档和配置见 [开发文档wiki](docs/wiki.md) 97 | 98 | ## 局限 99 | 100 | 1. 由于 Javassist 的机制,DroidAssist 在处理的过程中将会产生额外的局部变量用以指向参数变量和保存返回值,但处理后有一些局部变量并没有实际作用。 101 | 2. DroidAssist 在处理某些代码时可能会新增一些额外的代理方法。 102 | 3. DroidAssist 插件用于 `library model` 只能处理 Java 源码产生的 class,不能处理本地依赖中的 jar 。 -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # DroidAssist ![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat) ![Release Version](https://img.shields.io/badge/release-1.1.1-blue.svg) 2 | 3 | `DroidAssist` is a lightweight Android Studio gradle plugin based on Javassist for editing bytecode in Android. Unlike other AOP solutions, DroidAssist provides a more lightweight, easy-to-use, non-intrusive, configurable bytecode operation. Even without any Java bytecode knowledge, developers can modify a class file dynamically by editing xml plugin configuration only. You can use AOP-style functionality by adding simple Java code without introducing additional dependencies. 4 | 5 | [中文说明](README_CN.md) 6 | 7 | ## Features 8 | 9 | * With one config, All bytecodes can be processed. 10 | * Rich bytecode modification, such as Insert, Replace, Around and so on. 11 | * Only one plugin, no extra dependencies. 12 | * Optimized build speed. DroidAssist takes up little build time. 13 | 14 | ## Quick Start 15 | 16 | DroidAssist is available for application model or library model in Android Studio. You need to add the plugin and edit a proprietary xml plugin configuration. 17 | 18 | ##### 1. Configure your project-level `build.gradle` to include DroidAssist plugin: 19 | 20 | ```groovy 21 | dependencies { 22 | classpath "com.didichuxing.tools:droidassist:1.1.1" 23 | } 24 | ``` 25 | 26 | ##### 2. Then, apply the DroidAssist plugin in your android project module's `build.gradle`: 27 | 28 | ```groovy 29 | apply plugin: 'com.didichuxing.tools.droidassist' 30 | droidAssistOptions { 31 | config file("droidassist.xml"), file("droidassist2.xml")// Point to the DroidAssist config files. (required) 32 | } 33 | ``` 34 | 35 | Other plugin options: 36 | 37 | * `enable ` Enable plugin or not, Default value is `true`. (optional) 38 | * `logLevel` For control plugin's log level: `0` for no log, `1` for log to console, `2` for log to file, `3` for log to file and console. Default value is `1`. (optional) 39 | * `logDir` Directory of plugin's log output, default value is `build/outputs/logs` (optional) 40 | 41 | 42 | ## Sample Usage 43 | 44 | Logging is an important component of the software development. The following xml plugin configuration replaces the `d` method of `android.util.Log` with custom `log` method in which you can manage log dynamically such as replacing log tag, revising log level and so on. 45 | 46 | ```xml 47 | 48 | 49 | 50 | int android.util.Log.d(java.lang.String,java.lang.String) 51 | 52 | 53 | $_=com.didichuxing.tools.test.LogUtils.log($1,$2); 54 | 55 | 56 | 57 | ``` 58 | 59 | Class before process: 60 | 61 | ```java 62 | public class MainActivity extends Activity { 63 | public static final String TAG = "MainActivity"; 64 | 65 | @Override 66 | protected void onCreate(Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | Log.d(TAG, "MainActivity onCreate"); // The source method using android.util.Log(..) 69 | } 70 | } 71 | ``` 72 | 73 | Class after process: 74 | 75 | ```java 76 | public class MainActivity extends Activity { 77 | public static final String TAG = "MainActivity"; 78 | 79 | protected void onCreate(Bundle savedInstanceState) { 80 | super.onCreate(savedInstanceState); 81 | String var2 = "MainActivity"; 82 | String var3 = "MainActivity onCreate"; 83 | int var4 = LogUtils.log(var2, var3); // The target method using custom log method. 84 | } 85 | } 86 | ``` 87 | 88 | ## Full documentation 89 | For full documentation and advanced configuration, see [wiki](docs/wiki.md). 90 | 91 | ## Limitations 92 | 93 | 1. Due to the use of Javassist to process bytecode, there will be a few useless local variables in class files processed by DroidAssist. 94 | 95 | 2. For the need to process bytecode, DroidAssist may add some extra intermediate methods In some scenarios. 96 | 97 | 3. When used in library model, DroidAssist can handle classes generated by Java source code only (The jars which are dependent locally will not be handled). 98 | 99 | 100 | ## License 101 | See [LICENSE](LICENSE) 102 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | 3 | repositories { 4 | mavenLocal() 5 | mavenCentral() 6 | maven { url "https://maven.google.com" } 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath "com.android.tools.build:gradle:3.2.0" 12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' 13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 14 | } 15 | } 16 | 17 | 18 | ext { 19 | lintVersion = '26.0.1' 20 | kotlinVersion = '1.2.31' 21 | androidGradleVersion = '3.0.0' 22 | assistVersion = '3.24.0-GA' 23 | } 24 | 25 | allprojects { 26 | repositories { 27 | mavenLocal() 28 | mavenCentral() 29 | maven { url "https://maven.google.com" } 30 | jcenter() 31 | } 32 | } 33 | 34 | task clean(type: Delete) { 35 | delete rootProject.buildDir 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'groovy' 3 | apply plugin: 'idea' 4 | 5 | ext { 6 | androidGradleVersion = '3.2.0' 7 | assistVersion = '3.24.1-GA' 8 | } 9 | 10 | repositories { 11 | mavenLocal() 12 | mavenCentral() 13 | maven { url "https://maven.google.com" } 14 | jcenter() 15 | } 16 | 17 | dependencies { 18 | compile localGroovy() 19 | compile gradleApi() 20 | compile "org.javassist:javassist:$assistVersion" //'3.24.1-GA' 21 | compile "com.android.tools.build:gradle:$androidGradleVersion" 22 | } 23 | 24 | sourceSets { 25 | main { 26 | groovy.srcDirs = ['src/main/groovy', '../plugin/src/main/groovy'] 27 | resources.srcDirs = ['src/main/resources', '../plugin/src/main/resources'] 28 | java.srcDirs = ['src/main/java', '../plugin/src/main/java'] 29 | } 30 | } -------------------------------------------------------------------------------- /debug.sh: -------------------------------------------------------------------------------- 1 | ./gradlew :sample:clean :sample:assembleDebug -Dorg.gradle.debug=true --no-daemon --stacktrace -------------------------------------------------------------------------------- /docs/droidassist.dtd: -------------------------------------------------------------------------------- 1 | ]> -------------------------------------------------------------------------------- /docs/pic1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/DroidAssist/a791f130e8e80860e07e51020c6cc19f0c71195a/docs/pic1.png -------------------------------------------------------------------------------- /docs/wiki.md: -------------------------------------------------------------------------------- 1 | # DroidAssist 2 | 3 | `DroidAssist` 是一个轻量级的 Android 字节码编辑插件,基于 `Javassist` 对字节码操作,根据 xml 配置处理 class 文件,以达到对 class 文件进行动态修改的效果。和其他 AOP 方案不同,DroidAssist 提供了一种更加轻量,简单易用,无侵入,可配置化的字节码操作方式,你不需要 Java 字节码的相关知识,只需要在 Xml 插件配置中添加简单的 Java 代码即可实现类似 AOP 的功能,同时不需要引入其他额外的依赖。 4 | 5 | > [ Javassist: A Java bytecode engineering toolkit since 1999](http://www.Javassist.org/ "Java bytecode engineering toolkit since 1999") 6 | 7 | ## 功能 8 | - **替换**:把指定位置代码替换为指定代码 9 | - **插入**:在指定位置的前后插入指定代码 10 | - **环绕**:在指定位置环绕插入指定代码 11 | - **增强**: 12 | - **TryCatch** 对指定代码添加 try catch 代码 13 | - **Timing** 对指定代码添加耗时统计代码 14 | 15 | ## 特点 16 | * 灵活的配置化方式,使得一个配置就可以处理项目中所有的 class 文件。 17 | * 丰富的字节码处理功能,针对 Android 移动端的特点提供了例如代码替换,添加 try catch,方法耗时等功能。 18 | * 简单易用,只需要依赖一个插件,处理过程以及处理后的代码中也不需要添加额外的依赖。 19 | * 处理速度较快,只占用较少的编译时间。 20 | 21 | ## 开发文档 22 | 23 | ### DroidAssist 配置文件 24 | 25 | DroidAssist 将扫描工程中的每一个单独的 class 以及 jar 中的 class, 并对 class 与配置文件中的规则进行匹配,如果有规则能够匹配到 class,则根据 DroidAssist 配置对此 class 进行字节码修改。 26 | DroidAssist 配置是一个 xml 文件,根节点是 `DroidAssist` , 根节点下包含 `Global` , `Insert` , `Around` , `Replace` , `Enhance` 代码操作配置,完整的 DroidAssist 配置文件格式如下: 27 | ```xml 28 | 29 | 30 | 31 | 32 | ... 33 | 34 | 35 | 36 | 37 | ... 38 | 39 | 40 | 41 | 42 | ... 43 | 44 | 45 | 46 | 47 | ... 48 | 49 | 50 | 51 | 52 | ... 53 | 54 | 55 | ``` 56 | > 为了方便编写配置文件,在 IDE 中能自动提示,请将根目录下 [DTD文件](droidassist.dtd) 拷贝到配置文件第二行。 57 | 58 | 59 | ### 配置分类: 60 | 61 | - **Insert**:代码插入类 62 | - **Replace**:代码替换类 63 | - **Around**:代码环绕类 64 | - **Enhance**:代码增强类 65 | 66 | ### Source 和 Target 67 | Insert、Replace、Around、Enhance 类型代码操作配置中均需要包含 `Source` 和 `Target` 元素: 68 | 例: 69 | ```xml 70 | 71 | 72 | 73 | int android.util.Log.d(java.lang.String,java.lang.String) 74 | 75 | 76 | $_= com.didichuxing.tools.test.LogUtils.log($$); 77 | 78 | 79 | 80 | ``` 81 | Source 的值 `int android.util.Log.d(java.lang.String,java.lang.String)` 表示需要匹配方法调用 `android.util.Log.d( )` 82 | Target 的值 `$_= com.didichuxing.tools.test.LogUtils.log($$); `表示将调用 `android.util.Log.d( )` 方法调用的代码替换为 `com.didichuxing.tools.test.LogUtils.log( )` 83 | 84 | #### Source 85 | 表示需要进行修改的代码位置,用以精确匹配代码位置,Source 按照代码位置类型可以分为方法、构造方法、字段、静态初始化块: 86 | ##### 1. 方法 87 | Source 表示方法时,格式为 `returnType className.methodName(argType1,argType2)` : 88 | ```xml 89 | int android.util.Log.d(java.lang.String,java.lang.String) 90 | ``` 91 | 92 | ##### 2. 构造方法 93 | Source 表示方法时,格式为 `new className(argType1,argType2)` 或者 `className.new(argType1,argType2)`: 94 | ```xml 95 | new com.didichuxing.tools.test.ExampleSpec(int) 96 | ``` 97 | 或 98 | ```xml 99 | com.didichuxing.tools.test.ExampleSpec.new(int) 100 | ``` 101 | 102 | ##### 3. 字段 103 | Source 表示字段时,格式为 `fieldType className.fieldName` : 104 | ```xml 105 | int com.didichuxing.tools.test.ExampleSpec.id 106 | ``` 107 | 108 | ##### 4. 静态初始化块 109 | Source 表示静态初始化块时,格式为 `className` : 110 | ```xml 111 | com.didichuxing.tools.test.ExampleSpec 112 | ``` 113 | > 注意: 114 | > 1. Source 的范围为本类和在子类有效(构造方法和静态初始化块除外),方法和字段如果在子类中可见,则也会被匹配,如果不需要匹配子类只匹配当前配置类,可在` `标签中添加 `extend` 属性:`extend = "false"` 115 | > 2. Source 中所有的 class 均需要配置全限定名。 116 | > 3. Source 中 class 如果是内部类,需要使用分隔符 `$` 和外部类隔开,如 `com.didichuxing.tools.test.ExampleSpec$Inner`。 117 | 118 | 119 | #### Target 120 | 需要修改成的目标代码,该值接受一个 Java 表达式或者以大括号`{}`包围的代码块。如果表达式是一个单独的表达式,需要以分号`;`结尾。 121 | 122 | 例: 123 | ```xml 124 | java.lang.System.out.println("BeforeMethodCall"); 125 | ``` 126 | 或: 127 | ```xml 128 | {System.out.println("Hello"); System.out.println("World");} 129 | ``` 130 | 131 | > 注意: 132 | > 133 | > 1. 如果 Source 的表达式中 `returnType` 为非 `void` 类型时, Target 中表达式必须 要包含 `$_=` 以保存返回值,否则可能会出现错误。 134 | 135 | 136 | ##### 扩展变量 137 | 基于 Javassist 的支持,可以在 Target 中使用语言扩展,`$` 开头的标识符有特殊的含义: 138 | 139 | | 符号 | 含义 | scope| 140 | | :-------- | --------| --- | 141 | |`$0`,`$1`,`$2` .. |`this` 和方法的参数 |runtime | 142 | |`$args` |方法参数数组,类型为 `Object[]` |runtime | 143 | |`$$` |所有实参, 例如 m(`$$`) 等价于 m(`$1`,`$2`,...) |runtime | 144 | |`$proceed` |表示原始的方法、构造方法、字段调用 |runtime | 145 | |`$cflow(...)` |cflow 变量 |runtime | 146 | |`$r` |返回结果的类型,用于强制类型转换 |runtime | 147 | |`$w` |包装器类型,用于强制类型转换 |runtime | 148 | |`$_` |返回值 |runtime | 149 | |`$sig` |参数类型数组,类型为 `java.lang.Class[]` |runtime | 150 | |`$type` |返回值类型,类型为 `java.lang.Class` |runtime | 151 | |`$class`|表示当前正在修改的类,类型为 `java.lang.Class` |compile | 152 | |`$line` |表示当前正在修改的行号,类型为 `int` |compile | 153 | |`$name` |表示当前正在方法或字段名,类型为 `java.lang.String` |compile | 154 | |`$file` |表示当前正在修改的文件名,类型为 `java.lang.String` |compile | 155 | 156 | > 1. Target 中对于 `java.lang` 包中的类可以直接使用,不用添加包名。 157 | > 2. Around 类型配置中 `Target` 分解为 `TargetBefore` 和 `TargetAfter` 158 | > 3. Scope 为 `compile` 类型的扩展在编译后将直接替换成结果值,`runtime` 类型的扩展只在运行期有效。 159 | 160 | 161 | ### 类过滤器 Filter 162 | 默认情况下 DroidAssist 将扫描工程中的每一个 class 并进行匹配和处理,为了加快处理速度以及排除某些不需要处理的类,可以使用类过滤器 Filter 配置将不需要处理的类排除。Filter 配置中包含: 163 | 164 | - **Include**:需要处理的类,支持通配符匹配和精确匹配 165 | - **Exclude**:不需要处理的类,支持通配符匹配和精确匹配 166 | 167 | 每个 Filter 中可以包含多个 Include、Exclude 配置,支持通配符匹配,class 被匹配的条件是类名可以被 Include 规则匹配但是不能被 Exclude 匹配,即 `(Include & !Exclude)` 。 168 | 169 | 例: 170 | ```xml 171 | 172 | 173 | 174 | int android.util.Log.d(java.lang.String,java.lang.String) 175 | 176 | 177 | com.didichuxing.tools.test.LogUtils.log($$); 178 | 179 | 180 | 181 | * 182 | com.didichuxing.tools.test.Utils 183 | android.* 184 | com.android.* 185 | 186 | 187 | ``` 188 | 该配置中的 Filter 中 有1个 Include 配置,值 `*` 表示将处理所有的 class,有 3 个 Exclude 配置表示将不处理`com.didichuxing.tools.test.Utils` 类,以及类名匹配 `android.*` 和 `com.android.*` 的类。 189 | 190 | >1. 每一个代码操作配置规则下都可以添加 Filter 配置(可选) 191 | >2. Global 配置中可以包含 Filter,当 Filter 出现在 Global 配置中时,对所有的代码操作配置都生效,如果需要忽略全局 Filter 配置,可在 Filter 标签中添加 ignoreGlobalIncludes="true" 和 ignoreGlobalExcludes="true" 192 | 例: 193 | ```xml 194 | 195 | * 196 | android.* 197 | com.android.* 198 | 199 | ``` 200 | 201 | 202 | ### Global 配置 203 | Global 配置可以包含类过滤器 Filter: 204 | ```xml 205 | 206 | 207 | * 208 | android.* 209 | com.android.* 210 | 211 | 212 | ``` 213 | 214 | ### Replace 配置 215 | Replace 类型代码操作配置的作用是将指定代码替换成目标代码,包含以下配置: 216 | - **MethodCall** 方法调用 217 | - **MethodExecution** 方法体执行 218 | - **ConstructorCall** 构造方法调用 219 | - **ConstructorExecution** 构造方法体执行 220 | - **InitializerExecution** 静态代码初始化块执行 221 | - **FieldRead** 字段读取 222 | - **FieldWrite** 字段赋值 223 | 224 | >Call 表示方法或者构造方法被其他代码调用,Execution 代表方法、构造方法或者静态初始化代码块的方法体本身。 225 | 226 | 例: 227 | ```xml 228 | 229 | 230 | 231 | int android.util.Log.d(java.lang.String,java.lang.String) 232 | 233 | 234 | $_= com.didichuxing.tools.test.LogUtils.log($$); 235 | 236 | 237 | 238 | new com.didichuxing.tools.test.ExampleSpec(int) 239 | {$_= com.didichuxing.tools.test.ExampleSpec.getInstance();} 240 | 241 | 242 | ``` 243 | 244 | 245 | ### Insert 配置 246 | Insert 类型代码操作配置的作用是将指定代码之前或之后插入目标代码,包含以下配置: 247 | - **BeforeMethodCall** 方法调用之前 248 | - **AfterMethodCall** 方法调用之后 249 | - **BeforeMethodExecution** 方法体执行之前 250 | - **AfterMethodExecution** 方法体执行之后 251 | - **BeforeConstructorCall** 构造方法体调用之前 252 | - **AfterConstructorCall** 构造方法体调用之后 253 | - **BeforeConstructorExecution** 构造方法体执行之前 254 | - **AfterConstructorExecution** 构造方法体执行之前 255 | - **BeforeInitializerExecution** 静态代码初始化块执行之前 256 | - **AfterInitializerExecution** 静态代码初始化块执行之前 257 | - **BeforeFieldRead** 字段读取之前 258 | - **AfterFieldRead** 字段读取之后 259 | - **BeforeFieldWrite** 字段赋值之前 260 | - **AfterFieldWrite** 字段赋值之后 261 | 262 | 例: 263 | ```xml 264 | 265 | 266 | void com.didichuxing.tools.test.ExampleSpec.run() 267 | {java.lang.System.out.println("BeforeMethodCall");} 268 | 269 | 270 | 271 | new com.didichuxing.tools.test.ExampleSpec() 272 | java.lang.System.out.println("AfterConstructorExecution"); 273 | 274 | 275 | ``` 276 | 277 | ### Around 配置 278 | Around 类型代码操作配置的作用是将指定代码前后环绕插入目标代码,包含以下配置: 279 | - **MethodCall** 方法调用环绕插入代码 280 | - **MethodExecution** 方法体执行环绕插入代码 281 | - **ConstructorCall** 构造方法调用环绕插入代码 282 | - **ConstructorExecution** 构造方法体执行环绕插入代码 283 | - **InitializerExecution** 静态代码初始化块执行环绕插入代码 284 | - **FieldRead** 字段读取环绕插入代码 285 | - **FieldWrite** 字段赋值环绕插入代码 286 | 287 | 在 Around 类型配置中 Target 配置分解为 `TargetBefore` 和 `TargetAfter`,分别表示 Source 代码之前和之后插入的代码,在 `TargetBefore` 中声明的变量,在 `TargetAfter` 可以直接使用。 288 | 289 | 例: 290 | ```xml 291 | 292 | 293 | 294 | void com.didichuxing.tools.test.ExampleSpec.call() 295 | 296 | 297 | java.lang.System.out.println("around before MethodCall"); 298 | 299 | 300 | java.lang.System.out.println("around after MethodCall"); 301 | 302 | 303 | 304 | ``` 305 | 306 | ### Enhance 配置 307 | Enhance 类型代码操作配置的作用是加入增强性代码,可以对 Source 代码添加 `TryCatch` 方法和 `Timing` 耗时统计方法 : 308 | 309 | #### TryCatch 310 | TryCatch 类型配置可以对 Source 代码添加 `try{...} catch(...){...}`代码,包含以下配置: 311 | - **TryCatchMethodCall** 方法调用添加 Try Catch 代码 312 | - **TryCatchMethodExecution** 方法体执行添加 Try Catch 代码 313 | - **TryCatchConstructorCall** 构造方法调用添加 Try Catch 代码 314 | - **TryCatchConstructorExecution** 构造方法体执行添加 Try Catch 代码 315 | - **TryCatchInitializerExecution** 静态代码初始化块执行添加 Try Catch 代码 316 | 317 | ##### Exception 318 | TryCatch 配置默认将捕获 `java.lang.Exception` 类型异常,如果需要捕获其他异常,需要添加 `Exception` 配置,声明需要捕获的异常,在 Target 表达式中可以使用 `$e` 扩展变量接收捕获的异常对象。 319 | 320 | 例: 321 | ```xml 322 | 323 | 324 | void android.content.Context.startActivity(android.content.Intent) 325 | 326 | 327 | android.content.ActivityNotFoundException 328 | 329 | 330 | android.util.Log.d("test", "startActivity error", $e); 331 | 332 | 333 | ``` 334 | 335 | #### Timing 336 | Timing 类型配置可以对 Source 代码添加耗时统计代码,包含以下配置: 337 | - **TimingMethodCall** 方法调用添加耗时统计代码 338 | - **TimingMethodExecution** 方法体执行耗时统计代码 339 | - **TimingConstructorCall** 构造方法调用耗时统计代码 340 | - **TimingConstructorExecution** 构造方法体执行耗时统计代码 341 | - **TimingInitializerExecution** 静态代码初始化块执行耗时统计代码 342 | 343 | Timing 类型配置会自动在 Source 代码前后添加耗时计算代码,并将耗时毫秒值保存到 `$time` 扩展变量中,可以在 Target 配置中直接使用该扩展变量。 344 | 例: 345 | ```xml 346 | 347 | void com.didichuxing.tools.test.ExampleSpec.timing() 348 | 349 | android.util.Log.d("test", "time cost= "+ $time); 350 | 351 | 352 | ``` 353 | > `$time` 扩展变量为 `long` 型,单位为毫秒,如果需要获取耗时的微秒值,可以使用 `$nanotime` 扩展变量。 354 | 355 | 356 | #### Reparent 357 | Reparent 类型配置可以重新设置制定 class 的父类 358 | - **ReparentClass** 重新设置类的父类型 359 | 360 | Reparent 类型配置将指定的类型( Source 中配置的类型)的直接子类的父类型设置到另外一个类型( Target 中配置的类型)。 361 | 例: 362 | ```xml 363 | 364 | com.didichuxing.tools.test.Parent 365 | com.didichuxing.tools.test.ReParent 366 | 367 | ``` 368 | 上面例子中 class 'com.didichuxing.tools.test.Parent' 的直接子类在处理后父类型将被设置为 'com.didichuxing.tools.test.ReParent' 369 | 370 | > 使用此配置时需要注意 Source 类和 Target 类的 api 兼容性,需要注意子类中构造方法、方法、字段在重新设置到指定的父类后还能否有正确的继承关系。 371 | 372 | 373 | ## Q & A 374 | 375 | #### 1. DroidAssist 可以实现什么功能? 376 | 377 | DroidAssist 提供了一套轻量级的字节码操作方案,可以轻易实现诸如代码替换,代码插入等功能,滴滴出行APP 目前利用DroidAssist 实现了日志输出替换,系统 SharedPreferences 替换,SharedPreferences commit 替换 apply,Dialog show 保护,获取 deviceId 接口替换,getPackageInfo 接口替换,getSystemService 接口替换,startActivity 保护,匿名线程重命名,线程池创建监控,主线程卡顿监控,文件夹创建监控,Activity 生命周期耗时统计,APP启动耗时统计等功能。 378 | 379 | #### 2. DroidAssist 和 AspectJ 有什么区别? 380 | 381 | DroidAssist 采用配置化方案,编写相关配置就可以实现 AOP 的功能,可以完全不用修改 Java 代码,DroidAssist 使用比较简单,不需要复杂的注解配置,DroidAssist 可以比较方便的实现 AspectJ 不容易实现的代码替换功能。 -------------------------------------------------------------------------------- /droidassist.dtd: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ]> 52 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | #lintVersion = '26.0.1' 20 | #kotlinVersion = '1.2.31' 21 | #androidGradleVersion = '2.3.3' 22 | #assistVersion = '3.18.2-GA' -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/DroidAssist/a791f130e8e80860e07e51020c6cc19f0c71195a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 16 14:33:50 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'java' 3 | 4 | apply from: 'upload.gradle' 5 | 6 | group = GROUP_ID 7 | version = VERSION 8 | project.archivesBaseName = ARTIFACT_ID 9 | 10 | sourceCompatibility = "1.8" 11 | targetCompatibility = "1.8" 12 | 13 | sourceSets { 14 | main { 15 | groovy { 16 | srcDirs = ['src/main/groovy'] 17 | } 18 | } 19 | 20 | test { 21 | java { 22 | srcDirs = ['src/main/java', 'src/main/groovy', 'src/test/java'] 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | testCompile 'junit:junit:4.12' 29 | testCompile "com.android.tools.build:gradle:$androidGradleVersion" 30 | 31 | compile fileTree(dir: 'libs', include: ['*.jar']) 32 | compile gradleApi() 33 | compile localGroovy() 34 | compileOnly "com.android.tools.build:gradle:$androidGradleVersion" 35 | 36 | compile "org.javassist:javassist:$assistVersion" //'3.24.0-GA' 37 | } -------------------------------------------------------------------------------- /plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP_ID=com.didichuxing.tools 2 | ARTIFACT_ID=droidassist 3 | VERSION=1.1.1 4 | 5 | -------------------------------------------------------------------------------- /plugin/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied config files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/DroidAssistContext.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist 2 | 3 | import com.android.build.api.transform.Context 4 | import com.android.build.api.transform.TransformInput 5 | import com.didichuxing.tools.droidassist.ex.DroidAssistException 6 | import com.didichuxing.tools.droidassist.transform.Transformer 7 | import com.didichuxing.tools.droidassist.util.IOUtils 8 | import com.didichuxing.tools.droidassist.util.Logger 9 | import javassist.ClassPool 10 | import org.gradle.api.Project 11 | 12 | import java.util.stream.Collectors 13 | import java.util.stream.Stream 14 | 15 | /** 16 | * Context for plugin build env 17 | */ 18 | class DroidAssistContext { 19 | 20 | Context context 21 | Project project 22 | ClassPool classPool 23 | DroidAssistExtension extension 24 | Collection referencedInputs 25 | Collection transformers 26 | 27 | DroidAssistContext( 28 | Context context, 29 | Project project, 30 | DroidAssistExtension extension, 31 | Collection referencedInputs) { 32 | this.context = context 33 | this.project = project 34 | this.extension = extension 35 | this.referencedInputs = referencedInputs 36 | } 37 | 38 | def configure() { 39 | try { 40 | createClassPool() 41 | } catch (Throwable e) { 42 | throw new DroidAssistException("Failed to create class pool", e) 43 | } 44 | 45 | transformers = loadConfiguration() 46 | } 47 | 48 | def loadConfiguration() { 49 | def transformers = extension.configFiles 50 | .parallelStream() 51 | .flatMap { 52 | try { 53 | def list = new DroidAssistConfiguration(project).parse(it) 54 | return list.stream().peek { 55 | transformer -> 56 | transformer.classFilterSpec.addIncludes(extension.includes) 57 | transformer.classFilterSpec.addExcludes(extension.excludes) 58 | transformer.setClassPool(classPool) 59 | transformer.setAbortOnUndefinedClass(extension.abortOnUndefinedClass) 60 | transformer.check() 61 | } 62 | } catch (Throwable e) { 63 | throw new DroidAssistException("Unable to load configuration," + 64 | " unexpected exception occurs when parsing config file:$it, " + 65 | "What went wrong:\n${e.message}", e) 66 | } 67 | }//parse each file 68 | .collect(Collectors.toList()) 69 | 70 | Logger.info("Dump transformers:") 71 | transformers.each { 72 | Logger.info("transformer: $it") 73 | } 74 | return transformers 75 | } 76 | 77 | def createClassPool() { 78 | classPool = new DroidAssistClassPool() 79 | classPool.appendBootClasspath(project.android.bootClasspath) 80 | 81 | def dirStream = referencedInputs 82 | .parallelStream() 83 | .flatMap { it.directoryInputs.parallelStream() } 84 | .filter { it.file.exists() } 85 | 86 | def jarStream = referencedInputs 87 | .parallelStream() 88 | .flatMap { it.jarInputs.parallelStream() } 89 | .filter { it.file.exists() } 90 | 91 | Stream.concat(dirStream, jarStream).forEach { 92 | Logger.info("Append classpath: ${IOUtils.getPath(it.file)}") 93 | classPool.appendClassPath(it.file) 94 | } 95 | } 96 | 97 | class DroidAssistClassPool extends ClassPool { 98 | DroidAssistClassPool() { 99 | super(true) 100 | childFirstLookup = true 101 | } 102 | 103 | void appendBootClasspath(Collection paths) { 104 | paths.stream().parallel().forEach { 105 | appendClassPath(it) 106 | Logger.info "Append boot classpath: ${IOUtils.getPath(it)}" 107 | } 108 | } 109 | 110 | void appendClassPath(File path) { 111 | appendClassPath(path.absolutePath) 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/DroidAssistExecutor.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist 2 | 3 | import com.android.build.api.transform.* 4 | import com.didichuxing.tools.droidassist.tasks.DirInputTask 5 | import com.didichuxing.tools.droidassist.tasks.InputTask 6 | import com.didichuxing.tools.droidassist.tasks.JarInputTask 7 | import com.didichuxing.tools.droidassist.util.GradleUtils 8 | import groovy.json.JsonOutput 9 | import groovy.json.JsonSlurper 10 | import org.apache.commons.io.FileUtils 11 | 12 | import java.util.concurrent.ConcurrentHashMap 13 | import java.util.concurrent.atomic.AtomicInteger 14 | import java.util.stream.Stream 15 | 16 | /** 17 | * process {@link DirectoryInput} and {@link JarInput} parallel 18 | */ 19 | class DroidAssistExecutor { 20 | 21 | static class BuildContext { 22 | def totalCounter = new AtomicInteger(0) 23 | def affectedCounter = new AtomicInteger(0) 24 | File temporaryDir 25 | } 26 | 27 | TransformOutputProvider outputProvider 28 | boolean incremental 29 | DroidAssistContext context 30 | BuildContext buildContext 31 | File destCacheFile 32 | def destCacheMapping = new ConcurrentHashMap() 33 | 34 | DroidAssistExecutor( 35 | DroidAssistContext context, 36 | TransformOutputProvider outputProvider, 37 | boolean incremental) { 38 | this.outputProvider = outputProvider 39 | this.incremental = incremental 40 | this.context = context 41 | 42 | def temporaryDir = context.context.temporaryDir 43 | buildContext = new BuildContext(temporaryDir: temporaryDir) 44 | 45 | def buildDir = context.project.buildDir 46 | def variant = context.context.variantName 47 | destCacheFile = 48 | new File("$buildDir/intermediates/droidAssist/$variant/dest-cache.json") 49 | 50 | if (destCacheFile.exists()) { 51 | if (incremental) { 52 | destCacheMapping.putAll(new JsonSlurper().parse(destCacheFile)) 53 | } else { 54 | FileUtils.forceDelete(destCacheFile) 55 | } 56 | } 57 | } 58 | 59 | void execute(Collection inputs) { 60 | def dirStream = inputs.stream() 61 | .flatMap { it.directoryInputs.stream() } 62 | 63 | def jarStream = inputs.stream() 64 | .flatMap { it.jarInputs.stream() } 65 | 66 | Stream.concat(dirStream, jarStream) 67 | .parallel() 68 | .map { createTask(it) } 69 | .filter { it != null } 70 | .forEach { it.run() } 71 | 72 | FileUtils.forceMkdir(destCacheFile.parentFile) 73 | destCacheFile.write(JsonOutput.toJson(destCacheMapping)) 74 | } 75 | 76 | InputTask createTask(QualifiedContent content) { 77 | def taskInput = 78 | new InputTask.TaskInput( 79 | input: content, 80 | dest: getDestFile(content), 81 | incremental: incremental) 82 | if (content instanceof JarInput) { 83 | return new JarInputTask(context, buildContext, taskInput) 84 | } 85 | if (content instanceof DirectoryInput) { 86 | return new DirInputTask(context, buildContext, taskInput) 87 | } 88 | return null 89 | } 90 | 91 | File getDestFile(QualifiedContent content) { 92 | def path = destCacheMapping.get(content.name) 93 | def buildDir = context.project.buildDir 94 | File dest = path == null ? null : new File(buildDir, path) 95 | if (dest == null || !dest.exists()) { 96 | dest = GradleUtils.getTransformOutputLocation(outputProvider, content) 97 | destCacheMapping.put(content.name, buildDir.toPath().relativize(dest.toPath()).toString()) 98 | } 99 | return dest 100 | } 101 | 102 | int getAffectedCount() { 103 | return buildContext.affectedCounter.get() 104 | } 105 | 106 | int getClassCount() { 107 | return buildContext.totalCounter.get() 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/DroidAssistExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist 2 | 3 | import com.google.common.collect.Lists 4 | 5 | /** 6 | * Options container for plugin 7 | */ 8 | class DroidAssistExtension { 9 | boolean enable = true 10 | int logLevel = -1 11 | List configFiles = Lists.newArrayList() 12 | File logDir 13 | boolean abortOnUndefinedClass = false 14 | boolean incremental = true 15 | 16 | List includes = Lists.newArrayList() 17 | List excludes = Lists.newArrayList() 18 | 19 | void exclude(String... filter) { 20 | excludes.addAll(filter) 21 | } 22 | 23 | void include(String... filter) { 24 | includes.addAll(filter) 25 | } 26 | 27 | void config(File... file) { 28 | configFiles.addAll(file) 29 | } 30 | 31 | List getConfig() { 32 | return configFiles 33 | } 34 | 35 | @Override 36 | String toString() { 37 | return "\n{" + 38 | "\n enable=" + enable + 39 | "\n logLevel=" + logLevel + 40 | "\n config=" + configFiles + 41 | "\n logDir=" + logDir + 42 | "\n includes=" + includes + 43 | "\n excludes=" + excludes + 44 | '\n}' 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/DroidAssistPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist 2 | 3 | import com.android.build.gradle.AppExtension 4 | import com.android.build.gradle.AppPlugin 5 | import com.android.build.gradle.LibraryExtension 6 | import com.android.build.gradle.LibraryPlugin 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | 10 | /** 11 | * Plugin entrance 12 | */ 13 | class DroidAssistPlugin implements Plugin { 14 | 15 | @Override 16 | void apply(Project project) { 17 | 18 | project.extensions.create("droidAssistOptions", DroidAssistExtension) 19 | 20 | if (project.plugins.hasPlugin(AppPlugin.class)) { 21 | AppExtension extension = project.extensions.getByType(AppExtension) 22 | extension.registerTransform( 23 | new DroidAssistTransform(project, true)) 24 | } 25 | if (project.plugins.hasPlugin(LibraryPlugin.class)) { 26 | LibraryExtension extension = project.extensions.getByType(LibraryExtension) 27 | extension.registerTransform( 28 | new DroidAssistTransform(project, false)) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/DroidAssistTransform.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist 2 | 3 | import com.android.annotations.NonNull 4 | import com.android.build.api.transform.* 5 | import com.android.build.gradle.internal.pipeline.TransformManager 6 | import com.didichuxing.tools.droidassist.util.GradleUtils 7 | import com.didichuxing.tools.droidassist.util.Logger 8 | import com.didichuxing.tools.droidassist.util.TimingLogger 9 | import com.google.common.collect.Lists 10 | import com.google.common.collect.Sets 11 | import org.apache.commons.io.FileUtils 12 | import org.gradle.api.Project 13 | 14 | import java.util.stream.Stream 15 | 16 | /** 17 | * DroidAssist Transformer 18 | */ 19 | class DroidAssistTransform extends Transform { 20 | 21 | Project project 22 | boolean application 23 | DroidAssistExtension gradleExtension 24 | 25 | DroidAssistTransform(Project project, boolean application) { 26 | this.project = project 27 | this.application = application 28 | gradleExtension = this.project.droidAssistOptions 29 | } 30 | 31 | @Override 32 | String getName() { 33 | return "droidAssist" 34 | } 35 | 36 | @Override 37 | Set getInputTypes() { 38 | return TransformManager.CONTENT_CLASS //all classes 39 | } 40 | 41 | @Override 42 | Set getScopes() { 43 | return application ? 44 | TransformManager.SCOPE_FULL_PROJECT 45 | : Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT) 46 | } 47 | 48 | /** 49 | * classpath needed 50 | */ 51 | @Override 52 | Set getReferencedScopes() { 53 | return TransformManager.SCOPE_FULL_PROJECT 54 | } 55 | 56 | @Override 57 | boolean isIncremental() { 58 | return gradleExtension.incremental 59 | } 60 | 61 | /** 62 | * Magic here, Changes to the plugin config file will trigger a non incremental execution 63 | */ 64 | @Override 65 | Collection getSecondaryFiles() { 66 | Objects.requireNonNull(gradleExtension.config) 67 | return Lists.newArrayList( 68 | SecondaryFile.nonIncremental(project.files(gradleExtension.config))) 69 | } 70 | 71 | void transform(@NonNull TransformInvocation invocation) 72 | throws TransformException, InterruptedException, IOException { 73 | try { 74 | def logLevel = gradleExtension.logLevel 75 | Logger.init( 76 | logLevel < 0 ? Logger.LEVEL_CONSOLE : logLevel, 77 | gradleExtension.logDir ?: project.file("${project.buildDir}/outputs/logs/")) 78 | 79 | onTransform( 80 | invocation.getContext(), 81 | invocation.getInputs(), 82 | invocation.getReferencedInputs(), 83 | invocation.getOutputProvider(), 84 | invocation.isIncremental()) 85 | } catch (Throwable e) { 86 | Logger.error("Build failed with an exception: ${e.cause?.message}", e) 87 | e.fillInStackTrace() 88 | throw e 89 | } finally { 90 | Logger.close() 91 | } 92 | } 93 | 94 | /** 95 | * When droidAssist is enable, process files and write them to an output folder 96 | * 97 | *

{@link DroidAssistExecutor#execute} process files specifically 98 | */ 99 | void onTransform( 100 | Context gradleContext, 101 | Collection inputs, 102 | Collection referencedInputs, 103 | TransformOutputProvider outputProvider, 104 | boolean isIncremental) 105 | throws IOException, TransformException, InterruptedException { 106 | 107 | Logger.info("Transform start, " + 108 | "enable:${gradleExtension.enable}, " + 109 | "incremental:${isIncremental}") 110 | 111 | // If droidAssist is disable, just copy the input folder to the output folder 112 | if (!gradleExtension.enable) { 113 | outputProvider.deleteAll() 114 | def dirStream = inputs 115 | .parallelStream() 116 | .flatMap { it.directoryInputs.parallelStream() } 117 | .filter { it.file.exists() } 118 | 119 | def jarStream = inputs 120 | .parallelStream() 121 | .flatMap { it.jarInputs.parallelStream() } 122 | .filter { it.file.exists() } 123 | 124 | Stream.concat(dirStream, jarStream).forEach { 125 | def copy = it.file.isFile() ? "copyFile" : "copyDirectory" 126 | FileUtils."$copy"( 127 | it.file, 128 | GradleUtils.getTransformOutputLocation(outputProvider, it)) 129 | } 130 | return 131 | } 132 | 133 | def start = System.currentTimeMillis() 134 | Logger.info("DroidAssist options: ${gradleExtension}") 135 | def timingLogger = new TimingLogger("Timing", "execute") 136 | 137 | //Delete output folder and reprocess files, when it is not incremental 138 | if (!isIncremental) { 139 | outputProvider.deleteAll() 140 | timingLogger.addSplit("delete output") 141 | } 142 | 143 | def context = 144 | new DroidAssistContext( 145 | gradleContext, 146 | project, 147 | gradleExtension, 148 | referencedInputs) 149 | context.configure() 150 | timingLogger.addSplit("configure context") 151 | 152 | def executor = 153 | new DroidAssistExecutor( 154 | context, 155 | outputProvider, 156 | isIncremental) 157 | timingLogger.addSplit("create executor") 158 | 159 | //Execute all input classed with byte code operation transformers 160 | executor.execute(inputs) 161 | timingLogger.addSplit("execute inputs") 162 | 163 | timingLogger.dumpToLog() 164 | Logger.info("Transform end, " + 165 | "input classes count:${executor.classCount}, " + 166 | "affected classes:${executor.affectedCount}, " + 167 | "time use:${System.currentTimeMillis() - start} ms") 168 | } 169 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/ex/DroidAssistBadStatementException.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.ex; 2 | 3 | @SuppressWarnings("WeakerAccess") 4 | public class DroidAssistBadStatementException extends DroidAssistException { 5 | 6 | 7 | public DroidAssistBadStatementException(String msg) { 8 | super(msg); 9 | } 10 | 11 | public DroidAssistBadStatementException(Throwable e) { 12 | super(e); 13 | } 14 | 15 | public DroidAssistBadStatementException(String msg, Throwable e) { 16 | super(msg, e); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/ex/DroidAssistBadTypeException.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.ex; 2 | 3 | public class DroidAssistBadTypeException extends DroidAssistException { 4 | 5 | public DroidAssistBadTypeException(String msg) { 6 | super(msg); 7 | } 8 | 9 | public DroidAssistBadTypeException(Throwable e) { 10 | super(e); 11 | } 12 | 13 | public DroidAssistBadTypeException(String msg, Throwable e) { 14 | super(msg, e); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/ex/DroidAssistException.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.ex; 2 | 3 | 4 | public class DroidAssistException extends RuntimeException { 5 | 6 | private Throwable myCause; 7 | 8 | public Throwable getCause() { 9 | return (myCause == this ? null : myCause); 10 | } 11 | 12 | public synchronized Throwable initCause(Throwable cause) { 13 | myCause = cause; 14 | return this; 15 | } 16 | 17 | private String message; 18 | 19 | public String getReason() { 20 | if (message != null) { 21 | return message; 22 | } else { 23 | return this.toString(); 24 | } 25 | } 26 | 27 | public DroidAssistException(String msg) { 28 | super(msg); 29 | message = msg; 30 | initCause(null); 31 | } 32 | 33 | 34 | public DroidAssistException(Throwable e) { 35 | super(e); 36 | message = null; 37 | initCause(e); 38 | } 39 | 40 | 41 | public DroidAssistException(String msg, Throwable e) { 42 | this(msg); 43 | initCause(e); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/ex/DroidAssistNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.ex; 2 | 3 | public class DroidAssistNotFoundException extends DroidAssistException { 4 | 5 | public DroidAssistNotFoundException(String msg) { 6 | super(msg); 7 | } 8 | 9 | public DroidAssistNotFoundException(Throwable e) { 10 | super(e); 11 | } 12 | 13 | public DroidAssistNotFoundException(String msg, Throwable e) { 14 | super(msg, e); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/spec/ClassFilterSpec.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.spec; 2 | 3 | import org.apache.commons.io.FilenameUtils; 4 | 5 | import java.util.Collection; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | /** 10 | * ClassFilterSpec with wildcard match 11 | */ 12 | @SuppressWarnings("WeakerAccess") 13 | public class ClassFilterSpec { 14 | private Set includes = new HashSet<>(); 15 | private Set excludes = new HashSet<>(); 16 | 17 | public void addInclude(String filter) { 18 | if (filter == null) { 19 | return; 20 | } 21 | filter = filter.trim(); 22 | if (filter.equals("")) { 23 | return; 24 | } 25 | includes.add(filter); 26 | } 27 | 28 | public void addExclude(String filter) { 29 | if (filter == null) { 30 | return; 31 | } 32 | filter = filter.trim(); 33 | if (filter.equals("")) { 34 | return; 35 | } 36 | excludes.add(filter); 37 | } 38 | 39 | public void addIncludes(Collection filters) { 40 | for (String filter : filters) { 41 | addInclude(filter); 42 | } 43 | } 44 | 45 | public void addExcludes(Collection filters) { 46 | for (String filter : filters) { 47 | addExclude(filter); 48 | } 49 | } 50 | 51 | public Set getIncludes() { 52 | return includes; 53 | } 54 | 55 | public Set getExcludes() { 56 | return excludes; 57 | } 58 | 59 | private boolean isIncludeClass(String className) { 60 | if (includes.isEmpty()) { 61 | return false; 62 | } 63 | for (String fi : includes) { 64 | if (FilenameUtils.wildcardMatch(className, fi)) { 65 | return true; 66 | } 67 | } 68 | return false; 69 | } 70 | 71 | private boolean isExcludeClass(String className) { 72 | if (excludes.isEmpty()) { 73 | return false; 74 | } 75 | for (String fi : excludes) { 76 | if (FilenameUtils.wildcardMatch(className, fi)) { 77 | return true; 78 | } 79 | } 80 | return false; 81 | } 82 | 83 | public boolean classAllowed(String className) { 84 | return isIncludeClass(className) && !isExcludeClass(className); 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return "{" + 90 | "includes=" + includes + 91 | ", excludes=" + excludes + 92 | '}'; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/tasks/DirInputTask.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.tasks 2 | 3 | import com.android.build.api.transform.DirectoryInput 4 | import com.android.build.api.transform.Status 5 | import com.didichuxing.tools.droidassist.DroidAssistContext 6 | import com.didichuxing.tools.droidassist.DroidAssistExecutor.BuildContext 7 | import com.didichuxing.tools.droidassist.util.WorkerExecutor 8 | import com.google.common.collect.Lists 9 | import org.apache.commons.io.FileUtils 10 | import org.apache.commons.io.FilenameUtils 11 | 12 | import java.nio.file.Files 13 | import java.nio.file.Path 14 | import java.nio.file.Paths 15 | import java.util.stream.Collectors 16 | 17 | class DirInputTask extends InputTask { 18 | 19 | DirInputTask( 20 | DroidAssistContext context, 21 | BuildContext buildContext, 22 | TaskInput taskInput) { 23 | super(context, buildContext, taskInput) 24 | } 25 | 26 | @Override 27 | String getInputType() { 28 | return "dir" 29 | } 30 | 31 | /** 32 | * process DirectoryInput 33 | */ 34 | void execute() { 35 | DirectoryInput input = taskInput.input 36 | def inputDir = input.file 37 | def executor = new WorkerExecutor(1) 38 | List files = Lists.newArrayList() 39 | 40 | if (taskInput.incremental) { 41 | //process changedFiles in incremental mode. 42 | //if file is removed, delete corresponding dest file. 43 | //if file is changed or added, add file to pending collections. 44 | input.changedFiles.each { 45 | file, status -> 46 | def destFile = getDestFileMapping(file, inputDir, taskInput.dest) 47 | if (status == Status.REMOVED) { 48 | if (destFile != null) { 49 | FileUtils.deleteQuietly(destFile) 50 | } 51 | } 52 | if (status == Status.CHANGED || status == Status.ADDED) { 53 | files << file 54 | if (destFile != null && file.isFile()) { 55 | executor.execute { 56 | FileUtils.copyFile(file, destFile) 57 | } 58 | } 59 | } 60 | } 61 | } else { 62 | //process every class file in Non-incremental mode 63 | executor.execute { 64 | FileUtils.copyDirectory(inputDir, taskInput.dest) 65 | } 66 | 67 | def fileList = Files.walk(inputDir.toPath()) 68 | .parallel() 69 | .map { it.toFile() }//Path to file 70 | .filter { it.isFile() } 71 | .filter { it.name.endsWith(DOT_CLASS) } //Filter class file 72 | .collect(Collectors.toList()) 73 | 74 | files.addAll(fileList) 75 | } 76 | 77 | files.stream() 78 | .filter { it.isFile() }//Path to file 79 | .filter { it.name.endsWith(DOT_CLASS) }//Filter class file 80 | .forEach { executeClass(it, inputDir, temporaryDir) } 81 | 82 | executor.execute { 83 | FileUtils.copyDirectory(temporaryDir, taskInput.dest) 84 | } 85 | 86 | executor.finish() 87 | } 88 | 89 | void executeClass(File classFile, File inputDir, File tempDir) { 90 | def className = 91 | FilenameUtils. 92 | removeExtension( 93 | inputDir.toPath() 94 | .relativize(classFile.toPath()) 95 | .toString()) 96 | .replace(File.separator, '.') 97 | executeClass(className, tempDir) 98 | } 99 | 100 | static File getDestFileMapping(File file, File baseDir, File destDir) { 101 | if (file == null || !file.exists() || destDir == null || !destDir.exists()) { 102 | return null 103 | } 104 | Path relativePath = baseDir.toPath().relativize(file.toPath()) 105 | Paths.get(destDir.absolutePath, relativePath.toString()).toFile() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/tasks/InputTask.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.tasks 2 | 3 | import com.android.build.api.transform.QualifiedContent 4 | import com.didichuxing.tools.droidassist.DroidAssistContext 5 | import com.didichuxing.tools.droidassist.DroidAssistExecutor.BuildContext 6 | import com.didichuxing.tools.droidassist.ex.DroidAssistBadStatementException 7 | import com.didichuxing.tools.droidassist.ex.DroidAssistException 8 | import com.didichuxing.tools.droidassist.ex.DroidAssistNotFoundException 9 | import com.didichuxing.tools.droidassist.util.IOUtils 10 | import com.didichuxing.tools.droidassist.util.Logger 11 | import javassist.CannotCompileException 12 | import javassist.NotFoundException 13 | 14 | import static com.android.utils.FileUtils.cleanOutputDir 15 | 16 | /** 17 | * Interface to process QualifiedContent. 18 | * 19 | *

It provides the ability to handle classes, see {@link #executeClass} 20 | */ 21 | abstract class InputTask implements Runnable { 22 | 23 | public static final String DOT_CLASS = ".class" 24 | public static final String DOT_JAR = ".jar" 25 | 26 | DroidAssistContext context 27 | BuildContext buildContext 28 | TaskInput taskInput 29 | File temporaryDir 30 | 31 | static class TaskInput { 32 | T input 33 | File dest 34 | boolean incremental 35 | } 36 | 37 | InputTask( 38 | DroidAssistContext context, 39 | BuildContext buildContext, 40 | TaskInput taskInput) { 41 | this.context = context 42 | this.buildContext = buildContext 43 | this.taskInput = taskInput 44 | temporaryDir = ensureTemporaryDir() 45 | } 46 | 47 | @Override 48 | final void run() { 49 | try { 50 | Logger.info("execute ${inputType}: ${IOUtils.getPath(taskInput.input.file)}") 51 | execute() 52 | } catch (DroidAssistException e) { 53 | throw e 54 | } catch (Throwable e) { 55 | throw new DroidAssistException("Execution failed for " + 56 | "input:${IOUtils.getPath(taskInput.input.file)}", e) 57 | } 58 | } 59 | 60 | abstract void execute() 61 | 62 | abstract String getInputType() 63 | 64 | File ensureTemporaryDir() { 65 | def dir = new File( 66 | "${buildContext.temporaryDir}/" + 67 | "${inputType}/" + 68 | "${taskInput.input.name.replace(":", "-")}") 69 | cleanOutputDir(dir) 70 | return dir 71 | } 72 | 73 | boolean executeClass(String className, File directory) { 74 | buildContext.totalCounter.incrementAndGet() 75 | def inputClass = null 76 | def transformers = context.transformers.findAll { 77 | it.classAllowed(className) 78 | } 79 | 80 | if (transformers.isEmpty()) { 81 | return false 82 | } 83 | 84 | inputClass = context.classPool.getOrNull(className) 85 | if (inputClass == null) { 86 | return false 87 | } 88 | 89 | transformers.each { 90 | try { 91 | it.performTransform(inputClass, className) 92 | } catch (NotFoundException e) { 93 | throw new DroidAssistNotFoundException( 94 | "Transform failed for class: ${className}" + 95 | " with not found exception: ${e.cause?.message}", e) 96 | } catch (CannotCompileException e) { 97 | throw new DroidAssistBadStatementException( 98 | "Transform failed for class: ${className} " + 99 | "with compile error: ${e.cause?.message}", e) 100 | } catch (Throwable e) { 101 | throw new DroidAssistException( 102 | "Transform failed for class: ${className} " + 103 | "with error: ${e.cause?.message}", e) 104 | } 105 | } 106 | 107 | if (inputClass.modified) { 108 | buildContext.affectedCounter.incrementAndGet() 109 | inputClass.writeFile(directory.absolutePath) 110 | return true 111 | } 112 | return false 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/tasks/JarInputTask.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.tasks 2 | 3 | import com.android.build.api.transform.JarInput 4 | import com.android.build.api.transform.Status 5 | import com.didichuxing.tools.droidassist.DroidAssistContext 6 | import com.didichuxing.tools.droidassist.DroidAssistExecutor.BuildContext 7 | import com.didichuxing.tools.droidassist.util.IOUtils 8 | import com.didichuxing.tools.droidassist.util.Logger 9 | import com.didichuxing.tools.droidassist.util.ZipUtils 10 | import org.apache.commons.io.FileUtils 11 | 12 | class JarInputTask extends InputTask { 13 | 14 | JarInputTask( 15 | DroidAssistContext context, 16 | BuildContext buildContext, 17 | TaskInput taskInput) { 18 | super(context, buildContext, taskInput) 19 | } 20 | 21 | @Override 22 | String getInputType() { 23 | return "jar" 24 | } 25 | 26 | /** 27 | * If jar is changed, reprocess jar and otherwise skip. 28 | * 29 | *

See for details {@link InputTask#executeClass} 30 | */ 31 | void execute() { 32 | JarInput input = taskInput.input 33 | File inputJar = input.file 34 | if (taskInput.incremental) { 35 | if (input.status != Status.NOTCHANGED) { 36 | Logger.info("Jar incremental build: \ninput:${ taskInput.input.name} \npath: ${IOUtils.getPath(inputJar)} \ndest:${taskInput.dest} \nstatus:${input.status}") 37 | FileUtils.deleteQuietly(taskInput.dest) 38 | } else { 39 | Logger.info("${IOUtils.getPath(inputJar)} not changed, skip.") 40 | return 41 | } 42 | } 43 | 44 | if (input.status != Status.REMOVED) { 45 | def written = false 46 | ZipUtils.collectAllClassesFromJar(inputJar).forEach { 47 | written = executeClass(it, temporaryDir) || written 48 | } 49 | if (written) { 50 | ZipUtils.zipAppend(inputJar, taskInput.dest, temporaryDir) 51 | } else { 52 | FileUtils.copyFile(inputJar, taskInput.dest) 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/ExprExecTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform; 2 | 3 | 4 | import com.google.common.collect.Sets; 5 | 6 | import java.util.Set; 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | 9 | import javassist.CannotCompileException; 10 | import javassist.CtBehavior; 11 | import javassist.CtClass; 12 | import javassist.CtConstructor; 13 | import javassist.CtMethod; 14 | import javassist.Modifier; 15 | import javassist.NotFoundException; 16 | import javassist.expr.ConstructorCall; 17 | import javassist.expr.ExprEditor; 18 | import javassist.expr.FieldAccess; 19 | import javassist.expr.MethodCall; 20 | import javassist.expr.NewExpr; 21 | 22 | @SuppressWarnings("RedundantThrows") 23 | public abstract class ExprExecTransformer extends SourceTargetTransformer { 24 | 25 | protected static final String CONSTRUCTOR_CALL = "ConstructorCall"; 26 | protected static final String METHOD_CALL = "MethodCall"; 27 | protected static final String FIELD_ACCESS = "FieldAccess"; 28 | protected static final String NEW_EXPR = "NewExpr"; 29 | 30 | protected static final String METHOD = "method"; 31 | protected static final String INITIALIZER = "initializer"; 32 | protected static final String CONSTRUCTOR = "constructor"; 33 | 34 | public static final String TRANSFORM_EXEC = "exec"; 35 | public static final String TRANSFORM_EXPR = "expr"; 36 | 37 | 38 | class Editor extends ExprEditor { 39 | CtBehavior behavior; 40 | AtomicBoolean modified; 41 | } 42 | 43 | protected abstract String getExecuteType(); 44 | 45 | protected Set getExtraExecuteTypes() { 46 | return Sets.newHashSet(); 47 | } 48 | 49 | protected abstract String getTransformType(); 50 | 51 | @Override 52 | public String getPrettyName() { 53 | StringBuilder name = new StringBuilder(getCategoryName() + " ["); 54 | 55 | String disposeType = getExecuteType(); 56 | if (FIELD_ACCESS.equals(disposeType)) { 57 | name.append("field"); 58 | } 59 | if (METHOD_CALL.equals(disposeType)) { 60 | name.append("method"); 61 | } 62 | 63 | if (NEW_EXPR.equals(disposeType)) { 64 | name.append("constructor"); 65 | } 66 | 67 | if (METHOD.equals(disposeType)) { 68 | name.append("method"); 69 | } 70 | 71 | if (CONSTRUCTOR.equals(disposeType)) { 72 | name.append("constructor"); 73 | } 74 | 75 | if (INITIALIZER.equals(disposeType)) { 76 | name.append("initializer"); 77 | } 78 | 79 | //expr 80 | String transformType = getTransformType(); 81 | if (TRANSFORM_EXPR.equals(transformType)) { 82 | name.append(" call"); 83 | } 84 | //exec 85 | if (TRANSFORM_EXEC.equals(getTransformType())) { 86 | name.append(" exec"); 87 | } 88 | name.append("]"); 89 | return name.toString(); 90 | } 91 | 92 | @Override 93 | protected final boolean onTransform( 94 | CtClass inputClass, 95 | String inputClassName) 96 | throws NotFoundException, CannotCompileException { 97 | //expr 98 | if (TRANSFORM_EXPR.equals(getTransformType())) { 99 | return onTransformExpr(inputClass, inputClassName); 100 | } 101 | //exec 102 | if (TRANSFORM_EXEC.equals(getTransformType())) { 103 | return onTransformExec(inputClass, inputClassName); 104 | } 105 | return false; 106 | } 107 | 108 | private boolean onTransformExec( 109 | CtClass inputClass, 110 | String inputClassName) 111 | throws NotFoundException, CannotCompileException { 112 | 113 | if (!filterClass(inputClass, inputClassName)) { 114 | return false; 115 | } 116 | if (!isMatchSourceClass(inputClass)) { 117 | return false; 118 | } 119 | if (!execute(inputClass, inputClassName)) { 120 | return false; 121 | } 122 | 123 | boolean modified = false; 124 | Set executeTypes = getExtraExecuteTypes(); 125 | executeTypes.add(getExecuteType()); 126 | if (executeTypes.contains(METHOD)) { 127 | CtMethod[] declaredMethods = tryGetDeclaredMethods(inputClass); 128 | for (CtMethod method : declaredMethods) { 129 | if (Modifier.isAbstract(method.getModifiers())) { 130 | continue; 131 | } 132 | if (execute(inputClass, inputClassName, method)) { 133 | modified = true; 134 | } 135 | } 136 | } 137 | 138 | if (executeTypes.contains(CONSTRUCTOR)) { 139 | CtConstructor[] declaredConstructors = tryGetDeclaredConstructors(inputClass); 140 | for (CtConstructor constructor : declaredConstructors) { 141 | if (execute(inputClass, inputClassName, constructor)) { 142 | modified = true; 143 | } 144 | } 145 | } 146 | 147 | if (executeTypes.contains(INITIALIZER)) { 148 | CtConstructor initializer = tryGetClassInitializer(inputClass); 149 | if (initializer != null) { 150 | if (execute(inputClass, inputClassName, initializer)) { 151 | modified = true; 152 | } 153 | } 154 | } 155 | return modified; 156 | } 157 | 158 | private boolean onTransformExpr( 159 | CtClass inputClass, 160 | String inputClassName) 161 | throws NotFoundException, CannotCompileException { 162 | 163 | if (!filterClass(inputClass, inputClassName)) { 164 | return false; 165 | } 166 | if (!execute(inputClass, inputClassName)) { 167 | return false; 168 | } 169 | 170 | final Set executeTypes = getExtraExecuteTypes(); 171 | executeTypes.add(getExecuteType()); 172 | final AtomicBoolean modified = new AtomicBoolean(false); 173 | Editor editor = new Editor() { 174 | 175 | @Override 176 | public void edit(ConstructorCall call) throws CannotCompileException { 177 | if (executeTypes.contains(CONSTRUCTOR_CALL)) { 178 | boolean disposed; 179 | try { 180 | disposed = execute(inputClass, inputClassName, call); 181 | } catch (NotFoundException e) { 182 | String msg = e.getMessage() + " for input class " + inputClassName; 183 | throw new CannotCompileException(msg, e); 184 | } 185 | modified.set(modified.get() | disposed); 186 | } 187 | } 188 | 189 | @Override 190 | public void edit(MethodCall call) throws CannotCompileException { 191 | if (executeTypes.contains(METHOD_CALL)) { 192 | boolean disposed; 193 | try { 194 | disposed = execute(inputClass, inputClassName, call); 195 | } catch (NotFoundException e) { 196 | String msg = e.getMessage() + " for input class " + inputClassName; 197 | throw new CannotCompileException(msg, e); 198 | } 199 | modified.set(modified.get() | disposed); 200 | } 201 | } 202 | 203 | @Override 204 | public void edit(FieldAccess fieldAccess) throws CannotCompileException { 205 | if (executeTypes.contains(FIELD_ACCESS)) { 206 | boolean disposed; 207 | try { 208 | disposed = execute(inputClass, inputClassName, fieldAccess); 209 | } catch (NotFoundException e) { 210 | String msg = e.getMessage() + " for input class " + inputClassName; 211 | throw new CannotCompileException(msg, e); 212 | } 213 | modified.set(modified.get() | disposed); 214 | } 215 | } 216 | 217 | @Override 218 | public void edit(NewExpr newExpr) throws CannotCompileException { 219 | if (executeTypes.contains(NEW_EXPR)) { 220 | boolean disposed; 221 | try { 222 | disposed = execute(inputClass, inputClassName, newExpr); 223 | } catch (NotFoundException e) { 224 | String msg = e.getMessage() + " for input class " + inputClassName; 225 | throw new CannotCompileException(msg, e); 226 | } 227 | modified.set(modified.get() | disposed); 228 | } 229 | } 230 | }; 231 | 232 | CtConstructor initializer = tryGetClassInitializer(inputClass); 233 | if (initializer != null) { 234 | if (instrument(initializer, editor)) { 235 | modified.set(true); 236 | } 237 | } 238 | CtMethod[] declaredMethods = tryGetDeclaredMethods(inputClass); 239 | for (CtMethod method : declaredMethods) { 240 | if (instrument(method, editor)) { 241 | modified.set(true); 242 | } 243 | } 244 | CtConstructor[] declaredConstructors = tryGetDeclaredConstructors(inputClass); 245 | for (CtConstructor constructor : declaredConstructors) { 246 | if (instrument(constructor, editor)) { 247 | modified.set(true); 248 | } 249 | } 250 | return modified.get(); 251 | } 252 | 253 | private boolean instrument(CtBehavior behavior, Editor editor) throws CannotCompileException { 254 | editor.modified = new AtomicBoolean(false); 255 | editor.behavior = behavior; 256 | behavior.instrument(editor); 257 | return editor.modified.get(); 258 | } 259 | 260 | protected boolean filterClass( 261 | CtClass inputClass, 262 | String inputClassName) 263 | throws NotFoundException, CannotCompileException { 264 | return true; 265 | } 266 | 267 | protected boolean execute( 268 | CtClass inputClass, 269 | String inputClassName) 270 | throws CannotCompileException, NotFoundException { 271 | return true; 272 | } 273 | 274 | protected boolean execute( 275 | CtClass inputClass, 276 | String inputClassName, 277 | CtMethod method) 278 | throws CannotCompileException, NotFoundException { 279 | return false; 280 | } 281 | 282 | protected boolean execute( 283 | CtClass inputClass, 284 | String inputClassName, 285 | CtConstructor constructor) 286 | throws CannotCompileException, NotFoundException { 287 | return false; 288 | } 289 | 290 | protected boolean execute( 291 | CtClass inputClass, 292 | String inputClassName, 293 | MethodCall methodCall) 294 | throws CannotCompileException, NotFoundException { 295 | return false; 296 | } 297 | 298 | protected boolean execute( 299 | CtClass inputClass, 300 | String inputClassName, 301 | ConstructorCall constructorCall) 302 | throws CannotCompileException, NotFoundException { 303 | return false; 304 | } 305 | 306 | protected boolean execute( 307 | CtClass inputClass, 308 | String inputClassName, 309 | FieldAccess fieldAccess) 310 | throws CannotCompileException, NotFoundException { 311 | return false; 312 | } 313 | 314 | protected boolean execute( 315 | CtClass inputClass, 316 | String inputClassName, 317 | NewExpr newExpr) 318 | throws CannotCompileException, NotFoundException { 319 | return false; 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/Transformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform; 2 | 3 | import com.didichuxing.tools.droidassist.ex.DroidAssistNotFoundException; 4 | import com.didichuxing.tools.droidassist.spec.ClassFilterSpec; 5 | import com.didichuxing.tools.droidassist.util.Logger; 6 | 7 | import javassist.CannotCompileException; 8 | import javassist.ClassPool; 9 | import javassist.CtClass; 10 | import javassist.CtConstructor; 11 | import javassist.CtMethod; 12 | import javassist.NotFoundException; 13 | 14 | /** 15 | * An abstract Transform that processes class with different way. 16 | * 17 | *

There are five major categories of {@link com.didichuxing.tools.droidassist.transform.around.AroundTransformer}, 18 | * {@link com.didichuxing.tools.droidassist.transform.enhance.TimingTransformer}, 19 | * {@link com.didichuxing.tools.droidassist.transform.enhance.TryCatchTransformer}, 20 | * {@link com.didichuxing.tools.droidassist.transform.insert.InsertTransformer}, 21 | * {@link com.didichuxing.tools.droidassist.transform.replace.ReplaceTransformer} 22 | * and 28 implementation class. 23 | */ 24 | @SuppressWarnings("WeakerAccess") 25 | public abstract class Transformer { 26 | public ClassPool classPool; 27 | public ClassFilterSpec classFilterSpec = new ClassFilterSpec(); 28 | protected boolean abortOnUndefinedClass = false; 29 | 30 | //Transformer name 31 | public String getName() { 32 | return "Transformer"; 33 | } 34 | 35 | //Category name that transformer belongs to 36 | public String getCategoryName() { 37 | return "Transformer"; 38 | } 39 | 40 | public String getPrettyName() { 41 | return "Transformer"; 42 | } 43 | 44 | protected abstract boolean onTransform( 45 | CtClass inputClass, 46 | String inputClassName) 47 | throws NotFoundException, CannotCompileException; 48 | 49 | public boolean performTransform( 50 | CtClass inputClass, 51 | String className) 52 | throws NotFoundException, CannotCompileException { 53 | 54 | inputClass.stopPruning(true); 55 | if (inputClass.isFrozen()) { 56 | inputClass.defrost(); 57 | } 58 | beforeTransform(); 59 | return onTransform(inputClass, className); 60 | } 61 | 62 | protected void beforeTransform() { 63 | } 64 | 65 | public boolean classAllowed(String className) { 66 | return classFilterSpec.classAllowed(className); 67 | } 68 | 69 | public Transformer setClassPool(ClassPool classPool) { 70 | this.classPool = classPool; 71 | return this; 72 | } 73 | 74 | public ClassPool getClassPool() { 75 | return classPool; 76 | } 77 | 78 | public void check() { 79 | } 80 | 81 | public boolean isAbortOnUndefinedClass() { 82 | return abortOnUndefinedClass; 83 | } 84 | 85 | public Transformer setAbortOnUndefinedClass(boolean abortOnUndefinedClass) { 86 | this.abortOnUndefinedClass = abortOnUndefinedClass; 87 | return this; 88 | } 89 | 90 | //Get class in the class pool 91 | protected CtClass tryGetClass(String className, String loc) { 92 | CtClass ctClass = classPool.getOrNull(className); 93 | if (ctClass == null) { 94 | String msg = "cannot find " + className + " in " + loc; 95 | if (abortOnUndefinedClass) { 96 | throw new DroidAssistNotFoundException(msg); 97 | } else { 98 | Logger.warning(msg); 99 | } 100 | } else { 101 | return ctClass; 102 | } 103 | return null; 104 | } 105 | 106 | protected Boolean isInterface(CtClass inputClass) { 107 | try { 108 | return inputClass.isInterface(); 109 | } catch (Exception ignore) { 110 | return null; 111 | } 112 | } 113 | 114 | //Get all interfaces of the specified class 115 | protected CtClass[] tryGetInterfaces(CtClass inputClass) { 116 | try { 117 | return inputClass.getInterfaces(); 118 | } catch (NotFoundException e) { 119 | String msg = "Cannot find interface " + e.getMessage() + " in " + inputClass.getName(); 120 | if (abortOnUndefinedClass) { 121 | throw new DroidAssistNotFoundException(msg); 122 | } else { 123 | Logger.warning(msg); 124 | } 125 | } 126 | return new CtClass[0]; 127 | } 128 | 129 | //Get all declared methods of the specified class 130 | protected CtMethod[] tryGetDeclaredMethods(CtClass inputClass) { 131 | CtMethod[] declaredMethods = new CtMethod[0]; 132 | try { 133 | declaredMethods = inputClass.getDeclaredMethods(); 134 | } catch (Exception e) { 135 | String msg = "Cannot get declared methods " + " in " + inputClass.getName(); 136 | if (abortOnUndefinedClass) { 137 | throw new DroidAssistNotFoundException(msg); 138 | } else { 139 | Logger.warning(msg); 140 | } 141 | } 142 | return declaredMethods; 143 | } 144 | 145 | //Get all declared constructors of the specified class 146 | protected CtConstructor[] tryGetDeclaredConstructors(CtClass inputClass) { 147 | CtConstructor[] declaredConstructors = new CtConstructor[0]; 148 | try { 149 | declaredConstructors = inputClass.getDeclaredConstructors(); 150 | } catch (Exception e) { 151 | String msg = "Cannot get declared constructors " + " in " + inputClass.getName(); 152 | if (abortOnUndefinedClass) { 153 | throw new DroidAssistNotFoundException(msg); 154 | } else { 155 | Logger.warning(msg); 156 | } 157 | } 158 | return declaredConstructors; 159 | } 160 | 161 | //Get initialization method of the specified class 162 | protected CtConstructor tryGetClassInitializer(CtClass inputClass) { 163 | CtConstructor initializer = null; 164 | try { 165 | initializer = inputClass.getClassInitializer(); 166 | } catch (Exception e) { 167 | String msg = "Cannot get class initializer " + " in " + inputClass.getName(); 168 | if (abortOnUndefinedClass) { 169 | throw new DroidAssistNotFoundException(msg); 170 | } else { 171 | Logger.warning(msg); 172 | } 173 | } 174 | return initializer; 175 | } 176 | 177 | 178 | @Override 179 | public String toString() { 180 | return getName() + "{" + 181 | "filterClass=" + classFilterSpec + 182 | '}'; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/around/AroundTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.around; 2 | 3 | import com.didichuxing.tools.droidassist.transform.ExprExecTransformer; 4 | 5 | /** 6 | * An abstract transform that adds code before and after the target pointcut simultaneously. 7 | * 8 | *

See {@link ConstructorCallAroundTransformer}, {@link ConstructorExecutionAroundTransformer}, 9 | * {@link FieldAccessAroundTransformer}, {@link InitializerExecutionAroundTransformer}, 10 | * {@link MethodCallAroundTransformer}, {@link MethodExecutionAroundTransformer} 11 | */ 12 | public abstract class AroundTransformer extends ExprExecTransformer { 13 | 14 | private String targetBefore; 15 | 16 | private String targetAfter; 17 | 18 | public String getTargetBefore() { 19 | return targetBefore; 20 | } 21 | 22 | public AroundTransformer setTargetBefore(String targetBefore) { 23 | this.targetBefore = targetBefore.endsWith(";") ? targetBefore : targetBefore + ";"; 24 | return this; 25 | } 26 | 27 | public String getTargetAfter() { 28 | return targetAfter; 29 | } 30 | 31 | public AroundTransformer setTargetAfter(String targetAfter) { 32 | this.targetAfter = targetAfter.endsWith(";") ? targetAfter : targetAfter + ";"; 33 | return this; 34 | } 35 | 36 | @Override 37 | public String getName() { 38 | return "AroundTransformer"; 39 | } 40 | 41 | @Override 42 | public String getCategoryName() { 43 | return "Around"; 44 | } 45 | 46 | @Override 47 | protected String getAnnotationTarget() { 48 | return targetBefore + targetAfter; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return getName() + "\n{" + 54 | "\n source='" + getSource() + '\'' + 55 | "\n targetBefore='" + targetBefore + '\'' + 56 | "\n targetAfter='" + targetAfter + '\'' + 57 | "\n filterClass=" + classFilterSpec + 58 | "\n}"; 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/around/ConstructorCallAroundTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.around; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.NewExpr; 9 | 10 | /** 11 | * Transform that adds code before and after the constructor method call simultaneously. 12 | */ 13 | public class ConstructorCallAroundTransformer extends AroundTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "ConstructorCallAroundTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXPR; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return NEW_EXPR; 28 | } 29 | 30 | @Override 31 | protected boolean filterClass(CtClass inputClass, String inputClassName) { 32 | return !isMatchSourceClassName(inputClassName); 33 | } 34 | 35 | @Override 36 | protected boolean execute( 37 | CtClass inputClass, 38 | String inputClassName, 39 | NewExpr expr) 40 | throws CannotCompileException, NotFoundException { 41 | 42 | String insnClassName = expr.getClassName(); 43 | String insnSignature = expr.getSignature(); 44 | 45 | if (!isMatchConstructorSource(insnClassName, insnSignature)) { 46 | return false; 47 | } 48 | 49 | String before = getTargetBefore(); 50 | String after = getTargetAfter(); 51 | // "$_=$proceed($$);" represents the original method body 52 | String statement = "{" + before + "$_=$proceed($$);" + after + "}"; 53 | String replacement = replaceInstrument(inputClassName, expr, statement); 54 | 55 | Logger.warning(getPrettyName() + " by: " + replacement 56 | + " at " + inputClassName + ".java" + ":" + expr.getLineNumber()); 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/around/ConstructorExecutionAroundTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.around; 2 | 3 | import com.didichuxing.tools.droidassist.util.ClassUtils; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | 6 | import javassist.CannotCompileException; 7 | import javassist.CtClass; 8 | import javassist.CtConstructor; 9 | import javassist.NotFoundException; 10 | 11 | /** 12 | * Transform that adds code before and after the constructor execute simultaneously. 13 | */ 14 | public class ConstructorExecutionAroundTransformer extends AroundTransformer { 15 | 16 | @Override 17 | public String getName() { 18 | return "ConstructorExecutionAroundTransformer"; 19 | } 20 | 21 | @Override 22 | protected String getTransformType() { 23 | return TRANSFORM_EXEC; 24 | } 25 | 26 | @Override 27 | protected String getExecuteType() { 28 | return CONSTRUCTOR; 29 | } 30 | 31 | @Override 32 | protected boolean execute( 33 | CtClass inputClass, 34 | String inputClassName, 35 | CtConstructor constructor) 36 | throws CannotCompileException, NotFoundException { 37 | 38 | if (!isMatchConstructorSource(inputClassName, constructor)) { 39 | return false; 40 | } 41 | String before = getTargetBefore(); 42 | String after = getTargetAfter(); 43 | 44 | ClassUtils.newConstructorDelegate( 45 | classPool, 46 | inputClass, 47 | constructor, 48 | (source, result) -> { 49 | String body = "{" + before + source + after + "}"; 50 | return getReplaceStatement(inputClassName, (CtConstructor) result.getSource(), body); 51 | } 52 | ); 53 | 54 | Logger.warning(getPrettyName() + " by: " + before + " $proceed($$) " 55 | + after + " at " + inputClassName + ".java" + ":" + constructor.getName()); 56 | return true; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/around/FieldAccessAroundTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.around; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.FieldAccess; 9 | 10 | /** 11 | * Transform that adds code before and after the field access simultaneously. 12 | */ 13 | public class FieldAccessAroundTransformer extends AroundTransformer { 14 | 15 | //When fieldWrite is true, representing variable is written. 16 | private boolean fieldWrite = false; 17 | 18 | @Override 19 | public String getName() { 20 | return "FieldAccessAroundTransformer"; 21 | } 22 | 23 | @Override 24 | protected String getTransformType() { 25 | return TRANSFORM_EXPR; 26 | } 27 | 28 | @Override 29 | protected String getExecuteType() { 30 | return FIELD_ACCESS; 31 | } 32 | 33 | @Override 34 | protected boolean filterClass(CtClass inputClass, String inputClassName) { 35 | return !isMatchSourceClassName(inputClassName); 36 | } 37 | 38 | @Override 39 | protected boolean execute( 40 | CtClass inputClass, 41 | String inputClassName, 42 | FieldAccess fieldAccess) 43 | throws CannotCompileException, NotFoundException { 44 | 45 | String insnClassName = fieldAccess.getClassName(); 46 | String insnSignature = fieldAccess.getSignature(); 47 | String insnFieldName = fieldAccess.getFieldName(); 48 | 49 | if (!isMatchFieldSource(insnClassName, insnSignature, insnFieldName) 50 | || !meetConditions(fieldAccess)) { 51 | return false; 52 | } 53 | 54 | String before = getTargetBefore(); 55 | String after = getTargetAfter(); 56 | 57 | String proceed = fieldAccess.isWriter() ? "$proceed($$);" : "$_=$proceed($$);"; 58 | String statement = "{" + before + proceed + after + "}"; 59 | String replacement = replaceInstrument(inputClassName, fieldAccess, statement); 60 | 61 | Logger.warning(getPrettyName() + " by: " + replacement 62 | + " at " + inputClassName + ".java" + ":" + fieldAccess.getLineNumber()); 63 | return true; 64 | } 65 | 66 | private boolean meetConditions(FieldAccess fieldAccess) { 67 | return fieldAccess.isWriter() == fieldWrite; 68 | } 69 | 70 | public FieldAccessAroundTransformer setFieldWrite(boolean write) { 71 | this.fieldWrite = write; 72 | return this; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/around/InitializerExecutionAroundTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.around; 2 | 3 | import com.didichuxing.tools.droidassist.util.ClassUtils; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | 6 | import javassist.CannotCompileException; 7 | import javassist.CtClass; 8 | import javassist.CtConstructor; 9 | import javassist.NotFoundException; 10 | 11 | /** 12 | * Transform that adds code before and after the initializer call simultaneously. 13 | */ 14 | public class InitializerExecutionAroundTransformer extends AroundTransformer { 15 | @Override 16 | public String getName() { 17 | return "InitializerExecutionAroundTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXEC; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return INITIALIZER; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | CtConstructor initializer) 35 | throws CannotCompileException, NotFoundException { 36 | 37 | String before = getTargetBefore(); 38 | String after = getTargetAfter(); 39 | 40 | ClassUtils.newInitializerDelegate( 41 | classPool, 42 | inputClass, 43 | initializer, 44 | (source, result) -> { 45 | String body = "{" + before + source + after + "}"; 46 | return getReplaceStatement(inputClassName, initializer, true, body); 47 | } 48 | ); 49 | Logger.warning(getPrettyName() + " by: " + before + " $proceed($$) " + after 50 | + " at " + inputClassName + ".java" + ":" + initializer.getName()); 51 | 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/around/MethodCallAroundTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.around; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.MethodCall; 9 | 10 | /** 11 | * Transform that adds code before and after the method call simultaneously. 12 | */ 13 | public class MethodCallAroundTransformer extends AroundTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "MethodCallAroundTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXPR; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return METHOD_CALL; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | MethodCall methodCall) 35 | throws CannotCompileException, NotFoundException { 36 | 37 | if (methodCall.isSuper()) { 38 | return false; 39 | } 40 | 41 | String insnClassName = methodCall.getClassName(); 42 | String insnName = methodCall.getMethodName(); 43 | String insnSignature = methodCall.getSignature(); 44 | 45 | CtClass insnClass = tryGetClass(insnClassName, inputClassName); 46 | if (insnClass == null) { 47 | return false; 48 | } 49 | 50 | if (!isMatchSourceMethod(insnClass, insnName, insnSignature, false)) { 51 | return false; 52 | } 53 | String before = getTargetBefore(); 54 | String after = getTargetAfter(); 55 | 56 | Logger.warning(getPrettyName() + " by: " + before + " $proceed($$) " + after 57 | + " at " + inputClassName + ".java" + ":" + methodCall.getLineNumber()); 58 | 59 | String proceed = isVoidSourceReturnType() ? "$proceed($$);" : "$_ =$proceed($$);"; 60 | String statement = "{" + before + proceed + after + "}"; 61 | 62 | replaceInstrument(inputClassName, methodCall, statement); 63 | 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/around/MethodExecutionAroundTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.around; 2 | 3 | import com.didichuxing.tools.droidassist.util.ClassUtils; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | 6 | import javassist.CannotCompileException; 7 | import javassist.CtClass; 8 | import javassist.CtMethod; 9 | import javassist.NotFoundException; 10 | 11 | /** 12 | * Transform that adds code before and after the method execute simultaneously. 13 | */ 14 | public class MethodExecutionAroundTransformer extends AroundTransformer { 15 | 16 | @Override 17 | public String getName() { 18 | return "MethodExecutionAroundTransformer"; 19 | } 20 | 21 | @Override 22 | protected String getTransformType() { 23 | return TRANSFORM_EXEC; 24 | } 25 | 26 | @Override 27 | protected String getExecuteType() { 28 | return METHOD; 29 | } 30 | 31 | @Override 32 | protected boolean execute( 33 | CtClass inputClass, 34 | String inputClassName, 35 | CtMethod method) 36 | throws CannotCompileException, NotFoundException { 37 | 38 | String name = method.getName(); 39 | String signature = method.getSignature(); 40 | 41 | if (!isMatchSourceMethod(inputClass, false, name, signature, method, true)) { 42 | return false; 43 | } 44 | String before = getTargetBefore(); 45 | String after = getTargetAfter(); 46 | 47 | ClassUtils.newMethodDelegate( 48 | inputClass, 49 | method, 50 | (source, result) -> { 51 | String body = "{" + before + source + after + "}"; 52 | return getReplaceStatement(inputClassName, (CtMethod) result.getSource(), body); 53 | }); 54 | 55 | Logger.warning(getPrettyName() + " by: " + before + " $proceed($$) " 56 | + after + " at " + inputClassName + ".java" + ":" + name); 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/ConstructorCallTimingTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.NewExpr; 9 | 10 | /** 11 | * Transform that adds constructor-call time-consuming statistics code. 12 | */ 13 | public class ConstructorCallTimingTransformer extends TimingTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "ConstructorCallTimingTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXPR; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return NEW_EXPR; 28 | } 29 | 30 | @Override 31 | protected boolean filterClass(CtClass inputClass, String inputClassName) { 32 | return !isMatchSourceClassName(inputClassName); 33 | } 34 | 35 | @Override 36 | protected boolean execute( 37 | CtClass inputClass, 38 | String inputClassName, 39 | NewExpr expr) 40 | throws CannotCompileException, NotFoundException { 41 | 42 | String insnClassName = expr.getClassName(); 43 | String insnSignature = expr.getSignature(); 44 | 45 | if (!isMatchConstructorSource(insnClassName, insnSignature)) { 46 | return false; 47 | } 48 | 49 | String statement = getDefaultTimingStatement(false, getTarget()); 50 | String replacement = replaceInstrument(inputClassName, expr, statement); 51 | 52 | Logger.warning(getPrettyName() + " by: " + replacement 53 | + " at " + inputClassName + ".java" + ":" + expr.getLineNumber()); 54 | return true; 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/ConstructorCallTryCatchTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.NewExpr; 9 | 10 | /** 11 | * Transform that wraps constructor-call with try-catch. 12 | */ 13 | public class ConstructorCallTryCatchTransformer extends TryCatchTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "ConstructorCallTryCatchTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXPR; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return NEW_EXPR; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | NewExpr expr) 35 | throws CannotCompileException, NotFoundException { 36 | 37 | if (isMatchSourceClassName(inputClassName)) { 38 | return false; 39 | } 40 | 41 | String insnClassName = expr.getClassName(); 42 | String insnSignature = expr.getSignature(); 43 | 44 | if (!isMatchConstructorSource(insnClassName, insnSignature)) { 45 | return false; 46 | } 47 | 48 | String proceed = "$_=$proceed($$);"; 49 | 50 | String statement = "try{" + proceed + "} catch (" + getException() + " e) {" 51 | + getTarget().replace("$e", "e") + "}"; 52 | 53 | String replacement = replaceInstrument(inputClassName, expr, statement); 54 | 55 | Logger.warning(getPrettyName() + " by: " + replacement 56 | + " at " + inputClassName + ".java" + ":" + expr.getLineNumber()); 57 | return true; 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/ConstructorExecutionTimingTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.ClassUtils; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | 6 | import javassist.CannotCompileException; 7 | import javassist.CtClass; 8 | import javassist.CtConstructor; 9 | import javassist.NotFoundException; 10 | 11 | /** 12 | * Transform that adds constructor-execute time-consuming statistics code. 13 | */ 14 | public class ConstructorExecutionTimingTransformer extends TimingTransformer { 15 | 16 | @Override 17 | public String getName() { 18 | return "ConstructorExecutionTimingTransformer"; 19 | } 20 | 21 | @Override 22 | protected String getTransformType() { 23 | return TRANSFORM_EXEC; 24 | } 25 | 26 | @Override 27 | protected String getExecuteType() { 28 | return CONSTRUCTOR; 29 | } 30 | 31 | @Override 32 | protected boolean execute( 33 | CtClass inputClass, 34 | String inputClassName, 35 | CtConstructor constructor) 36 | throws CannotCompileException, NotFoundException { 37 | 38 | String name = constructor.getName(); 39 | 40 | if (!isMatchConstructorSource(inputClassName, constructor)) { 41 | return false; 42 | } 43 | String target = getTarget(); 44 | 45 | ClassUtils.newConstructorDelegate( 46 | classPool, 47 | inputClass, 48 | constructor, 49 | (source, result) -> { 50 | String statement = "{" + getTimingStatement(source, target) + "}"; 51 | statement = getReplaceStatement(inputClassName, (CtConstructor) result.getSource(), statement); 52 | return statement; 53 | }); 54 | 55 | Logger.warning(getPrettyName() + " by: " + target 56 | + " at " + inputClassName + ".java" + ":" + name); 57 | 58 | return true; 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/ConstructorExecutionTryCatchTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.ClassUtils; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | 6 | import javassist.CannotCompileException; 7 | import javassist.CtClass; 8 | import javassist.CtConstructor; 9 | import javassist.NotFoundException; 10 | 11 | /** 12 | * Transform that wraps constructor-execute with try-catch. 13 | */ 14 | public class ConstructorExecutionTryCatchTransformer extends TryCatchTransformer { 15 | 16 | @Override 17 | public String getName() { 18 | return "ConstructorExecutionTryCatchTransformer"; 19 | } 20 | 21 | @Override 22 | protected String getTransformType() { 23 | return TRANSFORM_EXEC; 24 | } 25 | 26 | @Override 27 | protected String getExecuteType() { 28 | return CONSTRUCTOR; 29 | } 30 | 31 | @Override 32 | protected boolean execute( 33 | CtClass inputClass, 34 | String inputClassName, 35 | CtConstructor constructor) 36 | throws CannotCompileException, NotFoundException { 37 | 38 | if (!isMatchConstructorSource(inputClassName, constructor)) { 39 | return false; 40 | } 41 | String target = getTarget(); 42 | 43 | ClassUtils.newConstructorDelegate( 44 | classPool, 45 | inputClass, 46 | constructor, 47 | (source, result) -> { 48 | String targetStatement = target; 49 | targetStatement = targetStatement.replace("\\s+", " "); 50 | String statement = "try{" + source + "} catch (" + getException() + " e) {" 51 | + targetStatement.replace("$e", "e") + "}"; 52 | 53 | return getReplaceStatement(inputClassName, (CtConstructor) result.getSource(), statement); 54 | }); 55 | 56 | Logger.warning(getPrettyName() + " by: " + target 57 | + " at " + inputClassName + ".java" + ":" + constructor.getName()); 58 | return true; 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/InitializerExecutionTimingTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.ClassUtils; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | 6 | import javassist.CannotCompileException; 7 | import javassist.CtClass; 8 | import javassist.CtConstructor; 9 | import javassist.NotFoundException; 10 | 11 | /** 12 | * Transform that adds initializer-execute time-consuming statistics code. 13 | */ 14 | public class InitializerExecutionTimingTransformer extends TimingTransformer { 15 | 16 | @Override 17 | public String getName() { 18 | return "InitializerExecutionTimingTransformer"; 19 | } 20 | 21 | @Override 22 | protected String getTransformType() { 23 | return TRANSFORM_EXEC; 24 | } 25 | 26 | @Override 27 | protected String getExecuteType() { 28 | return INITIALIZER; 29 | } 30 | 31 | @Override 32 | protected boolean execute( 33 | CtClass inputClass, 34 | String inputClassName, 35 | CtConstructor initializer) 36 | throws CannotCompileException, NotFoundException { 37 | 38 | String target = getTarget(); 39 | 40 | ClassUtils.newInitializerDelegate( 41 | classPool, 42 | inputClass, 43 | initializer, 44 | (source, result) -> { 45 | String statement = "{" + getTimingStatement(source, target) + "}"; 46 | statement = getReplaceStatement(inputClassName, initializer, true, statement); 47 | return statement; 48 | }); 49 | 50 | Logger.warning(getPrettyName() + " by: " + target 51 | + " at " + inputClassName + ".java" + ":" + initializer.getName()); 52 | 53 | return true; 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/InitializerExecutionTryCatchTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.ClassUtils; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | 6 | import javassist.CannotCompileException; 7 | import javassist.CtClass; 8 | import javassist.CtConstructor; 9 | import javassist.NotFoundException; 10 | 11 | /** 12 | * Transform that wraps initializer-execute with try-catch. 13 | */ 14 | public class InitializerExecutionTryCatchTransformer extends TryCatchTransformer { 15 | 16 | @Override 17 | public String getName() { 18 | return "InitializerExecutionTryCatchTransformer"; 19 | } 20 | 21 | @Override 22 | protected String getTransformType() { 23 | return TRANSFORM_EXEC; 24 | } 25 | 26 | @Override 27 | protected String getExecuteType() { 28 | return INITIALIZER; 29 | } 30 | 31 | @Override 32 | protected boolean execute( 33 | CtClass inputClass, 34 | String inputClassName, 35 | CtConstructor initializer) 36 | throws CannotCompileException, NotFoundException { 37 | String target = getTarget(); 38 | 39 | ClassUtils.newInitializerDelegate( 40 | classPool, 41 | inputClass, 42 | initializer, 43 | (source, result) -> { 44 | String statement = "try{" 45 | + source + 46 | "} catch (" + getException() + " e) {" 47 | + target.replace("$e", "e") 48 | + "}"; 49 | statement = getReplaceStatement(inputClassName, initializer, true, statement); 50 | return statement; 51 | } 52 | ); 53 | 54 | Logger.warning(getPrettyName() + " add catch for constructor exec by: " + target 55 | + " at " + inputClassName + ".java" + ":" + initializer.getName()); 56 | return true; 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/MethodCallTimingTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.MethodCall; 9 | 10 | /** 11 | * Transform that adds method-call time-consuming statistics code. 12 | */ 13 | public class MethodCallTimingTransformer extends TimingTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "MethodCallTimingTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXPR; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return METHOD_CALL; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | MethodCall methodCall) 35 | throws CannotCompileException, NotFoundException { 36 | 37 | if (methodCall.isSuper()) { 38 | return false; 39 | } 40 | 41 | String insnClassName = methodCall.getClassName(); 42 | String insnName = methodCall.getMethodName(); 43 | String insnSignature = methodCall.getSignature(); 44 | 45 | CtClass insnClass = tryGetClass(insnClassName, inputClassName); 46 | if (insnClass == null) { 47 | return false; 48 | } 49 | 50 | if (!isMatchSourceMethod(insnClass, insnName, insnSignature, false)) { 51 | return false; 52 | } 53 | 54 | String target = getTarget(); 55 | String statement = getDefaultTimingStatement(isVoidSourceReturnType(), target); 56 | String replacement = replaceInstrument(inputClassName, methodCall, statement); 57 | 58 | Logger.warning(getPrettyName() + " by: " + replacement 59 | + " at " + inputClassName + ".java" + ":" + methodCall.getLineNumber()); 60 | return true; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/MethodCallTryCatchTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.MethodCall; 9 | 10 | /** 11 | * Transform that wraps method-call with try-catch. 12 | */ 13 | public class MethodCallTryCatchTransformer extends TryCatchTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "MethodCallTryCatchTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXPR; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return METHOD_CALL; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | MethodCall methodCall) 35 | throws CannotCompileException, NotFoundException { 36 | 37 | if (methodCall.isSuper()) { 38 | return false; 39 | } 40 | 41 | String insnClassName = methodCall.getClassName(); 42 | String insnName = methodCall.getMethodName(); 43 | String insnSignature = methodCall.getSignature(); 44 | 45 | CtClass insnClass = tryGetClass(insnClassName, inputClassName); 46 | if (insnClass == null) { 47 | return false; 48 | } 49 | 50 | if (!isMatchSourceMethod(insnClass, insnName, insnSignature, false)) { 51 | return false; 52 | } 53 | String target = getTarget(); 54 | 55 | String proceed = isVoidSourceReturnType() ? "$proceed($$);" : "$_ =$proceed($$);"; 56 | 57 | String statement = "try{" + 58 | proceed + 59 | "} catch (" + getException() + " e) {" + 60 | target.replace("$e", "e") + 61 | "}"; 62 | 63 | String replacement = replaceInstrument(inputClassName, methodCall, statement); 64 | 65 | Logger.warning(getPrettyName() + " by: " + replacement 66 | + " at " + inputClassName + ".java" + ":" + methodCall.getLineNumber()); 67 | 68 | return true; 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/MethodExecutionTimingTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.ClassUtils; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | 6 | import javassist.CannotCompileException; 7 | import javassist.CtClass; 8 | import javassist.CtMethod; 9 | import javassist.NotFoundException; 10 | 11 | /** 12 | * Transform that adds method-execute time-consuming statistics code. 13 | */ 14 | public class MethodExecutionTimingTransformer extends TimingTransformer { 15 | 16 | @Override 17 | public String getName() { 18 | return "MethodExecutionTimingTransformer"; 19 | } 20 | 21 | @Override 22 | protected String getTransformType() { 23 | return TRANSFORM_EXEC; 24 | } 25 | 26 | @Override 27 | protected String getExecuteType() { 28 | return METHOD; 29 | } 30 | 31 | @Override 32 | protected boolean execute( 33 | CtClass inputClass, 34 | String inputClassName, 35 | CtMethod method) 36 | throws CannotCompileException, NotFoundException { 37 | 38 | String name = method.getName(); 39 | String signature = method.getSignature(); 40 | 41 | if (!isMatchSourceMethod(inputClass, false, name, signature, method, true)) { 42 | return false; 43 | } 44 | String target = getTarget(); 45 | 46 | ClassUtils.newMethodDelegate( 47 | inputClass, 48 | method, 49 | (source, result) -> { 50 | String body = "{" + getTimingStatement(source, target) + "}"; 51 | return getReplaceStatement(inputClassName, (CtMethod) result.getSource(), body); 52 | }); 53 | 54 | Logger.warning(getPrettyName() + " by: " + target 55 | + " at " + inputClassName + ".java" + ":" + name); 56 | 57 | return true; 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/MethodExecutionTryCatchTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.util.ClassUtils; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | 6 | import javassist.CannotCompileException; 7 | import javassist.CtClass; 8 | import javassist.CtMethod; 9 | import javassist.NotFoundException; 10 | 11 | /** 12 | * Transform that wraps method-execute with try-catch. 13 | */ 14 | public class MethodExecutionTryCatchTransformer extends TryCatchTransformer { 15 | 16 | @Override 17 | public String getName() { 18 | return "MethodExecutionTryCatchTransformer"; 19 | } 20 | 21 | @Override 22 | protected String getTransformType() { 23 | return TRANSFORM_EXEC; 24 | } 25 | 26 | @Override 27 | protected String getExecuteType() { 28 | return METHOD; 29 | } 30 | 31 | @Override 32 | protected boolean execute( 33 | CtClass inputClass, 34 | String inputClassName, 35 | CtMethod method) 36 | throws CannotCompileException, NotFoundException { 37 | 38 | String name = method.getName(); 39 | String signature = method.getSignature(); 40 | 41 | if (!isMatchSourceMethod(inputClass, false, name, signature, method, true)) { 42 | return false; 43 | } 44 | String target = getTarget(); 45 | 46 | addCatchForMethod(inputClass, method, getExceptionClass(), target); 47 | 48 | Logger.warning(getPrettyName() + "by: " + target 49 | + " at " + inputClassName + ".java" + ":" + name); 50 | 51 | return true; 52 | } 53 | 54 | 55 | private void addCatchForMethod( 56 | CtClass clazz, 57 | CtMethod method, 58 | CtClass exceptionType, 59 | String target) 60 | throws NotFoundException, CannotCompileException { 61 | 62 | ClassUtils.DelegateResult result = 63 | ClassUtils.newMethodDelegate(clazz, method, null); 64 | 65 | CtMethod srcMethod = result.getSource(); 66 | String type = method.getReturnType().getName(); 67 | boolean isVoid = "void".equals(type); 68 | 69 | StringBuilder builder = new StringBuilder(target); 70 | target = target.replace("\\s+", " "); 71 | boolean returnStatement = 72 | target.contains(" return ") 73 | || target.contains("return ") 74 | || target.contains(" return;") 75 | || target.contains(";return;") 76 | || target.contains(";return ") 77 | || target.contains(" throw ") 78 | || target.contains("throw ") 79 | || target.contains(";throw "); 80 | if (isVoid) { 81 | if (!returnStatement) { 82 | builder.append("return;"); 83 | } 84 | } else { 85 | if (!returnStatement) { 86 | throw new CannotCompileException( 87 | "Catch block code fragment must end with a throw or return statement, " + 88 | "but found: " + target); 89 | } 90 | } 91 | target = getReplaceStatement(clazz.getName(), method, builder.toString()); 92 | srcMethod.addCatch(target, exceptionType); 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/ReparentClassTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.transform.ExprExecTransformer; 4 | import com.didichuxing.tools.droidassist.util.Logger; 5 | import com.google.common.collect.Sets; 6 | 7 | import java.util.Set; 8 | 9 | import javassist.CannotCompileException; 10 | import javassist.CtClass; 11 | import javassist.NotFoundException; 12 | import javassist.bytecode.Descriptor; 13 | import javassist.expr.ConstructorCall; 14 | import javassist.expr.FieldAccess; 15 | import javassist.expr.MethodCall; 16 | 17 | /** 18 | * An transform that support for class parent reset 19 | * 20 | *

See {@link ConstructorCallTryCatchTransformer}, {@link ConstructorExecutionTryCatchTransformer}, 21 | * {@link InitializerExecutionTryCatchTransformer}, {@link MethodCallTryCatchTransformer}, 22 | * {@link MethodCallTryCatchTransformer} 23 | */ 24 | public class ReparentClassTransformer extends ExprExecTransformer { 25 | 26 | private CtClass targetClass; 27 | 28 | @Override 29 | public String getName() { 30 | return "ReparentClassTransformer"; 31 | } 32 | 33 | public String getCategoryName() { 34 | return "Reparent"; 35 | } 36 | 37 | @Override 38 | protected String getTransformType() { 39 | return TRANSFORM_EXPR; 40 | } 41 | 42 | @Override 43 | protected String getExecuteType() { 44 | return METHOD_CALL; 45 | } 46 | 47 | @Override 48 | protected Set getExtraExecuteTypes() { 49 | return Sets.newHashSet(CONSTRUCTOR_CALL, FIELD_ACCESS); 50 | } 51 | 52 | @Override 53 | protected boolean filterClass(CtClass inputClass, String inputClassName) 54 | throws NotFoundException { 55 | return inputClass.getSuperclass().getName().equals(getSourceDeclaringClassName()) 56 | && !inputClassName.equals(getTargetClass().getName()); 57 | } 58 | 59 | private CtClass getTargetClass() throws NotFoundException { 60 | if (targetClass == null) { 61 | targetClass = classPool.get(getTarget()); 62 | } 63 | return targetClass; 64 | } 65 | 66 | @Override 67 | protected boolean execute(CtClass inputClass, String inputClassName) 68 | throws CannotCompileException, NotFoundException { 69 | 70 | Logger.warning(getCategoryName() + " reset parent for class " + inputClassName + " from " 71 | + inputClass.getSuperclass().getName() + " to " + getTarget()); 72 | 73 | inputClass.setSuperclass(getTargetClass()); 74 | return true; 75 | } 76 | 77 | @Override 78 | protected boolean execute(CtClass inputClass, String inputClassName, MethodCall methodCall) 79 | throws CannotCompileException, NotFoundException { 80 | if (methodCall.isSuper()) { 81 | String signature = methodCall.getSignature(); 82 | boolean voidType = 83 | Descriptor.getReturnType(signature, classPool) == CtClass.voidType; 84 | String methodName = methodCall.getMethodName(); 85 | 86 | Logger.warning(getCategoryName() + " reset parent for class " + inputClassName 87 | + " adapt super method call" + 88 | " at " + inputClassName + ".java" + ":" + methodCall.getLineNumber()); 89 | 90 | methodCall.replace((!voidType ? "$_=" : "") + "super." + methodName + "($$);"); 91 | return true; 92 | } 93 | return false; 94 | } 95 | 96 | @Override 97 | protected boolean execute(CtClass inputClass, String inputClassName, ConstructorCall constructorCall) 98 | throws CannotCompileException, NotFoundException { 99 | if (constructorCall.isSuper()) { 100 | 101 | Logger.warning(getCategoryName() + " reset parent for class " + inputClassName 102 | + " adapt super constructor call" + 103 | " at " + inputClassName + ".java" + ":" + constructorCall.getLineNumber()); 104 | 105 | 106 | constructorCall.replace("super($$);"); 107 | return true; 108 | } 109 | return super.execute(inputClass, inputClassName, constructorCall); 110 | } 111 | 112 | @Override 113 | protected boolean execute(CtClass inputClass, String inputClassName, FieldAccess fieldAccess) 114 | throws CannotCompileException, NotFoundException { 115 | if (!fieldAccess.isStatic() 116 | && fieldAccess.getClassName().equals(getSourceDeclaringClassName())) { 117 | 118 | Logger.warning(getCategoryName() + " reset parent for class " + inputClassName 119 | + " adapt super field access" + 120 | " at " + inputClassName + ".java" + ":" + fieldAccess.getLineNumber()); 121 | 122 | String fieldName = fieldAccess.getFieldName(); 123 | if (fieldAccess.isReader()) { 124 | fieldAccess.replace("$_=super." + fieldName + ";"); 125 | } else if (fieldAccess.isWriter()) { 126 | fieldAccess.replace("super." + fieldName + "=$1;"); 127 | } 128 | return true; 129 | } 130 | return false; 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/TimingTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.transform.ExprExecTransformer; 4 | 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * An abstract transform that adds time-consuming statistics code. 10 | * 11 | *

See {@link ConstructorCallTimingTransformer}, {@link ConstructorExecutionTimingTransformer}, 12 | * {@link InitializerExecutionTimingTransformer}, {@link MethodCallTimingTransformer}, 13 | * {@link MethodExecutionTimingTransformer} 14 | */ 15 | public abstract class TimingTransformer extends ExprExecTransformer { 16 | @Override 17 | public String getCategoryName() { 18 | return "Timing"; 19 | } 20 | 21 | String getDefaultTimingStatement(boolean isVoidReturnType, String target) { 22 | String proceed = isVoidReturnType ? "$proceed($$);" : "$_ =$proceed($$);"; 23 | return getTimingStatement(proceed, target); 24 | } 25 | 26 | String getTimingStatement(String proceed, String target) { 27 | return "long start = java.lang.System.nanoTime();" + 28 | proceed + 29 | "long nanoTime = java.lang.System.nanoTime()-start;" + 30 | target.replaceAll(Pattern.quote("$time"), 31 | Matcher.quoteReplacement("(nanoTime/1000000)"). 32 | replaceAll(Pattern.quote("$nanotime"), 33 | Matcher.quoteReplacement("(nanoTime)"))); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/enhance/TryCatchTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.enhance; 2 | 3 | import com.didichuxing.tools.droidassist.spec.SourceSpec; 4 | import com.didichuxing.tools.droidassist.transform.ExprExecTransformer; 5 | 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | 9 | /** 10 | * An abstract transform that wraps target code with try-catch. 11 | * 12 | *

See {@link ConstructorCallTryCatchTransformer}, {@link ConstructorExecutionTryCatchTransformer}, 13 | * {@link InitializerExecutionTryCatchTransformer}, {@link MethodCallTryCatchTransformer}, 14 | * {@link MethodCallTryCatchTransformer} 15 | */ 16 | public abstract class TryCatchTransformer extends ExprExecTransformer { 17 | private String exception; 18 | private CtClass exceptionClass; 19 | 20 | @Override 21 | public String getCategoryName() { 22 | return "TryCatch"; 23 | } 24 | 25 | protected String getException() { 26 | if (exception == null || exception.trim().equals("")) { 27 | exception = " java.lang.Exception"; 28 | } 29 | return SourceSpec.Type.forName(exception).getName(); 30 | } 31 | 32 | protected CtClass getExceptionClass() throws NotFoundException { 33 | if (exceptionClass == null) { 34 | exceptionClass = classPool.get(getException()); 35 | } 36 | return exceptionClass; 37 | } 38 | 39 | public TryCatchTransformer setException(String exception) { 40 | this.exception = exception; 41 | return this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/insert/ConstructorCallInsertTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.insert; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.NewExpr; 9 | 10 | /** 11 | * Transform that inserts custom code at the pointcut where constructor is called. 12 | */ 13 | public class ConstructorCallInsertTransformer extends InsertTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "ConstructorCallInsertTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXPR; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return NEW_EXPR; 28 | } 29 | 30 | @Override 31 | protected boolean filterClass(CtClass inputClass, String inputClassName) { 32 | return !isMatchSourceClassName(inputClassName); 33 | } 34 | 35 | @Override 36 | protected boolean execute( 37 | CtClass inputClass, 38 | String inputClassName, 39 | NewExpr newExpr) 40 | throws CannotCompileException, NotFoundException { 41 | 42 | String insnClassName = newExpr.getClassName(); 43 | String insnSignature = newExpr.getSignature(); 44 | 45 | if (!isMatchConstructorSource(insnClassName, insnSignature)) { 46 | return false; 47 | } 48 | String target = getTarget(); 49 | 50 | if (!target.endsWith(";")) { 51 | target = target + ";"; 52 | } 53 | String before = isAsBefore() ? target : ""; 54 | String after = isAsAfter() ? target : ""; 55 | String statement = "{" + before + "$_=$proceed($$);" + after + "}"; 56 | String replacement = replaceInstrument(inputClassName, newExpr, statement); 57 | 58 | if (isAsBefore()) { 59 | Logger.warning(getPrettyName() + " by before: " + replacement 60 | + " at " + inputClassName + ".java" + ":" + newExpr.getLineNumber()); 61 | } 62 | 63 | if (isAsAfter()) { 64 | Logger.warning(getPrettyName() + " by after: " + replacement 65 | + " at " + inputClassName + ".java" + ":" + newExpr.getLineNumber()); 66 | } 67 | return true; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/insert/ConstructorExecutionInsertTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.insert; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.CtConstructor; 8 | import javassist.NotFoundException; 9 | 10 | /** 11 | * Transform that inserts custom code at the pointcut where constructor executes. 12 | */ 13 | public class ConstructorExecutionInsertTransformer extends InsertTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "ConstructorExecutionInsertTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXEC; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return CONSTRUCTOR; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | CtConstructor constructor) 35 | throws CannotCompileException, NotFoundException { 36 | 37 | if (!isMatchConstructorSource(inputClassName, constructor)) { 38 | return false; 39 | } 40 | String target = getTarget(); 41 | String name = constructor.getName(); 42 | 43 | target = getReplaceStatement(inputClassName, constructor, target); 44 | 45 | if (isAsBefore()) { 46 | constructor.insertBeforeBody(target); 47 | Logger.warning(getPrettyName() + " by before: " + target 48 | + " at " + inputClassName + ".java" + ":" + name); 49 | } 50 | if (isAsAfter()) { 51 | constructor.insertAfter(target); 52 | Logger.warning(getPrettyName() + " by after: " + target 53 | + " at " + inputClassName + ".java" + ":" + name); 54 | } 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/insert/FieldAccessInsertTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.insert; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.FieldAccess; 9 | 10 | /** 11 | * Transform that inserts custom code at the pointcut where field is read or written. 12 | */ 13 | public class FieldAccessInsertTransformer extends InsertTransformer { 14 | 15 | private boolean fieldWrite = false; 16 | 17 | @Override 18 | public String getName() { 19 | return "FieldAccessInsertTransformer"; 20 | } 21 | 22 | @Override 23 | protected String getTransformType() { 24 | return TRANSFORM_EXPR; 25 | } 26 | 27 | @Override 28 | protected String getExecuteType() { 29 | return FIELD_ACCESS; 30 | } 31 | 32 | @Override 33 | protected boolean filterClass(CtClass inputClass, String inputClassName) { 34 | return !isMatchSourceClassName(inputClassName); 35 | } 36 | 37 | @Override 38 | protected boolean execute( 39 | CtClass inputClass, 40 | String inputClassName, 41 | FieldAccess fieldAccess) 42 | throws CannotCompileException, NotFoundException { 43 | 44 | String insnClassName = fieldAccess.getClassName(); 45 | String insnSignature = fieldAccess.getSignature(); 46 | String insnFieldName = fieldAccess.getFieldName(); 47 | 48 | if (!isMatchFieldSource(insnClassName, insnSignature, insnFieldName) 49 | || !meetConditions(fieldAccess)) { 50 | return false; 51 | } 52 | 53 | String target = getTarget(); 54 | String proceed = fieldAccess.isWriter() ? "$proceed($$);" : "$_=$proceed($$);"; 55 | String statement = "" 56 | + "{" 57 | + (isAsBefore() ? target : "") 58 | + proceed 59 | + (isAsAfter() ? target : "") 60 | + "}"; 61 | 62 | String replacement = replaceInstrument(inputClassName, fieldAccess, statement); 63 | 64 | Logger.warning(getPrettyName() + " by: " + replacement 65 | + " at " + inputClassName + ".java" + ":" + fieldAccess.getLineNumber()); 66 | return true; 67 | } 68 | 69 | private boolean meetConditions(FieldAccess fieldAccess) { 70 | return fieldAccess.isWriter() == fieldWrite; 71 | } 72 | 73 | public FieldAccessInsertTransformer setFieldWrite(boolean write) { 74 | this.fieldWrite = write; 75 | return this; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/insert/InitializerExecutionInsertTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.insert; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.CtConstructor; 8 | import javassist.NotFoundException; 9 | 10 | /** 11 | * Transform that inserts custom code at the pointcut where initializer executes. 12 | */ 13 | public class InitializerExecutionInsertTransformer extends InsertTransformer { 14 | @Override 15 | public String getName() { 16 | return "InitializerExecutionInsertTransformer"; 17 | } 18 | 19 | @Override 20 | protected String getTransformType() { 21 | return TRANSFORM_EXEC; 22 | } 23 | 24 | @Override 25 | protected String getExecuteType() { 26 | return INITIALIZER; 27 | } 28 | 29 | @Override 30 | protected boolean execute( 31 | CtClass inputClass, 32 | String inputClassName, 33 | CtConstructor initializer) 34 | throws CannotCompileException, NotFoundException { 35 | 36 | String target = getTarget(); 37 | 38 | target = getReplaceStatement(inputClassName, initializer, true, target); 39 | if (isAsBefore()) { 40 | initializer.insertBefore(target); 41 | Logger.warning(getPrettyName() + " insert after execution by: " + target 42 | + " at " + inputClassName + ".java" + ":" + initializer.getName()); 43 | } 44 | if (isAsAfter()) { 45 | initializer.insertAfter(target); 46 | Logger.warning(getPrettyName() + " insert after execution by: " + target 47 | + " at " + inputClassName + ".java" + ":" + initializer.getName()); 48 | } 49 | return true; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/insert/InsertTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.insert; 2 | 3 | import com.didichuxing.tools.droidassist.transform.ExprExecTransformer; 4 | 5 | /** 6 | * An abstract transform that inserts code at the pointcut. 7 | * 8 | *

See {@link ConstructorCallInsertTransformer}, {@link ConstructorExecutionInsertTransformer}, 9 | * {@link FieldAccessInsertTransformer}, {@link InitializerExecutionInsertTransformer}, 10 | * {@link MethodCallInsertTransformer}, {@link MethodExecutionInsertTransformer} 11 | */ 12 | @SuppressWarnings("WeakerAccess") 13 | public abstract class InsertTransformer extends ExprExecTransformer { 14 | 15 | private boolean asBefore = false; 16 | private boolean asAfter = false; 17 | 18 | @Override 19 | public String getCategoryName() { 20 | return "Insert"; 21 | } 22 | 23 | @Override 24 | public String getName() { 25 | return "InsertTransformer"; 26 | } 27 | 28 | public boolean isAsBefore() { 29 | return asBefore; 30 | } 31 | 32 | public InsertTransformer setAsBefore(boolean asBefore) { 33 | this.asBefore = asBefore; 34 | return this; 35 | } 36 | 37 | public boolean isAsAfter() { 38 | return asAfter; 39 | } 40 | 41 | public InsertTransformer setAsAfter(boolean asAfter) { 42 | this.asAfter = asAfter; 43 | return this; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/insert/MethodCallInsertTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.insert; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.MethodCall; 9 | 10 | /** 11 | * Transform that inserts custom code at the pointcut where method is called. 12 | */ 13 | public class MethodCallInsertTransformer extends InsertTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "MethodCallInsertTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXPR; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return METHOD_CALL; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | MethodCall methodCall) 35 | throws CannotCompileException, NotFoundException { 36 | 37 | if (methodCall.isSuper()) { 38 | return false; 39 | } 40 | String insnClassName = methodCall.getClassName(); 41 | String insnName = methodCall.getMethodName(); 42 | String insnSignature = methodCall.getSignature(); 43 | 44 | CtClass insnClass = tryGetClass(insnClassName, inputClassName); 45 | if (insnClass == null) { 46 | return false; 47 | } 48 | if (!isMatchSourceMethod(insnClass, insnName, insnSignature, false)) { 49 | return false; 50 | } 51 | 52 | String target = getTarget(); 53 | int line = methodCall.getLineNumber(); 54 | if (!target.endsWith(";")) target = target + ";"; 55 | 56 | String before = isAsBefore() ? target : ""; 57 | String after = isAsAfter() ? target : ""; 58 | 59 | String proceed = isVoidSourceReturnType() ? "$proceed($$);" : "$_ =$proceed($$);"; 60 | 61 | String statement = before + proceed + after; 62 | 63 | String replacement = replaceInstrument(inputClassName, methodCall, statement); 64 | 65 | if (isAsBefore()) { 66 | Logger.warning(getPrettyName() + " insert before call by: " + replacement 67 | + " at " + inputClassName + ".java" + ":" + line); 68 | } 69 | 70 | if (isAsAfter()) { 71 | Logger.warning(getPrettyName() + " insert after call by: " + replacement 72 | + " at " + inputClassName + ".java" + ":" + line); 73 | } 74 | return true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/insert/MethodExecutionInsertTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.insert; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.CtMethod; 8 | import javassist.NotFoundException; 9 | 10 | /** 11 | * Transform that inserts custom code at the pointcut where method executes. 12 | */ 13 | public class MethodExecutionInsertTransformer extends InsertTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "MethodExecutionInsertTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXEC; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return METHOD; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | CtMethod method) 35 | throws CannotCompileException, NotFoundException { 36 | 37 | String name = method.getName(); 38 | String signature = method.getSignature(); 39 | 40 | if (!isMatchSourceMethod(inputClass, false, name, signature, method, true)) { 41 | return false; 42 | } 43 | 44 | String target = getTarget(); 45 | target = getReplaceStatement(inputClassName, method, target); 46 | if (isAsBefore()) { 47 | method.insertBefore(target); 48 | Logger.warning(getPrettyName() + " by before: " + target 49 | + " at " + inputClassName + ".java" + ":" + name); 50 | } 51 | if (isAsAfter()) { 52 | method.insertAfter(target); 53 | Logger.warning(getPrettyName() + " by after: " + target 54 | + " at " + inputClassName + ".java" + ":" + name); 55 | } 56 | return true; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/replace/ConstructorCallReplaceTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.replace; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.NewExpr; 9 | 10 | /** 11 | * Transform that replaces constructor-call with new code. 12 | */ 13 | public class ConstructorCallReplaceTransformer extends ReplaceTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "ConstructorCallReplaceTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXPR; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return NEW_EXPR; 28 | } 29 | 30 | @Override 31 | protected boolean filterClass(CtClass inputClass, String inputClassName) { 32 | return !isMatchSourceClassName(inputClassName); 33 | } 34 | 35 | @Override 36 | protected boolean execute( 37 | CtClass inputClass, 38 | String inputClassName, 39 | NewExpr newExpr) 40 | throws CannotCompileException, NotFoundException { 41 | 42 | String insnClassName = newExpr.getClassName(); 43 | String insnSignature = newExpr.getSignature(); 44 | 45 | if (!isMatchConstructorSource(insnClassName, insnSignature)) { 46 | return false; 47 | } 48 | 49 | String target = getTarget(); 50 | 51 | if (!target.startsWith("$_=") || !target.startsWith("$_ =")) { 52 | if (target.startsWith("{")) { 53 | target = "{" + "$_=" + target.substring(1); 54 | } else { 55 | target = "$_=" + target; 56 | } 57 | } 58 | 59 | String replacement = replaceInstrument(inputClassName, newExpr, target); 60 | 61 | Logger.warning(getPrettyName() + "by: " + replacement 62 | + " at " + inputClassName + ".java" + ":" + newExpr.getLineNumber()); 63 | return true; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/replace/ConstructorExecutionReplaceTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.replace; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.CtConstructor; 8 | import javassist.NotFoundException; 9 | 10 | /** 11 | * Transform that replaces constructor-execute with new code. 12 | */ 13 | public class ConstructorExecutionReplaceTransformer extends ReplaceTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "ConstructorExecutionReplaceTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXEC; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return CONSTRUCTOR; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | CtConstructor constructor) 35 | throws CannotCompileException, NotFoundException { 36 | 37 | if (!isMatchConstructorSource(inputClassName, constructor)) { 38 | return false; 39 | } 40 | String target = getTarget(); 41 | replaceInstrument(inputClassName, constructor, target); 42 | Logger.warning(getPrettyName() + " replaced execution by: " + target 43 | + " at " + inputClassName + ".java" + ":" + constructor.getName()); 44 | return true; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/replace/FieldAccessReplaceTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.replace; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.NotFoundException; 8 | import javassist.expr.FieldAccess; 9 | 10 | /** 11 | * Transform that replaces field-access with new code. 12 | */ 13 | public class FieldAccessReplaceTransformer extends ReplaceTransformer { 14 | 15 | private boolean fieldWrite = false; 16 | 17 | @Override 18 | public String getName() { 19 | return "FieldAccessReplaceTransformer"; 20 | } 21 | 22 | @Override 23 | protected String getTransformType() { 24 | return TRANSFORM_EXPR; 25 | } 26 | 27 | @Override 28 | protected String getExecuteType() { 29 | return FIELD_ACCESS; 30 | } 31 | 32 | @Override 33 | protected boolean filterClass(CtClass inputClass, String inputClassName) { 34 | return !isMatchSourceClassName(inputClassName); 35 | } 36 | 37 | @Override 38 | protected boolean execute( 39 | CtClass inputClass, 40 | String inputClassName, 41 | FieldAccess fieldAccess) 42 | throws CannotCompileException, NotFoundException { 43 | 44 | String insnClassName = fieldAccess.getClassName(); 45 | String insnSignature = fieldAccess.getSignature(); 46 | String insnFieldName = fieldAccess.getFieldName(); 47 | 48 | if (!isMatchFieldSource(insnClassName, insnSignature, insnFieldName) 49 | || !meetConditions(fieldAccess)) { 50 | return false; 51 | } 52 | 53 | String target = getTarget(); 54 | String replacement = replaceInstrument(inputClassName, fieldAccess, target); 55 | 56 | Logger.warning(getPrettyName() + " by: " + 57 | (fieldAccess.isWriter() ? " write" : " read") + replacement + " at " + 58 | inputClassName + ".java" + ":" + fieldAccess.getLineNumber()); 59 | return true; 60 | } 61 | 62 | private boolean meetConditions(FieldAccess fieldAccess) { 63 | return fieldAccess.isWriter() == fieldWrite; 64 | } 65 | 66 | public FieldAccessReplaceTransformer setFieldWrite(boolean write) { 67 | this.fieldWrite = write; 68 | return this; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/replace/InitializerExecutionReplaceTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.replace; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.CtConstructor; 8 | import javassist.NotFoundException; 9 | 10 | /** 11 | * Transform that replaces initializer-execute with new code. 12 | */ 13 | public class InitializerExecutionReplaceTransformer extends ReplaceTransformer { 14 | @Override 15 | public String getName() { 16 | return "InitializerExecutionReplaceTransformer"; 17 | } 18 | 19 | @Override 20 | protected String getTransformType() { 21 | return TRANSFORM_EXEC; 22 | } 23 | 24 | @Override 25 | protected String getExecuteType() { 26 | return INITIALIZER; 27 | } 28 | 29 | @Override 30 | protected boolean execute( 31 | CtClass inputClass, 32 | String inputClassName, 33 | CtConstructor constructor) 34 | throws CannotCompileException, NotFoundException { 35 | 36 | String target = getTarget(); 37 | target = getReplaceStatement(inputClassName, constructor, true, target); 38 | constructor.setBody(target); 39 | Logger.warning(getPrettyName() + " replaced execution by: " + target 40 | + " at " + inputClassName + ".java" + ":" + constructor.getName()); 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/replace/MethodCallReplaceTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.replace; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | import javassist.CannotCompileException; 9 | import javassist.CtClass; 10 | import javassist.NotFoundException; 11 | import javassist.expr.MethodCall; 12 | 13 | /** 14 | * Transform that replaces method-call with new code. 15 | */ 16 | public class MethodCallReplaceTransformer extends ReplaceTransformer { 17 | 18 | @Override 19 | public String getName() { 20 | return "MethodCallReplaceTransformer"; 21 | } 22 | 23 | @Override 24 | protected String getTransformType() { 25 | return TRANSFORM_EXPR; 26 | } 27 | 28 | @Override 29 | protected String getExecuteType() { 30 | return METHOD_CALL; 31 | } 32 | 33 | @Override 34 | protected boolean execute( 35 | CtClass inputClass, 36 | String inputClassName, 37 | MethodCall methodCall) 38 | throws CannotCompileException, NotFoundException { 39 | if (methodCall.isSuper()) { 40 | return false; 41 | } 42 | 43 | String insnClassName = methodCall.getClassName(); 44 | String insnName = methodCall.getMethodName(); 45 | String insnSignature = methodCall.getSignature(); 46 | 47 | CtClass insnClass = tryGetClass(insnClassName, inputClassName); 48 | if (insnClass == null) { 49 | return false; 50 | } 51 | 52 | if (!isMatchSourceMethod(insnClass, insnName, insnSignature, false)) { 53 | return false; 54 | } 55 | 56 | String target = getTarget(); 57 | String replacement = replaceInstrument(inputClassName, methodCall, target); 58 | Logger.warning(getPrettyName() + " by: " + replacement 59 | + " at " + inputClassName + ".java" + ":" + methodCall.getLineNumber()); 60 | return true; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/replace/MethodExecutionReplaceTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.replace; 2 | 3 | import com.didichuxing.tools.droidassist.util.Logger; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.CtClass; 7 | import javassist.CtMethod; 8 | import javassist.NotFoundException; 9 | 10 | /** 11 | * Transform that replaces method-execute with new code. 12 | */ 13 | public class MethodExecutionReplaceTransformer extends ReplaceTransformer { 14 | 15 | @Override 16 | public String getName() { 17 | return "MethodExecutionReplaceTransformer"; 18 | } 19 | 20 | @Override 21 | protected String getTransformType() { 22 | return TRANSFORM_EXEC; 23 | } 24 | 25 | @Override 26 | protected String getExecuteType() { 27 | return METHOD; 28 | } 29 | 30 | @Override 31 | protected boolean execute( 32 | CtClass inputClass, 33 | String inputClassName, 34 | CtMethod method) 35 | throws CannotCompileException, NotFoundException { 36 | String name = method.getName(); 37 | String signature = method.getSignature(); 38 | 39 | if (!isMatchSourceMethod(inputClass, false, name, signature, method, true)) { 40 | return false; 41 | } 42 | String target = getTarget(); 43 | target = getReplaceStatement(inputClassName, method, target); 44 | method.setBody(target); 45 | Logger.warning(getPrettyName() + " by: " + target 46 | + " at " + inputClassName + ".java" + ":" + name); 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/transform/replace/ReplaceTransformer.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.transform.replace; 2 | 3 | import com.didichuxing.tools.droidassist.transform.ExprExecTransformer; 4 | 5 | /** 6 | * An abstract transform that replaces the specified code with new code. 7 | * 8 | *

See{@link ConstructorCallReplaceTransformer}, {@link ConstructorExecutionReplaceTransformer}, 9 | * {@link FieldAccessReplaceTransformer}, {@link InitializerExecutionReplaceTransformer}, 10 | * {@link MethodCallReplaceTransformer}, {@link MethodExecutionReplaceTransformer} 11 | */ 12 | public abstract class ReplaceTransformer extends ExprExecTransformer { 13 | @Override 14 | public String getName() { 15 | return "ReplaceTransformer"; 16 | } 17 | 18 | @Override 19 | public String getCategoryName() { 20 | return "Replace"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/util/ClassUtils.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.util; 2 | 3 | import java.util.List; 4 | 5 | import javassist.CannotCompileException; 6 | import javassist.ClassPool; 7 | import javassist.CtClass; 8 | import javassist.CtConstructor; 9 | import javassist.CtMember; 10 | import javassist.CtMethod; 11 | import javassist.CtNewMethod; 12 | import javassist.Modifier; 13 | import javassist.NotFoundException; 14 | import javassist.bytecode.AccessFlag; 15 | import javassist.bytecode.AnnotationsAttribute; 16 | import javassist.bytecode.AttributeInfo; 17 | import javassist.bytecode.BadBytecode; 18 | import javassist.bytecode.CodeAttribute; 19 | import javassist.bytecode.CodeIterator; 20 | import javassist.bytecode.ConstPool; 21 | import javassist.bytecode.ExceptionTable; 22 | import javassist.bytecode.MethodInfo; 23 | import javassist.bytecode.Opcode; 24 | 25 | @SuppressWarnings("RedundantThrows") 26 | public class ClassUtils { 27 | public interface BodyProducer { 28 | String createBody(String source, DelegateResult result); 29 | } 30 | 31 | public static class DelegateResult { 32 | private final CtMember source; 33 | private final CtMember delegate; 34 | private final String statement; 35 | 36 | private DelegateResult(CtMember source, CtMember delegate, String statement) { 37 | this.source = source; 38 | this.delegate = delegate; 39 | this.statement = statement; 40 | } 41 | 42 | @SuppressWarnings("unchecked") 43 | public T getSource() { 44 | return (T) source; 45 | } 46 | 47 | @SuppressWarnings("unchecked") 48 | public T getDelegate() { 49 | return (T) delegate; 50 | } 51 | 52 | public String getDelegateStatement() { 53 | return statement; 54 | } 55 | } 56 | 57 | public static boolean subclassOf( 58 | CtClass superClass, 59 | CtClass subClass) 60 | throws NotFoundException { 61 | if (subClass.subclassOf(superClass)) { 62 | return true; 63 | } 64 | 65 | CtClass[] interfaces = subClass.getInterfaces(); 66 | if (interfaces != null) { 67 | for (CtClass itf : interfaces) { 68 | if (itf == superClass) { 69 | return true; 70 | } 71 | } 72 | } 73 | return false; 74 | } 75 | 76 | public static CtMethod getDeclaredMethod( 77 | CtClass ctClass, 78 | String name, 79 | String signature) 80 | throws NotFoundException { 81 | 82 | for (CtMethod method : ctClass.getDeclaredMethods()) { 83 | if (method.getName().equals(name) 84 | && method.getSignature().equals(signature)) { 85 | return method; 86 | } 87 | } 88 | throw new NotFoundException("Method " + name + signature + 89 | " not found in class " + ctClass.getName()); 90 | } 91 | 92 | public static CtMethod getMethod( 93 | CtClass ctClass, 94 | String name, 95 | String signature) 96 | throws NotFoundException { 97 | 98 | for (CtMethod method : ctClass.getDeclaredMethods()) { 99 | if (method.getName().equals(name) 100 | && method.getSignature().equals(signature)) { 101 | return method; 102 | } 103 | } 104 | 105 | for (CtMethod method : ctClass.getMethods()) { 106 | if (method.getName().equals(name) 107 | && method.getSignature().equals(signature)) { 108 | return method; 109 | } 110 | } 111 | throw new NotFoundException("Method " + name + signature + 112 | " not found in class " + ctClass.getName()); 113 | } 114 | 115 | public static DelegateResult newMethodDelegate( 116 | CtClass clazz, 117 | CtMethod method, 118 | BodyProducer bodyProducer) 119 | throws NotFoundException, CannotCompileException { 120 | 121 | String methodName = method.getName(); 122 | String delegateMethodName = clazz.makeUniqueName(methodName + "$delegate"); 123 | method.setName(delegateMethodName); 124 | 125 | CtMethod newMethod = CtNewMethod.copy(method, methodName, clazz, null); 126 | int oldModifiers = method.getModifiers(); 127 | int mod = Modifier.setPrivate(oldModifiers); 128 | 129 | if ((mod & AccessFlag.BRIDGE) != 0) { 130 | mod = mod & ~AccessFlag.BRIDGE; 131 | } 132 | if ((mod & AccessFlag.SYNTHETIC) != 0) { 133 | mod = mod & ~AccessFlag.SYNTHETIC; 134 | } 135 | 136 | method.setModifiers(mod); 137 | 138 | String type = method.getReturnType().getName(); 139 | StringBuilder body = new StringBuilder(); 140 | body.append("{"); 141 | 142 | boolean isVoid = "void".equals(type); 143 | if (!isVoid) { 144 | body.append(type).append(" result = "); 145 | } 146 | String statement = delegateMethodName + "($$);"; 147 | body.append(statement); 148 | if (!isVoid) { 149 | body.append("return result;"); 150 | } 151 | body.append("}"); 152 | 153 | DelegateResult result = new DelegateResult(newMethod, method, statement); 154 | String bodyString = 155 | bodyProducer == null ? 156 | body.toString() : bodyProducer.createBody(body.toString(), result); 157 | try { 158 | String s = bodyString.replaceAll("\n", ""); 159 | newMethod.setBody(s); 160 | } catch (CannotCompileException e) { 161 | Logger.error("Create method delegate error with body \n" + bodyString + "\n", e); 162 | throw e; 163 | } 164 | clazz.addMethod(newMethod); 165 | return result; 166 | } 167 | 168 | public static DelegateResult newConstructorDelegate( 169 | ClassPool classPool, 170 | CtClass clazz, 171 | CtConstructor constructor, 172 | BodyProducer bodyProducer) 173 | throws NotFoundException, CannotCompileException { 174 | 175 | String delegateMethodName = clazz.makeUniqueName("_init_$delegate"); 176 | CtMethod delegateMethod = constructor.toMethod(delegateMethodName, clazz); 177 | delegateMethod.setModifiers(Modifier.PRIVATE); 178 | 179 | MethodInfo methodInfo = constructor.getMethodInfo(); 180 | CodeAttribute sourceCodeAttr = methodInfo.getCodeAttribute(); 181 | 182 | DelegateResult result = null; 183 | try { 184 | byte[] code = sourceCodeAttr.getCode(); 185 | CodeIterator iterator = sourceCodeAttr.iterator(); 186 | int pos = iterator.skipConstructor(); 187 | if (pos >= 0) { 188 | int len = pos + 3; 189 | byte[] codeCopy = new byte[len + 1]; 190 | codeCopy[len] = (byte) Opcode.RETURN; 191 | System.arraycopy(code, 0, codeCopy, 0, len); 192 | 193 | CtConstructor constructorCopy = 194 | new CtConstructor( 195 | constructor.getParameterTypes(), 196 | clazz); 197 | 198 | constructorCopy.setExceptionTypes(constructor.getExceptionTypes()); 199 | constructorCopy.setModifiers(constructor.getModifiers()); 200 | MethodInfo methodInfoCopy = constructorCopy.getMethodInfo(); 201 | ConstPool cp = methodInfo.getConstPool(); 202 | 203 | CodeAttribute codeAttribute = 204 | new CodeAttribute( 205 | cp, 206 | 0, 207 | 0, 208 | codeCopy, 209 | new ExceptionTable(cp)); 210 | 211 | methodInfoCopy.setCodeAttribute(codeAttribute); 212 | methodInfoCopy.rebuildStackMap(classPool); 213 | 214 | //Copy the original annotation 215 | List attributes = methodInfo.getAttributes(); 216 | for (AttributeInfo attribute : attributes) { 217 | if (attribute instanceof AnnotationsAttribute) { 218 | methodInfoCopy.addAttribute(attribute); 219 | } 220 | } 221 | 222 | clazz.removeConstructor(constructor); 223 | clazz.addConstructor(constructorCopy); 224 | 225 | clazz.addMethod(delegateMethod); 226 | String statement = delegateMethodName + "($$);"; 227 | 228 | result = new DelegateResult(constructor, delegateMethod, statement); 229 | String body = 230 | bodyProducer == null ? statement : 231 | bodyProducer.createBody(statement, result); 232 | try { 233 | String s = body.replaceAll("\n", ""); 234 | constructorCopy.insertBeforeBody(s); 235 | } catch (CannotCompileException e) { 236 | Logger.error("Create constructor delegate error with body \n" + 237 | body + "\n", e); 238 | throw e; 239 | } 240 | 241 | } 242 | } catch (BadBytecode e) { 243 | throw new CannotCompileException(e); 244 | } 245 | return result; 246 | } 247 | 248 | public static DelegateResult newInitializerDelegate( 249 | ClassPool classPool, 250 | CtClass clazz, 251 | CtConstructor constructor, 252 | BodyProducer bodyProducer) 253 | throws NotFoundException, CannotCompileException { 254 | 255 | String delegateMethodName = clazz.makeUniqueName("_clinit_$delegate"); 256 | CtMethod delegateMethod = constructor.toMethod(delegateMethodName, clazz); 257 | delegateMethod.setModifiers(Modifier.PRIVATE | Modifier.STATIC); 258 | clazz.addMethod(delegateMethod); 259 | String statement = delegateMethodName + "();"; 260 | 261 | DelegateResult result = new DelegateResult(constructor, delegateMethod, statement); 262 | String body = 263 | bodyProducer == null ? "{" + statement + "}" : 264 | bodyProducer.createBody(statement, result); 265 | 266 | try { 267 | String s = body.replaceAll("\n", ""); 268 | constructor.setBody(s); 269 | } catch (CannotCompileException e) { 270 | Logger.error("Create initializer delegate error with body \n" + body + "\n", e); 271 | throw e; 272 | } 273 | return result; 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/util/GradleUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.util 2 | 3 | import com.android.build.api.transform.* 4 | 5 | class GradleUtils { 6 | 7 | static File getTransformOutputLocation( 8 | TransformOutputProvider provider, 9 | QualifiedContent content) { 10 | 11 | if (content instanceof JarInput) { 12 | return provider. 13 | getContentLocation( 14 | content.name, 15 | content.contentTypes, 16 | content.scopes, 17 | Format.JAR) 18 | } 19 | if (content instanceof DirectoryInput) { 20 | return provider. 21 | getContentLocation( 22 | content.name, 23 | content.contentTypes, 24 | content.scopes, 25 | Format.DIRECTORY) 26 | } 27 | return null 28 | } 29 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/util/IOUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.util 2 | 3 | import org.apache.commons.io.FileUtils 4 | 5 | class IOUtils { 6 | 7 | public static final String USER_DIRECTORY_PATH = FileUtils.getUserDirectoryPath() 8 | 9 | static String getPath(File file) { 10 | return file.path.replace(USER_DIRECTORY_PATH, "~") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/util/Logger.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.util 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.concurrent.ExecutorService 5 | import java.util.concurrent.Executors 6 | import java.util.concurrent.TimeUnit 7 | import java.util.logging.ConsoleHandler 8 | import java.util.logging.FileHandler 9 | import java.util.logging.Level 10 | import java.util.logging.LogRecord 11 | 12 | /** 13 | * Plugin logger 14 | */ 15 | class Logger { 16 | public static final String TAG = "DroidAssist: " 17 | public static final String LOG_FILE_PREFIX = "droidassist-" 18 | 19 | private static java.util.logging.Logger sLogger = 20 | java.util.logging.Logger.getLogger("DroidAssist") 21 | private static ExecutorService sExecutor 22 | 23 | public static final int LEVEL_CONSOLE = 1 24 | public static final int LEVEL_FILE = 2 25 | public static final int LEVEL_ALL = 3 26 | 27 | static { 28 | sLogger.handlers.each { 29 | sLogger.removeHandler(it) 30 | } 31 | } 32 | 33 | static void init(int level, File dir) { 34 | sLogger.handlers.each { 35 | sLogger.removeHandler(it) 36 | } 37 | sExecutor = Executors.newSingleThreadExecutor() 38 | 39 | def consoleOutput = false 40 | def fileOutput = false 41 | if (level == LEVEL_CONSOLE) { 42 | consoleOutput = true 43 | } 44 | if (level == LEVEL_FILE) { 45 | fileOutput = true 46 | } 47 | if (level >= LEVEL_ALL) { 48 | consoleOutput = true 49 | fileOutput = true 50 | } 51 | 52 | if (consoleOutput) { 53 | def consoleHandler = new ConsoleHandler().with { 54 | setLevel(Level.ALL) 55 | setFormatter({ record -> 56 | def thrown = getThrownString(record) 57 | return record.message + thrown + "\n" 58 | }) 59 | return it 60 | } 61 | sLogger.addHandler(consoleHandler) 62 | } 63 | 64 | if (fileOutput) { 65 | dir.mkdirs() 66 | def format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss") 67 | def ts = format.format(Calendar.getInstance().getTime()) 68 | def file = new File(dir, LOG_FILE_PREFIX + ts + ".log") 69 | def fileHandler = new FileHandler(file.absolutePath).with { 70 | setLevel(Level.ALL) 71 | return it 72 | } 73 | fileHandler.setFormatter { 74 | record -> 75 | def logTimeFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") 76 | def time = logTimeFormatter.format(Calendar.getInstance().getTime()) 77 | def message = record.getMessage() 78 | message = message.substring(18, message.length() - 3) 79 | def thrown = getThrownString(record) 80 | return time + " " + message + thrown + "\n" 81 | } 82 | sLogger.addHandler(fileHandler) 83 | } 84 | } 85 | 86 | static String getThrownString(LogRecord record) { 87 | def thrown = "" 88 | if (record.getThrown() != null) { 89 | StringWriter stringWriter = new StringWriter() 90 | PrintWriter printWriter = new PrintWriter(stringWriter) 91 | record.getThrown().printStackTrace(printWriter) 92 | printWriter.close() 93 | thrown = stringWriter.toString() 94 | } 95 | return thrown 96 | } 97 | 98 | static void close() { 99 | sExecutor.shutdown() 100 | sExecutor.awaitTermination(60, TimeUnit.SECONDS) 101 | sLogger.handlers.each { it.close() } 102 | } 103 | 104 | static void debug(String msg) { 105 | sExecutor.execute { sLogger.log(Level.INFO, "\033[36m" + TAG + msg + " \033[0m") } 106 | } 107 | 108 | static void info(String msg) { 109 | sExecutor.execute { sLogger.log(Level.INFO, "\033[34m" + TAG + msg + " \033[0m") } 110 | } 111 | 112 | static void warning(String msg) { 113 | sExecutor.execute { sLogger.log(Level.INFO, "\033[33m" + TAG + msg + " \033[0m") } 114 | } 115 | 116 | static void error(String msg, Throwable err) { 117 | sLogger.log(Level.INFO, "\033[31m" + TAG + msg + " \033[0m", err) 118 | } 119 | 120 | static void debug(String tag, String msg) { 121 | sExecutor.execute { 122 | sLogger.log(Level.INFO, "\033[36m" + TAG + tag + ": " + msg + " \033[0m") 123 | } 124 | } 125 | 126 | static void info(String tag, String msg) { 127 | sExecutor.execute { 128 | sLogger.log(Level.INFO, "\033[34m" + TAG + tag + ": " + msg + " \033[0m") 129 | } 130 | } 131 | 132 | static void warning(String tag, String msg) { 133 | sExecutor.execute { 134 | sLogger.log(Level.INFO, "\033[33m" + TAG + tag + ": " + msg + " \033[0m") 135 | } 136 | } 137 | 138 | static void warning(String tag, String msg, Throwable err) { 139 | sLogger.log(Level.INFO, "\033[33m" + TAG + tag + ": " + msg + " \033[0m", err) 140 | } 141 | 142 | static void warning(String msg, Throwable err) { 143 | sLogger.log(Level.INFO, "\033[33m" + TAG + msg + " \033[0m", err) 144 | } 145 | 146 | static void error(String tag, String msg) { 147 | sExecutor.execute { 148 | sLogger.log(Level.INFO, "\033[31m" + TAG + tag + ": " + msg + " \033[0m") 149 | } 150 | } 151 | 152 | static void error(String tag, String msg, Throwable err) { 153 | 154 | sLogger.log(Level.INFO, "\033[31m" + TAG + tag + ": " + msg + " \033[0m", err) 155 | 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/util/TimingLogger.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.util 2 | 3 | import com.google.common.collect.Lists 4 | 5 | class TimingLogger { 6 | 7 | private String tag 8 | private String label 9 | private String beginMsg 10 | private boolean disabled 11 | private ArrayList splits 12 | private ArrayList splitLabels 13 | 14 | TimingLogger(String tag, String label, String beginMsg) { 15 | reset(tag, label, beginMsg) 16 | } 17 | 18 | TimingLogger(String tag, String label) { 19 | this(tag, label, "") 20 | } 21 | 22 | void reset(String tag, String label) { 23 | reset(tag, label, "") 24 | } 25 | 26 | void reset(String tag, String label, String beginMsg) { 27 | this.tag = tag 28 | this.label = label 29 | this.beginMsg = beginMsg 30 | reset() 31 | } 32 | 33 | void reset() { 34 | if (disabled) { 35 | return 36 | } 37 | if (splits == null) { 38 | splits = Lists.newArrayList() 39 | splitLabels = Lists.newArrayList() 40 | } else { 41 | splits.clear() 42 | splitLabels.clear() 43 | } 44 | addSplit(null) 45 | } 46 | 47 | 48 | void addSplit(String splitLabel) { 49 | if (disabled) { 50 | return 51 | } 52 | long now = System.currentTimeMillis() 53 | splits.add(now) 54 | splitLabels.add(splitLabel) 55 | } 56 | 57 | 58 | void dumpToLog() { 59 | if (disabled) { 60 | return 61 | } 62 | Logger.debug(tag, label + ": begin: " + beginMsg) 63 | final long first = splits.get(0) 64 | long now = first 65 | for (int i = 1; i < splits.size(); i++) { 66 | now = splits.get(i) 67 | final String splitLabel = splitLabels.get(i) 68 | final long prev = splits.get(i - 1) 69 | 70 | Logger.debug(tag, label + ":" + 71 | " ${(now - prev)} ms".padRight(7) + ", " + 72 | splitLabel) 73 | } 74 | Logger.debug(tag, label + ": end, " + (now - first) + " ms") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/util/WorkerExecutor.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.util 2 | 3 | import com.google.common.collect.Lists 4 | 5 | import java.util.concurrent.ExecutionException 6 | import java.util.concurrent.ExecutorService 7 | import java.util.concurrent.Executors 8 | 9 | 10 | class WorkerExecutor { 11 | 12 | private ExecutorService executorService 13 | private List futureList = 14 | Collections.synchronizedList(Lists.newArrayList()) 15 | 16 | WorkerExecutor() { 17 | executorService = Executors.newCachedThreadPool() 18 | } 19 | 20 | WorkerExecutor(int threads) { 21 | executorService = Executors.newFixedThreadPool(threads) 22 | } 23 | 24 | void execute(Runnable task) { 25 | futureList.add(executorService.submit(task)) 26 | } 27 | 28 | void finish() { 29 | futureList.each { 30 | try { 31 | it.get() 32 | } catch (InterruptedException ignore) { 33 | } catch (ExecutionException e) { 34 | throw new ExecutionException(e) 35 | } 36 | } 37 | executorService.shutdown() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/didichuxing/tools/droidassist/util/ZipUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist.util 2 | 3 | import org.apache.commons.io.FilenameUtils 4 | import org.apache.tools.ant.NoBannerLogger 5 | import org.apache.tools.ant.Project 6 | import org.apache.tools.ant.ProjectHelper 7 | 8 | import java.nio.file.Files 9 | import java.util.stream.Collectors 10 | import java.util.stream.Stream 11 | import java.util.zip.ZipFile 12 | 13 | class ZipUtils { 14 | 15 | static AntBuilder ant 16 | public static final String DOT_CLASS = '.class' 17 | 18 | static { 19 | def project = new Project() 20 | def helper = ProjectHelper.getProjectHelper() 21 | project.addReference(ProjectHelper.PROJECTHELPER_REFERENCE, helper) 22 | helper.getImportStack().addElement("AntBuilder") // checks that stack is not empty 23 | def logger = new NoBannerLogger() 24 | logger.setMessageOutputLevel(Project.MSG_ERR) 25 | logger.setOutputPrintStream(System.out) 26 | logger.setErrorPrintStream(System.err) 27 | project.addBuildListener(logger) 28 | project.init() 29 | ant = new AntBuilder(project) 30 | } 31 | 32 | static Stream collectAllClassesFromJar(File jar) { 33 | return new ZipFile(jar) 34 | .stream() 35 | .filter { !it.isDirectory() } 36 | .map { it.name } 37 | .filter { it.endsWith(DOT_CLASS) } 38 | .map { it.replace("/", ".") }//always '/' from java side 39 | .map { FilenameUtils.removeExtension(it) } 40 | } 41 | 42 | static void zip(String baseDir, String destFile) { 43 | ant.zip(destfile: destFile, basedir: baseDir) 44 | } 45 | 46 | static void zipAppend(File srcFile, File destFile, File dir) { 47 | ant.zip(destfile: destFile.absolutePath) { 48 | zipfileset(dir: dir.absolutePath) 49 | def basedirPath = dir.toPath() 50 | def excludes = Files.walk(dir.toPath()) 51 | .parallel() 52 | .map { basedirPath.relativize(it).toString() } 53 | .collect(Collectors.joining(",")) 54 | zipfileset(src: srcFile.absolutePath, excludes: excludes) 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /plugin/src/main/resources/META-INF/gradle-plugins/com.didichuxing.tools.droidassist.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.didichuxing.tools.droidassist.DroidAssistPlugin 2 | -------------------------------------------------------------------------------- /plugin/upload.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | apply plugin: 'com.jfrog.bintray' 3 | 4 | 5 | def siteUrl = 'https://github.com/didichuxing/DroidAssist' // 项目的主页 6 | def gitUrl = 'https://github.com/didichuxing/DroidAssist.git' // Git仓库的url 7 | 8 | group = GROUP_ID 9 | archivesBaseName = ARTIFACT_ID 10 | version = VERSION 11 | 12 | install { 13 | repositories.mavenInstaller { 14 | // This generates POM.xml with proper parameters 15 | pom { 16 | artifactId = ARTIFACT_ID 17 | 18 | project { 19 | packaging 'aar' 20 | // Add your description here 21 | name 'a lightweight Android Studio gradle plugin based on Javassist for editing bytecode in Android.' //项目描述 22 | url siteUrl 23 | // Set your license 24 | licenses { 25 | license { 26 | name 'Apache License 2.0' 27 | url 'http://www.apache.org/licenses/LICENSE-2.0' 28 | } 29 | } 30 | scm { 31 | connection gitUrl 32 | developerConnection gitUrl 33 | url siteUrl 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | task sourcesJar(type: Jar) { 41 | from sourceSets.main.allJava 42 | classifier = 'sources' 43 | } 44 | task javadocJar(type: Jar, dependsOn: javadoc) { 45 | classifier = 'javadoc' 46 | from groovydoc.destinationDir 47 | } 48 | artifacts { 49 | //archives javadocJar 50 | archives sourcesJar 51 | } 52 | 53 | Properties properties = new Properties() 54 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 55 | bintray { 56 | user = properties.getProperty("bintray.user") 57 | key = properties.getProperty("bintray.apikey") 58 | configurations = ['archives'] 59 | pkg { 60 | repo = "DroidAssist" 61 | name = "${GROUP_ID}:${ARTIFACT_ID}" //发布到JCenter上的项目名字 62 | websiteUrl = siteUrl 63 | vcsUrl = gitUrl 64 | licenses = ["Apache-2.0"] 65 | publish = true 66 | } 67 | } -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | 4 | android { 5 | compileSdkVersion 28 6 | defaultConfig { 7 | applicationId "com.didichuxing.tools.droidassist" 8 | minSdkVersion 15 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | dexOptions { 22 | preDexLibraries false 23 | } 24 | } 25 | 26 | dependencies { 27 | testImplementation 'junit:junit:4.12' 28 | 29 | androidTestImplementation('com.android.support.test:runner:1.0.1', { 30 | exclude group: 'com.android.support', module: 'support-annotations' 31 | }) 32 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { 33 | exclude group: 'com.android.support', module: 'support-annotations' 34 | }) 35 | implementation 'com.android.support:appcompat-v7:28.0.0' 36 | } 37 | 38 | apply plugin: 'com.didichuxing.tools.droidassist' 39 | droidAssistOptions { 40 | enable true 41 | logLevel 3 42 | config file("droidassist.xml") 43 | logDir file("${project.buildDir.absolutePath}/logs") 44 | } 45 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/didichuxing/tools/droidassist/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.didichuxing.tools.droidassist", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/Child.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | 4 | public class Child extends Parent { 5 | 6 | //protected int age; 7 | 8 | public Child(int age) { 9 | super(age); 10 | } 11 | 12 | @Override 13 | protected void methodB() { 14 | super.methodB(); 15 | int b = age; 16 | int a = super.age; 17 | super.age = 10; 18 | System.out.println("methodB" + a); 19 | } 20 | 21 | public static void main(String[] args) { 22 | Child child = new Child(1); 23 | child.methodB(); 24 | child.methodA(); 25 | System.out.println("child super class: " + child.getClass().getSuperclass()); 26 | System.out.println(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/ExampleSpec.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | @SuppressWarnings("WeakerAccess") 4 | public final class ExampleSpec implements IInterface { 5 | 6 | public static String name = "test"; 7 | public int id = 0; 8 | public int id2 = 0; 9 | 10 | public static ExampleSpec instance = new ExampleSpec(); 11 | 12 | public ExampleSpec() { 13 | System.out.println(""); 14 | } 15 | 16 | public ExampleSpec(int i) { 17 | System.out.println("ExampleSpec(" + i + ")"); 18 | } 19 | 20 | public ExampleSpec(int i, int y) { 21 | System.out.println("ExampleSpec(" + i + "," + y + ")"); 22 | } 23 | 24 | @TestAnnotation 25 | public ExampleSpec(int i, int y, int z) { 26 | System.out.println("ExampleSpec(" + i + "," + y + "," + z + ")"); 27 | } 28 | 29 | public ExampleSpec(String string) { 30 | System.out.println(string); 31 | } 32 | 33 | public static ExampleSpec getInstance() { 34 | return instance; 35 | } 36 | 37 | public void run() { 38 | System.out.println("run"); 39 | } 40 | 41 | public void call() { 42 | System.out.println("call"); 43 | } 44 | 45 | public void uncaught() { 46 | System.out.println("uncaught"); 47 | } 48 | 49 | @TestAnnotation3 50 | public void uncaught2() { 51 | System.out.println("uncaught2"); 52 | int i = 1 / 0; 53 | } 54 | 55 | public void timing() { 56 | System.out.println("timing"); 57 | } 58 | 59 | public int timing2() { 60 | System.out.println("timing"); 61 | return 0; 62 | } 63 | 64 | public int timing3() { 65 | System.out.println("timing"); 66 | return 0; 67 | } 68 | 69 | @TestAnnotation(intValue = 100, strValue = "constructor") 70 | public void test() { 71 | System.out.println("test"); 72 | } 73 | 74 | @TestAnnotation2(intValue = 200) 75 | public void test2() { 76 | System.out.println("test2"); 77 | } 78 | 79 | @Override 80 | public void onCall() { 81 | System.out.println("onCall"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/IInterface.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | /** 4 | * Test interface 5 | */ 6 | public interface IInterface { 7 | 8 | void onCall(); 9 | 10 | /** 11 | * Test interface with generic 12 | * 13 | * @param type 14 | */ 15 | interface Callback { 16 | void onCallback(final T value); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/LogUtils.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | /** 4 | * Dummy logger for replace logcat 5 | */ 6 | public class LogUtils { 7 | 8 | public static int log(String tag, String msg) { 9 | //do nothing 10 | return 0; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.Button; 9 | 10 | 11 | /** 12 | * Dummy activity for test 13 | */ 14 | public class MainActivity extends Activity implements IInterface.Callback { 15 | public static final String TAG = "MainActivity"; 16 | 17 | static { 18 | //Test with static initializer block 19 | Log.d(TAG, "static initializer: "); 20 | } 21 | 22 | public MainActivity() { 23 | //Test with constructor block 24 | Log.d(TAG, "constructor: "); 25 | } 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | 31 | Button button = new Button(this); 32 | 33 | //Replace test 34 | 35 | Log.d(TAG, "onCreate: ");//test method call replace 36 | 37 | ExampleSpec example = new ExampleSpec(0); 38 | String name = ExampleSpec.name; //field get 39 | ExampleSpec.name = "test";//field set 40 | 41 | // Insert test 42 | example.run(); //method call 43 | 44 | //test inner class 45 | 46 | button.setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | //method exec 50 | Log.d(TAG, "onClick: "); 51 | } 52 | }); 53 | ExampleSpec example2 = new ExampleSpec(); 54 | int id = example2.id;//field get 55 | example2.id = 1;//field set 56 | 57 | // Around test 58 | example2.call(); 59 | 60 | ExampleSpec example3 = new ExampleSpec("test"); 61 | int id2 = example3.id2;//field get 62 | example3.id2 = 2;//field set 63 | 64 | //Enhance 65 | 66 | //Try catch test 67 | startActivity(new Intent());//will crash here, test crash 68 | //Timing test 69 | ExampleSpec test4 = new ExampleSpec(1, 2); 70 | test4.timing(); 71 | 72 | //Annotation test 73 | ExampleSpec test5 = new ExampleSpec(1, 2, 3); 74 | test5.test(); 75 | test5.test2(); 76 | test5.uncaught2(); 77 | 78 | onCallback(""); 79 | 80 | Child.main(null); 81 | } 82 | 83 | @Override 84 | public void onCallback(String value) { 85 | System.out.println("onCallback"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/Parent.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | 4 | public class Parent { 5 | 6 | protected int age; 7 | 8 | public Parent(int age) { 9 | this.age = age; 10 | } 11 | 12 | public void methodA() { 13 | } 14 | 15 | protected void methodB() { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/ReParent.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | 4 | public class ReParent extends Parent { 5 | 6 | protected int age; 7 | 8 | public ReParent(int age) { 9 | super(age); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/TestAnnotation.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.CLASS) 9 | @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) 10 | public @interface TestAnnotation { 11 | int intValue() default 300; 12 | 13 | String strValue() default "empty"; 14 | } 15 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/TestAnnotation2.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.CLASS) 9 | @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) 10 | public @interface TestAnnotation2 { 11 | int intValue() default 300; 12 | 13 | String strValue() default "TestAnnotation2"; 14 | } 15 | -------------------------------------------------------------------------------- /sample/src/main/java/com/didichuxing/tools/test/TestAnnotation3.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.test; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.CLASS) 9 | @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) 10 | public @interface TestAnnotation3 { 11 | } 12 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DroidAssistTest 3 | 4 | -------------------------------------------------------------------------------- /sample/src/test/java/com/didichuxing/tools/droidassist/JavaUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.didichuxing.tools.droidassist; 2 | 3 | /** 4 | * Example local unit test, which will execute on the development machine (host). 5 | * 6 | * @see Testing documentation 7 | */ 8 | public class JavaUnitTest { 9 | 10 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample' 2 | include ':plugin' 3 | 4 | --------------------------------------------------------------------------------