使用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

相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone5 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui