使用Javassist 在android运行时生成类

序言

最近在写框架,有一个需求就是动态的生成一个类,然后查阅了相关文献,发现在android中动态生成一个类还挺麻烦。因次把一些内容分享出来,帮助大家少走弯路。

方案一 DexMaker

DexMaker 是一个针对 Android 平台的库,用于在运行时生成 Dalvik 字节码。Dalvik 字节码是 Android 应用程序的底层运行代码,而 DexMaker 允许开发人员在运行时动态地创建和修改这些代码。这对于某些类型的测试和动态代码生成非常有用,例如在单元测试中模拟对象或者生成代理类。 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

反编译查看结果

运行结果

我的项目

送佛送到西,我还把项目发布到了jitpack

仓库地址

groovy 复制代码
	repositories {
		
			maven { url 'https://jitpack.io' }
		}

要引入以下两个依赖

groovy 复制代码
	dependencies {
	        implementation 'com.github.zhuguohui.Android-Javassist:dx:v1.0.0'
	         implementation 'com.github.zhuguohui.Android-Javassist:javassist-android:v1.0.0'
	}

项目源码

zhuguohui/Android-Javassist

相关推荐
图王大胜26 分钟前
Android Framework AMS(17)APP 异常Crash处理流程解读
android·app·异常处理·ams·crash·binderdied·讣告
ch_s_t1 小时前
电子商务网站之首页设计
android
豆 腐3 小时前
MySQL【四】
android·数据库·笔记·mysql
想取一个与众不同的名字好难5 小时前
android studio导入OpenCv并改造成.kts版本
android·ide·android studio
Jewel1056 小时前
Flutter代码混淆
android·flutter·ios
Yawesh_best7 小时前
MySQL(5)【数据类型 —— 字符串类型】
android·mysql·adb
曾经的三心草10 小时前
Mysql之约束与事件
android·数据库·mysql·事件·约束
guoruijun_2012_414 小时前
fastadmin多个表crud连表操作步骤
android·java·开发语言
Winston Wood14 小时前
一文了解Android中的AudioFlinger
android·音频
B.-15 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio