javassist是一个操作class文件即class字节码的动态类库;在打包过程中,用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。
为了方便看,我将javassist的demo剥离出来
gradle从1.5开始,gradle插件包含了一个叫Transform的API,这个API允许第三方插件在class文件转为为dex文件前操作编译好的class文件,这个API的目标是简化自定义类操作,而不必处理Task,并且在操作上提供更大的灵活性。并且可以更加灵活地进行操作。
官方文档:http://google.github.io/android-gradle-dsl/javadoc/
demo的原理也是基于此来完成的。即java代码编译后生成class文件,当要打包.class-->.dex时,Javassit库操作.class文件,读取所有的文件的注解,根据注解将EventBus的注解与反注解以及触发事件的方法生成到当前的.class文件中。
CtConstructor:用来访问构造器
不过,Javassist 并未提供删除类中字段、方法或者构造函数的任何方法。
CtClass.getDeclaredMethods()获取自己申明的方法,CtClass.getMethods()会把所有父类的方法都加上
注意:修改插件中的代码一定要重新发布,发布之前可先将之前的插件删除,将build删除,重新运行./gradlew -p plugin clean build uploadArchives --info
1.1在插件module的build.gradle中加入javassist库,并配置发布的插件
apply plugin: 'groovy'
apply plugin: 'maven'
dependencies {
compile gradleApi()//gradle sdk
compile localGroovy()//groovy sdk
compile 'com.android.tools.build:gradle:2.3.1'
// compile 'com.android.tools.build:transform-api:1.5.0'
compile 'org.javassist:javassist:3.20.0-GA'//javassist库
}
uploadArchives {
repositories.mavenDeployer {//重新发布之后,会在本地的repo路径生成仓库(也可发布到远程)可参照http://geek.csdn.net/news/detail/53459
repository(url: uri('../repo'))
pom.groupId = 'com.app.plugin'
pom.artifactId = 'gradleplugin'
pom.version = '1.0.0'
}
}
repositories {
jcenter()
}
//注意⚠️: 插件修改后运行前需要重新发布: ./gradlew -p plugin clean build uploadArchives --info
1.2继承插件Plugin并将自定义的Transform注册到android中的插件中
public class JavassistPlugin implements Plugin {
void apply(Project project) {
def log = project.logger
log.error "========================";
log.error "Javassist开始修改Class!";
log.error "========================";
//版本不一样获取的AppExtension方式不一样,这里一定要注意
//project.android.registerTransform(new JavassistTransform(project)) 这里是无效的
def android = project.extensions.getByType(AppExtension);
android.registerTransform(new JavassistTransform(project));
project.task('transformPath') {
doLast {
System.out.println('+++++++++++++++++++++transformPath task')
}
}
}
}
1.3自定义Transform
public class JavassistTransform extends Transform {
Project project
public JavassistTransform(Project project) { // 构造函数,我们将Project保存下来备用
this.project = project
project.logger.error("====start===JavassistTransform");
}
//transformClassesWithMyClassTransformForDebug 运行时的名字
//transformClassesWith + getName() + For + Debug或Release
@Override
String getName() {// 设置我们自定义的Transform对应的Task名称
return com.app.plugin.JavassistTransform.simpleName;
}
@Override
// 指定输入的类型,通过这里的设定,可以指定我们要处理的文件类型这样确保其他类型的文件不会传入
//需要处理的数据类型,有两种枚举类型
//CLASSES和RESOURCES,CLASSES代表处理的java的class文件,RESOURCES代表要处理java的资源
Set getInputTypes() {
return Sets.immutableEnumSet(QualifiedContent.DefaultContentType.CLASSES)
}
@Override
/****指定Transform的作用范围
*
* 指Transform要操作内容的范围,官方文档Scope有7种类型:
* EXTERNAL_LIBRARIES 只有外部库
* PROJECT 只有项目内容
* PROJECT_LOCAL_DEPS 只有项目的本地依赖(本地jar)
* PROVIDED_ONLY 只提供本地或远程依赖项
* SUB_PROJECTS 只有子项目。
* SUB_PROJECTS_LOCAL_DEPS 只有子项目的本地依赖项(本地jar)。
* TESTED_CODE 由当前变量(包括依赖项)测试的代码
*/
Set getScopes() {
return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT, QualifiedContent.Scope.PROJECT_LOCAL_DEPS,
QualifiedContent.Scope.SUB_PROJECTS, QualifiedContent.Scope.SUB_PROJECTS_LOCAL_DEPS,
QualifiedContent.Scope.EXTERNAL_LIBRARIES)
}
/***当前Transform是否支持增量编译*/
@Override
boolean isIncremental() {
return false
}
/***
* Transform中的核心方法,
* @param context
* @param inputs 中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。
* @param referencedInputs
* @param outputProvider outputProvider 获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做不然编译会报错
* @param isIncremental
* @throws IOException
* @throws TransformException
* @throws InterruptedException
*/
@Override
void transform(Context context, Collection inputs,
Collection referencedInputs,
TransformOutputProvider outputProvider, boolean isIncremental)
throws IOException, TransformException, InterruptedException {//只有打包apk时才会执行,build不会执行
def startTime = System.currentTimeMillis();
project.logger.error("===exe=====transform" + inputs.size());
// Transform的inputs有两种类型,一种是目录,一种是jar包,要分开遍历
inputs.each { TransformInput input ->
try {
input.jarInputs.each {
project.logger.error("===exe=====jarInputs" );
MyInject.injectDir(it.file.getAbsolutePath(), BusHelper.PKG_NAME, project)
String outputFileName = it.name.replace(".jar", "") + '-' + it.file.path.hashCode()
def output = outputProvider.getContentLocation(outputFileName, it.contentTypes, it.scopes, Format.JAR)
FileUtils.copyFile(it.file, output)
}
} catch (Exception e) {
project.logger.error e.getMessage();
}
//对类型为“文件夹”的input进行遍历
input.directoryInputs.each { DirectoryInput directoryInput ->
project.logger.error("===exe=====directoryInputs" );
//文件夹里面包含的是我们手写的类以及R.class、BuildConfig.class以及R$XXX.class等
MyInject.injectDir(directoryInput.file.absolutePath, BusHelper.PKG_NAME, project)
// 获取output目录
def dest = outputProvider.getContentLocation(directoryInput.name,
directoryInput.contentTypes, directoryInput.scopes,
Format.DIRECTORY)
// 将input的目录复制到output指定目录
FileUtils.copyDirectory(directoryInput.file, dest)
}
}
ClassPool.getDefault().clearImportedPackages();
project.logger.error("JavassistTransform cast :" + (System.currentTimeMillis() - startTime) / 1000 + " secs");
}
}
主要看transform(xxx)方法。当编译打包时,会执行t该方法,通过看代码,主要是遍历两个jar和文件夹列表。随后主要是以下这行代码。
MyInject.injectDir(it.file.getAbsolutePath(), BusHelper.PKG_NAME, project)
将当前文件夹路径以及当前项目的包名作为参数传进去。注意包名:要有统一的包名的开头,用于自动匹配app\build\intermediates\classes\debug\统一包名\....的包名路径
如这里:BusHelper.PKG_NAME=“me\”;那么自动匹配app\build\intermediates\classes\debug\me\...
MyInject.injectDir:
/****
*
* @param path jar包或者文件夹路径
* @param packageName 自己的包名,一定要有一个统一的包名
* @param project
*/
public static void injectDir(String path, String packageName, Project project) {
project.logger.error( "p == "+path+" pkg "+packageName);
pool.appendClassPath(path)
//project.android.bootClasspath 加入android.jar,否则找不到android相关的所有类
pool.appendClassPath(project.android.bootClasspath[0].toString());
Utils.importBaseClass(pool);
File dir = new File(path)
if (dir.isDirectory()) {//遍历文件夹
dir.eachFileRecurse { File file ->
String filePath = file.absolutePath//确保当前文件是class文件,并且不是系统自动生成的class文件
if (filePath.endsWith(".class") && !filePath.contains('R$') && !filePath.contains('$')//代理类
&& !filePath.contains('R.class') && !filePath.contains("BuildConfig.class")) {
// 判断当前目录是否是在我们的应用包里面
int index = filePath.indexOf(packageName);
project.logger.error( index+" filePath == "+filePath);//C:\Users\Administrator\Desktop\AOP\EventBus-master-javassit\ViewFinder-master\sample\build\intermediates\classes\debug\me\brucezz\viewfinder\sample\SecondActivity.class
project.logger.error( index+" packageName == "+packageName);
boolean isMyPackage = index != -1;
project.logger.error( index+"isMyPackage == "+isMyPackage);
if (isMyPackage) {
//将路径转成包名+类名
String className = Utils.getClassName(index, filePath);
project.logger.error( "pool : "+pool.toString());
project.logger.error( "className : "+className);
CtClass c = pool.getCtClass(className)
if (c.isFrozen()) c.defrost()
BusInfo mBusInfo = new BusInfo()
mBusInfo.setProject(project)
mBusInfo.setClazz(c)
if (c.getName().endsWith("Activity") || c.getSuperclass().getName().endsWith("Activity")) mBusInfo.setIsActivity(true)
boolean isAnnotationByBus = false;
//getDeclaredMethods获取自己申明的方法,c.getMethods()会把所有父类的方法都加上
for (CtMethod ctmethod : c.getDeclaredMethods()) {
String methodName = Utils.getSimpleName(ctmethod);
if (BusHelper.ON_CREATE.contains(methodName)) mBusInfo.setOnCreateMethod(ctmethod)
if (BusHelper.ON_DESTROY.contains(methodName)) mBusInfo.setOnDestroyMethod(ctmethod)
for (Annotation mAnnotation : ctmethod.getAnnotations()) {
if (mAnnotation.annotationType().canonicalName.equals(BusHelper.OkBusRegisterAnnotation))
mBusInfo.setBusRegisterMethod(ctmethod)
if (mAnnotation.annotationType().canonicalName.equals(BusHelper.OkBusUnRegisterAnnotation))
mBusInfo.setBusUnRegisterMethod(ctmethod)
if (mAnnotation.annotationType().canonicalName.equals(BusHelper.OkBusAnnotation)) {
project.logger.info " method:" + c.getName() + " -" + ctmethod.getName()
mBusInfo.methods.add(ctmethod)
mBusInfo.annotations.add(mAnnotation)
if (!isAnnotationByBus) isAnnotationByBus = true
}
}
}
if (((mBusInfo.BusRegisterMethod != null && mBusInfo.BusUnRegisterMethod == null
|| mBusInfo.BusRegisterMethod == null && mBusInfo.BusUnRegisterMethod != null)))
assert false: Utils.getBusErr()
if (mBusInfo != null && isAnnotationByBus) {
try {
project.logger.error( "intBus path == "+path);
BusHelper.intBus(mBusInfo, path)
} catch (DuplicateMemberException e) {
}
}
c.detach()//用完一定记得要卸载,否则pool里的永远是旧的代码
}
}
}
}
}
一行行代码看:
1:
private final static ClassPool pool = ClassPool.getDefault();Utils.importBaseClass(pool);
/**
* 事先载入相关类
* @param pool
*/
static void importBaseClass(ClassPool pool) {
pool.importPackage(LogTimeHelper.LogTimeAnnotation);
pool.importPackage(BusHelper.OkBusAnnotation);
pool.importPackage(BusHelper.OkBusRegisterAnnotation);
pool.importPackage(BusHelper.OkBusUnRegisterAnnotation);
pool.importPackage("android.os.Bundle");
pool.importPackage("me.brucezz.viewfinder.sample.event.OkBus")
pool.importPackage("me.brucezz.viewfinder.sample.event.Event")
pool.importPackage("android.os.Message")
}
将需要的包名都导入池子中。主要是新增的代码所需要的导入的包。
2:递归遍历文件夹中所有class文件,并根据文件路径和包名开头获取class的包名+类名,从而从javassist库的池子中获取CtClass类操作当前.class
CtClass c = pool.getCtClass(className)
if (c.isFrozen()){
c.defrost()
}
BusInfo mBusInfo = new BusInfo()
mBusInfo.setProject(project)
mBusInfo.setClazz(c)
if (c.getName().endsWith("Activity") || c.getSuperclass().getName().endsWith("Activity")){
mBusInfo.setIsActivity(true)
}
获取当前class实例,并将其解冻使其可用,随后生成BusInfo实例,用以记录增加EventBus注册和反注册以及触发事件方法的注解。再者当前Activity命名必须是以activity结尾或父类的命名是以Activity结尾,这个写一个BaseActivity就可以了。
BusInfo
public class BusInfo {
Project project//保留当前工程的引用
CtClass clazz//当前处理的class
List methods = new ArrayList<>()//带有Bus注解的方法列表
List annotations = new ArrayList<>()//带有Bus注解的注解列表
List eventIds = new ArrayList<>()//带有Bus注解的注解id列表
boolean isActivity = false;//是否是在Activity
CtMethod OnCreateMethod//Activity或Fragment的初始化方法
CtMethod OnDestroyMethod//Activity或Fragment的销毁方法
CtMethod BusRegisterMethod//被Register注解标注的初始化方法
CtMethod BusUnRegisterMethod//被UnRegister注解标注的销毁方法
}
接着,遍历当前类的方法是否有使用注解以及是否重写onCreate()和onDestory(),如果有,则将注解信息解析存储到busInfo实例中。
//getDeclaredMethods获取自己申明的方法,c.getMethods()会把所有父类的方法都加上
for (CtMethod ctmethod : c.getDeclaredMethods()) {
String methodName = Utils.getSimpleName(ctmethod);
if (BusHelper.ON_CREATE.contains(methodName)) mBusInfo.setOnCreateMethod(ctmethod)
if (BusHelper.ON_DESTROY.contains(methodName)) mBusInfo.setOnDestroyMethod(ctmethod)
for (Annotation mAnnotation : ctmethod.getAnnotations()) {
if (mAnnotation.annotationType().canonicalName.equals(BusHelper.OkBusRegisterAnnotation))
mBusInfo.setBusRegisterMethod(ctmethod)
if (mAnnotation.annotationType().canonicalName.equals(BusHelper.OkBusUnRegisterAnnotation))
mBusInfo.setBusUnRegisterMethod(ctmethod)
if (mAnnotation.annotationType().canonicalName.equals(BusHelper.OkBusAnnotation)) {
project.logger.info " method:" + c.getName() + " -" + ctmethod.getName()
mBusInfo.methods.add(ctmethod)
mBusInfo.annotations.add(mAnnotation)
if (!isAnnotationByBus) isAnnotationByBus = true
}
}
最后:解析完信息后,在onCreate()、onDestory()或带有注解的方法实现注册与反注册的EventBus,以及在当前类增加触发事件的方法。
调用BusHelper.intBus(mBusInfo, path):
static void intBus(BusInfo mBusInfo, String path) {
if (mBusInfo.clazz.isFrozen()) mBusInfo.clazz.defrost()//解冻
if (mBusInfo.BusRegisterMethod != null) {//有被BusRegister注解
mBusInfo.project.logger.error "BusRegisterMethod not null" +
mBusInfo.BusRegisterMethod.insertAfter(getRegisterEventMethodStr(mBusInfo));
} else if (mBusInfo.getOnCreateMethod() == null) {//没有OnCreateMethod,创建并加上新代码
mBusInfo.project.logger.quiet "getOnCreateMethod null " + mBusInfo.isActivity
String pre_create_str = mBusInfo.isActivity ? Activity_OnCreate : Fragment_OnCreate;
String m = pre_create_str + getRegisterEventMethodStr(mBusInfo) + "}"
mBusInfo.project.logger.quiet m
mBusInfo.project.logger.error m
CtMethod mInitEventMethod = CtNewMethod.make(m, mBusInfo.clazz);
mBusInfo.clazz.addMethod(mInitEventMethod)
} else {//有OnCreateMethod,直接插入新代码
mBusInfo.project.logger.error "OnCreateMethod not null"
mBusInfo.project.logger.quiet "OnCreateMethod not null"
mBusInfo.OnCreateMethod.insertAfter(getRegisterEventMethodStr(mBusInfo));
}
if (mBusInfo.BusUnRegisterMethod != null) {//有被BusUnRegister注解的方法
mBusInfo.project.logger.quiet "BusUnRegisterMethod not null"
mBusInfo.project.logger.error "BusUnRegisterMethod not null"
mBusInfo.BusUnRegisterMethod.insertAfter(getUnRegisterEventMethodStr(mBusInfo));
} else if (mBusInfo.OnDestroyMethod == null) {
mBusInfo.project.logger.quiet "OnDestroyMethod null"
mBusInfo.project.logger.error "OnDestroyMethod null"
String m = Pre_OnDestroy + getUnRegisterEventMethodStr(mBusInfo) + "}";
mBusInfo.project.logger.quiet m
CtMethod destroyMethod = CtNewMethod.make(m, mBusInfo.clazz)
mBusInfo.clazz.addMethod(destroyMethod)
} else {
mBusInfo.project.logger.quiet "OnDestroyMethod not null"
mBusInfo.project.logger.error "OnDestroyMethod not null"
mBusInfo.OnDestroyMethod.insertAfter(getUnRegisterEventMethodStr(mBusInfo));
}
mBusInfo.clazz.writeFile(path)
}
主要是查看是否有使用注解或是否重写oncreate()和onDestory(),随后在带有注解的方法或onCreate()和onDestory()方法的最后通过ctMethod.insertAfter()插入EventBus的注册与反注册java语句或者class.addMethod()添加java方法,随后通过javassit库中的ctclass.writeFile()将新增的内容写回文件中。
反注册:
/**
* 生成取消事件注册的代码
* @param mBusInfo
*/
static String getUnRegisterEventMethodStr(mBusInfo) {
String dis_Str = "";
mBusInfo.eventIds.each { id -> dis_Str += "OkBus.getInstance().unRegister(" + id + ");\n" }
return dis_Str;
}
注册:
/**
* 获取初始化OkBus方法的代码
* @param mBusInfo 事件信息
* @return
*/
static String getRegisterEventMethodStr(BusInfo mBusInfo) {
String CreateStr = "";
//为当前的类添加事件处理的接口:当前类要实现事件Event接口
mBusInfo.clazz.addInterface(mBusInfo.clazz.classPool.get("me.brucezz.viewfinder.sample.event.Event"));
for (int i = 0; i < mBusInfo.getMethods().size(); i++) {
MethodInfo methodInfo = mBusInfo.getMethods().get(i).getMethodInfo();
Annotation mAnnotation = mBusInfo.getAnnotations().get(i)
AnnotationsAttribute attribute = methodInfo.getAttribute(AnnotationsAttribute.visibleTag);
//获取注解属性
javassist.bytecode.annotation.Annotation annotation = attribute.getAnnotation(mAnnotation.annotationType().canonicalName);
//获取注解
int id = ((IntegerMemberValue) annotation.getMemberValue("value")).getValue();//获取注解的值
int thread = -1;
if (annotation.getMemberValue("thread") != null)
thread = ((IntegerMemberValue) annotation.getMemberValue("thread")).getValue();
mBusInfo.eventIds.add(id)
CreateStr += "OkBus.getInstance().register(" + id + ",(Event)this," + thread + ");\n"
}
initEventDispatch(mBusInfo)
return CreateStr;
}
当前类是实现统一事件的接口Event:注册完后,生成事件分发的逻辑代码方法如下:
/**
* 生成event事件分发的逻辑代码
* @param mBusInfo
* @return
*/
static initEventDispatch(BusInfo mBusInfo) {
String SwitchStr = Pre_Switch_Str;//接口实现方法
for (int i = 0; i < mBusInfo.eventIds.size(); i++) {
CtMethod method = mBusInfo.getMethods().get(i)
CtClass[] mParameterTypes = method.getParameterTypes();
assert mParameterTypes.length <= 1
boolean one = mParameterTypes.length == 1
boolean isBaseType = false;
String packageName = "";
if (one) {
String mParameterType = mParameterTypes[0].name;
switch (mParameterType) {
//Primitive Types(原始型) Reference Types(Wrapper Class)(引用型,(包装类))
case "boolean": mParameterType = "Boolean"; isBaseType = true; break;
case "byte": mParameterType = "Byte"; isBaseType = true; break;
case "char": mParameterType = "Character"; isBaseType = true; break;
case "float": mParameterType = "Float"; isBaseType = true; break;
case "int": mParameterType = "Integer"; isBaseType = true; break;
case "long": mParameterType = "Long"; isBaseType = true; break;
case "short": mParameterType = "Short"; isBaseType = true; break;
case "double": mParameterType = "Double"; isBaseType = true; break;
}
mBusInfo.project.logger.quiet "name:" + mParameterType;
packageName = isBaseType ? "java.lang." + mParameterType : mParameterType;
mBusInfo.clazz.classPool.importPackage(packageName)
}//如果是基本数据类型,需要手动拆箱,否则会报错
String ParamStr = isBaseType ? ("((" + packageName + ")msg.obj)." +
mParameterTypes[0].name + "Value()") : ("(" + packageName + ")msg.obj");
SwitchStr += "case " + mBusInfo.eventIds.get(i) + ":" + method.getName() +
"(" + (one ? ParamStr : "") + ");\n break;\n"
mBusInfo.project.logger.error "pgkname:" + packageName;
}
String m = SwitchStr + "}\n}"
mBusInfo.project.logger.quiet m
CtMethod mDispatchEventMethod = CtMethod.make(m, mBusInfo.clazz);
mBusInfo.clazz.addMethod(mDispatchEventMethod)
}
MainActivity源代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
TextView mTextView;
Button mButton;
EditText mEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.tv);
mButton = (Button) findViewById(R.id.btn);
mEditText = (EditText) findViewById(R.id.et);
mTextView.setOnClickListener(this);
mButton.setOnClickListener(this);
mEditText.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v == mTextView) {
Toast.makeText(this, "1 onTextClick", Toast.LENGTH_SHORT).show();
} else if (v == mButton) {
Toast.makeText(this, "1 onButtonClick", Toast.LENGTH_SHORT).show();
OkBus.getInstance().onEvent(EventTags.FLASH_INIT_UI);
} else if (v == mEditText) {
}
}
@Bus(EventTags.FLASH_INIT_UI)
public void initUI() {
AlphaAnimation anim = new AlphaAnimation(0.8f, 0.1f);
anim.setDuration(500);
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
Log.d("jumpToMainPage","====onAnimationEnd");
OkBus.getInstance().onEvent(EventTags.JUMP_TO_MAIN);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
mEditText.startAnimation(anim);
}
@Bus(EventTags.JUMP_TO_MAIN)
public void jumpToMainPage() {
// TRouter.go(C.HOME);
SecondActivity.startInstance(this);
Log.d("jumpToMainPage","====jumpToMainPage");
finish();
}
}
经过编译运行之后,class的代码就会被修改为:只列出生成的代码及方法,其它不变的删除了。如下
查看class路径:sample\build\intermediates\classes\debug
protected void onCreate(Bundle savedInstanceState) {
OkBus.getInstance().register(1, (Event)this, -1);
OkBus.getInstance().register(2, (Event)this, -1);
}
public void call(Message var1) {
switch(var1.what) {
case 1:
this.initUI();
break;
case 2:
this.jumpToMainPage();
}
}
protected void onDestroy() {
super.onDestroy();
OkBus.getInstance().unRegister(1);
OkBus.getInstance().unRegister(2);
}
可以看见自动生成了call(msg)方法和注册与发注册的语句。这样就完成了频繁书写注册与反注册麻烦事件。
javassit的用法就是这么简单,其它的可以查看javassit手册或者https://blog.csdn.net/u011425751/article/details/51917895
demo:https://download.csdn.net/download/zhongwn/10450171