Android JNI介绍
附:相关代码路径
/frameworks/base/media/java/android/media/MediaScanner.java
/frameworks/base/media/jni/android_media_MediaScanner.cpp
/frameworks/base/media/jni/android_media_MediaPlayer.cpp
/franmeworks/base/core/jni/AndroidRunTime.cpp
/dalvik/libnativehelper/JNIHelp.cpp
一、什么是JNI
JavaNativeInterface(JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI是本地编程接口,它使得在Java虚拟机(VM)内部运行的Java代码能够与用其它编程语言(如C、C++和汇编语言)编写的应用程序和库进行交互操作。
二、为什么推出JNI
1)、Java世界虚拟机使用Native语言编写的,虚拟机运行在具体的平台上,虚拟机本身无法做到与平台无关,jni可以对java层屏蔽不同平台操作的差异,这样就能实现java本身平台无关特性
2)、适应已经用Native语言实现的技术。
3)、一些效率的问题需要Native语言实现。
Android中JNI就是一座将Native层和java层联系在一起的桥梁。(本文将参考《深入理解Android卷一》以MediaScanner为例)
三、什么时候使用JNI
1)、JavaAPI可能不支某些平台相关的功能。比如,应用程序执行中要使用JavaAPI不
支持的文件类型,而如果使用跨进程操作方式,即繁琐又低效
2)、避免进程间低效的数据拷贝操作
3)、多进程的派生:耗时、耗资源(内存)
4)、用本地代码或汇编代码重写Java中低效方法
注意:
当Java程序集成了本地代码,它将丢掉Java的一些好处。
首先,脱离Java后,可移植性问题你要自己解决,且需重新在其他平台编译链接本地库。
第二,要小心处理JNI编程中各方面问题和来自C/C++语言本身的细节性问题,处理不当,应用将崩溃。
一般性原则:做好应用程序架构,使nativemethods定义在尽可能少的几个类里。
四、AndroidJNI使用流程解析
以MediaScanner为例
1)、载入*.so库文件
VM去载入Android的/system/lib/libmedia_jni.so档案。载入*.so之后,Java类与*.so档案就汇合起来,一起执行了。
-->MediaScanner.java
......
static{
System.loadLibrary("media_jni");
native_init();
}
......
privatestaticnativefinalvoidnative_init();
......
<--
2)、如何撰写*.so的入口函数
JNI_OnLoad()与JNI_OnUnload()函数的用途,当Android的VM(VirtualMachine)执行到System.loadLibrary()函数时,首先会去执行C组件里的JNI_OnLoad()函数。它的用途有二:
(1)告诉VM此C组件使用那一个JNI版本。如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的JNI1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
(2)由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),所以
C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization)。
例如,在Android的/system/lib/libmedia_jni.so档案里,就提供了JNI_OnLoad()函数。
由于VM通常是多执行绪(Multi-threading)的执行环境。每一个执行绪在呼叫JNI_OnLoad()时,所传递进来的JNIEnv指标值都是不同的。为了配合这种多执行绪的环境,C组件开发者在撰写本地函数时,可藉由JNIEnv指标值之不同而避免执行绪的资料冲突问题,才能确保所写的本地函数能安全地在Android的多执行绪VM里安全地执行。基于这个理由,当在呼叫C组件的函数时,都会将JNIEnv指标值传递给它。
-->android_media_MediaPlayer.cpp
......
externintregister_android_media_MediaScanner(JNIEnv*env);
......
jintJNI_OnLoad(JavaVM*vm,void*reserved)
{
JNIEnv*env=NULL;
jintresult=-1;
if(vm->GetEnv((void**)&env,JNI_VERSION_1_4)!=JNI_OK){
ALOGE("ERROR:GetEnvfailed\n");
gotobail;
}
assert(env!=NULL);
......
if(register_android_media_MediaScanner(env)<0){
ALOGE("ERROR:MediaScannernativeregistrationfailed\n");
gotobail;
}
......
<--
3)、JNI注册,使javanative函数和JNI函数一一对应(动态注册)
JNI中记录javanative函数和JNI函数一一对应的结构
typedefstruct{
//java中native函数的名字如”native_init”,不带函数路径
constchar*name;
//java函数的签名信息,含参数和返回值
constchar*signature;
//JNI层对应函数的指针,是void类型
void*fnPtr;
}JNINativeMethod;
如(2)中所述,在调用*.so入口函数JNI_OnLoad时有调用register_android_media_MediaScanner(env)等方法。
-->andorid_media_MediaScanner.cpp
......
staticJNINativeMethodgMethods[]={
......
{
"native_init",
"()V",
(void*)android_media_MediaScanner_native_init
},
......
}
......
intregister_android_media_MediaScanner(JNIEnv*env)
{
returnAndroidRuntime::registerNativeMethods(env,
kClassMediaScanner,gMethods,NELEM(gMethods));
}
......
<--
-->AndroidRunTime.cpp
......
/*
*RegisternativemethodsusingJNI.
*/
/*static*/intAndroidRuntime::registerNativeMethods(JNIEnv*env,
constchar*className,constJNINativeMethod*gMethods,intnumMethods)
{
returnjniRegisterNativeMethods(env,className,gMethods,numMethods);
}
......
<--
向VM(即AndroidRuntime)登记gMethods[]表格所含的本地函数了。简而言之,registerNativeMethods()函数的用途有二:(1)更有效率去找到函数。(2)可在执行期间进行抽换。由于gMethods[]是一个<名称,函数指针>对照表,在程序执行时,可多次呼叫registerNativeMethods()函数来更换本地函数之指针,而达到弹性抽换本地函数之目的。
--->JNIHelp.cpp
......
staticjclassfindClass(C_JNIEnv*env,constchar*className){
JNIEnv*e=reinterpret_cast<JNIEnv*>(env);
return(*env)->FindClass(e,className);
}
extern"C"intjniRegisterNativeMethods(C_JNIEnv*env,constchar*className,
constJNINativeMethod*gMethods,intnumMethods)
{
JNIEnv*e=reinterpret_cast<JNIEnv*>(env);
LOGV("Registering%snatives",className);
scoped_local_ref<jclass>c(env,findClass(env,className));//没有看明白
if(c.get()==NULL){
LOGE("Nativeregistrationunabletofindclass'%s',aborting",className);
abort();
}
if((*env)->RegisterNatives(e,c.get(),gMethods,numMethods)<0){
LOGE("RegisterNativesfailedfor'%s',aborting",className);
abort();
}
return0;
}
......
<---
看似很繁琐,其实动态注册只用了两个函数完成。
(*env)->FindClass(e,className);
(*env)->RegisterNatives(e,c.get(),gMethods,numMethods)
如果想要完成动态注册,就必须实现JNI_OnLoad函数
这JNI_OnLoad()呼叫register_android_media_MeidaScnaner(env)函数时,就将env指标值传递过去。如此,在register_android_media_MeidaScnaner()函数就能藉由该指标值而区
别不同的执行绪,,以便化解资料冲突的问题。
例如,在register_android_media_MeidaScnaner()函数里,可撰写下述指令:
if((*env)->MonitorEnter(env,obj)!=JNI_OK){
}
查看是否已经有其他执行绪进入此物件,如果没有,此执行绪就进入该物件里执行了。
还有,也可撰写下述指令:
if((*env)->MonitorExit(env,obj)!=JNI_OK){
}
查看是否此执行绪正在此物件内执行,如果是,此执行绪就会立即离开。
五、JNI内容简单介绍
1)、JNIEnv是一个与线程相关的变量
2)、native函数转换成JNI函数时,虚拟机会传入JNIEnv变量,如果后台线程主动回调java层函数是在JNI_OnLoad函数中JavaVM会产生JNIEnv变量,在线程退出时,会释放对应的资源
插入内容:JNI签名
Java中有函数重载,区别在于函数的参数,JNI则是通过函数返回值和函数参数合成一个签名信息与java中的函数对应。
如下:签名标示表
类型标识 | Java类型 | 类型标识 | Java类型 |
Z | boolean | F | float |
B | byte | D | double |
C | char | L/java/lang/String; | String |
S | short | [I | Int[] |
I | int | [L/java/lang/Object; | Object[] |
J | long |
注意:Java类型是一个数组,则标识中会有一个”[”,引用类型最后都有一个’;’。
例如:
函数签名 | Java函数 |
“()Ljava/lang/String;” | Stringf() |
“(ILjava/lang/Class)J” | longf(inti,Classclass) |
“([B)V” | voidf(byte[]byte) |
附:用javap-s-pxxx(编译生成的.class文件)可以查看xxx中函数对应的签名。
1)、jfieldID和jmethodID介绍和使用
我们知道,成员变量和成员函数都是由类定义的,他们都是类的属性,所以在JNI规则中用jfieldID和jmethodID来标示java类成员和函数。JNI中可以通过如下方式操作。
jfieldIDfid=(*env)->GetFieldID(env,cls,"s","Ljava/lang/String;");
cls:代表java类
"s":成员变量的名字
"Ljava/lang/String;":变量的签名信息
这时,通过在对象上调用下述方法获得成员的值:
jstr=(*env)->GetObjectField(env,obj,fid);
通过在对象上调用下述方法改变成员的值:
(*env)->SetObjectField(env,obj,fid,field);
此外JNI还提供Get/SetIntField,Get/SetFloatField等访问不同类型成员。
同样,也提供了static变量的访问方法,即Get/SetStatic<ReturnValueType>Field。
jmethodIDfid=(*env)->GetMethodID(env,cls,"s","Ljava/lang/String;");
Java中有三类方法:实例方法、静态方法和构造方法。示例:
Java代码: classInstanceMethodCall{ privatenativevoidnativeMethod(); privatevoidcallback(){ System.out.println("InJava"); } publicstaticvoidmain(Stringargs[]){ InstanceMethodCallc=newInstanceMethodCall(); c.nativeMethod(); } static{ System.loadLibrary("InstanceMethodCall"); } } JNI代码 JNIEXPORTvoidJNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv*env,jobjectobj) { jclasscls=(*env)->GetObjectClass(env,obj); jmethodIDmid=(*env)->GetMethodID(env,cls,"callback","()V"); if(mid==NULL){ return;/*methodnotfound*/ } printf("InC\n"); (*env)->CallVoidMethod(env,obj,mid); } 输出: InC InJava |
A)、回调Java方法分两步:
•首先通过GetMethodID在给定类中查询方法.查询基于方法名称和签名
•本地方法调用CallVoidMethod,该方法表明被调Java方法的返回值为void
从JNI调用实例方法命名规则:Call<ReturnValueType>Method
-->android_media_MediaScanner.cpp::MyMediaScannerClient
......
staticconstchar*constkClassMediaScannerClient=
"android/media/MediaScannerClient";
......
//先找到android/media/MediaScannerClient类在JNI中对应的jclass实例
jclassmediaScannerClientInterface=
env->FindClass(kClassMediaScannerClient);
if(mediaScannerClientInterface==NULL){
ALOGE("Class%snotfound",kClassMediaScannerClient);
}else{
//获得MediaScannerClient类中方法scanFile的ID
mScanFileMethodID=env->GetMethodID(
mediaScannerClientInterface,
"scanFile",
"(Ljava/lang/String;JJZZ)V");
......
}
......
<--
如上在MyMediaScannerClient会缓存mScanFileMethodID,这是关于程序运行效率的一个问题。(避免每次都去做查询ID的操作)
B)、同实例方法,回调Java静态方法分两步:
•首先通过GetStaticMethodID在给定类中查找方法
•通过CallStatic<ReturnValueType>Method调用
静态方法与实例方法的不同,前者传入参数为jclass,后者为jobject
C)、调用被子类覆盖的父类方法:JNI支持用CallNonvirtual<Type>Method满足这类需求:
•GetMethodID获得methodID
•调用CallNonvirtualVoidMethod,CallNonvirtualBooleanMethod
上述,等价于如下Java语言的方式:
super.f();
D)、CallNonvirtualVoidMethod可以调用构造函数
六、JNI数据类型转换
1)、java基本类型和JNI基本类型转换
Java | Native类型 | 符号属性 | 字长 |
boolean | jboolean | 无符号 | 8位 |
byte | jbyte | 无符号 | 8位 |
char | jchar | 无符号 | 16位 |
short | jshort | 有符号 | 16位 |
int | jint | 有符号 | 32位 |
long | jlong | 有符号 | 64位 |
float | jfloat | 有符号 | 32位 |
double | jdouble | 有符号 | 64位 |
(java基本类型和JNI基本类型转换关系表)
注意:转换成Native类型后对应类型的字长,如jchar在Native语言中时16位,占两个字节,和普通的char占一个字节是不一样的。
2)、引用数据类型的转换
Java引用类型 | Native类型 | Java引用类型 | Native类型 |
Allobjects | jobject | char[] | jcharArray |
java.lang.Class实例 | jclass | short[] | jshortArray |
java.lang.String实例 | jstring | int[] | jintArray |
Object[] | jobjectArray | long[] | jlongArray |
boolean[] | jbooleanArray | float[] | jfloatArray |
byte[] | jbyteArray | double[] | jdoubleArray |
java.lang.Throwable实例 | jthrowable |
看MediaScanner中的processFile
//java层processFile中有3个参数
privatenativevoidprocessFile(Stringpath,StringmimeType,MediaScannerClientclient);
//JNI层对应的函数响应
staticvoidandroid_media_MediaScanner_processFile(JNIEnv*env,jobjectthiz,jstringpath,
jstringmimeType,jobjectclient){
......
}
如果对象都用jobject,就好比Native层的void*类型,对“码农”来讲,他们是透明的,既然是透明的,该如何操作呢?注意上述JNI对应函数中JNIEnv*env,和jobjectthiz(调用processFile函数的对象)参数。
3)、JNI局部变量,全局变量和垃圾回收
Java中有许多引用的概念,我们只关心GlobalRef和LocalRef两种。JNI编程很复杂,
建议不要引入更多复杂的东西,正确、高效的实现功能就可以了。比如对引用来说,最好不要在JNI中考虑:虚引用和影子引用等复杂的东西。
GlobalRef:当你需要在JNI层维护一个Java对象的引用,而避免该对象被垃圾回收时,使用NewGlobalRef告诉VM不要回收此对象,当本地代码最终结束该对象的引用时,用
DeleteGlobalRef释放之。
LocalRef:每个被创建的Java对象,首先会被加入一个LocalRefTable,这个Table大
小是有限的,当超出限制,VM会报LocalRefOverflowException,然后崩溃.这个问题是JNI编程中经常碰到的问题,请引起高度警惕,在JNI中及时通过DeleteLocalRef释放对象的LocalRef.又,JNI中提供了一套函数:Push/PopLocalFrame,因为LocalRefTable大小是固定的,这套函数只是执行类似函数调用时,执行的压栈操作,在LocalRefTable中预留一部分供当前函数使用,当你在JNI中产生大量对象时,虚拟机仍然会因LocalRefOverflowException崩溃,所以使用该套函数你要对LocalRef使用量有准确估计。
下面来看具体代码
-->android_media_MediaScanner.cpp
......
classMyMediaScannerClient:publicMediaScannerClient
{
public:
MyMediaScannerClient(JNIEnv*env,jobjectclient)
:mEnv(env),
//用NewGlobalRef创建一个GlobalRef的mClient,这样就避免mClient被回收
mClient(env->NewGlobalRef(client)),
mScanFileMethodID(0),
mHandleStringTagMethodID(0),
mSetMimeTypeMethodID(0)
......
virtual~MyMediaScannerClient()
{
ALOGV("MyMediaScannerClientdestructor");
//析构函数中调用DeleteGlobalRef释放全局变量
mEnv->DeleteGlobalRef(mClient);
}
......
<--
注意:
a)、当写nativemethod的实现时,要认真处理循环中产生的LocalRef.VM规范中规定每个本地方法至少要支持16个LocalRef供自由使用并在本地方法返回后回收.本地方法绝对不能滥用GlobalRef和WeakGlobalRef,因为此类型引用不会被自动回收。工具函数,对LocalRef的使用更要提起警惕,因为该类函数调用上下文不确定,而且会被重复调用,每个代码路径都要保证不存在LocalRef泄露。
b)、Push/PopLocalFrame常被用来管理LocalRef.在进入本地方法时,调用一次PushLocalFrame,并在本地方法结束时调用PopLocalFrame.此对方法执行效率非常高,建议使用这对方法。
你只要对当前上下文内使用的对象数量有准确估计,建议使用这对方法,在这对方法间,不必调用DeleteLocalRef,只要该上下文结尾处调用PopLocalFrame会一次性释放所有LocalRef。一定保证该上下文出口只有一个,或每个return语句都做严格检查是否调用了PopLocalFrame。忘记调用PopLocalFrame可能会使VM崩溃。
4)、JNI对象比较
有两个对象,用如下方法比较相容性:
(*env)->IsSameObject(env,obj1,obj2)
如果相容,返回JNI_TRUE,否则返回JNI_FALSE。
与NULL的比较,LocalRef与GlobalRef语义显然,前提是释放了两个引用,程序员重新为相应变量做了NULL初始化。
但对于WeakGlobalRef来说,需要使用下述代码判定:
(*env)->IsSameObject(env,wobj,NULL)
因为,对于一个WeakGlobalRef来说可能指向已经被GC的无效对象。
七、数据类型的传递
注意:代码中如果env->是C++语言,如果是(*env)->是C语言
1)、基本类型的传递
Java的基本类型和对应的JNI类型传递时没有问题的。
2)、String参数的传递
......
privatenativeStringgetLine(Stringprompt);//java定义的native方法
......
......
JNIEXPORTjstringJNICALLJava_Prompt_getLine(JNIEnv*env,jobjectthis,
jstringprompt);//JNI中与native方法对应的JNI方法
......
如上,在JNI函数中是不能直接使用jstringprompt,编译会报错,因为JNI都是用C和C++编写的,这两种语言中没有jstring类型,所以使用的过程中必须要做一些处理。
......
char*str;
str=env->GetStringUTFChars(prompt,false);//将jstring类型变成一个char*类型
......
(*env)->ReleaseStringUTFChars(env,prompt,str);//使用完后要记得释放内存
......
返回的时候,要生成一个jstring类型的对象,也必须通过如下命令
jstringrtstr=env->NewStringUTF(tmpstr);
3)、数组类型的传递
和String一样,JNI为Java基本类型的数组提供了j*Array类型,比如int[]对应的就是jintArray。来看一个传递int数组的例子,
JNIEXPORTjintJNICALL Java_IntArray_sumArray(JNIEnv*env,jobjectobj,jintArrayarr){ jint*carr; carr=env->GetIntArrayElements(arr,false);//分配内存空间 if(carr==NULL){ return0; } jintsum=0; for(inti=0;i<10;i++){ sum+=carr[i]; } env->ReleaseIntArrayElements(arr,carr,0);//释放内存空间 returnsum; } |
这个例子中的GetIntArrayElements和ReleaseIntArrayElements函数就是JNI提供用
于处理int数组的函数。如果试图用arr的方式去访问jintArray类型,毫无疑问会出错。JNI
还提供了另一对函数GetIntArrayRegion和ReleaseIntArrayRegion访问int数组,就不介绍
了,对于其他基本类型的数组,方法类似。
4)、二维数组和String数组
在JNI中,二维数组和String数组都被视为object数组,因为数组和String被视为object。用一个例子来说明,这次是一个二维int数组,作为返回值。
JNIEXPORTjobjectArrayJNICALL Java_ObjectArrayTest_initInt2DArray(JNIEnv*env,jclasscls,intsize){ jobjectArrayresult;//因为要返回值,所以需要新建一个jobjectArray对象。 jclassintArrCls=env->FindClass("[I"); result=env->NewObjectArray(size,intArrCls,NULL);//为result分配空间。 for(inti=0;i<size;i++){ jinttmp[256]; jintArrayiarr=env->NewIntArray(size);//是为一维int数组iarr分配空间。 for(intj=0;j<size;j++){ tmp[j]=i+j; } env->SetIntArrayRegion(iarr,0,size,tmp);//是为iarr赋值。 env->SetObjectArrayElement(result,i,iarr);//是为result的第i个元素赋值。 env->DeleteLocalRef(iarr);//释放局部对象的引用 } returnresult; } |
jclassintArrCls=env->FindClass("[I");
是创建一个jclass的引用,因为result的元素是一维int数组的引用,所以intArrCls必须是一维int数组的引用,这一点是如何保证的呢?注意FindClass的参数"[I",JNI就是通
过它来确定引用的类型的,I表示是int类型,[标识是数组。对于其他的类型,都有相应的表
示方法,详细见JNI签名。
八、JNI异常处理
JNI中也有异常,不过它和C++,java中的异常不一样。如果JNI层出现异常,它不会中断本地函数,直到返回java层,由java虚拟机抛出异常。虽然JNI层不会抛出异常,但是在异常产生的时候它会做一些资源清理的工作,所以,如果在JNI层的函数出现异常时,调用JNIEnv异常函数外的其他函数会导致程序死掉。
示例代码
-->android_media_MediaScanner.cpp
......
virtualstatus_tscanFile(constchar*path,longlonglastModified,
longlongfileSize,boolisDirectory,boolnoMedia)
{
ALOGV("scanFile:path(%s),time(%lld),size(%lld)andisDir(%d)",
path,lastModified,fileSize,isDirectory);
jstringpathStr;
//调用失败后直接返回,不要再让程序做任何事情
if((pathStr=mEnv->NewStringUTF(path))==NULL){
mEnv->ExceptionClear();
returnNO_MEMORY;
}
mEnv->CallVoidMethod(mClient,mScanFileMethodID,pathStr,lastModified,
fileSize,isDirectory,noMedia);
mEnv->DeleteLocalRef(pathStr);
returncheckAndClearExceptionFromCallback(mEnv,"scanFile");
}
......
<--
异常Demo
java
classCatchThrow{ privatenativevoiddoit()throwsIllegalArgumentException; privatevoidcallback()throwsNullPointerException{ thrownewNullPointerException("CatchThrow.callback"); } publicstaticvoidmain(Stringargs[]){ CatchThrowc=newCatchThrow(); try{ c.doit(); }catch(Exceptione){ System.out.println("InJava:\n\t"+e); } } static{ System.loadLibrary("CatchThrow"); } } |
C
JNIEXPORTvoidJNICALL Java_CatchThrow_doit(JNIEnv*env,jobjectobj) { jthrowableexc; jclasscls=(*env)->GetObjectClass(env,obj); jmethodIDmid= (*env)->GetMethodID(env,cls,"callback","()V"); if(mid==NULL){ return; } (*env)->CallVoidMethod(env,obj,mid); //判断是否有异常发生 exc=(*env)->ExceptionOccurred(env); if(exc){ /*Wedon'tdomuchwiththeexception,exceptthat weprintadebugmessageforit,clearit,and throwanewexception.*/ jclassnewExcCls; //描述异常 (*env)->ExceptionDescribe(env); //清除异常 (*env)->ExceptionClear(env); newExcCls=(*env)->FindClass(env, "java/lang/IllegalArgumentException"); if(newExcCls==NULL){ /*Unabletofindtheexceptionclass,giveup.*/ return; } //向java层抛出异常 (*env)->ThrowNew(env,newExcCls,"thrownfromCcode"); } } |
结果
java.lang.NullPointerException: atCatchThrow.callback(CatchThrow.java) atCatchThrow.doit(NativeMethod) atCatchThrow.main(CatchThrow.java) InJava: java.lang.IllegalArgumentException:thrownfromCcode |
九、文档描述
本文结合《深入理解Android》《jni详解》等文章对jni技术做了简单的剖析,这些对学习androidjni层会有不错的帮助,在后续还会对文档修改完善。
本地JNIDemo调试步骤(Linux):
@以下步骤都是在同一个目录下
1)创建Hello.java
Hello.java
publicclassHello.java{
privatenativevoidprint();
static{
System.loadLibrary(“Hello”);
}
publicstaticvoidmain(String[]args){
newHello().print();
}
}
2)JNI静态注册
$javacHello.java(生成Hello.class)
$javah-jniHello(生成Hello.h)
Hello.h
/*DONOTEDITTHISFILE-itismachinegenerated*/
#include<jni.h>
/*HeaderforclassHello*/
#ifndef_Included_Hello
#define_Included_Hello
#ifdef__cplusplus
extern"C"{
#endif
/*
*Class:Hello
*Method:print
*Signature:()V
*/
JNIEXPORTvoidJNICALLJava_Hello_print
(JNIEnv*,jobject);
#ifdef__cplusplus
}
#endif
#endif
3)创建Hello.c
Hello.c
#include<stdio.h>
#include"Hello.h"/*注意要引入Hello.h头文件*/
JNIEXPORTvoidJNICALL//静态注册native方法
Java_Hello_print(JNIEnv*env,jobjectobj)
{
printf("HelloWorld!\n");
}
4)编译成*.so库文件
$gccHello.c-fPIC-shared-olibHello.so//注意这里(linux中库文件都是以lib开头的)
5)设置Lib库文件环境变量
运行前,必须保证连接器,能找到待装载的库,不然,将抛如下异常:
java.lang.UnsatisfiedLinkError:noHelloWorldinlibrarypath
atjava.lang.Runtime.loadLibrary(Runtime.java)
atjava.lang.System.loadLibrary(System.java)
$LD_LIBRARY_PATH=.
$exportLD_LIBRARY_PATH
6)运行
$javaHello
<!--EndFragment-->更多相关文章
- C语言函数以及函数的使用
- Unity调用Android配置方法
- Android 的相关文件类型
- Android 滑动手势侦测方法介绍
- 安卓selector使用方法
- 应用界面主题Theme使用方法
- Android 使用html做UI的方法---js与java的相互调用
- Android关闭JIT的方法