Android JNI开发基础

什么是JNI

  • JNI是Java Native Interface的简称,即Java本地开发接口
  • JNI是一个协议,用来沟通Java代码和本地代码(C/C++)

为什么使用JNI

  • Java语言没有的功能(很少)
  • 代码已经有现成的C/C++版本,不想重写(一般情况)
  • Java太慢了(怎么慢?)
  • 跨平台实现(Mac, Windows, Android, iOS)
  • 特殊的功能性语言(Fortran的数学计算

使用JNI的注意事项

  • 只提供了C/C++的支持,其他语言需要额外的步骤
  • 不安全,特别是内存管理,以及Java对象的引用计数
  • 不可移值,不同的CPU架构需要重新编译(arm, mips, x86)

如何使用JNI-基础知识

  • Android SDK(Java)
  • C/C++语言
  • NDK(Android.mk, ndk-build)

如何使用JNI-C/C++语言基础

  • 指针的使用
  • 内存管理:malloc, free, new, delete
  • 变量类型:int, float, double, char, …
  • C++导出C函数:extern “C” { }

如何使用JNI-基本过程

  1. 下载NDK,支持Windows、Linux和Mac OS X;
  2. 解压NDK到本地磁盘,设置好PATH;
  3. 新建Android工程,在工程目录下新建jni文件夹(文件夹名称必须是jni);
  4. 在jni文件夹下建立Android.mk文件(Android.mk文件的编写方法见后文);
  5. 在jni文件夹下编写好C/C++代码文件;
  6. 命令行切换到当前目录,运行ndk-build,等待编译完成,编译完成后so文件在libs/armeabi文件夹下;
  7. 在eclipse里运行Android工程,so文件会打包到apk里
  • TIPS:ADT插件安装选择Native Development,在工程的右键菜单有Android Tools/Add Native Support,集成了CDT的C/C++ IDE环境,在Java编译前执行ndk-build

如何使用JNI-JNI语法

  • HelloJni例子(NDK_DIR/samples/hello-jni)
    • HelloJni.java
    • HelloJni.c
    • Android.mk

如何使用JNI-HelloJni.java

public class HelloJni extends Activity
{
	// ...
       public native String  stringFromJNI(); // 声明native
       static {
             // 在libs/armeabi下加载libhello-jni.so
             System.loadLibrary("hello-jni"); 
             // 指定路径加载libhello-jni.so		
             // System.load("path/libhello-jni.so");
    	}
}

  • 声明native函数,不用在Java中实现,建议native函数为private
  • 加载jni so文件,有以下两种方式,按需选择一种即可
    • 调用System.loadLibrary默认在libs/armeabi下加载so文件,名称与Android.mk里的LOCAL_MODULE一样
    • 调用System.load,指定so文件的完整路径,必须写完整so文件名

如何使用JNI-HelloJni.c

#include <string.h>
#include <jni.h>

jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    // C的写法
    return (*env)->NewStringUTF(env, "Hello from JNI !");
    // C++的写法
    // return env->NewStringUTF("Hello form JNI !");
}
  • 函数名规则:Java_包名_类名_Java函数名( JNIEnv* env, jobject thiz, 其他参数)
    • 函数名可以用javah自动生成,命令行 $ cd bin $ javah -classpath . -jni com.example.hellojni.HelloJni 
    • 产生对应包名的.h文件,实现.h的函数
  • JNIEnv* env:JNI环境的指针,JVM当前线程的句柄,JNI函数调用入口
  • jobject thiz:调用JNI函数当前对象的引用,相当于this指针

如何使用JNI-类型对应表

类型对应表
Java TypeNative TypeSize in bits
booleanjboolean8, unsigned
bytejbyte8
charjchar16, unsigned
shortjshort16
intjint32
longjlong64
floatjfloat32
doublejdouble64
voidvoidN/a
byte []jbyteArray8*n
class Objectjobject32


如何使用JNI-字符串

//访问jstring
char *str = (*env)->GetStringUTFChars(env,prompt,0);
/* this maps into regular C char*  */

printf(“%s”, str); /* now it is ok to print */

(*env)->ReleaseStringUTFChars(env, prompt, str);
/* must release String to avoid memory leaks */
//返回jstring
char buf[128]; /* allocate memory for local char* in C */
scanf(“%s”, buf); /* read into char* from stdio */
return( (*env)->NewStringUTF(env, buf)); 
/* construct and return the Java String */ 

如何使用JNI-数组

jsize len = (*env)->GetArrayLength(env,arr);
jint *body = (*env)->GetIntArrayElements(env,arr,0);
for (i=0;i<len;++i){
  sum += body[i]; 
}
(*env)->ReleaseIntArrayElements(env,arr,body,0);
/* very important – copies back to java array if copy had to be made */
  • 其他类型(float, byte, double)的调用方式类似:
    • Get<type>ArrayElements
    • Release<type>ArrayElements
  • 注意: 这些Get函数会拷贝数组,如果不需要内存拷贝,使用:Get/Set<type>ArrayRegion functions
Get FunctionRelease FunctionArray Type
GetBooleanArrayElementsReleaseBooleanArrayElementsboolean
GetByteArrayElementsReleaseByteArrayElementsbyte
GetShortArrayElementsReleaseShortArrayElementsshort
GetIntArrayElementsReleaseIntArrayElementsint
GetLongArrayElementsReleaseLongArrayElementslong
GetFloatArrayElementsReleaseFloatArrayElementsfloat
GetDoubleArrayElementsReleaseDoubleArrayElementsdouble
GetObjectArrayElementsReleaseObjectArrayElementsobject


如何使用JNI-调用Java函数

jclass cls = (*env)->GetObjectClass(env, thiz);
jmethodID methodId = (*env)->GetMethodID(env, cls, "getAout", "()I");
bool use_opensles = (*env)->CallIntMethod(env, thiz, methodId) == AOUT_OPENSLES;
// ...
(*env)->DeleteLocalRef(env, cls);

  • GetObjectClass获取得到调用JNI函数当前对象的类,如果不是当前对象的类,使用FindClass(JNIEnv *env, const char* className)
  • GetMethodID获取调用JNI函数当前对象的类函数,getAout为Java函数名,()I为输入参数和返回值签名,签名的规则见后文
  • CallIntMethod调用Java对象的getAout函数,返回值是int类型,无返回值函数调用CallVoidMethod,其他类型返回值依此类推,静态函数调用CallStaticTypeMethod
  • DeleteLocalRef删除引用,cls使用完后,记得删除引用计数

//JNI回调函数的调用Java函数的方法如下
JavaVM *myVm;
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    myVm = vm; // Keep a reference on the Java VM.
    return JNI_VERSION_1_2;
}
static void CallbackFunc() {
    JNIEnv *env;
    if ((*myVm)->AttachCurrentThread(myVm, &env, NULL) < 0)
        return;
    // ...
    (*myVm)->DetachCurrentThread(myVm);
} 

  • 在JNI_OnLoad里保存JavaVM
  • 调用AttachCurrentThread获得JNIEnv,使用JNIEnv调用Java函数
  • 调用DetachCurrentThread解绑

如何使用JNI-Java函数变量签名

变量签名表
Java TypeNative TypeSize in bits
booleanjbooleanZ
bytejbyteB
charjcharC
shortjshortS
intjintI
longjlongL
floatjfloatF
doublejdoubleD
voidvoidV
StringjstringLjava/lang/String;



如何使用JNI-加入Log

#include <android/log.h>
#define LOG_TAG "System.out”

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

LOGI("info\n");
LOGD("debug\n");
LOGD(“The string is %s”, p_str);
  • Android.mk文件增加LOCAL_LDLIBS += -llog
  • 在LogCat里可以看到JNI输出的日志信息

如何使用JNI-Android.mk

LOCAL_PATH := $(call my-dir) // ①

include $(CLEAR_VARS) // ②

LOCAL_MODULE    := hello-jni // ③
LOCAL_SRC_FILES := hello-jni.c // ④

include $(BUILD_SHARED_LIBRARY) // ⑤
① LOCAL_PATH用于指定源代码的目录,my-dir宏函数是编译环境提供,表示当前文件夹
② CLEAR_VARS由编译系统提供,用于清除一些LOCAL_XXX的值(如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES …),不清除LOCAL_PATH
③ LOCAL_MODULE定义模块名称标识,名称必须唯一,不能包含空格,编译系统会编译生成lib<modulename>.so或lib<modulename>.a文件
④ LOCAL_SRC_FILES指定要编译的.c和.cpp文件,多个文件以空格分隔,换行连接符为 \
⑤ 编译成共享库,生成so文件,还可以是BUILD_STATIC_LIBRARY,编译静态库,即a文件,.a文件必须链接到so文件里
  • LOCAL_LDLIBS := -llog –lz 加载liblog.a静态库和libz.a静态库,这两个静态库由NDK提供,也可以指定搜索路径加载编译好的静态库,使用-Lpath_to_lib/xxx.a
  • LOCAL_STATIC_LIBRARIES += hello-jni表示编译依赖的静态库,在项目里的其他Android.mk文件定义
  • LOCAL_C_INCLUDES += include指定include路径
  • LOCAL_CFLAGS += -DANDROID=1定义ANDROID宏的值为1
  • 多个共享库都需要调用loadLibrary加载,包括依赖的so库,Android不会自动寻找依赖库

内存管理

  • 全局引用
    • NewGlobalRef
    • jobject NewGlobalRef(JNIEnv *env, jobject obj);
    • 创建一个新的obj全局引用,调用DeleteGlobalRef()释放全局引用
  • 局部引用
    • 每一个传入到native函数的参数和返回值都是本地引用,其值只有在当前线程的native函数调用期间有效,当native函数返回,引用失效。适用于所以jobject派生类,包括jclass, jstring, jarray等。
    • 判断两个object引用是否一样,不能使用==,要使用函数IsSameObject。
    • 不能过度分配局部引用,当创建了大量的局部引用的适合,必须调用DeleteLocalRef手动释放局部引用,Android支持最多512个局部引用。
    • jfieldID和jmethodID不是引用对象。

本文小结

        之前的博客被关闭暂时找不回,打算重新开一个博客,坚持记录一些。刚好最近公司的项目有个通过现有db来查找searchfile的功能,java层效率低,把之前的逻辑通过jni转到c++层去实现,于是就从这个开始写吧。
        其实jni教程在现在的网络已经泛滥了,所以至于开发环境那些就懒得做了,结合之前公司技术分享的PPT写了这个还算详细的小结。
        现在的JNI只是教别人怎么调底层的方法,但是在调用后毕竟要有回馈结果到界面给用户,打算之后会结合源码把JNI讲得更深入点,同时会用回调的逻辑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值