上篇介绍了自定义 plugin,这一篇我们学习下 Transform。这个在平时应用中会有更多的应用情景。
和上一篇不同的是这回,我们直接在 AndroidStudio 中新建一个 Android module 删除我们不需要的 res 和其他和android 相关的目录。具体如图所示。一定要新建 groovy 目录下放 groovy文件。还有gradle-plugins 目录下的文件,具体参照上一篇。
一、创建
1、创建自定义 Plugin
import com.android.build.gradle.AppExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class TestPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.extensions.findByType(AppExtension).registerTransform(new MyTransform(project))
}
}
2、创建自定义 Transform
import com.android.build.api.transform.DirectoryInput
import com.android.build.api.transform.Format
import com.android.build.api.transform.JarInput
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformInvocation
import com.android.build.api.transform.TransformOutputProvider
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import org.gradle.api.Project
class MyTransform extends Transform {
Project project
MyTransform(Project project) {
this.project = project
}
@Override
String getName() {
return "ssy transform"
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
//当前是否是增量编译
@Override
boolean isIncremental() {
return false
}
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
// super.transform(transformInvocation)
println "=============hello, body!=============="
TransformOutputProvider outputProvider = transformInvocation.outputProvider
transformInvocation.inputs.each {
TransformInput input ->
println "===========================--------------------"
input.directoryInputs.each {
DirectoryInput directoryInput ->
MyInject.injectDir(directoryInput.file.absolutePath, "com.ssy.demo0628", project)
def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
println "===========================--------------------${dest.absolutePath}"
FileUtils.copyDirectory(directoryInput.file, dest)
}
input.jarInputs.each {
JarInput jarInput ->
String jarName = jarInput.name
def md5Name = org.apache.commons.codec.digest.DigestUtils.md5Hex(jarInput.file.getAbsoluteFile().toString())
if (jarName.endsWith(".jar")) {
jarName = jarName.substring(0, jarName.length() - 4)
}
def dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
FileUtils.copyFile(jarInput.file, dest)
}
}
}
}
3、处理代码
import javassist.ClassPool
import javassist.CtClass
import javassist.CtConstructor
import javassist.CtField
import javassist.CtMethod
import org.gradle.api.Project
public class MyInject {
private static ClassPool pool = ClassPool.getDefault()
private static String injectStr = "System.out.println(\"I im Mr.S\" ); "
static void injectDir(String path, String packageName, Project project) {
pool.appendClassPath(project.android.bootClasspath[0].toString())
pool.appendClassPath(path)
File dir = new File(path)
if (dir.directory) {
dir.eachFileRecurse {
File file ->
String filePath = file.absolutePath
//确保当前文件是class 文件,并且不是系统自动生成的 class 文件
if (filePath.endsWith(".class")
&& !filePath.contains('R$')
&& !filePath.contains('R.class')
&& !filePath.contains('BuildConfig.class')
) {
//判断当前目录是否在我们的应用包里面
int index = filePath.indexOf(packageName.replace('.', File.separator))
boolean isMyPackage = index != -1
if (isMyPackage) {
int end = filePath.length() - 6
String className = filePath.substring(index, end).replace('\\', '.').replace('/', '.')
//开始修改 class 文件
CtClass c = pool.getCtClass(className)
if (c.isFrozen()) {
c.defrost()
}
//这是构造方法里增加方法
// CtConstructor[] constructors = c.getDeclaredConstructors()
// if (constructors==null||constructors.length==0){
// //手动创建一个构造函数
// CtConstructor constructor = new CtConstructor(new CtClass[0],c)
// constructor.insertBeforeBody(injectStr)
// c.addConstructor(constructor)
// }else {
// constructors[0].insertBeforeBody(injectStr)
// }
for (CtMethod currentMethod : c.getDeclaredMethods()) {
if (currentMethod.name == 'onClick') {
if (currentMethod.signature == ('(Landroid/view/View;)V')) {
currentMethod.insertAfter("com.ssy.demo0628.Utils.onMyClick(\$1);")
}
}
}
c.writeFile(path)
c.detach()
}
}
}
}
}
4、添加时间监听和打印
class TimingsListener implements TaskExecutionListener, BuildListener {
public Clock clock
private timings = []
@Override
void buildStarted(Gradle gradle) {
}
@Override
void settingsEvaluated(Settings settings) {
}
@Override
void projectsLoaded(Gradle gradle) {
}
@Override
void projectsEvaluated(Gradle gradle) {
}
/**
*
* 类似这样:
BUILD SUCCESSFUL in 22s
57 actionable tasks: 57 executed
Task timings:
146ms :app:generateDebugBuildConfig
86ms :gradlescriptdemo:compileGroovy
273ms :app:mergeDebugResources
169ms :app:processDebugManifest
248ms :app:processDebugResources
1181ms :app:compileDebugJavaWithJavac
439ms :app:transformClassesWith-->WQTransform<--ForDebug
415ms :app:transformClassesWithDexForDebug
451ms :app:packageDebug
180ms :app:mergeReleaseResources
69ms :app:processReleaseResources
130ms :app:compileReleaseJavaWithJavac
172ms :buildsrc:compileGroovy
1197ms :app:lintVitalRelease
86ms :app:packageRelease
4246ms :app:lint
6528ms :app:mockableAndroidJar
*
* @param buildResult
*/
@Override
void buildFinished(BuildResult buildResult) {
println "Task timings:"
for (timing in timings) {
if (timing[0] >= 50) {
printf "%7sms %s\n", timing
}
}
}
@Override
void beforeExecute(Task task) {
clock = Clock.systemDefaultZone()
}
@Override
void afterExecute(Task task, TaskState state) {
def ms = clock.millis()
timings.add([ms, task.path])
task.project.logger.warn "${task.path} took ${ms}ms"
if(task.outputs.files.files) {
task.project.logger.warn "taskName:${task.name}"
task.project.logger.warn "outputs.files.files: ------------start----------------- "
task.outputs.files.files.each {
task.project.logger.warn "${it.absolutePath} "
}
task.project.logger.warn "outputs.files.files: ---------------end------------------ "
}
}
}
5、配置module build.gradle
plugins {
id 'groovy'
id 'maven'
}
group 'com.ssy'
version '1.0.2'
sourceCompatibility = 1.8
repositories {
mavenCentral()
google()
jcenter()
}
dependencies {
//Gradle Plugin 依赖
implementation gradleApi()
//本地发布 Plugin
implementation localGroovy()
//因为我们需要在android 项目中使用,所以需要android的tool build库
implementation 'com.android.tools.build:gradle:3.1.3'
compile 'commons-io:commons-io:2.5'
implementation 'commons-io:commons-io:2.5'
implementation 'org.javassist:javassist:3.20.0-GA'
}
uploadArchives {
repositories {
mavenDeployer {
//设置插件的GAV参数
pom.groupId = 'com.ssy'
pom.version = '1.0.2'
//文件发布到下面目录
repository(url: uri('../repo'))
}
}
}
点击 uploadArchives
repo 目录就生成在了我们的工程里。
二、使用
1、在 project 下的 build.gradle 添加我们自己的 classpath。
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven {
url uri('repo')
}
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
//这个就是 仓库中指定我们需要的版本
classpath 'com.ssy:TestPlugin:1.0.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
2、在 app 的 build.gradle 使用我们的plugin。
3、编译运行就好了。