摘要
插件很重要,比如咱们
apply plugin: 'com.android.application'// 这样我们才可以在build.gradle中使用 android的api
apply plugin: 'kotlin-android-extensions'
复制代码
都是系统的plugin。自定义plugin对于android开发来说很重要,可以做很多内容 ,比如 多渠道打包,修改字节码,切面编程aop思想等等,简单学习一下 制定自己的 plugin
开始自定义
一般有三种
- 直接写在app的 build.gradle 中,这个一般没人用
- 把插件代码放在buildSrc中 也很少这样使用,我们做的例子用的是这个
- 发布到本地或者远程仓库供其他项目使用
第一种
直接在我们app的build.gradle中最外层,加入下面的类继承自Plugin,并且引用我们的plugin
// 引用咱们 自己的
apply plugin: DemoPlugin
// 编写plugin类
class DemoPlugin implements Plugin<Project>{
@Override
void apply(Project project) {
println "DemoPlugin-----"
}
}
复制代码
然后我们执行 ./gradlew 就会打印 “DemoPlugin—–” 我们的代码了
第二种
创建文件夹
插件可以使用Groovy、Koglin和java语言我们在主工程目录下创建 buildSrc/src/main/java/com.nzy。然后创建一个class
public class DemoPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
System.out.println("DemoPlugin-----");
}
}
复制代码
创建配置文件
在src文件夹中创建 resources,在 resources 创建META-INF目录,在META-INF创建gradle-plugins目录,之后再里面创建一个com.nzy.plugin.properties 配置文件,名字就是我们引用的时候需要写的。
// 把咱们的类指定在这里
implementation-class=com.nzy.DemoPlugin
复制代码
在buildSrc中创建build.gradle,加入以下代码,主要是咱们的plugin需要android的一下api
apply plugin:'java'
dependencies {
implementation 'com.android.tools.build:gradle:4.1.1'
implementation 'com.android.tools.build:gradle-api:4.1.1'
// ASM 相关
implementation 'org.ow2.asm:asm:7.1'
implementation 'org.ow2.asm:asm-util:7.1'
implementation 'org.ow2.asm:asm-commons:7.1'
}
repositories {
google()
jcenter()
}
复制代码
使用
在app的build.gradle中加入 apply plugin: ‘com.nzy.plugin’,这里的名字就是咱们配置文件的名字。
之后执行 ./gradlew 就会打印 “DemoPlugin—–” 我们的代码了
第三种 这里就不讲了,需要maven仓库
我们利用ASM字节码插庄实现一个方法耗时的功能
需要了解 Transform 以及 ASM字节码的知识,Gradle Transform 是 Android 官方提供的在这一阶段用来修改 .class 文件的一套标准 API。 这一应用现在主要集中在字节码查找、代码注入等
3.1 写咱们自己的plugin
把咱们自己的 TimeTransform 注册到android中
public class TimePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
System.out.println("-------NzyPlugin------");
// 注册 Transform, AppExtension 依赖 gradle,所以该模块需要导入 gradle
AppExtension appExtension = project.getExtensions().getByType(AppExtension.class);
// 打印每个方法的时间
appExtension.registerTransform(new TimeTransform(project));
}
}
复制代码
3.2 自定义 TimeTransform
遍历每个class 和jar 找到如果有 DebugLog的注解的就开始添加
public class TimeTransform extends Transform {
private Map<String, File> modifyMap = new HashMap<>();
private static final String NAME = "TimeTransform";
private static final String TAG = "TimeTransform:";
private final Logger mLogger;
public Project mProject;
public TimeTransform(Project project) {
mProject = project;
mLogger = mProject.getLogger();
}
@Override
public String getName() {
return NAME;
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
/**
* 指Transform要操作内容的范围,官方文档Scope有7种类型:
* <p>
* EXTERNAL_LIBRARIES 只有外部库
* PROJECT 只有项目内容
* PROJECT_LOCAL_DEPS 只有项目的本地依赖(本地jar)
* PROVIDED_ONLY 只提供本地或远程依赖项
* SUB_PROJECTS 只有子项目。
* SUB_PROJECTS_LOCAL_DEPS 只有子项目的本地依赖项(本地jar)。
* TESTED_CODE 由当前变量(包括依赖项)测试的代码
* SCOPE_FULL_PROJECT 整个项目
*/
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT;
}
@Override
public boolean isIncremental() {
return false;
}
@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
// inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。
Collection<TransformInput> inputs = transformInvocation.getInputs();
// 获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做不然编译会报错
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
if(outputProvider!=null){
outputProvider.deleteAll();
}
// 遍历
for (TransformInput input : inputs) {
// 处理jar
for (JarInput jarInput : input.getJarInputs()) {
try {
transformJar(transformInvocation, jarInput);
} catch (Exception e) {
e.printStackTrace();
}
}
// 处理目录里面的Class
for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
transformDirectory(transformInvocation, directoryInput);
}
}
}
private void transformJar(TransformInvocation invocation, JarInput input) throws Exception {
File tempDir = invocation.getContext().getTemporaryDir();
String destName = input.getFile().getName();
String hexName = DigestUtils.md5Hex(input.getFile().getAbsolutePath()).substring(0, 8);
if (destName.endsWith(".jar")) {
destName = destName.substring(0, destName.length() - 4);
}
// 获取输出路径
File dest = invocation.getOutputProvider()
.getContentLocation(destName + "_" + hexName, input.getContentTypes(), input.getScopes(), Format.JAR);
JarFile originJar = new JarFile(input.getFile());
File outputJar = new File(tempDir, "temp_"+input.getFile().getName());
JarOutputStream output = new JarOutputStream(new FileOutputStream(outputJar));
// 遍历原jar文件寻找class文件
Enumeration<JarEntry> enumeration = originJar.entries();
while (enumeration.hasMoreElements()) {
JarEntry originEntry = enumeration.nextElement();
InputStream inputStream = originJar.getInputStream(originEntry);
String entryName = originEntry.getName();
if (entryName.endsWith(".class")) {
JarEntry destEntry = new JarEntry(entryName);
output.putNextEntry(destEntry);
byte[] sourceBytes = IOUtils.toByteArray(inputStream);
// 修改class文件内容
byte[] modifiedBytes = referHackClass(sourceBytes);
if (modifiedBytes == null) {
modifiedBytes = sourceBytes;
}
output.write(modifiedBytes);
output.closeEntry();
}
}
output.close();
originJar.close();
// 复制修改后jar到输出路径
FileUtils.copyFile(outputJar, dest);
}
private void transformDirectory(TransformInvocation invocation, DirectoryInput input) throws IOException {
File tempDir = invocation.getContext().getTemporaryDir();
// 获取输出路径
File dest = invocation.getOutputProvider()
.getContentLocation(input.getName(), input.getContentTypes(), input.getScopes(), Format.DIRECTORY);
File dir = input.getFile();
if (dir != null && dir.exists()) {
traverseDirectory(tempDir, dir);
FileUtils.copyDirectory(input.getFile(), dest);
for (Map.Entry<String, File> entry : modifyMap.entrySet()) {
File target = new File(dest.getAbsolutePath() + entry.getKey());
if (target.exists()) {
target.delete();
}
FileUtils.copyFile(entry.getValue(), target);
entry.getValue().delete();
mLogger.log(LogLevel.ERROR,target.getAbsolutePath()+"-----");
}
}
}
private void traverseDirectory(File tempDir, File dir) throws IOException {
for (File file : Objects.requireNonNull(dir.listFiles())) {
if (file.isDirectory()) {
traverseDirectory(tempDir, file);
} else if (file.getAbsolutePath().endsWith(".class")) {
String className = path2ClassName(file.getAbsolutePath()
.replace(dir.getAbsolutePath() + File.separator, ""));
byte[] sourceBytes = IOUtils.toByteArray(new FileInputStream(file));
byte[] modifiedBytes = referHackClass(sourceBytes);
File modified = new File(tempDir, className.replace(".", "") + ".class");
if (modified.exists()) {
modified.delete();
}
modified.createNewFile();
new FileOutputStream(modified).write(modifiedBytes);
String key = file.getAbsolutePath().replace(dir.getAbsolutePath(), "");
modifyMap.put(key, modified);
mLogger.log(LogLevel.ERROR,key+"----"+file.getAbsolutePath());
}
}
}
private byte[] referHackClass(byte[] inputStream) {
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new AutoClassVisitor(Opcodes.ASM6, classWriter);
classReader.accept(cv, ClassReader.EXPAND_FRAMES);
return classWriter.toByteArray();
}
static String path2ClassName(String pathName) {
return pathName.replace(File.separator, ".").replace(".class", "");
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END