从native中创建一个JVM虚拟机比较简单,引入jni.h后就创建了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(void)
{
JavaVMOption options[n];//创建虚拟机时的选项
JNIEnv *env;//创建虚拟机后的环境变量指针
JavaVM *jvm;//VM对象
JavaVMInitArgs vm_args;//创建虚拟机时的所有选项
long status=JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if(status!=JNI_ERR)
{
//create jvm succ
}
}
但在NDK编程中,这种办法是不可行的,会提示:1
undefined reference to 'JNI_CreateJavaVM'
之类的,错误很明确,找不到JNI_CreateJavaVM这个方法。
原因在于NDK中的jni.h根本没有把相关的方法导出1
2
3
4
5
6
7
8
9
10/*
* VM initialization functions.
*
* Note these are the only symbols exported for JNI by the VM.
*/
jint JNI_GetDefaultJavaVMInitArgs(void*);
jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*);
所以一般的办法是不能按创建JVM的办法去创建一个dalvik虚拟机,需要另外找办法。
我们知道,在4.4后可以在设置中设置选用ART还是dalvik运行时环境,其实就是在选用使用libart.so还是libdvm.so。art模式暂不讨论,系统在为一个应用创建一个虚拟机的时候,使用的就是libdvm.so来创建的,那很有可能libdvm.so里是包含了创建虚拟机的办法的。
在native中加载一个so并使用其中的方法我在另一个文章里简述。>这里
使用dlopen加载libdvm.so1
2
3
4
5
6
7
8
9
10
11
12
13typedef int (*JNI_CreateJavaVM_Type)(JavaVM**,JNIEnv**,void*);//定义一个函数,用于访问JNI_CreateJavaVM的,参数要相同
void *handler=dlopen("/system/lib/libdvm.so",RTLD_LAZY);//art的话要使用libart.so
if(handler)
{
JNI_CreateJavaVM_Type JNI_CreateJavaVM_Func=(JNI_CreateJavaVM_Type)dlsym(handler,"JNI_CreateJavaVM");
if(!JNI_CreateJavaVM_Func)
{
if(JNI_CreateJavaVM_Func(&jvm,&env,&vm_args)>=0)
{
LOGV("create dalvik vm succ");
}
}
}
执行完JNI_CreateJavaVM_Func后,正常的话就可以启动。创建的同时就可以获取到jvm和env两个值了。
这里针对配置参数vm_args进行一点说明。JNI_CreateJavaVM函数的第三个参数为启动VM时的参数,类型为JavaVMInitArgs,一般需要指定以下几个值:
1 | JavaVMInitArgs vm_args; |
需要说明一下的是options,这个值需要设置启动VM时的配置参数,如指定类搜索的路径等,一开始的时候我是使用1
2options[0].optionString="-Djava.class.path=."
vm_args.options=options;
来指定类的搜索路径,但实际上是不work的,思和试了几个小时还没果。
后来想起android中有dalvikvm程序,可以用来创建dalvik vm,查看dalvikvm的帮助后,意外地发现,指定classpath的参数不是使用java.class.path(这特么只是用来创建JVM的=。=),而是”-classpath”,于是很兴奋地改了代码:1
2options[0].optionString="-classpath=.:hello.jar"
vm_args.options=options;
结果还是不行,需要加载的类无法找到。又思了一下,决定还是先在机上调用dalvikvm上使用命令来测试,我写了一个java的测试类:1
2
3
4
5
6
7
8import java.lang.String;
public class Hello{
public static String test(String[] args)
{
return "I am from Java";
}
}
然后javac 编译,dx打包1
2javac Hello.java
dx --dex --output=hello.jar Hello.class
得到一个hello.jar包,放到手机中,然后在手机中执行1
dalvikvm -classpath hello.jar Hello
得到了正确的输出,于是,把参数修改为:1
2options[0].optionString="-classpath hello.jar"
vm_args.options=options;
还是不行,没办法了,看源码,dalvikvm的源码在/dalvik/dalvikvm/Main.cpp下,查看main函数,发现原来参数是按标准输入的形式赋值的,就是说,-classpath与hello.jar应该分开来赋值,于是,修改如下:1
2
3
4
5
6
7
8
9
10JavaVMInitArgs vm_args;
JavaVMOption options[2];//自定义参数
options[0].optionString="-cp"
options[1].optionString="hello.jar"
vm_args.version=JNI_VERSION_1_4;//JVM版本
vm_args.nOptions=2;//自定义参数的个数,上面两个
vm_args.ignoreUnrecognized=JNI_FALSE;//是否忽略未识别的
vm_args.options=options;//设置自定义参数
然后使用一般的JNI方法获取方法,调用就好了。完整的示例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
typedef int (*JNI_CreateJavaVM_Type)(JavaVM**,JNIEnv**,void*);
int main(void)
{
JavaVMOption options[2];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;
long status;
jclass cls;
jmethodID mid;
jfieldID fid;
jobject obj;
options[0].optionString="-cp";
options[1].optionString="hello.jar";
memset(&vm_args,0,sizeof(vm_args));
vm_args.version=JNI_VERSION_1_4;
vm_args.nOptions=2;
vm_args.ignoreUnrecognized=JNI_FALSE;
vm_args.options=options;
char buffer[1024];
getcwd(buffer,1024);
LOGV(buffer);
//void *handle=dlopen("/system/lib/libart.so",RTLD_LAZY);//for ART
void *handle=dlopen("/system/lib/libdvm.so",RTLD_LAZY);//for dalvik
if(!handle)
{
LOGE("dlopen failed");
return 0;
}else{
JNI_CreateJavaVM_Type JNI_CreateJavaVM_Func=(JNI_CreateJavaVM_Type)dlsym(handle,"JNI_CreateJavaVM");
if(!JNI_CreateJavaVM_Func)
{
LOGE("create vm failed");
}else{
LOGV("create vm succ");
if((status=JNI_CreateJavaVM_Func(&jvm,&env,&vm_args))<0)
{
LOGE("Dalvik VM init failed");
}else{
LOGV("Dalvik VM init succ");
if(status!=JNI_ERR)
{
cls=(*env)->FindClass(env,"Hello");
if(cls!=0)
{
LOGV("class found");
mid=(*env)->GetStaticMethodID(env,cls,"test","([Ljava/lang/String;)Ljava/lang/String;");
if(mid==0)
{
LOGE("method not found!");
}else{
const char* name="I am from JNI";
jstring arg=(*env)->NewStringUTF(env,name);
jstring result=(jstring)(*env)->CallStaticObjectMethod(env,cls,mid,arg);
const char* str=(*env)->GetStringUTFChars(env,result,0);
printf("getResult=%s\n",str);
(*env)->ReleaseStringUTFChars(env,str,0);
}
}else{
LOGE("class not found");
}
}
}
}
dlclose(handle);
}
}
打包下载:createVM.tar
=====================================================================
另外,当我尝试在同一个进程中使用上述办法同时创建两个dvm时,给了我一个提示:
搜索到对应的源码如下
// dalvik/vm/Jni.cpp::JNI_CreateJavaVM
if (gDvmJni.jniVm != NULL) {
dvmFprintf(stderr, "ERROR: Dalvik only supports one VM per process\n");
return JNI_ERR;
}
简单来说就是dalvik只支持一个process上有一个vm。
那么,子进程如何呢?
先编写了一段fork进程的代码
pid_t childpid;
int status;
childpid=fork();
if(childpid<0)
{
LOGV("fork()");
}else if(childpid==0)
{
LOGV("child pid = %d,ppid = %d",getpid(),getppid());
createDvm();
}else{
createDvm();
LOGV("parent pid = %d,ppid = %d",getpid(),getppid());
}
在纯native程序中加载dalvik虚拟机,可以两个分支中分别调用上面的创建DVM的代码,事实上也是可以正常运行的。但是如果是在一般的应用中的话就不行。
jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
const JavaVMInitArgs* args = (JavaVMInitArgs*) vm_args;
......
/*
* Set up structures for JNIEnv and VM.
*/
JavaVMExt* pVM = (JavaVMExt*) calloc(1, sizeof(JavaVMExt));
pVM->funcTable = &gInvokeInterface;
pVM->envList = NULL;
dvmInitMutex(&pVM->envListLock);
......
if (gDvmJni.jniVm != NULL) {
dvmFprintf(stderr, "ERROR: Dalvik only supports one VM per process\n");
ALOGV("ERROR: Dalvik only supports one VM per process\n");
return JNI_ERR;
}
gDvmJni.jniVm = (JavaVM*) pVM;
......
/*
* Success! Return stuff to caller.
*/
dvmChangeStatus(NULL, THREAD_NATIVE);
*p_env = (JNIEnv*) pEnv;
*p_vm = (JavaVM*) pVM;
ALOGV("CreateJavaVM succeeded");
return JNI_OK;
}
原因在于应用在启动的时候就优先创建了一个vm,这个vm在调用JNI_CreateJavaVM时,会对gDvmJni进行赋值,gDvmJni定义在/dalvik/vm/Init.cpp中,目测一个应用启动时就只有一个,如果判断gDvmJni.jniVm为空,则把当次创建的JavaVm赋值给它;否则则弹出上述一个Process只能支持一个dalvik的提示;所以,在应用启动的时候因为已经调用了一次JNI_CreateJavaVM了,所以这个值是不为空的,即使在子进程中调用创建虚拟机,同样也绕不开这个问题,就一直报错。事实上如果把createDvm()放到fork之前,也一样会失败。如上边的代码所示。