序言
最近在写框架,有一个需求就是动态的生成一个类,然后查阅了相关文献,发现在android中动态生成一个类还挺麻烦。因次把一些内容分享出来,帮助大家少走弯路。
方案一 DexMaker
DexMaker 是一个针对 Android 平台的库,用于在运行时生成 Dalvik 字节码。Dalvik 字节码是 Android 应用程序的底层运行代码,而 DexMaker 允许开发人员在运行时动态地创建和修改这些代码。这对于某些类型的测试和动态代码生成非常有用,例如在单元测试中模拟对象或者生成代理类。 DexMaker 主要用于测试框架和其他需要在运行时生成代码的库中。
dexmaker源码
引入dexmaker
c
testCompile "com.crittercism.dexmaker:dexmaker:1.4"
testCompile "com.crittercism.dexmaker:dexmaker-dx:1.4"
testCompile "com.crittercism.dexmaker:dexmaker-mockito:1.4"
缺点
我最开始也是准备用这个库的,但是这个库是基于字节码的,要模拟虚拟机的运行。有点像是在写汇编语言,对使用者来说不是很优化。而且相关的资料也不多。所以准备找一个更友好的库。
方案二 Javassist
Javassist(Java Programming Assistant)是一个用于在运行时编辑字节码的 Java 库。它允许开发人员在运行时创建、编辑和修改 Java 类的字节码,从而实现动态生成类、动态修改类以及实现各种 AOP(面向切面编程)等功能。
Javassist 提供了简单易用的 API,使开发人员能够以编程方式操作字节码,而不必直接操作底层的字节码指令。通过 Javassist,开发人员可以在运行时动态地创建新的类、修改现有类的结构、添加、修改或删除类的字段和方法等。
Javassist 在许多领域都有广泛的应用,包括动态代理、AOP、ORM(对象关系映射)、代码生成等。它被广泛应用于 Java EE、Android 开发以及许多其他 Java 相关的项目中。
最关键的是API更友好,资料更多。
面临的问题
这个库很好用,但是基于的是java的字节码。而android运行的是dex字节码。所以需要在运行时,将java字节码,编译为dex文件,再加载dex文件,加载最后的类。翻遍github只找到了这个库,代码已经是12年前的了。而且已经不再维护了。
运行起来
经过我的不断的改造,终于把源文件改造为gralde形式,可以运行起来了。而且支持android 30。
测试代码
java
public class MainActivity extends Activity {
private static final String DEX_FILE_NAME_MYCLASSES = "myclasses.dex";
private static final boolean FORCE_GENRATE_DEX = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView view = (TextView)findViewById(R.id.textview_title);
final File dexFile = new File(getFilesDir(), DEX_FILE_NAME_MYCLASSES);
if (!dexFile.exists() || FORCE_GENRATE_DEX) {
// generate DEX and ODEX file.
try {
// generate "xxx.class" file via Javassist.
final ClassPool cp = ClassPool.getDefault(getApplicationContext());
final CtClass cls = cp.makeClass("hoge");
final CtConstructor ctor = new CtConstructor(null, cls);
ctor.setBody("{}");
cls.addConstructor(ctor);
final CtMethod m1 = CtMethod.make(
"public java.lang.String toString() { return \"hoge.toString() is called.\"; }",
cls);
cls.addMethod(m1);
final CtMethod m2 = CtMethod.make(
"public void setText(android.widget.TextView view) { view.setText((java.lang.CharSequence)\"hoge.setText() is called 222.\"); }",
cls);
// 创建注解
AnnotationsAttribute attr = new AnnotationsAttribute(m2.getMethodInfo().getConstPool(), AnnotationsAttribute.visibleTag);
Annotation annotation = new Annotation("android.annotation.TargetApi", m2.getMethodInfo().getConstPool());
attr.addAnnotation(annotation);
// 在方法上添加注解
m2.getMethodInfo().addAttribute(attr);
cls.addMethod(m2);
cls.writeFile(getFilesDir().getAbsolutePath());
// convert from "xxx.class" to "xxx.dex"
final DexFile df = new DexFile();
final String dexFilePath = dexFile.getAbsolutePath();
df.addClass(new File(getFilesDir(), "hoge.class"));
df.writeFile(dexFilePath);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
if (dexFile.exists()) {
try {
final DexClassLoader dcl = new DexClassLoader(
dexFile.getAbsolutePath(),
getCacheDir().getAbsolutePath(),
getApplicationInfo().nativeLibraryDir,
getClassLoader());
String title = null;
final Class<?> class_hoge = dcl.loadClass("hoge");
final Constructor<?> ctor = class_hoge.getConstructor(new Class<?>[0]);
final Object obj = ctor.newInstance(new Object[0]);
title = obj.toString();
view.setText(title);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
try {
final Method m = obj.getClass().getDeclaredMethod("setText", TextView.class);
m.invoke(obj, view);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}, 2000);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
生成的dex
反编译查看结果
推荐上面的反编译工具,可以直接把dex反编译为源文件,使用也很简单,拖进去就行了
运行结果
我的项目
送佛送到西,我还把项目发布到了jitpack
仓库地址
groovy
repositories {
maven { url 'https://jitpack.io' }
}
要引入以下两个依赖
groovy
dependencies {
implementation 'com.github.zhuguohui.Android-Javassist:dx:v1.0.2'
implementation 'com.github.zhuguohui.Android-Javassist:javassist-android:v1.0.2'
}