- jvm 执行字节码原理:java 程序运行时,是由一个 java 虚拟机来解释 java 字节码的,它将这些字节码翻译成本地 CPU 的指令码,然后执行。
- Android 执行指令码原理:Android 应用程序打包成 dex 包后,通过系统程序 dalvikvm 创建一个虚拟机来执行参数中指定的 java 类。相对于 java 而言,负责解释并执行的就是一个虚拟机,而对于 Linux 而言,这就是一个普通的进程,它与一个只有一行代码的 Hello World 的可执行程序无本质区别。
- Android 启动一个虚拟机的方法跟启动任何一个可执行程序的方法是相同的,在命令行下输入可执行程序的名称,并在参数中指定要执行的 java 类即可。
dalvikvm
dalvikvm 作用:创建一个虚拟机并执行指定的 java 类
dalvikvm 命令:dalvikvm -cp 文件路径 权限类名
如:
dalvikvm -cp /data/app/Demo.dex Demo
实例演示:
-
JVM 执行 java 程序的过程:
- 编译成二进制文件:
javac Demo.java
- 翻译成机器码并执行:
java Demo
/** * 2019-05-18 * java code for simple Demo */ public class Demo { public static void main(String[] args) { System.out.println("Demo:: Hello Android"); } }
- 编译成二进制文件:
-
DVM 执行 java 程序过程:对于 Android 而言,可执行代码需要转化成可执行的 dex 优化文件才能被系统加载执行。核心思想是将字节码文件转 dex 后,由 dvm 翻译执行。
- 打包为 jar 包:
jar cvf Demo.jar Demo.class
- jar 转 dex:
dx --dex --output= Demo1.jar Demo.jar
- 开一个 Android 模拟器,使用 Android 原生或 Genermotion 模拟器即可。
- 挂载设备:
adb root;adb remount
- 安装程序:
adb push Demo1.jar /data/app/Demo.dex
- 执行程序:
adb shell dalvikvm -cp /data/app/Demo.dex Demo
- 打包为 jar 包:
-
DVM 执行 java 程序的过程中可能遇到的错误:
- java sdk 和 dx 工具的要求版本不一致时,解决这种转换问题一般发生在第二步,比如我本地的 java 版本是 1.8.0_101-b13,可以使用 27.0.0 中的 build-tools 下的 dx 工具。
PARSE ERROR: unsupported class file version 52.0 ...while parsing Demo.class 1 error; aborting
- push 了非 dex 优化文件到系统,执行 dalvikvm 命令,出现如下错误:
Unable to locate class 'Demo' java.lang.ClassNotFoundException: Didn't find class "Demo" on path: DexPathList[[zip file "/data/app/Demo.jar"],nativeLibraryDirectories=[/system/lib, /vendor/lib, /system/lib, /vendor/lib]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56) at java.lang.ClassLoader.loadClass(ClassLoader.java:380) at java.lang.ClassLoader.loadClass(ClassLoader.java:312) Suppressed: java.io.IOException: No original dex files found for dex location /data/app/Demo.jar at dalvik.system.DexFile.openDexFileNative(Native Method) at dalvik.system.DexFile.openDexFile(DexFile.java:367) at dalvik.system.DexFile.<init>(DexFile.java:112) at dalvik.system.DexFile.<init>(DexFile.java:77) at dalvik.system.DexPathList.loadDexFile(DexPathList.java:359) at dalvik.system.DexPathList.makeElements(DexPathList.java:323) at dalvik.system.DexPathList.makeDexElements(DexPathList.java:263) at dalvik.system.DexPathList.<init>(DexPathList.java:126) at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48) at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64) at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:224) at java.lang.ClassLoader.-wrap0(ClassLoader.java) at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:183) at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1097) Exception in thread "main" java.lang.ClassNotFoundException: Didn't find class "Demo" on path: DexPathList[[zip file "/data/app/Demo.jar"],nativeLibraryDirectories=[/system/lib, /vendor/lib, /system/lib, /vendor/lib]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56) at java.lang.ClassLoader.loadClass(ClassLoader.java:380) at java.lang.ClassLoader.loadClass(ClassLoader.java:312) Suppressed: java.io.IOException: No original dex files found for dex location /data/app/Demo.jar at dalvik.system.DexFile.openDexFileNative(Native Method) at dalvik.system.DexFile.openDexFile(DexFile.java:367) at dalvik.system.DexFile.<init>(DexFile.java:112) at dalvik.system.DexFile.<init>(DexFile.java:77) at dalvik.system.DexPathList.loadDexFile(DexPathList.java:359) at dalvik.system.DexPathList.makeElements(DexPathList.java:323) at dalvik.system.DexPathList.makeDexElements(DexPathList.java:263) at dalvik.system.DexPathList.<init>(DexPathList.java:126) at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48) at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64) at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:224) at java.lang.ClassLoader.-wrap0(ClassLoader.java) at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:183) at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1097)
这种错误很常见,和之前应用程序加载类,加载函数的错误类似,这种错误如果在 APK 程序内发生。
出现这种情况,一般是应用程序未签名,引用的函数在系统中并不存在。而在 java 程序内,则是 java 程序不正确导致,需要确认此 jar 是否经过 dex 转化或当前虚拟机版本要求的系统优化。
dvz
dvz 作用:从 zygote 进程中孵化出一个新的进程,新的进程也是一个 Dalvik 虚拟机。该进程与 dalvikvm 启动的虚拟机相比,区别在于该进程中已经预装了 Framework 中的大部分类和资源。
dvz 命令:dvz -classpath 文件路径 权限类名
如:
dvz -classpath /system/app/Demo.apk com.my.demo.DemoActivity
关于 Demo.apk,其代码如下:
package com.my.demo;
import android.app.*;
/**
* 2019-05-18
* java code for dvz Demo
*/
public class DemoActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
}
public static void main(String[] args) {
System.out.println("DemoActivity::Hello Android:: DemoActivity");
}
}
上面的 main 函数并不是该程序的入口,只是用来作为开发调试。app 的主入口在 ActivityThread 中。
遗憾的是在系统中暂无 dvz 工具,暂时也未找到 dvz 的相关资料
app_process
app_process 作用:Framework 启动过程中,加载 ZygoteInit.java 和 SystemServer.java,也可以用来调试 java 程序
app_process 命令:app_process -Djava.class.path=文件路径 路径 权限类名
如:app_process -Djava.class.path=/data/app/Demo.dex /data/app Demo
-
app_process 执行 java 程序的过程:
- 创建
Demo.java
- 执行
javac Demo.java
- 打包 jar 文件:
java cvf Demo.jar Demo.class
- jar 转 dex:
dx --dex --output=Demo1.jar Demo.jar
- 挂载设备:
adb root;adb remount
- 将文件 push 到模拟器:
adb push Demo1.jar /data/app/Demo.dex
- 执行命令:
adb shell app_process -Djava.class.path=/data/app/Demo.dex /data/app Demo
- 创建
app_process 命令参数:
app_process [java-options] cmd-dir start-class-name [options]
由 app_process 想到的?
- 系统命令封装:
am
,pm
,wm
,svc
... - 自定义命令封装:
my_tool
适配不同平台工具差异。 - 深度定制
zygote
,framework
,原因是:app_process
是初始化zygote
的入口,属于安卓系统和Framework
启动的一个关键点。 - 优化开机流程,减少开机过程耗时。
补充:
- 补充1:
- 执行
app_process
,每次运行Java
程序时,系统都会给其分配一个pid
,并且进程名是app_process,通过追踪pid
和ppid
,可以发现 fork 进程的大致流程为:/init --> /sbin/adbd -–> /system/bin/sh --> app_process
,不同的安卓版本,会有差异,dalvik
和art
也不完全相同。例如在 Android 9.0 的设备上,流程则变为/init --> zygote -->当前程序
。
- 执行
- 补充2:
-
app_process
启动的Java
程序拥有shell级别的权限,所以像系统中的am
,pm
,wm
,svc
等程序才能执行。在/system/bin
目录下执行:cat /system/bin/am
可以看到它其实并非一个二进制文件,而是一个可执行的 shell 脚本,由app_process
执行了com.android.commands.am.Am "$@"
,其执行原理是在当前窗口,将参数传递给jvm
,找到Am
类的主函数,进行执行。console:/ # cat /system/bin/am #!/system/bin/sh if [ "$1" != "instrument" ] ; then cmd activity "$@" else base=/system export CLASSPATH=$base/framework/am.jar exec app_process $base/bin com.android.commands.am.Am "$@" fi console:/ #
在 java 程序中执行 shellcmd 是可行的,分享一个可执行的案例,使用命令为:
pm -l
:
-
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 2019-05-18
* java code for shell command demo, pm -l
*/
public class DemoShellCmd {
public static void main(String[] args) {
System.out.println("");
System.out.println("");
System.out.println("DemoShellCmd::PMS 程序开始执行...");
String cmd = "pm -l";
try {
Process exec = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(exec.getInputStream()));
String readLine=br.readLine();
while(readLine!=null){
System.out.println(readLine);
readLine=br.readLine();
}
br.close();
exec.destroy();
exec=null;
System.out.println("");
System.out.println("");
System.out.println("DemoShellCmd::PMS 程序执行完成");
} catch (IOException e) {
System.out.println("DemoShellCmd::PMS 程序执行异常");
e.printStackTrace();
}
}
}
- 补充3:
- 进程状态如何查看: cat /proc/$pid/status 即可,其中 /proc 下遍布着系统运行过程中,所有进程 id 的信息,此文件夹下可以浏览你关注的进行状态,内存信息,线程信息...
小结:
如上总结了 DVM 执行 java 程序的三种工具,也是谷歌早期调试 java 程序的重要工具。其中
dvz
工具暂未发现系统中有集成,也少有此工具的相关介绍。重要的是要学会使用dalvikvim
和app_process
工具,并对app_process
的触发和流程做拓展,在源码中理解它的工作过程。欢迎大家下方评论区留言,拍砖,交流。
qq 邮箱: 1281641968@qq.com