- jvm 執(zhí)行字節(jié)碼原理:java 程序運(yùn)行時(shí),是由一個(gè) java 虛擬機(jī)來解釋 java 字節(jié)碼的,它將這些字節(jié)碼翻譯成本地 CPU 的指令碼代乃,然后執(zhí)行。
- Android 執(zhí)行指令碼原理:Android 應(yīng)用程序打包成 dex 包后仿粹,通過系統(tǒng)程序 dalvikvm 創(chuàng)建一個(gè)虛擬機(jī)來執(zhí)行參數(shù)中指定的 java 類搁吓。相對(duì)于 java 而言,負(fù)責(zé)解釋并執(zhí)行的就是一個(gè)虛擬機(jī)吭历,而對(duì)于 Linux 而言堕仔,這就是一個(gè)普通的進(jìn)程,它與一個(gè)只有一行代碼的 Hello World 的可執(zhí)行程序無本質(zhì)區(qū)別晌区。
- Android 啟動(dòng)一個(gè)虛擬機(jī)的方法跟啟動(dòng)任何一個(gè)可執(zhí)行程序的方法是相同的摩骨,在命令行下輸入可執(zhí)行程序的名稱,并在參數(shù)中指定要執(zhí)行的 java 類即可。
dalvikvm
dalvikvm 作用:創(chuàng)建一個(gè)虛擬機(jī)并執(zhí)行指定的 java 類
dalvikvm 命令:dalvikvm -cp 文件路徑 權(quán)限類名
如:
dalvikvm -cp /data/app/Demo.dex Demo
實(shí)例演示:
-
JVM 執(zhí)行 java 程序的過程:
- 編譯成二進(jìn)制文件:
javac Demo.java
- 翻譯成機(jī)器碼并執(zhí)行:
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"); } }
- 編譯成二進(jìn)制文件:
-
DVM 執(zhí)行 java 程序過程:對(duì)于 Android 而言,可執(zhí)行代碼需要轉(zhuǎn)化成可執(zhí)行的 dex 優(yōu)化文件才能被系統(tǒng)加載執(zhí)行径簿。核心思想是將字節(jié)碼文件轉(zhuǎn) dex 后,由 dvm 翻譯執(zhí)行灾馒。
- 打包為 jar 包:
jar cvf Demo.jar Demo.class
- jar 轉(zhuǎn) dex:
dx --dex --output= Demo1.jar Demo.jar
- 開一個(gè) Android 模擬器,使用 Android 原生或 Genermotion 模擬器即可遣总。
- 掛載設(shè)備:
adb root;adb remount
- 安裝程序:
adb push Demo1.jar /data/app/Demo.dex
- 執(zhí)行程序:
adb shell dalvikvm -cp /data/app/Demo.dex Demo
- 打包為 jar 包:
-
DVM 執(zhí)行 java 程序的過程中可能遇到的錯(cuò)誤:
- java sdk 和 dx 工具的要求版本不一致時(shí)你虹,解決這種轉(zhuǎn)換問題一般發(fā)生在第二步,比如我本地的 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 優(yōu)化文件到系統(tǒng)夯辖,執(zhí)行 dalvikvm 命令琉预,出現(xiàn)如下錯(cuò)誤:
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)
這種錯(cuò)誤很常見,和之前應(yīng)用程序加載類蒿褂,加載函數(shù)的錯(cuò)誤類似圆米,這種錯(cuò)誤如果在 APK 程序內(nèi)發(fā)生卒暂。
出現(xiàn)這種情況,一般是應(yīng)用程序未簽名娄帖,引用的函數(shù)在系統(tǒng)中并不存在也祠。而在 java 程序內(nèi),則是 java 程序不正確導(dǎo)致近速,需要確認(rèn)此 jar 是否經(jīng)過 dex 轉(zhuǎn)化或當(dāng)前虛擬機(jī)版本要求的系統(tǒng)優(yōu)化诈嘿。
dvz
dvz 作用:從 zygote 進(jìn)程中孵化出一個(gè)新的進(jìn)程,新的進(jìn)程也是一個(gè) Dalvik 虛擬機(jī)削葱。該進(jìn)程與 dalvikvm 啟動(dòng)的虛擬機(jī)相比奖亚,區(qū)別在于該進(jìn)程中已經(jīng)預(yù)裝了 Framework 中的大部分類和資源。
dvz 命令:dvz -classpath 文件路徑 權(quán)限類名
如:
dvz -classpath /system/app/Demo.apk com.my.demo.DemoActivity
關(guān)于 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 函數(shù)并不是該程序的入口昔字,只是用來作為開發(fā)調(diào)試。app 的主入口在 ActivityThread 中首繁。
遺憾的是在系統(tǒng)中暫無 dvz 工具作郭,暫時(shí)也未找到 dvz 的相關(guān)資料
app_process
app_process 作用:Framework 啟動(dòng)過程中,加載 ZygoteInit.java 和 SystemServer.java弦疮,也可以用來調(diào)試 java 程序
app_process 命令:app_process -Djava.class.path=文件路徑 路徑 權(quán)限類名
如:app_process -Djava.class.path=/data/app/Demo.dex /data/app Demo
-
app_process 執(zhí)行 java 程序的過程:
- 創(chuàng)建
Demo.java
- 執(zhí)行
javac Demo.java
- 打包 jar 文件:
java cvf Demo.jar Demo.class
- jar 轉(zhuǎn) dex:
dx --dex --output=Demo1.jar Demo.jar
- 掛載設(shè)備:
adb root;adb remount
- 將文件 push 到模擬器:
adb push Demo1.jar /data/app/Demo.dex
- 執(zhí)行命令:
adb shell app_process -Djava.class.path=/data/app/Demo.dex /data/app Demo
- 創(chuàng)建
app_process 命令參數(shù):
app_process [java-options] cmd-dir start-class-name [options]
由 app_process 想到的夹攒?
- 系統(tǒng)命令封裝:
am
,pm
,wm
,svc
... - 自定義命令封裝:
my_tool
適配不同平臺(tái)工具差異。 - 深度定制
zygote
挂捅,framework
芹助,原因是:app_process
是初始化zygote
的入口,屬于安卓系統(tǒng)和Framework
啟動(dòng)的一個(gè)關(guān)鍵點(diǎn)闲先。 - 優(yōu)化開機(jī)流程状土,減少開機(jī)過程耗時(shí)。
補(bǔ)充:
- 補(bǔ)充1:
- 執(zhí)行
app_process
伺糠,每次運(yùn)行Java
程序時(shí)蒙谓,系統(tǒng)都會(huì)給其分配一個(gè)pid
,并且進(jìn)程名是app_process训桶,通過追蹤pid
和ppid
累驮,可以發(fā)現(xiàn) fork 進(jìn)程的大致流程為:/init --> /sbin/adbd -–> /system/bin/sh --> app_process
,不同的安卓版本舵揭,會(huì)有差異谤专,dalvik
和art
也不完全相同。例如在 Android 9.0 的設(shè)備上午绳,流程則變?yōu)?/init --> zygote -->當(dāng)前程序
置侍。
- 執(zhí)行
- 補(bǔ)充2:
-
app_process
啟動(dòng)的Java
程序擁有shell級(jí)別的權(quán)限,所以像系統(tǒng)中的am
,pm
,wm
,svc
等程序才能執(zhí)行。在/system/bin
目錄下執(zhí)行:cat /system/bin/am
可以看到它其實(shí)并非一個(gè)二進(jìn)制文件蜡坊,而是一個(gè)可執(zhí)行的 shell 腳本杠输,由app_process
執(zhí)行了com.android.commands.am.Am "$@"
,其執(zhí)行原理是在當(dāng)前窗口秕衙,將參數(shù)傳遞給jvm
蠢甲,找到Am
類的主函數(shù),進(jìn)行執(zhí)行据忘。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 程序中執(zhí)行 shellcmd 是可行的鹦牛,分享一個(gè)可執(zhí)行的案例,使用命令為:
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 程序開始執(zhí)行...");
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 程序執(zhí)行完成");
} catch (IOException e) {
System.out.println("DemoShellCmd::PMS 程序執(zhí)行異常");
e.printStackTrace();
}
}
}
- 補(bǔ)充3:
- 進(jìn)程狀態(tài)如何查看: cat /proc/$pid/status 即可若河,其中 /proc 下遍布著系統(tǒng)運(yùn)行過程中能岩,所有進(jìn)程 id 的信息,此文件夾下可以瀏覽你關(guān)注的進(jìn)行狀態(tài)萧福,內(nèi)存信息拉鹃,線程信息...
小結(jié):
如上總結(jié)了 DVM 執(zhí)行 java 程序的三種工具,也是谷歌早期調(diào)試 java 程序的重要工具鲫忍。其中
dvz
工具暫未發(fā)現(xiàn)系統(tǒng)中有集成膏燕,也少有此工具的相關(guān)介紹。重要的是要學(xué)會(huì)使用dalvikvim
和app_process
工具悟民,并對(duì)app_process
的觸發(fā)和流程做拓展坝辫,在源碼中理解它的工作過程。歡迎大家下方評(píng)論區(qū)留言射亏,拍磚近忙,交流。
qq 郵箱: 1281641968@qq.com