前言
本章節(jié)更新的慢了些是复,最近公司多事之秋山涡,今天靜下心來(lái)把AOP最后入門(mén)篇補(bǔ)上幻碱,做事還要有頭和尾的狂秦。
Javassist
Javassist作用是在編譯器間修改class文件蚯涮,與之相似的ASM(熱修復(fù)框架女?huà)z)也有這個(gè)功能坯临,可以讓我們直接修改編譯后的class二進(jìn)制代碼,首先我們得知道什么時(shí)候編譯完成恋昼,并且我們要趕在class文件被轉(zhuǎn)化為dex文件之前去修改看靠。在Transfrom這個(gè)api出來(lái)之前,想要在項(xiàng)目被打包成dex之前對(duì)class進(jìn)行操作液肌,必須自定義一個(gè)Task挟炬,然后插入到predex或者dex之前,在自定義的Task中可以使用javassist或者asm對(duì)class進(jìn)行操作嗦哆。而Transform則更為方便谤祖,Transfrom會(huì)有他自己的執(zhí)行時(shí)機(jī),不需要我們插入到某個(gè)Task前面老速。Tranfrom一經(jīng)注冊(cè)便會(huì)自動(dòng)添加到Task執(zhí)行序列中粥喜,并且正好是項(xiàng)目被打包成dex之前。
傳送門(mén):android-aop-samples
定義JavassistPlugin橘券,固定寫(xiě)法沒(méi)啥說(shuō)的
public class JavassistPlugin implements Plugin<Project> {
void apply(Project project) {
System.out.println("------------------開(kāi)始----------------------");
System.out.println("這是我們的自定義插件!");
//AppExtension就是build.gradle中android{...}這一塊
def android = project.extensions.getByType(AppExtension)
//注冊(cè)一個(gè)Transform
def classTransform = new JavassistTransform(project);
android.registerTransform(classTransform);
System.out.println("------------------結(jié)束了嗎----------------------");
}
}
Transfrom
Gradle是通過(guò)一個(gè)一個(gè)Task執(zhí)行完成整個(gè)流程的额湘,其中肯定也有將所有class打包成dex的task。
(在gradle plugin 1.5 以上和以下版本有些不同)
1.5以下旁舰,preDex這個(gè)task會(huì)將依賴(lài)的module編譯后的class打包成jar锋华,然后dex這個(gè)task則會(huì)將所有class打包成dex
1.5以上,preDex和Dex這兩個(gè)task已經(jīng)消失箭窜,取而代之的是TransfromClassesWithDexForDebug
自定義Transfrom
public class JavassistTransform extends Transform {
private Project mProject;
public JavassistTransform(Project p) {
this.mProject = p;
}
//transform的名稱(chēng)
//transformClassesWithMyClassTransformForDebug 運(yùn)行時(shí)的名字
//transformClassesWith + getName() + For + Debug或Release
@Override
public String getName() {
return "JavassistTransform";
}
//需要處理的數(shù)據(jù)類(lèi)型毯焕,有兩種枚舉類(lèi)型
//CLASSES和RESOURCES,CLASSES代表處理的java的class文件磺樱,RESOURCES代表要處理java的資源
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
// 指Transform要操作內(nèi)容的范圍纳猫,官方文檔Scope有7種類(lèi)型:
// EXTERNAL_LIBRARIES 只有外部庫(kù)
// PROJECT 只有項(xiàng)目?jī)?nèi)容
// PROJECT_LOCAL_DEPS 只有項(xiàng)目的本地依賴(lài)(本地jar)
// PROVIDED_ONLY 只提供本地或遠(yuǎn)程依賴(lài)項(xiàng)
// SUB_PROJECTS 只有子項(xiàng)目。
// SUB_PROJECTS_LOCAL_DEPS 只有子項(xiàng)目的本地依賴(lài)項(xiàng)(本地jar)竹捉。
// TESTED_CODE 由當(dāng)前變量(包括依賴(lài)項(xiàng))測(cè)試的代碼
@Override
public Set<QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT;
}
//指明當(dāng)前Transform是否支持增量編譯
@Override
public boolean isIncremental() {
return false;
}
// Transform中的核心方法芜辕,
// inputs中是傳過(guò)來(lái)的輸入流,其中有兩種格式活孩,一種是jar包格式一種是目錄格式物遇。
// outputProvider 獲取到輸出目錄,最后將修改的文件復(fù)制到輸出目錄憾儒,這一步必須做不然編譯會(huì)報(bào)錯(cuò)
@Override
public void transform(Context context,
Collection<TransformInput> inputs,
Collection<TransformInput> referencedInputs,
TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException {
System.out.println("你愁啥----------------進(jìn)入transform了--------------")
//遍歷input
inputs.each { TransformInput input ->
//遍歷文件夾
input.directoryInputs.each { DirectoryInput directoryInput ->
//注入代碼
MyInjects.inject(directoryInput.file.absolutePath, mProject)
// 獲取output目錄
def dest = outputProvider.getContentLocation(directoryInput.name,
directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
// 將input的目錄復(fù)制到output指定目錄
FileUtils.copyDirectory(directoryInput.file, dest)
}
////遍歷jar文件 對(duì)jar不操作询兴,但是要輸出到out路徑
input.jarInputs.each { JarInput jarInput ->
// 重命名輸出文件(同目錄copyFile會(huì)沖突)
def jarName = jarInput.name
println("jar = " + jarInput.file.getAbsolutePath())
def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
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)
}
}
System.out.println("瞅你咋地--------------結(jié)束transform了----------------")
}
}
主要說(shuō)下transform干嘛的 ?起趾?诗舰?在Transform里處理Task,通過(guò)inputs拿到一些東西训裆,處理完畢之后就輸出outputs眶根,而下一個(gè)Task的inputs則是上一個(gè)Task的outputs。
MyInjects.inject()插入代碼
public class MyInjects {
//初始化類(lèi)池
private final static ClassPool pool = ClassPool.getDefault();
public static void inject(String path, Project project) {
//將當(dāng)前路徑加入類(lèi)池,不然找不到這個(gè)類(lèi)
pool.appendClassPath(path);
//project.android.bootClasspath 加入android.jar边琉,不然找不到android相關(guān)的所有類(lèi)
pool.appendClassPath(project.android.bootClasspath[0].toString());
//引入android.os.Bundle包属百,因?yàn)閛nCreate方法參數(shù)有Bundle
pool.importPackage("android.os.Bundle");
File dir = new File(path);
if (dir.isDirectory()) {
//遍歷文件夾
dir.eachFileRecurse { File file ->
String filePath = file.absolutePath
println("filePath = " + filePath)
if (file.getName().equals("MainActivity.class")) {
//獲取MainActivity.class
CtClass ctClass = pool.getCtClass("com.zxy.aop.MainActivity");
println("ctClass = " + ctClass)
//解凍
if (ctClass.isFrozen())
ctClass.defrost()
//獲取到OnCreate方法
CtMethod ctMethod = ctClass.getDeclaredMethod("onCreate")
println("方法名 = " + ctMethod)
String insetBeforeStr = """ android.widget.Toast.makeText(this,"WTF emmmmmmm.....我是被插入的Toast代碼~!!",android.widget.Toast.LENGTH_LONG).show();
"""
//在方法開(kāi)頭插入代碼
ctMethod.insertBefore(insetBeforeStr);
ctClass.writeFile(path)
ctClass.detach()//釋放
}
}
}
}
}
運(yùn)行效果圖
看下build/intermediates/transforms/HavassustTransform/MainActivity.class
總結(jié)
ClassPool、CtClass变姨、CtMethod核心類(lèi)的使用在這里展示的很詳細(xì)族扰。
1、初始化ClassPool設(shè)置
2定欧、通過(guò)包名取到對(duì)應(yīng)的CtClass
3渔呵、通過(guò)CtClass取到CtMethod對(duì)應(yīng)的“OnCreate”方法
4、CtMethodi插入代碼塊砍鸠,寫(xiě)文件扩氢,釋放,結(jié)束整個(gè)注入代碼過(guò)程爷辱。
最后
本章節(jié)只是介紹了自定義Transform以及Javassist的三大核心類(lèi)的使用录豺,通過(guò)本章節(jié)的學(xué)習(xí)可以了解apk的編譯過(guò)程,可以做很多好玩有意義的事情饭弓!