|
1 | | -# JavaCompileEngine |
2 | | -[](https://jitpack.io/#xiaoyvyv/JavaCompileEngine) |
3 | | -> 这是一个能够在Android上面编译和运行Java代码的引擎,其原理为将Java代码编译为class文件。 |
4 | | -> 再将class文件转换为dex文件(dex是Android平台上(Dalvik虚拟机)的可执行文件)。 |
5 | | -> 最后再运行dex文件,并且代理系统的输入和输出。 |
| 1 | + |
| 2 | +# JavaCompileEngine (支持 JDK8 语法) |
| 3 | + |
| 4 | +[](https://search.maven.org/search?q=io.github.xiaoyvyv%20compiler-d8) |
| 5 | + |
| 6 | +这是一个能够在 `Android` 平台上面编译和运行 `Java` 代码的引擎,支持 `JDK8` 语法编译运行。 |
| 7 | + |
| 8 | +#### 运行原理: |
| 9 | + |
| 10 | +> 1、将 `*.java` 文件编译为 `*.class` 类文件。 |
| 11 | +> 2、通过 `Google` 的 `R8 | D8` 工具将 `*.class` 文件编译为 `Android` 平台能够运行的 `*.dex` 文件。这一步可以脱糖,可以在安卓平台运行 `JDK` 高版本的语法糖,其内部实现基本上还是语法糖转换成了低版本兼容的代码。 |
| 12 | +> 3、将 `*.dex` 文件通过 `DexClassLoader` 加载到应用程序中,并寻找 `main` 方法反射调用。 |
| 13 | +> 4、代理系统的输入`System.in` 和输出 `System.out` 。 |
6 | 14 |
|
7 | 15 |
|
8 | 16 | ## 示例下载 |
|
13 | 21 | |:--|:--|:--| |
14 | 22 | |  | |  | |
15 | 23 |
|
16 | | -## 1.安装 |
| 24 | +## 1、引入依赖 |
17 | 25 | 第一步:在`Project`的`build.gradle`内添加`jitpack.io`仓库 |
18 | | -```kotlin |
| 26 | +```groovy |
19 | 27 | allprojects { |
20 | 28 | repositories { |
21 | | - ... |
22 | | - maven { url 'https://jitpack.io' } |
| 29 | + //... |
| 30 | +
|
| 31 | + mavenCentral() |
23 | 32 | } |
24 | 33 | } |
25 | 34 | ``` |
26 | 35 | 第二步:添加依赖 |
27 | | -```kotlin |
| 36 | +```groovy |
28 | 37 | dependencies { |
29 | | - ... |
30 | | - // 两个都是必须,dx这个引用是转换dex文件相关 |
31 | | - implementation 'com.github.xiaoyvyv.JavaCompileEngine:library:1.1' |
32 | | - implementation 'com.github.xiaoyvyv.JavaCompileEngine:dx:1.1' |
33 | | - ... |
| 38 | + //... |
| 39 | +
|
| 40 | + // 这个依赖比较大(9M 左右),因为内部(assets)包含了一个精简的 Jre,你可以自己选择去除或精简。 |
| 41 | + implementation 'io.github.xiaoyvyv:compiler-d8:<maven-version>' |
34 | 42 | } |
35 | 43 | ``` |
36 | 44 |
|
37 | | -## 2.继承 JavaEngineAplication |
38 | | -将你的`Application`修改为继承于`JavaEngineAplication` |
39 | | -```java |
40 | | -public class YourApplication extends JavaEngineApplication { |
| 45 | +## 2、初始化 |
| 46 | +在你的 `Application` 的 `onCreate` 中初始化库。 |
41 | 47 |
|
42 | | -} |
43 | | -``` |
44 | | -将其并设置到`manifests` |
45 | | -```xml |
46 | | - <application |
47 | | - android:name=".YourApplication" |
48 | | - android:allowBackup="false" |
49 | | - android:icon="@mipmap/ic_launcher" |
50 | | - android:label="@string/app_name" |
51 | | - android:roundIcon="@mipmap/ic_launcher_round" |
52 | | - android:supportsRtl="true" |
53 | | - android:theme="@style/AppTheme.NoActionBar"> |
54 | | - ... |
55 | | - </application> |
56 | | -``` |
57 | | -## 3.创建JavaConsole |
58 | | -`JavaConsole`用于代理系统输入输出 |
59 | | -```java |
60 | | - private TextView printView; |
61 | | - |
62 | | - /** |
63 | | - * Java控制台对象 |
64 | | - */ |
65 | | - private JavaConsole javaConsole; |
66 | | - |
67 | | - @Override |
68 | | - protected void onCreate(Bundle savedInstanceState) { |
69 | | - super.onCreate(savedInstanceState); |
70 | | - |
71 | | - ... |
72 | | - printView = findViewById(R.id.printView); |
73 | | - ... |
74 | | - |
75 | | - // 新建一个控制台对象,传入输出监听(回调为主线程) |
76 | | - javaConsole = new JavaConsole(new JavaConsole.AppendStdListener() { |
77 | | - @Override |
78 | | - public void printStderr(CharSequence err) { |
79 | | - printView.append(err); |
80 | | - } |
| 48 | +```kotlin |
| 49 | +class App : Application() { |
81 | 50 |
|
82 | | - @Override |
83 | | - public void printStdout(CharSequence out) { |
84 | | - printView.append(out); |
85 | | - } |
86 | | - }); |
| 51 | + override fun onCreate() { |
| 52 | + super.onCreate() |
| 53 | + |
| 54 | + // init |
| 55 | + JavaEngine.init(this) |
87 | 56 | } |
| 57 | +} |
88 | 58 | ``` |
89 | 59 |
|
90 | | -## 4.编译 Java文件 |
91 | | -`ClassCompiler`提供了相关的方法,具体请查阅 [ClassCompiler.java](https://github.com/xiaoyvyv/JavaCompileEngine/blob/master/library/src/main/java/com/xiaoyv/javaengine/compile/ClassCompiler.java) |
92 | | - |
93 | | -```java |
94 | | - /** |
95 | | - * 开始编译 |
96 | | - */ |
97 | | - private void compileJava() { |
98 | | - printView.setText(null); |
99 | | - |
100 | | - // 创建Java文件,写入代码内容 |
101 | | - String javaFilePath = PathUtils.getExternalAppFilesPath() + "/SingleExample/Main.java"; |
102 | | - FileIOUtils.writeFileFromString(javaFilePath, String.valueOf(codeText.getText())); |
103 | | - |
104 | | - // 保存 Class文件 的文件夹 |
105 | | - String saveClassFolder = PathUtils.getExternalAppFilesPath() + "/SingleExample/Class"; |
106 | | - |
107 | | - // 编译 Java 文件 |
108 | | - JavaEngine.getClassCompiler().compile(javaFilePath, saveClassFolder, new CompilerListener() { |
109 | | - @Override |
110 | | - public void onSuccess(String path) { |
111 | | - LogUtils.e(path); |
112 | | - // 编译成功 path 为class文件路径 |
113 | | - // 接下来将 class文件 转换为 Dex文件 |
114 | | - compileDex(path); |
115 | | - } |
| 60 | +## 3、编译 Java 文件 |
| 61 | +`JavaClassCompiler` 提供了相关的方法,具体请查阅 [JavaClassCompiler.kt](https://github.com/xiaoyvyv/JavaCompileEngine/blob/master-d8/compiler-d8/src/main/java/com/xiaoyv/java/compiler/tools/java/JavaClassCompiler.kt)。 |
116 | 62 |
|
117 | | - @Override |
118 | | - public void onError(Throwable error) { |
119 | | - // 编译失败 |
120 | | - printView.setText(Html.fromHtml("<font color=\"#F00\">" + error.toString() + "</font>")); |
121 | | - } |
122 | | - }); |
123 | | - } |
| 63 | +> 注意该方法需要配合协程使用,在协程的作用域内调用 |
| 64 | +
|
| 65 | +```kotlin |
| 66 | +// 编译 class,libFolder 为第三方 jar 存放目录,没有传空即可 |
| 67 | +// 编译完成返回目标 classes.jar,内部通过协程在 IO 线程处理的 |
| 68 | +val compileJar: File = JavaEngine.classCompiler.compile( |
| 69 | + sourceFileOrDir = javaFilePath, |
| 70 | + buildDir = buildDir, |
| 71 | + libFolder = null |
| 72 | +) { taskName, progress -> |
| 73 | + |
| 74 | + // 这里是进度,回调在主线程... |
| 75 | + } |
124 | 76 | ``` |
125 | | -## 5.转换 class文件 到 dex文件 |
126 | | -`DexCompiler`提供了相关的方法,具体请查阅 [DexCompiler.java](https://github.com/xiaoyvyv/JavaCompileEngine/blob/master/library/src/main/java/com/xiaoyv/javaengine/compile/DexCompiler.java) |
127 | | - |
128 | | -```java |
129 | | - /** |
130 | | - * 将 Class文件 转换为 Dex文件 |
131 | | - * |
132 | | - * @param classFilePath class文件路径 |
133 | | - */ |
134 | | - private void compileDex(String classFilePath) { |
135 | | - // 先创建 Dex空白文件(不能创建在 classFilePath 的同级或子目录) |
136 | | - String dexFilePath = PathUtils.getExternalAppFilesPath() + "/SingleExample/Dex/Main.dex"; |
137 | | - |
138 | | - JavaEngine.getDexCompiler().compile(classFilePath, dexFilePath, new CompilerListener() { |
139 | | - @Override |
140 | | - public void onSuccess(String dexPath) { |
141 | | - LogUtils.e(dexPath); |
142 | | - // 编译成功 path 为dex文件路径 |
143 | | - // 接下运行 dex文件 |
144 | | - runDex(dexPath); |
145 | | - } |
| 77 | +## 4、转换 class文件 到 dex文件 |
| 78 | +`DexCompiler` 提供了相关的方法,具体请查阅 [JavaDexCompiler.kt](https://github.com/xiaoyvyv/JavaCompileEngine/blob/master-d8/compiler-d8/src/main/java/com/xiaoyv/java/compiler/tools/dex/JavaDexCompiler.kt) |
146 | 79 |
|
147 | | - @Override |
148 | | - public void onError(Throwable error) { |
149 | | - // 转换失败 |
150 | | - printView.setText(Html.fromHtml("<font color=\"#F00\">" + error.toString() + "</>")); |
151 | | - } |
152 | | - }); |
153 | | - } |
| 80 | +> 注意该方法需要配合协程使用,在协程的作用域内调用 |
| 81 | +
|
| 82 | +```kotlin |
| 83 | +// 编译 classes.dex,这一步相关的信息通过 System.xxx.print 输出 |
| 84 | +val dexFile = JavaEngine.dexCompiler.compile(compileJar.absolutePath, buildDir) |
154 | 85 | ``` |
155 | | -## 6.运行 dex文件 |
156 | | -运行 Dex文件前,必须开启控制台`javaConsole.start()`,程序执行完成后必须关闭控制台`javaConsole.stop()`。程序运行中时禁止重复运行(如,程序等待用户输入时会挂起,此时严禁再次运行程序,需要完成挂起的程序方可继续运行新程序) |
157 | | -`DexExecutor`提供了相关的方法,具体请查阅 [DexExecutor.java](https://github.com/xiaoyvyv/JavaCompileEngine/blob/master/library/src/main/java/com/xiaoyv/javaengine/executor/DexExecutor.java) |
158 | | -```java |
159 | | - /** |
160 | | - * 运行 Dex文件 |
161 | | - * |
162 | | - * @param dexPath Dex文件路径 |
163 | | - */ |
164 | | - private void runDex(String dexPath) { |
165 | | - // 运行 Dex文件前,必须开启控制台 |
166 | | - // 运行 Dex文件前,必须开启控制台 |
167 | | - // 运行 Dex文件前,必须开启控制台 |
168 | | - javaConsole.start(); |
169 | | - |
170 | | - // 第二个参数为运行时,传入 main(String[] args)方法 的参数 args |
171 | | - String[] args = new String[]{}; |
172 | | - JavaEngine.getDexExecutor().exec(dexPath, args, new ExecuteListener() { |
173 | | - @Override |
174 | | - public void onExecuteFinish() { |
175 | | - // 运行完成,关闭控制台 |
176 | | - javaConsole.stop(); |
177 | | - } |
| 86 | +## 5、运行 dex文件 |
| 87 | +`JavaProgram.kt` 提供了相关的方法,具体请查阅 JavaProgram.kt](https://github.com/xiaoyvyv/JavaCompileEngine/blob/master-d8/compiler-d8/src/main/java/com/xiaoyv/java/compiler/tools/exec/JavaProgram.kt) |
| 88 | + |
| 89 | +注意 `chooseMainClassToRun` 默认实现是选中匹配到的第一个 `main` 方法进行运行,你可以自己在该方法回调内去选择需要执行的方法。 |
| 90 | +- chooseMainClassToRun 第一个回调参数:匹配到的包含 `main` 方法的类 |
| 91 | +- chooseMainClassToRun 第二个回调参数:协程相关的回调,需要将选择的类回调回去 |
| 92 | + |
| 93 | +<font color="#ff0000">注意:chooseMainClassToRun 回调会使内部协程一直挂起,你应该及时的通过 continuation.resume() 或 resume.resumeWithException() 让内部知道处理结果,禁止忽略 continuation 的回调,否则会在后台一直占用资源</font> |
| 94 | + |
| 95 | +`chooseMainClassToRun`、`printOut`、`printErr` 回调均在主线程,可以进行 UI 操作。 |
178 | 96 |
|
179 | | - @Override |
180 | | - public void onExecuteError(Throwable error) { |
181 | | - // 运行完成,关闭控制台 |
182 | | - javaConsole.stop(); |
| 97 | +`run` 方法运行完成(并不代表程序执行完成,例如你的代码启动了其他线程)会返回一个 `programConsole` 句柄,可以用于关闭输入输出流。 |
183 | 98 |
|
184 | | - // 执行出错 |
185 | | - printView.setText(Html.fromHtml("<font color=\"#F00\">" + error.toString() + "</>")); |
| 99 | +> 注意该方法需要配合协程使用,在协程的作用域内调用 |
| 100 | +
|
| 101 | +```kotlin |
| 102 | +// JavaEngine. |
| 103 | +val programConsole = JavaEngine.javaProgram.run(dexFile, arrayOf("args"), |
| 104 | + chooseMainClassToRun = { classes, continuation -> |
| 105 | + val dialog = AlertDialog.Builder(this@CompileActivity) |
| 106 | + .setTitle("请选择一个主函数运行") |
| 107 | + .setItems(classes.toTypedArray()) { p0, p1 -> |
| 108 | + p0.dismiss() |
| 109 | + continuation.resume(classes[p1]) |
186 | 110 | } |
187 | | - }); |
188 | | - } |
| 111 | + .setCancelable(false) |
| 112 | + .setNegativeButton("取消") { d, v -> |
| 113 | + d.dismiss() |
| 114 | + continuation.resumeWithException(Exception("取消操作")) |
| 115 | + }.create() |
| 116 | + |
| 117 | + dialog.show() |
| 118 | + dialog.setCanceledOnTouchOutside(false) |
| 119 | + }, |
| 120 | + printOut = { |
| 121 | + binding.printView.append(it) |
| 122 | + }, |
| 123 | + printErr = { |
| 124 | + binding.printView.append(it) |
| 125 | + }) |
189 | 126 | ``` |
190 | | -## 7.输入数据 如 Scanner等等的处理 |
191 | | -直接调用` JavaConsole.`的`inputStdin(String stdin)`方法即可输入数据。 |
192 | | -```java |
193 | | - javaConsole.inputStdin(str); |
| 127 | +## 6、输入数据 如 Scanner等等的处理 |
| 128 | +直接调用 `programConsole.` 的 `inputStdin(String stdin)` 方法即可输入数据。 |
| 129 | +```kotlin |
| 130 | + programConsole.inputStdin(str) |
194 | 131 | ``` |
| 132 | +## 7、编译相关设置 |
| 133 | +`JavaEngine.compilerSetting` 提供了相关配置。[JavaEngineSetting.kt](https://github.com/xiaoyvyv/JavaCompileEngine/blob/master-d8/compiler-d8/src/main/java/com/xiaoyv/java/compiler/JavaEngineSetting.kt) |
195 | 134 |
|
196 | | -## 8.问题 |
197 | | -更多内容请查阅 [Demo](https://github.com/xiaoyvyv/JavaCompileEngine/tree/master/app) 和源码 [library](https://github.com/xiaoyvyv/JavaCompileEngine/tree/master/library/) |
| 135 | +## 8、问题 |
| 136 | +更多内容请查阅 [Demo](https://github.com/xiaoyvyv/JavaCompileEngine/tree/master/app) 和源码 [compiler-d8](https://github.com/xiaoyvyv/JavaCompileEngine/tree/master/compiler-d8/) |
198 | 137 |
|
199 | | -## 9.反馈 |
| 138 | +## 9、反馈 |
200 | 139 | QQEmail:1223414335@qq.com |
201 | 140 |
|
202 | 141 |
|
|
0 commit comments