Abstract
Plug-ins are important, like us
Application plugin: 'kotlin-android-extensions' // We can use the Android API in build.gradle to apply plugin: 'kotlin-android-extensions'Copy the code
Are plugins to the system. Custom plugin for Android development is very important, you can do a lot of content, such as multi-channel packaging, modify resources, compress images, modify bytecode, aspect programming AOP ideas and so on, simple learning to develop their own plugin
Start customizing
There are generally three kinds
- Write it directly in your app’s build.gradle
- Putting plug-in code in buildSrc is also rarely used, as is the example we did
- Publish to local or remote repositories for use by other projects
The first kind of
Add the following classes directly to the outermost layer of our app build.gradle that inherit from Plugin and reference our Plugin
// Use our own apply plugin: Class DemoPlugin implements plugin <Project>{@override void apply(Project Project) {println "DemoPlugin-----" } }Copy the code
Then we execute./gradlew to print “DemoPlugin—–” to our code
The second,
Creating a folder
Plug-ins can use Groovy, Koglin and Java language we in the main project directory to create buildSrc/SRC/main/Java/com. Nzy. Then create a class
public class DemoPlugin implements Plugin<Project> { @Override public void apply(Project project) { System.out.println("DemoPlugin-----"); }}Copy the code
Creating a Configuration File
Create resources in SRC folder, create meta-inf directory in the resources, create gradle in meta-inf – plugins directory, then create a com inside. Nzy. Plugin. The properties configuration file, The name is what we need to write when we quote it.
// specify our class here implementation-class= com.nzy.demopluginCopy the code
Build. Gradle in buildSrc. Add the following code to build
Apply the plugin: 'Java' dependencies {implementation 'com. Android. View the build: gradle: 4.4.1' implementation 'com. Android. Tools. Build: gradle - API: 4.4.1' / / ASM related implementation 'org. Ow2. ASM: ASM: 7.1' implementation 'org.ow2.asm: ASM: Commons :7.1'} Repositories {Google () jCenter ()}Copy the code
use
Add the apply plugin: ‘com.nzy.plugin’ to our build.gradle app. The name here is the name of our configuration file. Executing./gradlew will print “DemoPlugin—–” to our code
The third, which I won’t cover here, requires a Maven repository
We use ASM bytecode interpolation to implement a method time consuming function
Gradle Transform is a standard Android API for modifying.class files at this stage. The application now focuses on bytecode lookup, code injection, and so on
3.1 Write our own plugin
Register our own TimeTransform with Android
public class TimePlugin implements Plugin<Project> { @Override public void apply(Project project) { System.out.println("-------NzyPlugin------"); // Register Transform AppExtension to gradle. Gradle AppExtension AppExtension = project.getextensions ().getByType(AppExtension.class); / / print each method's time appExtension registerTransform (new TimeTransform (project)); }}Copy the code
3.2 Customizing TimeTransform
Iterate through each class and JAR to find if there are DebugLog annotations and start adding them
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", "");
}
}
Copy the code
The code address