Apk编译流程
Apk编译流程主要经过以下几步:
1、使用javac将java文件编译成class
2、使用dex工具将class打包成dex
3、使用apkbuilder工具将dex、资源文件打包成apk
4、使用jarsigner工具对apk签名
其实在编译过程中,google工程师留给了我们很多api用来添加自己的操作。如APT在编译时可以对代码进行处理,Transform在将class打包成dex中途,可以对class文件做自己的处理。
Apk编译流程操作流程
一、创建工程、基础配置
1、新建Java Library工程
新建工程
2、将monitor中build.gradle的plugins改成groovy
plugins {
id 'java-library'
}
//------------------改成----------------------
plugins {
id 'groovy'
}
3、删除java目录,并在main中新建groovy目录。
新建groovy目录
4、在monitor的build.gradle中添加依赖
plugins {
id 'groovy'
id 'maven-publish'
}
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation "com.android.tools.build:gradle:3.1.3"
implementation "org.javassist:javassist:3.20.0-GA"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
如果报错,Build was configured to prefer settings repositories over project repositories but repository 'Gradle Libs' was added by unknown code;可以进入settings.gradle,将repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)注释掉。
二、插件开发
1、新建groovy类
新建MonitorPlugin,实现Plugin接口,泛型为Project。
package com.niiiico.monitor
import org.gradle.api.Plugin
import org.gradle.api.Project;
public class MonitorPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "hello plugin"
}
}
2、设置properties
在main目录下新建文件夹resources/META-INF/gradle-plugins,新建文件com.niiiico.monitor.properties(包名.properties)。
文件内容为:implementation-class=插件全路径,以此来表示插件的入口。
implementation-class=com.niiiico.monitor.MonitorPlugin
3、打包插件,并发布到本地仓库
3.1、在monitor的build.gradle添加publishing代码:
plugins {
id 'groovy'
id 'maven-publish'
}
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation "com.android.tools.build:gradle:3.1.3"
implementation "org.javassist:javassist:3.20.0-GA"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
// 将插件打包发布到本地
publishing {
publications {
// Creates a Maven publication called "monitor".
monitor(MavenPublication) {
// 表示是一个java插件,最终会打包成jar包
from components.java
groupId = 'com.niiiico.monitor'
artifactId = 'monitor'
version = '1.0'
}
}
repositories {
maven {
// 发布地址
url('../monitor-jar')
}
}
}
3.2、点击右上角的Sync now,在右上角的gradle->Tasks便能找到publish任务。
publish任务
3.3、双击publish,可以在工程目录看到多了一个monitor-jar目录。打好的插件包便在这个目录下。
monitor-jar目录
4、依赖插件
4.1、在项目的build.gradle中添加maven本地路径,并在dependencies添加插件依赖。classpath "groupId:artifactId:version"
添加插件// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
maven {
url('monitor-jar')
}
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.3"
classpath "com.niiiico.monitor:monitor:1.0"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
4.2、在app的build.gradle中添加插件依赖:apply plugin: 'com.niiiico.monitor';内容即我们在resources/META-INF/gradle-plugins下创建的文件名称。
4.3、点击sync,即可在build中看到如下打印,表示插件引入成功。
打印
三、继承Transform
1、新建MonitorTransform继承自Transform
package com.niiiico.monitor
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.gradle.internal.pipeline.TransformManager;
public class MonitorTransform extends Transform {
def project
MonitorTransform(Project project) {
this.project = project
}
// 在app/build/intermediates/transforms/路径下生成新的文件夹
// 用来存储本次transform操作的数据
@Override
String getName() {
return "monitor"
}
// 接收什么类型的数据
@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
}
}
2、重写输入输出
2.1、在MonitorPlugin的apply方法中使用project.android.registerTransform(new MonitorTransform(project))注册自定义的Transform。
package com.niiiico.monitor
import org.gradle.api.Plugin
import org.gradle.api.Project;
public class MonitorPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.android.registerTransform(new MonitorTransform(project))
}
}
2.2、重新使用publish发布插件,然后运行app,发现apk无法运行。因为注册Transform后,系统会把我们的Transform插入编译打包流程,上一个节点会将编译好的class和jar等信息告诉我们,如果我们不进行任何处理,下一个节点便无法拿到这些信息,因此需要重写输入输出,将从上一个节点拿到的数据告诉下一个节点。
Transform
2.3、要将数据告诉下个节点,需要以下几步:
(1)遍历inputs目录,查询输入的文件
(2)查询输出文件路径
(3)将输入文件复制到下一个节点
重写transform函数
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOExcepti
super.transform(transformInvocation)
println "--------------------transform-------------------"
// 1、查询输入,遍历inputs目录
transformInvocation.inputs.each {
// 1.1 jar包目录
it.jarInputs.each {
// 2.查询输出
def dest = transformInvocation.outputProvider.getContentLocation(
it.name,
it.contentTypes,
it.scopes,
Format.JAR)
println "jar dest----->" + dest
// 3.复制到下一环节
FileUtils.copyFile(it.file, dest);
}
// 1.2 class目录
it.directoryInputs.each {
// 2.查询输出
def dest = transformInvocation.outputProvider.getContentLocation(
it.name,
it.contentTypes,
it.scopes,
Format.DIRECTORY)
println "class dest----->" + dest
// 3.复制到下一环节
FileUtils.copyDirectory(it.file, dest);
}
}
}
2.4、重新发布,点击安装,即可安装成功。此时,在build\intermediates\transforms\下可以发现,新增了monitor目录,这边是getName函数定义的名字。
monitor目录
四、Javassist修改class文件
1、通过ClassPool加载class文件
// 缓存class字节码对象的容器
def pool = ClassPool.getDefault()
def preFileName = it.file.absolutePath
// 加载路径下的class文件
pool.insertClassPath(preFileName)
// project.android.bootClasspath 加入android.jar,不然找不到android相关的所有类
pool.appendClassPath(project.android.bootClasspath[0].toString());
// 引入android.os.Bundle包,因为onCreate方法参数有Bundle
pool.importPackage("android.os.Bundle");
2、找到class文件
遍历系统传过来的class文件目录,找到class文件
// 找到需要处理的文件并处理
// fileName D:\workplace_github\JavassistDemo\app\build\intermediates\javac\debug\classes
private void findTargetAndSettle(File dir, String fileName) {
if (dir.isDirectory()) {
// 如果是目录,继续遍历
dir.listFiles().each {
findTargetAndSettle(it, fileName)
}
} else {
def filePath = dir.absolutePath
// 只处理class文件
if (filePath.endsWith(".class")) {
println "find class----->" + filePath
// 修改文件
modify(filePath, fileName)
}
}
}
3、过滤class
过滤系统生成的class,然后截取class全类名,通过ClassPool查找到CtClass 对象。
// 过滤class
private void filterClass(def filePath, String fileName) {
// 过滤系统文件
if (filePath.contains('R$')
|| filePath.contains('R.class')
|| filePath.contains("BuildConfig.class")) {
return
}
// 获取className
def className = filePath.replace(fileName, "")
.replace("\\", ".")
.replace("/", ".")
.replace(".class", "")
.substring(1)
println "find className----->" + className
// 获取CtClass对象,用来操作class
CtClass ctClass = pool.get(className)
addCode(ctClass, fileName)
}
4、修改代码并写入文件
// 添加代码
private void addCode(CtClass ctClass, String fileName) {
// 解冻
ctClass.defrost()
CtMethod[] methods = ctClass.getDeclaredMethods()
for (method in methods) {
println "method " + method.getName() + "参数个数 " + method.getParameterTypes().length
if ("onCreate".equals(method.getName())) {
method.insertBefore("{ System.out.println(\"调用了" + method.getName() + "\");}")
}
}
// 将修改的文件写出去
ctClass.writeFile(fileName)
ctClass.detach()
}
5、验证结果
点击publish重新打包插件,重新打包并运行apk。
查看build\intermediates\transforms\monitor\目录下的MainActivity.class文件,发现代码已经被修改。
MainActivity.class
6、MonitorTransform全部代码
package com.niiiico.monitor
import com.android.build.api.transform.Format
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.TransformInvocation
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import javassist.ClassPool
import javassist.CtClass
import javassist.CtMethod
import org.gradle.api.Project;
public class MonitorTransform extends Transform {
def project
// 缓存class字节码对象的容器
def pool = ClassPool.getDefault()
MonitorTransform(Project project) {
this.project = project
}
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
println "--------------------transform-------------------"
// 1、查询输入,遍历inputs目录
transformInvocation.inputs.each {
// 1.1 jar包目录
it.jarInputs.each {
// 2.查询输出
def destDir = transformInvocation.outputProvider.getContentLocation(
it.name,
it.contentTypes,
it.scopes,
Format.JAR)
println "jar destDir----->" + destDir
// 3.复制到下一环节
FileUtils.copyFile(it.file, destDir);
}
// 1.2 class目录
it.directoryInputs.each {
def preFileName = it.file.absolutePath
// 加载路径下的class文件
pool.insertClassPath(preFileName)
// project.android.bootClasspath 加入android.jar,不然找不到android相关的所有类
pool.appendClassPath(project.android.bootClasspath[0].toString());
// 引入android.os.Bundle包,因为onCreate方法参数有Bundle
pool.importPackage("android.os.Bundle");
println "========directoryInputs======== " + preFileName
findTargetAndSettle(it.file, preFileName)
// 2.查询输出
def destDir = transformInvocation.outputProvider.getContentLocation(
it.name,
it.contentTypes,
it.scopes,
Format.DIRECTORY)
println "class destDir----->" + destDir
// 3.复制到下一环节
FileUtils.copyDirectory(it.file, destDir);
}
}
}
// 找到需要处理的文件并处理
// fileName D:\workplace_github\JavassistDemo\app\build\intermediates\javac\debug\classes
private void findTargetAndSettle(File dir, String fileName) {
if (dir.isDirectory()) {
// 如果是目录,继续遍历
dir.listFiles().each {
findTargetAndSettle(it, fileName)
}
} else {
def filePath = dir.absolutePath
// 只处理class文件
if (filePath.endsWith(".class")) {
println "find class----->" + filePath
// 修改文件
filterClass(filePath, fileName)
}
}
}
// 过滤class
private void filterClass(def filePath, String fileName) {
// 过滤系统文件
if (filePath.contains('R$')
|| filePath.contains('R.class')
|| filePath.contains("BuildConfig.class")) {
return
}
// 获取className
def className = filePath.replace(fileName, "")
.replace("\\", ".")
.replace("/", ".")
.replace(".class", "")
.substring(1)
println "find className----->" + className
// 获取CtClass对象,用来操作class
CtClass ctClass = pool.get(className)
addCode(ctClass, fileName)
}
// 添加代码
private void addCode(CtClass ctClass, String fileName) {
// 解冻
ctClass.defrost()
CtMethod[] methods = ctClass.getDeclaredMethods()
for (method in methods) {
println "method " + method.getName() + "参数个数 " + method.getParameterTypes().length
if ("onCreate".equals(method.getName())) {
method.insertBefore("{ System.out.println(\"调用了" + method.getName() + "\");}")
}
}
// 将修改的文件写出去
ctClass.writeFile(fileName)
ctClass.detach()
}
// 在app/build/intermediates/transforms/路径下生成新的文件夹
// 用来存储本次transform操作的数据
@Override
String getName() {
return "monitor"
}
// 接收什么类型的数据
@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
}
}
网友评论