深入JNI

JNI连接着java和c++/c层。Android有大量驱动必须在native层实现,还有一些注重性能、功耗的功能在c++/c实现比较好。

从java到native建立的是函数关系。但是从natvie到java,必须先得到java的对象引用,才能调用该对象的方法。

通过jni调用是在同一个线程中的,但是可能打印出来的线程id不同。这是语言的关系。

1.装载JNI动态库

为了使用JNI,在调用本地方法前。必须把c/c++代码所在的动态库装载到内存中。

public static void loadLibrary(String libName)

传入的libName,在linux上为libmyjni.so在windows上是myjni.dll。这边是因为java是跨平台的所以只用了中间部分相同的名字。

一般将loadLibrary放在static域中,这样能在进程初始化的时候就加载了。

static {
    System.loadLibrary("my_jni");
}

2.定义native方法

Java类中native方法很简单

private static native final void init()

3.动态注册JNI函数

JNI动态库和非JNI动态库区别:JNI动态库中定义了一个JNI_Onloader函数,在动态库加载的时候被调用。用于完成JNI函数的注册。

jint JNI_Onload(JavaVM* vm, void *)

这个函数中最重要的一件事就是调用registerNativeMethods函数来完成JNI函数注册。就是将java函数和native函数一一对应。

static JNINativeMethod gMethods[] = {
    {"enableFunc", "(IZ)Z",(void*)native_enableFunc},
    {"disableFunc", "(IZ)Z",(void*)native_disableFunc},
};
jint JNI_Onload(JavaVM* vm, void*) {
    JNIEnv* env = NULL;
    jint result = -1;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("GetEnv failed!");
        return result;
     }
     if(AndroidRuntime::registerNativeMethods(env, "com/android/TestService",gMethods,NELEM(gMethods))>=0) {
        result = JNI_VERSION_1_4
     }
     return result;
}

其中gMethods的类型定义如下:

typedef struct {
    const char* name;//java中对应的函数名
    const char* signature;//签名,入参和返回值
    void* fnPtr;//native函数指针
} JNINativeMethod;

对应的类型,其中"["代表数组的意思。(IZ)Z,代表入参是int boolean 返回值是boolean

Z       boolean
C       char
B       byte
S       short
I       int
J       long
F       float
D       double

下面看看Android中Alarm的jni方法注册:

#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"

namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_AssetAtlasService(JNIEnv* env);
int register_android_server_BatteryStatsService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
int register_android_server_tv_TvInputHal(JNIEnv* env);
int register_android_server_PersistentDataBlockService(JNIEnv* env);
int register_android_server_fingerprint_FingerprintService(JNIEnv* env);
int register_android_server_Watchdog(JNIEnv* env);
};

using namespace android;

extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("GetEnv failed!");
        return result;
    }
    ALOG_ASSERT(env, "Could not retrieve the env!");

    register_android_server_PowerManagerService(env);
    register_android_server_SerialService(env);
    register_android_server_InputApplicationHandle(env);
    register_android_server_InputWindowHandle(env);
    register_android_server_InputManager(env);
    register_android_server_LightsService(env);
    register_android_server_AlarmManagerService(env);
    register_android_server_UsbDeviceManager(env);
    register_android_server_UsbHostManager(env);
    register_android_server_VibratorService(env);
    register_android_server_SystemServer(env);
    register_android_server_location_GpsLocationProvider(env);
    register_android_server_location_FlpHardwareProvider(env);
    register_android_server_connectivity_Vpn(env);
    register_android_server_AssetAtlasService(env);
    register_android_server_ConsumerIrService(env);
    register_android_server_BatteryStatsService(env);
    register_android_server_hdmi_HdmiCecController(env);
    register_android_server_tv_TvInputHal(env);
    register_android_server_PersistentDataBlockService(env);
    register_android_server_fingerprint_FingerprintService(env);
    register_android_server_Watchdog(env);

    return JNI_VERSION_1_4;
}

下面是具体的Alarm的JNI方法注册

static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
    {"init", "()J", (void*)android_server_AlarmManagerService_init},
    {"close", "(J)V", (void*)android_server_AlarmManagerService_close},
    {"set", "(JIJJ)V", (void*)android_server_AlarmManagerService_set},
    {"waitForAlarm", "(J)I", (void*)android_server_AlarmManagerService_waitForAlarm},
    {"setKernelTime", "(JJ)I", (void*)android_server_AlarmManagerService_setKernelTime},
    {"setKernelTimezone", "(JI)I", (void*)android_server_AlarmManagerService_setKernelTimezone},
};

int register_android_server_AlarmManagerService(JNIEnv* env)
{
    return jniRegisterNativeMethods(env, "com/android/server/AlarmManagerService",
                                    sMethods, NELEM(sMethods));
}


4.jstring的常用方法

NetStringUTF将根据一个UTF-8字符串得到一个jstring对象。输入就是本地字符串。

GetStringUTFChars函数可以将jstring对象转换成本地字符串

RealeaseStringUTFChars对应释放本地字符串。

const char* pathstr = env->GetStringUTF(path, NULL);//其中path是jstring类型

//最后调用释放
env->ReleaseStringUTFChars(path,pathstr);


5.调用java方法

NewObject生成一个java对象

jobject NewObject(jclass clazz, jmethod methodID,..)
methodID是构造函数


FindClass

jclass FindClass(const char* name);

例如:输入参数为"/java/lang/String"


调用一个java对象的方法之前需要知道方法或者域变量的Id。

jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);

其中name是类的方法名或是成员变量名


调用java类的方法

jchar CallCharMethod(jobject object, jmethodID, methodID,.......);
jboolean CallBooleanMethod.....
....
void CallVoidMethod

静态方法:

jchar CallStaticCharMethod(jclass clazz, jmethodID, methodID,.......);
jboolean CallStaticBooleanMethod.....
....
void CallStaticVoidMethod

获取成员变量:

jobject GetObjectField(jobject obj, jfieldID fieldID);
.......

设置成员变量

void SetObjectField(jobject obj, jfieldID fieldID, jobject value);
.......

得到静态变量值:

jobject GetStaticObjectField(jclass clazz, jfieldID fieldID);
.......

设置静态变量值:

void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value);
.......


6.JNI中的引用

static jobject save_this = NULL;//定义一个全局的jobject
static void android_media_MediaScanner_processFile(JNIEnv* env, jobject thiz, jstring path, jstring mimeType, jobject client)
{
    .......
    save_this = thiz;//保存java层传入的jobject对象
    .......
    return;
}
//假设在某个时间,有个地方调用callMediaScanner函数
void callMediaScanner() {
//在这个函数里面操作save_this有问题吗
}

上面这种做法肯定有问题,因为save_this是java层传过来的,很有可能被垃圾回收了。也就是save_this可能是一个野指针。而在c++层使用该对象是不会增加引用计数的。


这种情况可以使用全局引用和局部引用。

MyMediaScannerClient(JNIEnv* env, jobject client) : mEnv(env), 
    mClient(env->NewGlobalRef(client)),//调用NewGlobalRef创建一个全局引用,这样就不会被垃圾回收了
    mScanFileMethodID(0)
{
    ...........
}
virtual ~MyMediaScannerClient()
{
    mEnv->DeleteGlobalRef(mClient);//释放全局引用
}


局部引用的话使用DeleteLocalRef

jstring pathstr;
pathstr = mEnv->NewStringUTF(path);//新建一个局部变量

mEnv->DeleteLocalRef(pathstr);//释放局部变量



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值