本文部分内容包括图片等参考罗升阳的博客,出处链接:http://blog.csdn.net/luoshengyang/article/details/18006645
首先从老罗博客中找到了这张图能够非常清晰显示两者的关系。可以看到dalvik虚拟机和art都是通过调用so库文件的导出函数实现的,而这两个机制的so库导出函数相同,就导致了在接口处可以无缝地替换dalvik:
至于这三个导出函数的具体作用,那就需要看其具体实现的代码了。而这两者在实现上也有很大的区别,dalvik虚拟机执行的是dex字节码,而art执行的则是机器码(应该可以理解为汇编代码)。而无缝替换的还有一个关键点就是不需要重新编译原本能在dalvilk上的apk,这就需要一个将dex字节码转换成机器码的一个过程,这个过程放在了安装apk应用的时候,之前在dalvik上安装apk的时候也会做一个优化,就是将class.dex文件从apk文件中提取出来优化成.odex文件,只是现在把这个过程变成了转换为机器码而已。因此可以说art能够无缝替换dalvilk的原因有两个:
1.在启动过程中,dalvik和art的接口函数相同
2.都有dex文件优化过程
接下来就是分析源码,弄清楚上面两个的过程。根据前一篇笔记,也就是Zygote进程启动终了解到,虚拟机的启动是从AndroidRuntime.start函数中启动的,而启动其他应用程序时的虚拟机都是从zygote中复制过去的,因此只需要了解zygote进程是如何创建虚拟机就行了
1.dalvik和art具有相同的jni接口
1.1.AndroidRuntime.start
首先来看启动代码,首先调用了JniInvocation的init函数来初始化JNI环境,随后调用AndoirdRuntime的startVM函数来启动虚拟机:
void AndroidRuntime::start(const char* className, const char* options)
{
... ...
/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env) != 0) {
return;
}
onVmCreated(env);
... ...
}
1.2.JniInvocation.init
这个函数在libnativehelper\JniInvocation.cpp中,代码如下:
#ifdef HAVE_ANDROID_OS
static const char* kLibrarySystemProperty = "persist.sys.dalvik.vm.lib";
#endif
static const char* kLibraryFallback = "libdvm.so";
bool JniInvocation::Init(const char* library) {
#ifdef HAVE_ANDROID_OS
char default_library[PROPERTY_VALUE_MAX];
property_get(kLibrarySystemProperty, default_library, kLibraryFallback);
#else
const char* default_library = kLibraryFallback;
#endif
if (library == NULL) {
library = default_library;
}
handle_ = dlopen(library, RTLD_NOW);
if (handle_ == NULL) {
if (strcmp(library, kLibraryFallback) == 0) {
// Nothing else to try.
ALOGE("Failed to dlopen %s: %s", library, dlerror());
return false;
}
// Note that this is enough to get something like the zygote
// running, we can't property_set here to fix this for the future
// because we are root and not the system user. See
// RuntimeInit.commonInit for where we fix up the property to
// avoid future fallbacks. http://b/11463182
ALOGW("Falling back from %s to %s after dlopen error: %s",
library, kLibraryFallback, dlerror());
library = kLibraryFallback;
handle_ = dlopen(library, RTLD_NOW);
if (handle_ == NULL) {
ALOGE("Failed to dlopen %s: %s", library, dlerror());
return false;
}
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),
"JNI_GetDefaultJavaVMInitArgs")) {
return false;
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),
"JNI_CreateJavaVM")) {
return false;
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),
"JNI_GetCreatedJavaVMs")) {
return false;
}
return true;
}
在函数的上面还定义了kLibrarySystemProperty和kLibraryFallback两个变量。如果在之前有定义HAVE_ANDROID_OS这个宏,则调用property_get函数从persist.sys.dalvik.vm.lib这个系统属性中取出值,而这个值为"libdvm.so"或"libart.so"决定后面的代码调用的dalvik虚拟机还是art虚拟机的接口。可以看到HAVE_ANDROID_OS这个宏在4.4.2的代码实现中都设置为1,无论是x86、ARM还是MIPS平台:
后面的代码就比计较简单了,从相应的库中(dalvik或art)取出JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM和JNI_GetCreatedJavaVMs三个函数的接口并赋值给相应的变量。
1.3.AndroidRuntime.startVM
startVM函数的简要代码如下:
/*
* Start the Dalvik Virtual Machine.
* Various arguments, most determined by system properties, are passed in.
* The "mOptions" vector is updated.
*/
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
{
... ...
initArgs.version = JNI_VERSION_1_4;
initArgs.options = mOptions.editArray();
initArgs.nOptions = mOptions.size();
initArgs.ignoreUnrecognized = JNI_FALSE;
/*
* Initialize the VM.
*
* The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
* If this call succeeds, the VM is ready, and we can start issuing
* JNI calls.
*/
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
ALOGE("JNI_CreateJavaVM failed\n");
goto bail;
}
result = 0;
}
可以看到这个函数的主要功能就是调用JNI_CreateJavaVM函数来创建虚拟机,而第三个参数initArgs是由前面的代码根据各种各样的系统属性获得的(就像注释中说的那样)。JNI_CreateJavaVM函数的声明如下:
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
return JniInvocation::GetJniInvocation().JNI_CreateJavaVM(p_vm, p_env, vm_args);
}
可以看到它调用了JniInvocation的JNI_CreateJavaVM函数,如下:
jint JniInvocation::JNI_GetCreatedJavaVMs(JavaVM** vms, jsize size, jsize* vm_count) {
return JNI_GetCreatedJavaVMs_(vms, size, vm_count);
}
而它则是调用了函数指针JNIGetCreatedJavaVMs所指向的函数,在之前第一步的分析中得知这个指针指向的是dalvik或art虚拟机so文件中的JNI_CreateJavaVM函数。这篇文章主要关心的是art无缝对接dalvik虚拟机,所以具体后面的虚拟机启动过程就先放一放,等后面专门研究一下。
2.class.dex文件优化
2.1.PackageManagerService
在系统安卓apk文件的时候会调用PackageManagerService服务进行安装,代码在frameworks\base\services\java\com\android\server\pm\PackageManagerService.java中。在这个服务中会调用Install的dexopt函数来对apk文件中的class.dex进行优化 ,简要代码如下:
try {
if (dalvik.system.DexFile.isDexOptNeeded(path)) {
//对dex文件进行优化
mInstaller.dexopt(path, Process.SYSTEM_UID, true);
didDexOpt = true;
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Jar not found: " + path);
} catch (IOException e) {
Slog.w(TAG, "Exception reading jar: " + path, e);
}
2.2 Installer.dexopt
这个函数在文件frameworks\base\services\java\com\android\server\pm\Installer.java中,代码如下,它将参数合并成一个字符串然后调用execute函数:
public int dexopt(String apkPath, int uid, boolean isPublic) {
StringBuilder builder = new StringBuilder("dexopt");
builder.append(' ');
builder.append(apkPath);
builder.append(' ');
builder.append(uid);
builder.append(isPublic ? " 1" : " 0");
return execute(builder.toString());
}
2.3.Installer.execute
首先是execute函数代码,其又调用了transaction函数,并将命令作为参数cmd传入:
private int execute(String cmd) {
String res = transaction(cmd);
try {
return Integer.parseInt(res);
} catch (NumberFormatException ex) {
return -1;
}
}
2.4.Installer.transaction/connect/writeCommand
首先调用了connect函数,如果成功则调用writeCommand并将命令传入:
private synchronized String transaction(String cmd) {
if (!connect()) {
Slog.e(TAG, "connection failed");
return "-1";
}
if (!writeCommand(cmd)) {
... ...
}
}
... ...
}
下面首先来看看connect函数的代码:
private boolean connect() {
//判断是否已经创建了socket套接字
if (mSocket != null) {
return true;
}
Slog.i(TAG, "connecting...");
try {
mSocket = new LocalSocket();
//获取installd的套接字并进行连接
LocalSocketAddress address = new LocalSocketAddress("installd",
LocalSocketAddress.Namespace.RESERVED);
mSocket.connect(address);
//获取socket的输入和输出流以方便与installd进行通信
mIn = mSocket.getInputStream();
mOut = mSocket.getOutputStream();
} catch (IOException ex) {
disconnect();
return false;
}
return true;
}
它会先判断之前是否创建了socket,如果没有那就获取守护进程installd的socket并与其连接。再来看writeCommand的代码:
private boolean writeCommand(String _cmd) {
byte[] cmd = _cmd.getBytes();
int len = cmd.length;
if ((len < 1) || (len > 1024))
return false;
buf[0] = (byte) (len & 0xff);
buf[1] = (byte) ((len >> 8) & 0xff);
try {
//往输出流(相对于自身,相对installd则是输入)写入命令,使installd进程执行
mOut.write(buf, 0, 2);
mOut.write(cmd, 0, len);
} catch (IOException ex) {
Slog.e(TAG, "write error");
disconnect();
return false;
}
return true;
}
向installd进程发送socket请求,让其优化dex文件的代码。
2.5.installd.dexopt
installd的dexopt函数在文件frameworks\native\cmds\installd\Commands.c实现的,代码如下:
int dexopt(const char *apk_path, uid_t uid, int is_public)
{
... ...
/* platform-specific flags affecting optimization and verification */
//获取优化标志的属性
property_get("dalvik.vm.dexopt-flags", dexopt_flags, "");
ALOGV("dalvik.vm.dexopt_flags=%s\n", dexopt_flags);
/* The command to run depend ones the value of persist.sys.dalvik.vm.lib */
//获取persist.sys.dalvik.vm.lib属性
property_get("persist.sys.dalvik.vm.lib", persist_sys_dalvik_vm_lib, "libdvm.so");
//优化之后输出的文件名
sprintf(out_path, "%s%s", apk_path, ".odex");
... ...
pid_t pid;
pid = fork();
if (pid == 0) {
... ...
根据上面获取的属性判断当前环境是dalvik还是art,分别执行对应的代码
if (strncmp(persist_sys_dalvik_vm_lib, "libdvm", 6) == 0) {
run_dexopt(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
} else if (strncmp(persist_sys_dalvik_vm_lib, "libart", 6) == 0) {
run_dex2oat(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
}
... ...
}
... ...
}
可以看到上面仍然是根据"persist.sys.dalvik.vm.lib"属性来判断属于dalvik还是art模式下的,如果是dalvik则执行run_dexopt函数将dex优化成dexy文件,而执行run_dex2oat则将dex优化成自定义的elf文件,但是输出的位置和文件名都是相同的odex。下面再来看这两个函数的代码。
2.6.run_dexopt & run_dex2oat
首先是run_dexopt函数:
static void run_dexopt(int zip_fd, int odex_fd, const char* input_file_name,
const char* output_file_name, const char* dexopt_flags)
{
static const char* DEX_OPT_BIN = "/system/bin/dexopt";
static const int MAX_INT_LEN = 12; // '-'+10dig+'\0' -OR- 0x+8dig
char zip_num[MAX_INT_LEN];
char odex_num[MAX_INT_LEN];
sprintf(zip_num, "%d", zip_fd);
sprintf(odex_num, "%d", odex_fd);
ALOGV("Running %s in=%s out=%s\n", DEX_OPT_BIN, input_file_name, output_file_name);
execl(DEX_OPT_BIN, DEX_OPT_BIN, "--zip", zip_num, odex_num, input_file_name,
dexopt_flags, (char*) NULL);
ALOGE("execl(%s) failed: %s\n", DEX_OPT_BIN, strerror(errno));
}
然后是run_dex2oat函数:
static void run_dex2oat(int zip_fd, int oat_fd, const char* input_file_name,
const char* output_file_name, const char* dexopt_flags)
{
static const char* DEX2OAT_BIN = "/system/bin/dex2oat";
static const int MAX_INT_LEN = 12; // '-'+10dig+'\0' -OR- 0x+8dig
char zip_fd_arg[strlen("--zip-fd=") + MAX_INT_LEN];
char zip_location_arg[strlen("--zip-location=") + PKG_PATH_MAX];
char oat_fd_arg[strlen("--oat-fd=") + MAX_INT_LEN];
char oat_location_arg[strlen("--oat-name=") + PKG_PATH_MAX];
sprintf(zip_fd_arg, "--zip-fd=%d", zip_fd);
sprintf(zip_location_arg, "--zip-location=%s", input_file_name);
sprintf(oat_fd_arg, "--oat-fd=%d", oat_fd);
sprintf(oat_location_arg, "--oat-location=%s", output_file_name);
ALOGV("Running %s in=%s out=%s\n", DEX2OAT_BIN, input_file_name, output_file_name);
execl(DEX2OAT_BIN, DEX2OAT_BIN,
zip_fd_arg, zip_location_arg,
oat_fd_arg, oat_location_arg,
(char*) NULL);
ALOGE("execl(%s) failed: %s\n", DEX2OAT_BIN, strerror(errno));
}
这两个函数都是先获取一堆参数,随后run_dexopt使用了"/system/bin/dexopt"来优化dex文件,而run_dex2oat函数则是使用了"/system/bin/dex2oat"来优化。至于这两个程序是如何对dex文件进行优化的,就要涉及很多语法、词法分析的东西了,系统性的知识没有,要了解这两个过程还是很困难的,所以就先放一放了,后面会再出一个odex文件格式解析,了解这个会相对轻松很多。
至此,art完全兼容dalvik的原因应该已经非常明确了。首先是这两个虚拟机所导出的接口完全一致,使得调用这些接口的代码都无需修改,其次在优化dex文件时系统会判断目前所处的虚拟机状态来进行对应的优化,并且产生的优化文件的全路径完全相同,所以art能在不修改任何apk代码的情况下无缝对接dalvik虚拟机。